Merge branch 'sites' into feat/redo-blog-logic

pull/8447/head
Puru Vijay 3 years ago
commit fe5781f756

@ -1,11 +1,17 @@
# Svelte changelog
## Unreleased
## 3.58.0
- Add `bind:innerText` for `contenteditable` elements ([#3311](https://github.com/sveltejs/svelte/issues/3311))
- Relax `a11y-no-noninteractive-element-to-interactive-role` warning ([#8402](https://github.com/sveltejs/svelte/pull/8402))
- Add support for CSS `@container` queries ([#6969](https://github.com/sveltejs/svelte/issues/6969))
- Respect `preserveComments` in DOM output ([#7182](https://github.com/sveltejs/svelte/pull/7182))
- Allow use of `document` for `target` in typings ([#7554](https://github.com/sveltejs/svelte/pull/7554))
- Add `a11y-interactive-supports-focus` warning ([#8392](https://github.com/sveltejs/svelte/pull/8392))
- Fix equality check when updating dynamic text ([#5931](https://github.com/sveltejs/svelte/issues/5931))
- Relax `a11y-no-noninteractive-element-to-interactive-role` warning ([#8402](https://github.com/sveltejs/svelte/pull/8402))
- Properly handle microdata attributes ([#8413](https://github.com/sveltejs/svelte/issues/8413))
- Prevent name collision when using computed destructuring variables ([#8417](https://github.com/sveltejs/svelte/issues/8417))
- Fix escaping `<textarea value={...}>` values in SSR ([#8429](https://github.com/sveltejs/svelte/issues/8429))
## 3.57.0

4
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "svelte",
"version": "3.57.0",
"version": "3.58.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "svelte",
"version": "3.57.0",
"version": "3.58.0",
"license": "MIT",
"devDependencies": {
"@ampproject/remapping": "^0.3.0",

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.57.0",
"version": "3.58.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",

@ -0,0 +1,109 @@
---
title: "What's new in Svelte: April 2023"
description: "Loads of new Svelte compiler features, plus Svelte Summit and SvelteHack"
author: Dani Sandoval
authorURL: https://dreamindani.com
---
Happy April, everyone! This month, we're covering all the new features in the Svelte compiler, some quality-of-life improvements in SvelteKit and a huge showcase (like always).
In core team news, Dominic Gannaway has joined Vercel to work on Svelte full-time! Dominic is a world-class expert on wringing performance out of javascript engines, on the DOM, on reactivity, on accessibility, and more! You might know him as the creator of the [Inferno](https://www.infernojs.org/) UI framework or [Lexical](https://lexical.dev/), Meta's WYSIWYG editor. It'll be great to see his talents at work across the Svelte ecosystem 🌱
Don't forget! Svelte Summit Spring, Svelte's 6th virtual conference, will be happening on May 6th. Also, there's just two weeks left until the end of [SvelteHack](https://hack.sveltesociety.dev/)... It's a great opportunity to share your creations with the community and maybe even earn a prize!
Now let's jump into this month's changes...
## What's new in Svelte
- A bunch of new features are now available as of **3.56.0**!
- Add `|stopImmediatePropagation` event modifier for `on:eventname` ([#5085](https://github.com/sveltejs/svelte/issues/5085), [Docs](https://svelte.dev/docs#template-syntax-element-directives-on-eventname))
- Add `axis` parameter to `slide` transition ([#6182](https://github.com/sveltejs/svelte/issues/6182), [Docs](https://svelte.dev/docs#run-time-svelte-transition-slide))
- Add `readonly` utility to convert `writable` store to readonly ([#6518](https://github.com/sveltejs/svelte/pull/6518), [Docs](https://svelte.dev/docs#run-time-svelte-store-writable))
- Add `readyState` binding for media elements ([#6666](https://github.com/sveltejs/svelte/issues/6666), [Docs](https://svelte.dev/docs#template-syntax-element-directives-bind-property-media-element-bindings))
- Add `naturalWidth` and `naturalHeight` bindings to images ([#7771](https://github.com/sveltejs/svelte/issues/7771), [Docs](https://svelte.dev/docs#template-syntax-element-directives-bind-property-image-element-bindings))
- Support `<!-- svelte-ignore ... -->` on components ([#8082](https://github.com/sveltejs/svelte/issues/8082))
- Inputs in a `bind:group` will clear when their value is set to `undefined` (**3.56.0**, [#8214](https://github.com/sveltejs/svelte/issues/8214))
- `<input>` values will now persist when swapping elements with spread attributes in an `{#each}` block (**3.56.0**, [#7578](https://github.com/sveltejs/svelte/issues/7578))
- Better warnings across the board - from `noreferrer` to `aria` rules (**3.56.0**)
- Add <svelte:document> (**3.57.0**, [#3310](https://github.com/sveltejs/svelte/issues/3310))
- The `style:` directive will now take precedence over a `style=` attribute (**3.57.0**, [#7475](https://github.com/sveltejs/svelte/issues/7475))
- CSS units are now supported in the `fly` and `blur` transitions (**3.57.0**, [#7623](https://github.com/sveltejs/svelte/pull/7623), [Docs](https://svelte.dev/docs#run-time-svelte-transition))
For all the changes to the Svelte compiler, including unreleased changes, check out the [CHANGELOG](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md).
## What's new in SvelteKit
- You can now get all cookies for a request with `cookies.getAll` (**1.10.0**, [#9287](https://github.com/sveltejs/kit/pull/9287), [Docs](https://kit.svelte.dev/docs/types#public-types-cookies))
- Easily manage the submission status of (multiple) forms with the new exposed `submitter` parameter in `use:enhance` (**1.12.0**, [#9425](https://github.com/sveltejs/kit/pull/9425), [Docs](https://kit.svelte.dev/docs/types#public-types-submitfunction))
- The default error page now has dark mode styles (**1.13.0**, [#9460](https://github.com/sveltejs/kit/pull/9460))
- You can now omit types on all methods and variables with special meaning to SvelteKit and still benefit from full type safety! Read more about it in the [announcement blog post](https://svelte.dev/blog/zero-config-type-safety)
---
## Community Showcase
**Apps & Sites built with Svelte**
- [Peerbeer](https://peer.beer/) lets you share files peer-to-peer (p2p) without any third parties or data limits
- [unplaneted](https://unplaneted.com/) is an interface for exploring very large space images
- [PokeBook](https://github.com/pokegh0st/pokebook) is a digital notebook for writing poetry that provides a beautiful distraction-free environment and autosave
- [papi](https://papi.run/) lets you create prompts for AI models and share them with others with a unique link
- [Mathesar](https://github.com/centerofci/mathesar) is a straightforward open source tool that provides a spreadsheet-like interface to a PostgreSQL database
- [SQLite Playground](https://neil.macmunn.com/sqlite#) lets you learn how SQLite runs and stores data in the browser
- [svgl](https://github.com/pheralb/svgl) is a beautiful library with SVG logos
- [Swehl](https://swehl.com/) is an eCommerce store, community and tutorial site for breastfeeding mothers
- [Codeverter](https://github.com/TGlide/codeverter) is a GPT-powered code converter, allowing you to convert between different languages and frameworks
- [Game On Or Not](https://gameonornot.com/) is a free web app that helps you organize sports with your friends
- [Sveltia CMS](https://github.com/sveltia/sveltia-cms) is a Git-based lightweight headless CMS
**Learning Resources**
_Featuring Svelte Contributors and Ambassadors_
- [Streaming, snapshots, and other new features since SvelteKit 1.0](https://svelte.dev/blog/streaming-snapshots-sveltekit) by Geoff Rich on the svelte.dev Blog
- [Dev Vlog: Rich Harris shows us what's new in Svelte and Kit, March 2023](https://www.youtube.com/watch?v=vgXgex5E-8g) from Svelte Society
- If you missed this one live, check out [the next one](https://www.youtube.com/watch?v=MJHO6FSioPI) - scheduled for April 5th
- [Svelte Society - London February 2023](https://www.youtube.com/watch?v=RkQ_f7XxdMI)
- Svelte Radio episodes from this month:
- [We all live in a Svelte Submarine](https://www.svelteradio.com/episodes/we-all-live-in-a-svelte-submarine)
- [Building furniture using Svelte with Bert Bengtson](https://www.svelteradio.com/episodes/building-furniture-using-svelte-with-bert-bengtson)
- [Svelte Hackathon Announcement](https://www.svelteradio.com/episodes/svelte-hackathon-announcement)
- [LevelUpTuts 6 months later with Scott Tolinski](https://www.svelteradio.com/episodes/leveluptuts-6-months-later-with-scott-tolinski)
- [I got a cold and had fever dreams about React 😱](https://www.svelteradio.com/episodes/i-got-a-cold-and-had-fever-dreams-about-react)
- This Week In Svelte videos:
- [2023 March 10 - New prompts! Underline your links!](https://www.youtube.com/watch?v=WiCjQVoE-3k)
- [2023 March 17 - More a11y warnings! How to: Dynamic Form Actions!](https://www.youtube.com/watch?v=sRhZQ-2VxVU)
- [2023 March 23 - SvelteKit 1.13.0, Vitest and Playwright overview](https://www.youtube.com/watch?v=vpbhsbg2otg)
_To Watch or Hear_
- [Full Stack SvelteKit App Deployment Using Vercel And Supabase For $0](https://www.youtube.com/watch?v=uAF4Yd-gddo) by Joy of Code
- [Why Is Svelte.js so Popular?](https://www.youtube.com/watch?v=73Y8Yyg54zc) by Prismic
- [Interactive Tables in SvelteKit with TanStack Table](https://www.youtube.com/watch?v=-Zuo3UWjjI8) by hartenfellerdev
- [SvelteKit + GraphQL with Houdini](https://www.youtube.com/watch?v=ADnaRwQZfqw&list=PLm0ILX0LGQk_220vvpsbyXH2VesRlCm-E) by Aftab Alam
_To Read_
- [Thoughts on Svelte](https://tyhopp.com/notes/thoughts-on-svelte) by Ty Hopp
- [Storybook](https://storybook.js.org/blog/storybook-for-sveltekit/) on why (and how) it supports SvelteKit
- [Svelte Authentication Tutorial with Authorizer](https://thethinks.vercel.app/blog/svelte-authorizer) by The Thinks
- [Use Zod to Validate Forms on the Server with SvelteKit](https://blog.robino.dev/posts/svelte-zod-error) by Ross Robino
- [Do I need a sitemap for my SvelteKit app, and how do I create it?](https://maier.tech/posts/do-i-need-a-sitemap-for-my-sveltekit-app-and-how-do-i-create-it) and [Complement zero-effort type safety in SvelteKit with Zod for even more type safety](https://maier.tech/posts/complement-zero-effort-type-safety-in-sveltekit-with-zod-for-even-more-type-safety) and [Configuring Turborepo for a SvelteKit monorepo](https://maier.tech/posts/configuring-turborepo-for-a-sveltekit-monorepo) by Thilo Maier
- [Adding page transitions in SvelteKit](https://joshcollinsworth.com/blog/sveltekit-page-transitions) by Josh Collinsworth
- [E2E testing with SvelteKit and Playwright](https://www.okupter.com/blog/e2e-testing-with-sveltekit-and-playwright) and [Why you should use TypeScript in your next SvelteKit projects](https://www.okupter.com/blog/sveltekit-with-typescript) by Justin Ahinon
- [Understanding the structure of a SvelteKit project](https://www.inow.dev/understanding-the-structure-of-a-svelte-kit-project/) by Igor Nowosad
- [Secure Authentication in Svelte using Hooks](https://dev.to/brewhousedigital/secure-authentication-in-svelte-using-hooks-k5j) by Brewhouse Digital
**Libraries, Tools & Components**
- [@vavite/node-loader](https://github.com/cyco130/vavite/tree/main/packages/node-loader) is a Node ESM loader that uses Vite to transpile modules to enable sourcemap and breakpoints support in SvelteKit (or any Vite) project
- [Inlang](https://github.com/inlang/inlang) is building i18n for SvelteKit and is [looking for feedback](https://www.reddit.com/r/sveltejs/comments/11ydtui/sveltekit_and_i18n_lets_finally_solve_this_never/)
- [Skeleton](https://www.skeleton.dev/) - the UI toolkit for Svelte and Tailwind - is now 1.0 🎉
- [SvelteKit-integrated-WebSocket](https://github.com/suhaildawood/SvelteKit-integrated-WebSocket) provides first-class support for WebSockets within SvelteKit by attaching a WebSocket server to the global state
- [Svelte Legos](https://github.com/ankurrsinghal/svelte-legos) is a collection of essential Svelte Composition Utilities
- [svelte-stored-writable](https://github.com/efstajas/svelte-stored-writable) is a drop-in extension of Svelte's writable that additionally stores and restores its contents using localStorage.
- [svelte-virtual](https://github.com/ghostebony/svelte-virtual) provides Svelte components for efficiently rendering large lists.
- ChatGPT Clones and Starters
- [chatwithme.chat](https://github.com/kierangilliam/chatwithme.chat) is an open source ChatGPT UI
- [SlickGPT](https://github.com/ShipBit/slickgpt) is a light-weight "use-your-own-API-key" web client for the OpenAI API written in Svelte
- [AI Chat Bestie](https://github.com/KTruong008/aichatbestie) is an unofficial ChatGPT app
- [chatgpt-svelte](https://github.com/ichbtrv/chatgpt-svelte) is a simple UI for the ChatGPT Open AI API
Thanks for reading! And don't forget to try your hand at the [Svelte Hackathon](https://hack.sveltesociety.dev/) 🧑‍💻
As always, feel free to let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.gg/svelte).
See ya next time!

@ -13,7 +13,5 @@ GITHUB_CLIENT_SECRET=
SUPABASE_URL=https://kpaaohfbmxvespqoqdzp.supabase.co
SUPABASE_KEY=
PUBLIC_API_BASE="https://api.svelte.dev"
# client-side
VITE_MAPBOX_ACCESS_TOKEN=

@ -10,3 +10,4 @@
/src/routes/_components/Supporters/donors.jpg
/src/routes/_components/Supporters/donors.js
.vercel
examples-data.js

File diff suppressed because it is too large Load Diff

@ -4,8 +4,8 @@
"description": "Docs and examples for Svelte",
"type": "module",
"scripts": {
"dev": "node scripts/update.js && vite dev",
"build": "node scripts/update.js && vite build",
"dev": "node scripts/generate_examples.js && node scripts/update.js && vite dev",
"build": "node scripts/generate_examples.js && node scripts/update.js && vite build",
"update": "node scripts/update.js --force=true",
"preview": "vite preview",
"start": "node build",
@ -16,19 +16,18 @@
"test": "uvu -r ts-node/register src/lib/server/markdown"
},
"dependencies": {
"@supabase/supabase-js": "^2.13.0",
"@supabase/supabase-js": "^2.13.1",
"@sveltejs/repl": "^0.2.0",
"cookie": "^0.5.0",
"devalue": "^4.3.0",
"do-not-zip": "^1.0.0",
"flexsearch": "^0.7.31",
"flru": "^1.0.2",
"sourcemap-codec": "^1.4.8",
"svelte-local-storage-store": "^0.4.0"
"sourcemap-codec": "^1.4.8"
},
"devDependencies": {
"@resvg/resvg-js": "^2.4.1",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-vercel": "^2.4.1",
"@sveltejs/kit": "^1.15.0",
"@sveltejs/site-kit": "^3.3.6",
"@sveltejs/vite-plugin-svelte": "^2.0.4",

@ -0,0 +1,13 @@
import { get_examples_data } from '../src/lib/server/examples/get-examples.js';
import fs from 'node:fs';
const examples_data = get_examples_data(
new URL('../../../site/content/examples', import.meta.url).pathname
);
fs.mkdirSync(new URL('../src/lib/generated/', import.meta.url), { recursive: true });
fs.writeFileSync(
new URL('../src/lib/generated/examples-data.js', import.meta.url),
`export default ${JSON.stringify(examples_data)}`
);

@ -1,21 +1,21 @@
<script>
import Repl from '@sveltejs/repl';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { process_example } from '$lib/utils/examples';
import { PUBLIC_API_BASE } from '$env/static/public';
import Repl from '@sveltejs/repl';
import { onMount } from 'svelte';
export let version = '3';
export let gist = null;
export let example = null;
export let embedded = false;
/** @type {import('@sveltejs/repl').default} */
let repl;
let name = 'loading...';
let mounted = false;
function load(gist, example) {
async function load(gist, example) {
if (version !== 'local') {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then((r) => r.json())
@ -58,17 +58,14 @@
repl.set({ components });
});
} else if (example) {
fetch(`${PUBLIC_API_BASE}/docs/svelte/examples/${example}`).then(async (response) => {
if (response.ok) {
const data = await response.json();
const components = process_example(data.files);
const components = process_example(
(await fetch(`/examples/api/${example}.json`).then((r) => r.json())).files
);
repl.set({
components,
});
}
});
}
}
onMount(() => {

@ -1,18 +1,15 @@
// @ts-check
import fs from 'node:fs';
const base = '../../site/content/examples/';
const BASE = '../../site/content/examples/';
/**
* @returns {import('./types').ExamplesData}
*/
export function get_examples_data() {
export function get_examples_data(base = BASE) {
const examples = [];
for (const subdir of fs.readdirSync(base)) {
// Exclude embeds
if (subdir.endsWith('99-embeds')) continue;
const section = {
title: '', // Initialise with empty
slug: subdir.split('-').slice(1).join('-'),
@ -42,7 +39,7 @@ export function get_examples_data() {
.readdirSync(example_base_dir)
.filter((file) => !file.endsWith('meta.json'))) {
files.push({
filename: file,
name: file,
type: file.split('.').at(-1),
content: fs.readFileSync(`${example_base_dir}/${file}`, 'utf-8')
});

@ -6,8 +6,8 @@ export type ExamplesData = {
slug: string;
files: {
content: string;
type: 'svelte' | 'js';
filename: string;
type: string;
name: string;
}[];
}[];
}[];

@ -0,0 +1,24 @@
// @ts-check
import fs from 'node:fs';
import { extract_frontmatter } from '../markdown';
const base = '../../site/content/faq';
/**
* @returns {import('./types').FAQData}
*/
export function get_faq_data() {
const faqs = [];
for (const file of fs.readdirSync(base)) {
const { metadata, body } = extract_frontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8'));
faqs.push({
title: metadata.question, // Initialise with empty
slug: file.split('-').slice(1).join('-').replace('.md', ''),
content: body,
});
}
return faqs;
}

@ -0,0 +1,87 @@
// @ts-check
import { createShikiHighlighter } from 'shiki-twoslash';
import { transform } from '../markdown';
const languages = {
bash: 'bash',
env: 'bash',
html: 'svelte',
svelte: 'svelte',
sv: 'svelte',
js: 'javascript',
css: 'css',
diff: 'diff',
ts: 'typescript',
'': '',
};
/**
* @param {import('./types').FAQData} faq_data
*/
export async function get_parsed_faq(faq_data) {
const highlighter = await createShikiHighlighter({ theme: 'css-variables' });
return Promise.all(
faq_data.map(async ({ content, slug, title }) => {
return {
title,
slug,
content: transform(content, {
/**
* @param {string} html
*/
heading(html) {
const title = html
.replace(/<\/?code>/g, '')
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
return title;
},
code: (source, language) => {
let html = '';
source = source
.replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => {
if (prefix && language !== 'diff') return match;
// for no good reason at all, marked replaces tabs with spaces
let tabs = '';
for (let i = 0; i < spaces.length; i += 4) {
tabs += ' ';
}
return prefix + tabs;
})
.replace(/\*\\\//g, '*/');
html = highlighter.codeToHtml(source, { lang: languages[language] });
html = html
.replace(
/^(\s+)<span class="token comment">([\s\S]+?)<\/span>\n/gm,
(match, intro_whitespace, content) => {
// we use some CSS trickery to make comments break onto multiple lines while preserving indentation
const lines = (intro_whitespace + content + '').split('\n');
return lines
.map((line) => {
const match = /^(\s*)(.*)/.exec(line);
const indent = (match?.[1] ?? '').replace(/\t/g, ' ').length;
return `<span class="token comment wrapped" style="--indent: ${indent}ch">${
line ?? ''
}</span>`;
})
.join('');
}
)
.replace(/\/\*…\*\//g, '…');
return html;
},
codespan: (text) => '<code>' + text + '</code>',
}),
};
})
);
}

@ -0,0 +1,5 @@
export type FAQData = {
title: string;
slug: string;
content: string;
}[];

@ -2,12 +2,12 @@
import fs from 'node:fs';
import { extract_frontmatter } from '../markdown/index.js';
const base = '../../site/content/tutorial/';
const BASE = '../../site/content/tutorial/';
/**
* @returns {import('./types').TutorialData}
*/
export function get_tutorial_data() {
export function get_tutorial_data(base = BASE) {
const tutorials = [];
for (const subdir of fs.readdirSync(base)) {

@ -2,6 +2,6 @@ import * as session from '$lib/db/session';
export async function load({ request }) {
return {
user: await session.from_cookie(request.headers.get('cookie'))
user: session.from_cookie(request.headers.get('cookie')),
};
}

@ -1,15 +1,19 @@
import { error, json } from '@sveltejs/kit';
import { dev } from '$app/environment';
import * as session from '$lib/db/session';
import { client } from '$lib/db/client';
import * as gist from '$lib/db/gist';
import { PUBLIC_API_BASE } from '$env/static/public';
import { get_example } from '$lib/server/examples';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { error, json } from '@sveltejs/kit';
import examples_data from '$lib/generated/examples-data.js';
export const prerender = 'auto';
const UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/;
/** @type {Set<string>} */
let examples;
/** @param {import('$lib/server/examples/types').ExamplesData[number]['examples'][number]['files'][number][]} files */
function munge(files) {
return files
.map((file) => {
@ -18,6 +22,7 @@ function munge(files) {
let type = file.name.slice(dot + 1);
if (type === 'html') type = 'svelte';
// @ts-expect-error what is file.source? by @PuruVJ
return { name, type, source: file.source ?? file.content ?? '' };
})
.sort((a, b) => {
@ -31,33 +36,25 @@ function munge(files) {
}
export async function GET({ params }) {
if (!examples) {
const res = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples`);
// Currently, these pages(that are in examples/) are prerendered. To avoid making any FS requests,
// We prerender examples pages during build time. That means, when something like `/repl/hello-world.json`
// is accessed, this function won't be run at all, as it will be served from the filesystem
examples = new Set(
(await res.json())
get_examples_list(examples_data)
.map((category) => category.examples)
.flat()
.map((example) => example.slug)
);
}
if (examples.has(params.id)) {
const res = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples/${params.id}`);
if (!res.ok) {
return new Response(await res.json(), {
status: res.status,
headers: { 'Content-Type': 'application/json' }
});
}
const example = await res.json();
const example = get_example(examples_data, params.id);
return json({
id: params.id,
name: example.name,
name: example.title,
owner: null,
relaxed: example.relaxed, // TODO is this right?
relaxed: false, // TODO is this right? EDIT: It was example.relaxed before, which no example return to my knowledge. By @PuruVJ
components: munge(example.files)
});
}
@ -65,7 +62,16 @@ export async function GET({ params }) {
if (dev && !client) {
// in dev with no local Supabase configured, proxy to production
// this lets us at least load saved REPLs
return await fetch(`https://svelte.dev/repl/${params.id}.json`);
const res = await fetch(`https://svelte.dev/repl/${params.id}.json`);
// returning the response directly results in a bizarre
// content encoding error, so we create a new one
return new Response(await res.text(), {
status: res.status,
headers: {
'content-type': 'application/json'
}
});
}
if (!UUID_REGEX.test(params.id)) {
@ -83,17 +89,7 @@ export async function GET({ params }) {
name: app.name,
owner: app.userid,
relaxed: false,
// @ts-expect-error app.files has a `source` property
components: munge(app.files)
});
}
// TODO reimplement as an action
export async function PUT({ params, request }) {
const user = await session.from_cookie(request.headers.get('cookie'));
if (!user) throw error(401, 'Unauthorized');
const body = await request.json();
await gist.update(user, params.id, body);
return new Response(undefined, { status: 204 });
}

@ -11,6 +11,6 @@ export async function load({ fetch, params, url }) {
return {
gist,
version: url.searchParams.get('version') || '3'
version: url.searchParams.get('version') || '3',
};
}

@ -106,7 +106,7 @@
// ~> Any missing files are considered deleted!
const { components } = repl.toJSON();
const r = await fetch(`/repl/${gist.id}.json`, {
const r = await fetch(`/repl/save/${gist.id}.json`, {
method: 'PUT',
credentials: 'include',
headers: {

@ -2,7 +2,6 @@
import { browser } from '$app/environment';
import ReplWidget from '$lib/components/ReplWidget.svelte';
/** @type {import('./$types').PageData} */
export let data;
</script>

@ -0,0 +1,14 @@
import * as gist from '$lib/db/gist';
import * as session from '$lib/db/session';
import { error } from '@sveltejs/kit';
// TODO reimplement as an action
export async function PUT({ params, request }) {
const user = await session.from_cookie(request.headers.get('cookie'));
if (!user) throw error(401, 'Unauthorized');
const body = await request.json();
await gist.update(user, params.id, body);
return new Response(undefined, { status: 204 });
}

@ -15,6 +15,7 @@
</svelte:head>
<Shell nav_visible={$page.url.pathname !== '/repl/embed'}>
{#if $page.url.pathname !== '/repl/embed'}
<Nav logo="/svelte-logo.svg">
<svelte:fragment slot="nav-center">
{#if $page.url.pathname !== '/search'}
@ -45,6 +46,7 @@
</NavItem>
</svelte:fragment>
</Nav>
{/if}
<slot />
</Shell>

@ -1,11 +1,11 @@
<script>
import { Blurb } from '@sveltejs/site-kit/components';
import Supporters from './_components/Supporters/index.svelte';
import Balls from './svelte-balls.png?w=640;1280;2560;3840&format=avif;webp;png&picture';
import Demo from './_components/Demo.svelte';
import Hero from './_components/Hero.svelte';
import Image from './_components/Image.svelte';
import Demo from './_components/Demo.svelte';
import Supporters from './_components/Supporters/index.svelte';
import WhosUsingSvelte from './_components/WhosUsingSvelte/index.svelte';
import Balls from './svelte-balls.png?w=640;1280;2560;3840&format=avif;webp;png&picture';
</script>
<svelte:head>

@ -48,10 +48,12 @@
<a href="/examples">more <span class="large-show">&nbsp;examples</span> &rarr;</a>
</div>
<Example id={selected.id} />
{#if selected}
<Example id={selected?.id} />
{/if}
</div>
<p class="description">{@html selected.description}</p>
<p class="description">{@html selected?.description}</p>
</Section>
<style>

@ -16,7 +16,7 @@
let repl;
const clone = (file) => ({
name: file.filename.replace(/.\w+$/, ''),
name: file.name.replace(/.\w+$/, ''),
type: file.type,
source: file.content,
});

@ -14,6 +14,7 @@
<ul class="examples-toc">
{#each sections as section}
{#if section.title !== undefined}
<li>
<span class="section-title">{section.title}</span>
@ -39,6 +40,7 @@
</div>
{/each}
</li>
{/if}
{/each}
</ul>

@ -0,0 +1,8 @@
// @ts-check
import examples_data from '$lib/generated/examples-data.js';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { json } from '@sveltejs/kit';
export const GET = () => {
return json(get_examples_list(examples_data));
};

@ -0,0 +1,17 @@
import examples_data from '$lib/generated/examples-data.js';
import { get_example } from '$lib/server/examples';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { error, json } from '@sveltejs/kit';
export const GET = ({ params }) => {
const examples = new Set(
get_examples_list(examples_data)
.map((category) => category.examples)
.flat()
.map((example) => example.slug)
);
if (!examples.has(params.slug)) throw error(404, 'Example not found');
return json(get_example(examples_data, params.slug));
};

@ -1,12 +0,0 @@
import { PUBLIC_API_BASE } from '$env/static/public';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const faqs = await fetch(`${PUBLIC_API_BASE}/docs/svelte/faq?content`).then((r) => r.json());
setHeaders({
'cache-control': 'public, max-age=60'
});
return { faqs };
}

@ -0,0 +1,8 @@
import { get_parsed_faq } from '$lib/server/faq';
import { get_faq_data } from '$lib/server/faq/get-faq';
export const prerender = true;
export async function load() {
return { faqs: get_parsed_faq(get_faq_data()) };
}

@ -1,7 +1,6 @@
<script>
import '@sveltejs/site-kit/styles/code.css';
/** @type {import('./$types').PageData} */
export let data;
</script>
@ -85,11 +84,7 @@
@media (min-width: 768px) {
.faqs :global(.anchor:focus),
.faqs :global(h2):hover :global(.anchor),
.faqs :global(h3):hover :global(.anchor),
.faqs :global(h4):hover :global(.anchor),
.faqs :global(h5):hover :global(.anchor),
.faqs :global(h6):hover :global(.anchor) {
.faqs :global(:where(h2, h3, h4, h5, h6)):hover :global(.anchor) {
opacity: 1;
}

@ -1,5 +1,5 @@
import { get_parsed_tutorial } from '$lib/server/tutorial';
import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/get-tutorial-data';
import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/get-tutorial';
import { error } from '@sveltejs/kit';
export const prerender = true;

@ -153,16 +153,6 @@
{/if}
</div>
<!-- HACK to prerender -->
<p style="display: none;">
{#each data.tutorials_list as { tutorials }}
{#each tutorials as { slug }}
<!-- svelte-ignore a11y-missing-content -->
<a href="/tutorial/{slug}" />
{/each}
{/each}
</p>
<style>
.tutorial-outer {
position: relative;

@ -1,9 +1,31 @@
// @ts-check
import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-vercel';
import { get_examples_data, get_examples_list } from './src/lib/server/examples/get-examples.js';
import { get_tutorial_data, get_tutorial_list } from './src/lib/server/tutorial/get-tutorial.js';
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: adapter()
adapter: adapter(),
prerender: {
// TODO use route entries instead, once https://github.com/sveltejs/kit/pull/9571 is merged
entries: ['*', ...repl_json_entries(), ...tutorial_entries()]
}
}
};
function repl_json_entries() {
return get_examples_list(
get_examples_data(new URL('../../site/content/examples', import.meta.url).pathname)
).flatMap(({ examples }) =>
examples.map(({ slug }) => /** @type {(`/${string}`)} */ (`/repl/${slug}.json`))
);
}
function tutorial_entries() {
return get_tutorial_list(
get_tutorial_data(new URL('../../site/content/tutorial', import.meta.url).pathname)
).flatMap(({ tutorials }) =>
tutorials.map(({ slug }) => /** @type {(`/${string}`)} */ (`/tutorial/${slug}`))
);
}

@ -50,6 +50,22 @@ const config = {
fs: {
strict: false
}
},
// !HACK: Remove once vite 4.3 is out
worker: {
plugins: [
{
name: 'remove-manifest',
configResolved(c) {
const manifestPlugin = c.worker.plugins.findIndex((p) => p.name === 'vite:manifest');
c.worker.plugins.splice(manifestPlugin, 1);
const ssrManifestPlugin = c.worker.plugins.findIndex(
(p) => p.name === 'vite:ssr-manifest'
);
c.plugins.splice(ssrManifestPlugin, 1);
}
}
]
}
};

@ -173,7 +173,7 @@ class Atrule {
}
apply(node: Element) {
if (this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') {
if (this.node.name === 'container' || this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') {
this.children.forEach(child => {
child.apply(node);
});

@ -11,7 +11,7 @@ export type Context = DestructuredVariable | ComputedProperty;
interface ComputedProperty {
type: 'ComputedProperty';
property_name: string;
property_name: Identifier;
key: Expression | PrivateIdentifier;
}
@ -30,8 +30,7 @@ export function unpack_destructuring({
default_modifier = (node) => node,
scope,
component,
context_rest_properties,
number_of_computed_props = { n: 0 }
context_rest_properties
}: {
contexts: Context[];
node: Node;
@ -40,10 +39,6 @@ export function unpack_destructuring({
scope: TemplateScope;
component: Component;
context_rest_properties: Map<string, Node>;
// we want to pass this by reference, as a sort of global variable, because
// if we pass this by value, we could get computed_property_# variable collisions
// when we deal with nested object destructuring
number_of_computed_props?: { n: number };
}) {
if (!node) return;
@ -72,8 +67,7 @@ export function unpack_destructuring({
default_modifier,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
context_rest_properties.set((element.argument as Identifier).name, element);
} else if (element && element.type === 'AssignmentPattern') {
@ -93,8 +87,7 @@ export function unpack_destructuring({
)}` as Node,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
} else {
unpack_destructuring({
@ -104,8 +97,7 @@ export function unpack_destructuring({
default_modifier,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
}
});
@ -124,8 +116,7 @@ export function unpack_destructuring({
default_modifier,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
context_rest_properties.set((property.argument as Identifier).name, property);
} else if (property.type === 'Property') {
@ -136,8 +127,7 @@ export function unpack_destructuring({
if (property.computed) {
// e.g { [computedProperty]: ... }
const property_name = `computed_property_${number_of_computed_props.n}`;
number_of_computed_props.n += 1;
const property_name = component.get_unique_name('computed_property');
contexts.push({
type: 'ComputedProperty',
@ -178,8 +168,7 @@ export function unpack_destructuring({
)}` as Node,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
} else {
// e.g. { property } or { property: newName }
@ -190,8 +179,7 @@ export function unpack_destructuring({
default_modifier,
scope,
component,
context_rest_properties,
number_of_computed_props
context_rest_properties
});
}
}

@ -0,0 +1,41 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Comment from '../../nodes/Comment';
import Wrapper from './shared/Wrapper';
import { x } from 'code-red';
import { Identifier } from 'estree';
export default class CommentWrapper extends Wrapper {
node: Comment;
var: Identifier;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Comment
) {
super(renderer, block, parent, node);
this.var = x`c` as Identifier;
}
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (!this.renderer.options.preserveComments) return;
const string_literal = {
type: 'Literal',
value: this.node.data,
loc: {
start: this.renderer.locate(this.node.start),
end: this.renderer.locate(this.node.end)
}
};
block.add_element(
this.var,
x`@comment(${string_literal})`,
parent_nodes && x`@claim_comment(${parent_nodes}, ${string_literal})`,
parent_node
);
}
}

@ -364,7 +364,6 @@ const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [k
indeterminate: { applies_to: ['input'] },
inert: {},
ismap: { property_name: 'isMap', applies_to: ['img'] },
itemscope: {},
loop: { applies_to: ['audio', 'bgsound', 'video'] },
multiple: { applies_to: ['input', 'select'] },
muted: { applies_to: ['audio', 'video'] },

@ -14,6 +14,7 @@ import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import SlotTemplate from './SlotTemplate';
import Text from './Text';
import Comment from './Comment';
import Title from './Title';
import Window from './Window';
import { INode } from '../../nodes/interfaces';
@ -27,7 +28,7 @@ import { regex_starts_with_whitespace } from '../../../utils/patterns';
const wrappers = {
AwaitBlock,
Body,
Comment: null,
Comment,
DebugTag,
Document,
EachBlock,
@ -118,7 +119,7 @@ export default class FragmentWrapper {
link(last_child, last_child = wrapper);
} else {
const Wrapper = wrappers[child.type];
if (!Wrapper) continue;
if (!Wrapper || (child.type === 'Comment' && !renderer.options.preserveComments)) continue;
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling);
this.nodes.unshift(wrapper);

@ -19,11 +19,17 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
export function get_attribute_value(attribute: Attribute): ESTreeExpression {
if (attribute.chunks.length === 0) return x`""`;
/**
* For value attribute of textarea, it will render as child node of `<textarea>` element.
* Therefore, we need to escape as content (not attribute).
*/
const is_textarea_value = attribute.parent.name.toLowerCase() === 'textarea' && attribute.name.toLowerCase() === 'value';
return attribute.chunks
.map((chunk) => {
return chunk.type === 'Text'
? string_literal(chunk.data.replace(regex_double_quotes, '&quot;')) as ESTreeExpression
: x`@escape(${chunk.node}, true)`;
: x`@escape(${chunk.node}, ${is_textarea_value ? 'false' : 'true'})`;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}

@ -0,0 +1,31 @@
// @ts-nocheck
// Note: Must import from the `css-tree` browser bundled distribution due to `createRequire` usage if importing from
// `css-tree` Node module directly. This allows the production build of Svelte to work correctly.
import { fork } from '../../../../../node_modules/css-tree/dist/csstree.esm.js';
import * as node from './node';
/**
* Extends `css-tree` for container query support by forking and adding new nodes and at-rule support for `@container`.
*
* The new nodes are located in `./node`.
*/
const cqSyntax = fork({
atrule: { // extend or override at-rule dictionary
container: {
parse: {
prelude() {
return this.createSingleNodeList(
this.ContainerQuery()
);
},
block(isStyleBlock = false) {
return this.Block(isStyleBlock);
}
}
}
},
node
});
export const parse = cqSyntax.parse;

@ -0,0 +1,48 @@
// @ts-nocheck
import { Delim } from 'css-tree/tokenizer';
export const name = 'Comparison';
export const structure = {
value: String
};
export function parse() {
const start = this.tokenStart;
const char1 = this.consume(Delim);
// The first character in the comparison operator must match '<', '=', or '>'.
if (char1 !== '<' && char1 !== '>' && char1 !== '=') {
this.error('Malformed comparison operator');
}
let char2;
if (this.tokenType === Delim) {
char2 = this.consume(Delim);
// The second character in the comparison operator must match '='.
if (char2 !== '=') {
this.error('Malformed comparison operator');
}
}
// If the next token is also 'Delim' then it is malformed.
if (this.tokenType === Delim) {
this.error('Malformed comparison operator');
}
const value = char2 ? `${char1}${char2}` : char1;
return {
type: 'Comparison',
loc: this.getLocation(start, this.tokenStart),
value
};
}
export function generate(node) {
for (let index = 0; index < node.value.length; index++) {
this.token(Delim, node.value.charAt(index));
}
}

@ -0,0 +1,85 @@
// @ts-nocheck
import {
Function,
Ident,
Number,
Dimension,
RightParenthesis,
Colon,
Delim
} from 'css-tree/tokenizer';
export const name = 'ContainerFeatureStyle';
export const structure = {
name: String,
value: ['Function', 'Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
};
export function parse() {
const start = this.tokenStart;
let value = null;
const function_name = this.consumeFunctionName();
if (function_name !== 'style') {
this.error('Unknown container style query identifier; "style" is expected');
}
this.skipSC();
const name = this.consume(Ident);
this.skipSC();
if (this.tokenType !== RightParenthesis) {
this.eat(Colon);
this.skipSC();
switch (this.tokenType) {
case Number:
if (this.lookupNonWSType(1) === Delim) {
value = this.Ratio();
} else {
value = this.Number();
}
break;
case Dimension:
value = this.Dimension();
break;
case Function:
value = this.QueryCSSFunction();
break;
case Ident:
value = this.Identifier();
break;
default:
this.error('Number, dimension, ratio, function or identifier is expected');
break;
}
this.skipSC();
}
this.eat(RightParenthesis);
return {
type: 'ContainerFeatureStyle',
loc: this.getLocation(start, this.tokenStart),
name,
value
};
}
export function generate(node) {
this.token(Function, 'style(');
this.token(Ident, node.name);
if (node.value !== null) {
this.token(Colon, ':');
this.node(node.value);
}
this.token(RightParenthesis, ')');
}

@ -0,0 +1,92 @@
// @ts-nocheck
import {
WhiteSpace,
Comment,
Function,
Ident,
LeftParenthesis
} from 'css-tree/tokenizer';
import { lookahead_is_range } from './lookahead_is_range';
const CONTAINER_QUERY_KEYWORDS = new Set(['none', 'and', 'not', 'or']);
export const name = 'ContainerQuery';
export const structure = {
name: 'Identifier',
children: [[
'Identifier',
'QueryFeature',
'QueryFeatureRange',
'ContainerFeatureStyle',
'WhiteSpace'
]]
};
export function parse() {
const start = this.tokenStart;
const children = this.createList();
let child = null;
let name = null;
// Parse potential container name.
if (this.tokenType === Ident) {
const container_name = this.substring(this.tokenStart, this.tokenEnd);
// Container name doesn't match a query keyword, so assign it as container name.
if (!CONTAINER_QUERY_KEYWORDS.has(container_name.toLowerCase())) {
name = container_name;
this.eatIdent(container_name);
}
}
this.skipSC();
scan:
while (!this.eof) {
switch (this.tokenType) {
case Comment:
case WhiteSpace:
this.next();
continue;
case Ident:
child = this.Identifier();
break;
case Function:
child = this.ContainerFeatureStyle();
break;
case LeftParenthesis:
// Lookahead to determine if range feature.
child = lookahead_is_range.call(this) ? this.QueryFeatureRange() : this.QueryFeature();
break;
default:
break scan;
}
children.push(child);
}
if (child === null) {
this.error('Identifier or parenthesis is expected');
}
return {
type: 'ContainerQuery',
loc: this.getLocation(start, this.tokenStart - 1),
name,
children
};
}
export function generate(node) {
if (typeof node.name === 'string') {
this.token(Ident, node.name);
}
this.children(node);
}

@ -0,0 +1,7 @@
export * as Comparison from './comparison';
export * as ContainerFeatureStyle from './container_feature_style';
export * as ContainerQuery from './container_query';
export * as MediaQuery from './media_query';
export * as QueryFeature from './query_feature';
export * as QueryFeatureRange from './query_feature_range';
export * as QueryCSSFunction from './query_css_function';

@ -0,0 +1,44 @@
// @ts-nocheck
import {
EOF,
WhiteSpace,
Delim,
RightParenthesis,
LeftCurlyBracket,
Colon
} from 'css-tree/tokenizer';
/**
* Looks ahead to determine if query feature is a range query. This involves locating at least one delimiter and no
* colon tokens.
*
* @returns {boolean} Is potential range query.
*/
export function lookahead_is_range() {
let type;
let offset = 0;
let count = 0;
let delim_found = false;
let no_colon = true;
// A range query has maximum 5 tokens when formatted as 'mf-range' /
// '<mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>'. So only look ahead maximum of 6 non-whitespace tokens.
do {
type = this.lookupNonWSType(offset++);
if (type !== WhiteSpace) {
count++;
}
if (type === Delim) {
delim_found = true;
}
if (type === Colon) {
no_colon = false;
}
if (type === LeftCurlyBracket || type === RightParenthesis) {
break;
}
} while (type !== EOF && count <= 6);
return delim_found && no_colon;
}

@ -0,0 +1,64 @@
// @ts-nocheck
import {
WhiteSpace,
Comment,
Ident,
LeftParenthesis
} from 'css-tree/tokenizer';
import { lookahead_is_range } from './lookahead_is_range';
export const name = 'MediaQuery';
export const structure = {
children: [[
'Identifier',
'QueryFeature',
'QueryFeatureRange',
'WhiteSpace'
]]
};
export function parse() {
const children = this.createList();
let child = null;
this.skipSC();
scan:
while (!this.eof) {
switch (this.tokenType) {
case Comment:
case WhiteSpace:
this.next();
continue;
case Ident:
child = this.Identifier();
break;
case LeftParenthesis:
// Lookahead to determine if range feature.
child = lookahead_is_range.call(this) ? this.QueryFeatureRange() : this.QueryFeature();
break;
default:
break scan;
}
children.push(child);
}
if (child === null) {
this.error('Identifier or parenthesis is expected');
}
return {
type: 'MediaQuery',
loc: this.getLocationFromList(children),
children
};
}
export function generate(node) {
this.children(node);
}

@ -0,0 +1,41 @@
// @ts-nocheck
import {
RightParenthesis
} from 'css-tree/tokenizer';
const QUERY_CSS_FUNCTIONS = new Set(['calc', 'clamp', 'min', 'max']);
export const name = 'QueryCSSFunction';
export const structure = {
name: String,
expression: String
};
export function parse() {
const start = this.tokenStart;
const name = this.consumeFunctionName();
if (!QUERY_CSS_FUNCTIONS.has(name)) {
this.error('Unknown query single value function; expected: "calc", "clamp", "max", min"');
}
const body = this.Raw(this.tokenIndex, null, false);
this.eat(RightParenthesis);
return {
type: 'QueryCSSFunction',
loc: this.getLocation(start, this.tokenStart),
name,
expression: body.value
};
}
export function generate(node) {
this.token(Function, `${node.name}(`);
this.node(node.expression);
this.token(RightParenthesis, ')');
}

@ -0,0 +1,82 @@
// @ts-nocheck
import {
Ident,
Number,
Dimension,
Function,
LeftParenthesis,
RightParenthesis,
Colon,
Delim
} from 'css-tree/tokenizer';
export const name = 'QueryFeature';
export const structure = {
name: String,
value: ['Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
};
export function parse() {
const start = this.tokenStart;
let value = null;
this.eat(LeftParenthesis);
this.skipSC();
const name = this.consume(Ident);
this.skipSC();
if (this.tokenType !== RightParenthesis) {
this.eat(Colon);
this.skipSC();
switch (this.tokenType) {
case Number:
if (this.lookupNonWSType(1) === Delim) {
value = this.Ratio();
} else {
value = this.Number();
}
break;
case Dimension:
value = this.Dimension();
break;
case Function:
value = this.QueryCSSFunction();
break;
case Ident:
value = this.Identifier();
break;
default:
this.error('Number, dimension, ratio, function, or identifier is expected');
break;
}
this.skipSC();
}
this.eat(RightParenthesis);
return {
type: 'QueryFeature',
loc: this.getLocation(start, this.tokenStart),
name,
value
};
}
export function generate(node) {
this.token(LeftParenthesis, '(');
this.token(Ident, node.name);
if (node.value !== null) {
this.token(Colon, ':');
this.node(node.value);
}
this.token(RightParenthesis, ')');
}

@ -0,0 +1,87 @@
// @ts-nocheck
import {
Ident,
Number,
Delim,
Dimension,
Function,
LeftParenthesis,
RightParenthesis,
WhiteSpace
} from 'css-tree/tokenizer';
export const name = 'QueryFeatureRange';
export const structure = {
name: String,
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
};
function lookup_non_WS_type_and_value(offset, type, referenceStr) {
let current_type;
do {
current_type = this.lookupType(offset++);
if (current_type !== WhiteSpace) {
break;
}
} while (current_type !== 0); // NULL -> 0
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false;
}
export function parse() {
const start = this.tokenStart;
const children = this.createList();
let child = null;
this.eat(LeftParenthesis);
this.skipSC();
while (!this.eof && this.tokenType !== RightParenthesis) {
switch (this.tokenType) {
case Number:
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) {
child = this.Ratio();
} else {
child = this.Number();
}
break;
case Delim:
child = this.Comparison();
break;
case Dimension:
child = this.Dimension();
break;
case Function:
child = this.QueryCSSFunction();
break;
case Ident:
child = this.Identifier();
break;
default:
this.error('Number, dimension, comparison, ratio, function, or identifier is expected');
break;
}
children.push(child);
this.skipSC();
}
this.eat(RightParenthesis);
return {
type: 'QueryFeatureRange',
loc: this.getLocation(start, this.tokenStart),
children
};
}
export function generate(node) {
this.children(node);
}

@ -1,5 +1,6 @@
// @ts-ignore
import parse from 'css-tree/parser';
// import parse from 'css-tree/parser'; // When css-tree supports container queries uncomment.
import { parse } from './css-tree-cq/css_tree_parse'; // Use extended css-tree for container query support.
import { walk } from 'estree-walker';
import { Parser } from '../index';
import { Node } from 'estree';

@ -164,8 +164,9 @@ export interface SvelteComponentDev {
$destroy(): void;
[accessor: string]: any;
}
export interface ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>> {
target: Element | ShadowRoot;
target: Element | Document | ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;

@ -254,6 +254,10 @@ export function empty() {
return text('');
}
export function comment(content: string) {
return document.createComment(content);
}
export function listen(node: EventTarget, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) {
node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options);
@ -550,6 +554,19 @@ export function claim_space(nodes) {
return claim_text(nodes, ' ');
}
export function claim_comment(nodes:ChildNodeArray, data) {
return claim_node<Comment>(
nodes,
(node: ChildNode): node is Comment => node.nodeType === 8,
(node: Comment) => {
node.data = '' + data;
return undefined;
},
() => comment(data),
true
);
}
function find_comment(nodes, text, start) {
for (let i = start; i < nodes.length; i += 1) {
const node = nodes[i];

@ -13,7 +13,6 @@ const _boolean_attributes = [
'hidden',
'inert',
'ismap',
'itemscope',
'loop',
'multiple',
'muted',

@ -0,0 +1 @@
div.svelte-xyz{container:test-container / inline-size}@container (min-width: 400px){div.svelte-xyz{color:red}}@container test-container (min-width: 410px){div.svelte-xyz{color:green}}@container test-container (width < 400px){div.svelte-xyz{color:blue}}@container test-container (0 <= width < 300px){div.svelte-xyz{color:purple}}@container not (width < 400px){div.svelte-xyz{color:pink}}@container (width > 400px) and (height > 400px){div.svelte-xyz{color:lightgreen}}@container (width > 400px) or (height > 400px){div.svelte-xyz{color:lightblue}}@container (width > 400px) and (width > 800px) or (orientation: portrait){div.svelte-xyz{color:salmon}}@container style(color: blue){div.svelte-xyz{color:tan}}@container test-container (min-width: calc(400px + 1px)){div.svelte-xyz{color:green}}@container test-container (width < clamp(200px, 40%, 400px)){div.svelte-xyz{color:blue}}@container test-container (calc(400px + 1px) <= width < calc(500px + 1px)){div.svelte-xyz{color:purple}}@container style(--var: calc(400px + 1px)){div.svelte-xyz{color:sandybrown}}

@ -0,0 +1,87 @@
<div>container query</div>
<style>
div {
container: test-container / inline-size;
}
/* Most common container query statements. */
@container (min-width: 400px) {
div {
color: red;
}
}
@container test-container (min-width: 410px) {
div {
color: green;
}
}
@container test-container (width < 400px) {
div {
color: blue;
}
}
@container test-container (0 <= width < 300px) {
div {
color: purple;
}
}
@container not (width < 400px) {
div {
color: pink;
}
}
@container (width > 400px) and (height > 400px) {
div {
color: lightgreen;
}
}
@container (width > 400px) or (height > 400px) {
div {
color: lightblue;
}
}
@container (width > 400px) and (width > 800px) or (orientation: portrait) {
div {
color: salmon;
}
}
@container style(color: blue) {
div {
color: tan;
}
}
@container test-container (min-width: calc(400px + 1px)) {
div {
color: green;
}
}
@container test-container (width < clamp(200px, 40%, 400px)) {
div {
color: blue;
}
}
@container test-container (calc(400px + 1px) <= width < calc(500px + 1px)) {
div {
color: purple;
}
}
@container style(--var: calc(400px + 1px)) {
div {
color: sandybrown;
}
}
</style>

@ -1 +1 @@
@media(min-width: 400px){.large-screen.svelte-xyz{display:block}}
@media(min-width: 400px){.large-screen.svelte-xyz{display:block}}@media(min-width: calc(400px + 1px)){.large-screen.svelte-xyz{display:block}}@media(width >= 600px){.large-screen.svelte-xyz{display:block}}@media(400px <= width <= 1000px){.large-screen.svelte-xyz{display:block}}@media(width < clamp(200px, 40%, 400px)){.large-screen.svelte-xyz{display:block}}@media(calc(400px + 1px) <= width <= calc(1000px + 1px)){.large-screen.svelte-xyz{display:block}}

@ -6,4 +6,34 @@
display: block;
}
}
@media (min-width: calc(400px + 1px)) {
.large-screen {
display: block;
}
}
@media (width >= 600px) {
.large-screen {
display: block;
}
}
@media (400px <= width <= 1000px) {
.large-screen {
display: block;
}
}
@media (width < clamp(200px, 40%, 400px)) {
.large-screen {
display: block;
}
}
@media (calc(400px + 1px) <= width <= calc(1000px + 1px)) {
.large-screen {
display: block;
}
}
</style>

@ -0,0 +1 @@
<div><!-- test1 --><!-- test2 --></div>

@ -0,0 +1,20 @@
export default {
compileOptions: {
preserveComments:true
},
snapshot(target) {
const div = target.querySelector('div');
return {
div,
comment: div.childNodes[0]
};
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.childNodes[0], snapshot.comment);
assert.equal(div.childNodes[1].nodeType, 8);
}
};

@ -0,0 +1 @@
<div><!-- test1 --><!-- test2 --></div>

@ -0,0 +1,5 @@
export default {
options: {
preserveComments: true
}
};

@ -0,0 +1,58 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
comment,
detach,
element,
init,
insert,
noop,
safe_not_equal,
space
} from "svelte/internal";
function create_fragment(ctx) {
let div0;
let t1;
let c;
let t2;
let div1;
return {
c() {
div0 = element("div");
div0.textContent = "content";
t1 = space();
c = comment(" comment ");
t2 = space();
div1 = element("div");
div1.textContent = "more content";
},
m(target, anchor) {
insert(target, div0, anchor);
insert(target, t1, anchor);
insert(target, c, anchor);
insert(target, t2, anchor);
insert(target, div1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(div0);
if (detaching) detach(t1);
if (detaching) detach(c);
if (detaching) detach(t2);
if (detaching) detach(div1);
}
};
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,3 @@
<div>content</div>
<!-- comment -->
<div>more content</div>

@ -0,0 +1,10 @@
export default {
props: {
hidden: true
},
html: '<div hidden />',
test({ assert, component, target }) {
component.hidden = false;
assert.htmlEqual(target.innerHTML, '<div />');
}
};

@ -0,0 +1,5 @@
<script>
export let hidden = false;
</script>
<div {hidden} />

@ -1,11 +0,0 @@
export default {
props: {
itemscope: true
},
test({ assert, target, component }) {
const div = target.querySelector('div');
assert.ok(div.itemscope);
component.itemscope = false;
assert.ok(!div.itemscope);
}
};

@ -1,5 +0,0 @@
<script>
export let itemscope;
</script>
<div {itemscope} />

@ -0,0 +1,4 @@
export default {
html: '<textarea></textarea>',
ssrHtml: '<textarea>test\'"&gt;&lt;/textarea&gt;&lt;script&gt;alert(\'BIM\');&lt;/script&gt;</textarea>'
};

@ -0,0 +1 @@
<textarea value={`test'"></textarea><script>alert('BIM');</script>`} />

@ -0,0 +1,25 @@
// There is no relationship between the attribute and the dom node with regards to microdata attributes https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata
export default {
html: `<div itemscope itemtype="https://schema.org/SoftwareApplication">
<span itemprop="name">Game</span> - REQUIRES
<span itemprop="operatingSystem">OS</span><br/>
<link itemprop="applicationCategory" href="https://schema.org/GameApplication"/>
<div itemprop="aggregateRating" itemscope="" itemtype="https://schema.org/AggregateRating">RATING:
<span itemprop="ratingValue">4.6</span> (
<span itemprop="ratingCount">8864</span> ratings )</div>
<div itemref="offers"></div>
</div>
<div
itemprop="offers"
itemid="offers"
id="offers"
itemscope
itemtype="https://schema.org/Offer"
>
Price: $<span itemprop="price">1.00</span>
<meta itemprop="priceCurrency" content="USD"/>
</div>
`
};

@ -0,0 +1,31 @@
<!-- Example from https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata -->
<div itemscope itemtype="https://schema.org/SoftwareApplication">
<span itemprop="name">Game</span> - REQUIRES
<span itemprop="operatingSystem">OS</span><br />
<link
itemprop="applicationCategory"
href="https://schema.org/GameApplication"
/>
<div
itemprop="aggregateRating"
itemscope
itemtype="https://schema.org/AggregateRating"
>
RATING:
<span itemprop="ratingValue">4.6</span> (
<span itemprop="ratingCount">8864</span> ratings )
</div>
<div itemref="offers" />
</div>
<div
itemprop="offers"
itemid="offers"
id="offers"
itemscope
itemtype="https://schema.org/Offer"
>
Price: $<span itemprop="price">1.00</span>
<meta itemprop="priceCurrency" content="USD" />
</div>

@ -0,0 +1,20 @@
export default {
html: `
<p>4, 12, 60</p>
`,
async test({ component, target, assert }) {
component.permutation = [2, 3, 1];
await (component.promise1 = Promise.resolve({length: 1, width: 2, height: 3}));
try {
await (component.promise2 = Promise.reject({length: 97, width: 98, height: 99}));
} catch (e) {
// nothing
}
assert.htmlEqual(target.innerHTML, `
<p>2, 11, 2</p>
<p>9506, 28811, 98</p>
`);
}
};

@ -0,0 +1,27 @@
<script>
export let promise1 = {length: 5, width: 3, height: 4};
export let promise2 = {length: 12, width: 5, height: 13};
export let permutation = [1, 2, 3];
function calculate(length, width, height) {
return {
'1-Dimensions': [length, width, height],
'2-Dimensions': [length * width, width * height, length * height],
'3-Dimensions': [length * width * height, length + width + height, length * width + width * height + length * height]
};
}
const th = 'th';
</script>
{#await promise1 then { length, width, height }}
{@const { [0]: a, [1]: b, [2]: c } = permutation}
{@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) }
<p>{first}, {second}, {third}</p>
{/await}
{#await promise2 catch { [`leng${th}`]: l, [`wid${th}`]: w, height: h }}
{@const [a, b, c] = permutation}
{@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(l, w, h) }
<p>{first}, {second}, {third}</p>
{/await}

@ -0,0 +1,15 @@
export default {
html: `
<button>6, 12, 8, 24</button>
<button>45, 35, 63, 315</button>
<button>60, 48, 80, 480</button>
`,
async test({ component, target, assert }) {
component.boxes = [{ length: 10, width: 20, height: 30 }];
assert.htmlEqual(target.innerHTML,
'<button>200, 600, 300, 6000</button>'
);
}
};

@ -0,0 +1,44 @@
<script>
export let boxes = [
{length: 2, width: 3, height: 4},
{length: 9, width: 5, height: 7},
{length: 10, width: 6, height: 8}
];
function calculate(length, width, height) {
return {
twoDimensions: {
bottomArea: length * width,
sideArea1: width * height,
sideArea2: length * height
},
threeDimensions: {
volume: length * width * height
}
};
}
export let dimension = 'Dimensions';
function changeDimension() {
dimension = 'DIMENSIONS';
}
let area = 'Area';
let th = 'th';
</script>
{#each boxes as { [`leng${th}`]: length, [`wid${th}`]: width, height }}
{@const {
[`two${dimension}`]: areas,
[`three${dimension}`]: {
volume
}
} = calculate(length, width, height)}
{@const {
i = 1,
[`bottom${area}`]: bottom,
[`side${area}${i++}`]: sideone,
[`side${area}${i++}`]: sidetwo
} = areas}
<button on:click={changeDimension}>{bottom}, {sideone}, {sidetwo}, {volume}</button>
{/each}
Loading…
Cancel
Save