Merge branch 'master' into sites

pull/8453/head
Rich Harris 2 years ago
commit f4edb381bf

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

4
package-lock.json generated

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,5 +1,6 @@
// @ts-ignore
import parse from 'css-tree/parser';
// import parse from 'css-tree/parser'; // When css-tree supports container queries uncomment.
import { parse } from './css-tree-cq/css_tree_parse'; // Use extended css-tree for container query support.
import { walk } from 'estree-walker';
import { Parser } from '../index';
import { Node } from 'estree';
@ -78,7 +79,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
});
parser.read(regex_starts_with_closing_style_tag);
const end = parser.index;
return {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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