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
|
||||
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}`))
|
||||
);
|
||||
}
|
||||
|
||||
@ -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