mirror of https://github.com/sveltejs/svelte
commit
fe5781f756
@ -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!
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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)}`
|
||||||
|
);
|
||||||
@ -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(/"/g, '"')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/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;
|
||||||
|
}[];
|
||||||
@ -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 });
|
||||||
|
}
|
||||||
@ -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,9 +1,31 @@
|
|||||||
// @ts-check
|
// @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} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
export default {
|
export default {
|
||||||
kit: {
|
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}`))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
@ -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}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<div><!-- test1 --><!-- test2 --></div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<div><!-- test1 --></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\'"></textarea><script>alert(\'BIM\');</script></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…
Reference in new issue