Merge branch 'sveltejs:master' into master

pull/8275/head
Michael Leahy 4 years ago committed by GitHub
commit 39fb408c0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,9 +4,10 @@
* Add a11y warnings:
* `aria-activedescendant-has-tabindex`: elements with `aria-activedescendant` need to have a `tabindex` ([#8172](https://github.com/sveltejs/svelte/pull/8172))
*
* `role-supports-aria-props`: checks that the (implicit) element role supports the given aria attributes ([#8195](https://github.com/sveltejs/svelte/pull/8195))
* Omit a11y warning on `<video>` tags with `aria-hidden="true"` ([#7874](https://github.com/sveltejs/svelte/issues/7874))
* Omit a11y "no child content" warning on elements with `aria-label` ([#8299](https://github.com/sveltejs/svelte/pull/8299))
* Omit a11y warnings on `<svelte:element>` ([#7939](https://github.com/sveltejs/svelte/issues/7939))
* Make `noreferrer` warning less zealous ([#6289](https://github.com/sveltejs/svelte/issues/6289))
* `trusted-types` CSP compatibility for Web Components ([#8134](https://github.com/sveltejs/svelte/issues/8134))
* Add `data-sveltekit-replacestate` and `data-sveltekit-keepfocus` attribute typings ([#8281](https://github.com/sveltejs/svelte/issues/8281))
@ -18,6 +19,26 @@
* Compute node dimensions directly before crossfading ([#4111](https://github.com/sveltejs/svelte/issues/4111))
* Add `readonly` method to convert `writable` store to readonly ([#6518](https://github.com/sveltejs/svelte/pull/6518))
* Ensure `bind:offsetHeight` updates initially ([#4233](https://github.com/sveltejs/svelte/issues/4233))
* Better handling of `inert` attribute ([#7500](https://github.com/sveltejs/svelte/issues/7500))
* Clear inputs when `bind:group` to `undefined` ([#8214](https://github.com/sveltejs/svelte/issues/8214))
* Ensure nested arrays can change at the same time ([#8282](https://github.com/sveltejs/svelte/issues/8282))
* Reduce use of template literals in SSR output for better performance ([#7539](https://github.com/sveltejs/svelte/pull/7539))
* Allow assigning to property of const while destructuring ([#7964](https://github.com/sveltejs/svelte/issues/7964))
* Don't set selected options if value is unbound or not passed ([#5644](https://github.com/sveltejs/svelte/issues/5644))
* Ensure `<input>` value persists when swapping elements with spread attributes in an `#each` block ([#7578](https://github.com/sveltejs/svelte/issues/7578))
* Select first enabled option by default when initial value is undefined ([#7041](https://github.com/sveltejs/svelte/issues/7041))
* Fix race condition on `svelte:element` with transitions ([#7948](https://github.com/sveltejs/svelte/issues/7948))
* Optimise `<svelte:element>` output code for static tag and static attribute ([#8161](https://github.com/sveltejs/svelte/pull/8161))
* Decode html entities correctly ([#8026](https://github.com/sveltejs/svelte/issues/8026))
* Handle `{@html}` tags inside `<template>` tags ([#7364](https://github.com/sveltejs/svelte/pull/7364))
* Introduce parameter to allow for horizontal slide transition ([#6182](https://github.com/sveltejs/svelte/issues/6182))
* Add `naturalWidth` and `naturalHeight` bindings ([#7771](https://github.com/sveltejs/svelte/issues/7771))
* make `<!-- svelte-ignore ... -->` work above components ([#8082](https://github.com/sveltejs/svelte/issues/8082))
* add global compound selector validation ([#6272](https://github.com/sveltejs/svelte/issues/6272))
* add `stopImmediatePropagation` event modifier ([#5085](https://github.com/sveltejs/svelte/issues/5085))
* add `readyState` binding for media elements ([#6666](https://github.com/sveltejs/svelte/issues/6666))
* call `<svelte:component>` update to `this` only when it's dirty ([#4129](https://github.com/sveltejs/svelte/issues/4129))
* support exclusively special characters in component filenames ([#7143](https://github.com/sveltejs/svelte/issues/7143))
## 3.55.1

@ -196,6 +196,9 @@ export interface DOMAttributes<T extends EventTarget> {
// Message Events
'on:message'?: MessageEventHandler<T> | undefined | null;
'on:messageerror'?: MessageEventHandler<T> | undefined | null;
// Document Events
'on:visibilitychange'?: EventHandler<Event, T> | undefined | null;
// Global Events
'on:cancel'?: EventHandler<Event, T> | undefined | null;
@ -707,6 +710,9 @@ export interface HTMLImgAttributes extends HTMLAttributes<HTMLImageElement> {
srcset?: string | undefined | null;
usemap?: string | undefined | null;
width?: number | string | undefined | null;
readonly 'bind:naturalWidth'?: number | undefined | null;
readonly 'bind:naturalHeight'?: number | undefined | null;
}
export interface HTMLInsAttributes extends HTMLAttributes<HTMLModElement> {
@ -842,6 +848,7 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
*/
volume?: number | undefined | null;
readonly 'bind:readyState'?: 0 | 1 | 2 | 3 | 4 | undefined | null;
readonly 'bind:duration'?: number | undefined | null;
readonly 'bind:buffered'?: SvelteMediaTimeRange[] | undefined | null;
readonly 'bind:played'?: SvelteMediaTimeRange[] | undefined | null;

21
package-lock.json generated

@ -38,7 +38,7 @@
"jsdom": "^15.2.1",
"kleur": "^4.1.5",
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
"magic-string": "^0.30.0",
"mocha": "^7.0.0",
"periscopic": "^3.0.4",
"puppeteer": "^2.0.0",
@ -3532,12 +3532,15 @@
}
},
"node_modules/magic-string": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
"integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
"dev": true,
"dependencies": {
"sourcemap-codec": "^1.4.4"
"@jridgewell/sourcemap-codec": "^1.4.13"
},
"engines": {
"node": ">=12"
}
},
"node_modules/mdn-data": {
@ -7961,12 +7964,12 @@
}
},
"magic-string": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
"integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
"@jridgewell/sourcemap-codec": "^1.4.13"
}
},
"mdn-data": {

@ -148,7 +148,7 @@
"jsdom": "^15.2.1",
"kleur": "^4.1.5",
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
"magic-string": "^0.30.0",
"mocha": "^7.0.0",
"periscopic": "^3.0.4",
"puppeteer": "^2.0.0",

@ -539,6 +539,7 @@ The following modifiers are available:
* `preventDefault` — calls `event.preventDefault()` before running the handler
* `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
* `stopImmediatePropagation` - calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
* `nonpassive` — explicitly set `passive: false`
* `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase
@ -713,7 +714,7 @@ Elements with the `contenteditable` attribute support `innerHTML` and `textConte
---
Media elements (`<audio>` and `<video>`) have their own set of bindings — six *readonly* ones...
Media elements (`<audio>` and `<video>`) have their own set of bindings — seven *readonly* ones...
* `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects
@ -721,6 +722,7 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — six
* `seekable` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
* `readyState` (readonly) — number between (and including) 0 and 4
...and five *two-way* bindings:
@ -741,6 +743,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
bind:seekable
bind:seeking
bind:ended
bind:readyState
bind:currentTime
bind:playbackRate
bind:paused
@ -1517,6 +1520,8 @@ The content is exposed in the child component using the `<slot>` element, which
</Widget>
```
Note: If you want to render regular `<slot>` element, You can use `<svelte:element this="slot" />`.
#### `<slot name="`*name*`">`
---

@ -768,6 +768,7 @@ Slides an element in and out.
* `delay` (`number`, default 0) — milliseconds before starting
* `duration` (`number`, default 400) — milliseconds the transition lasts
* `easing` (`function`, default `cubicOut`) — an [easing function](/docs#run-time-svelte-easing)
- `axis` (`x` | `y`, default `y`) — the axis of motion along which the transition occurs
```sv
<script>
@ -776,8 +777,8 @@ Slides an element in and out.
</script>
{#if condition}
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut }}">
slides in and out
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut, axis: 'x'}}">
slides in and out horizontally
</div>
{/if}
```

@ -55,7 +55,7 @@ Enforce that `autofocus` is not used on elements. Autofocusing elements can caus
### `a11y-click-events-have-key-events`
Enforce `on:click` is accompanied by at least one of the following: `onKeyUp`, `onKeyDown`, `onKeyPress`. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users.
Enforce `on:click` is accompanied by at least one of the following: `on:keyup`, `on:keydown`, `on:keypress`. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users.
This does not apply for interactive or hidden elements.
@ -64,6 +64,8 @@ This does not apply for interactive or hidden elements.
<div on:click={() => {}} />
```
Note that the `keypress` event is now deprecated, so it is officially recommended to use either the `keyup` or `keydown` event instead, accordingly.
---
### `a11y-distracting-elements`
@ -308,6 +310,20 @@ Elements with ARIA roles must have all required attributes for that role.
---
### `a11y-role-supports-aria-props`
Elements with explicit or implicit roles defined contain only `aria-*` properties supported by that role.
```sv
<!-- A11y: The attribute 'aria-multiline' is not supported by the role 'link'. -->
<div role="link" aria-multiline />
<!-- A11y: The attribute 'aria-required' is not supported by the role 'listitem'. This role is implicit on the element <li>. -->
<li aria-required />
```
---
### `a11y-structure`
Enforce that certain DOM elements have the correct structure.

@ -4,7 +4,7 @@ title: Keyed each blocks
By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want.
It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `<Thing>` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji.
It's easier to show why than to explain. Click to expand the `Console`, then click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `<Thing>` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji.
Instead, we'd like to remove only the first `<Thing>` component and its DOM node, and leave the others unaffected.

@ -794,20 +794,31 @@ export default class Component {
}
let deep = false;
let names: string[] | undefined;
let names: string[] = [];
if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
if (node.left.type === 'ArrayPattern') {
walk(node.left, {
enter(node: Node, parent: Node) {
if (node.type === 'Identifier' &&
parent.type !== 'MemberExpression' &&
(parent.type !== 'AssignmentPattern' || parent.right !== node)) {
names.push(node.name);
}
}
});
} else {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
}
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
const { name } = get_object(node.argument);
names = [name];
names.push(name);
}
if (names) {
if (names.length > 0) {
names.forEach(name => {
let current_scope = scope;
let declaration;

@ -234,6 +234,10 @@ export default {
code: 'css-invalid-global-selector',
message: ':global(...) must contain a single selector'
},
css_invalid_global_selector_position: {
code: 'css-invalid-global-selector-position',
message: ':global(...) not at the start of a selector sequence should not contain type or universal selectors'
},
css_invalid_selector: (selector: string) => ({
code: 'css-invalid-selector',
message: `Invalid selector "${selector}"`

@ -123,6 +123,17 @@ export default {
code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}`
}),
a11y_role_supports_aria_props: (attribute: string, role: string, is_implicit: boolean, name: string) => {
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
if (is_implicit) {
message += ` This role is implicit on the element <${name}>.`;
}
return {
code: 'a11y-role-supports-aria-props',
message: `A11y: ${message}`
};
},
a11y_accesskey: {
code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey'
@ -175,10 +186,10 @@ export default {
code: 'a11y-mouse-events-have-key-events',
message: `A11y: on:${event} must be accompanied by on:${accompanied_by}`
}),
a11y_click_events_have_key_events: () => ({
a11y_click_events_have_key_events: {
code: 'a11y-click-events-have-key-events',
message: 'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.'
}),
},
a11y_missing_content: (name: string) => ({
code: 'a11y-missing-content',
message: `A11y: <${name}> element should have child content`

@ -76,7 +76,7 @@ export default class Selector {
this.blocks.forEach((block, i) => {
if (i > 0) {
if (block.start - c > 1) {
code.overwrite(c, block.start, block.combinator.name || ' ');
code.update(c, block.start, block.combinator.name || ' ');
}
}
@ -112,7 +112,7 @@ export default class Selector {
}
if (selector.type === 'TypeSelector' && selector.name === '*') {
code.overwrite(selector.start, selector.end, attr);
code.update(selector.start, selector.end, attr);
} else {
code.appendLeft(selector.end, attr);
}
@ -148,6 +148,7 @@ export default class Selector {
}
this.validate_global_with_multiple_selectors(component);
this.validate_global_compound_selector(component);
this.validate_invalid_combinator_without_selector(component);
}
@ -179,6 +180,23 @@ export default class Selector {
}
}
validate_global_compound_selector(component: Component) {
for (const block of this.blocks) {
for (let index = 0; index < block.selectors.length; index++) {
const selector = block.selectors[index];
if (selector.type === 'PseudoClassSelector' &&
selector.name === 'global' &&
index !== 0 &&
selector.children &&
selector.children.length > 0 &&
!/[.:#\s]/.test(selector.children[0].value[0])
) {
component.error(selector, compiler_errors.css_invalid_global_selector_position);
}
}
}
}
get_amount_class_specificity_increased() {
let count = 0;
for (const block of this.blocks) {

@ -35,7 +35,7 @@ function minify_declarations(
declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) {
code.overwrite(c, declaration.node.start, separator);
code.update(c, declaration.node.start, separator);
}
declaration.minify(code);
c = declaration.node.end;
@ -75,7 +75,7 @@ class Rule {
if (selector.used) {
const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) {
code.overwrite(c, selector.node.start, separator);
code.update(c, selector.node.start, separator);
}
selector.minify(code);
@ -133,7 +133,7 @@ class Declaration {
if (block.type === 'Identifier') {
const name = block.name;
if (keyframes.has(name)) {
code.overwrite(block.start, block.end, keyframes.get(name));
code.update(block.start, block.end, keyframes.get(name));
}
}
});
@ -156,7 +156,7 @@ class Declaration {
while (regex_whitespace.test(code.original[start])) start += 1;
if (start - c > 1) {
code.overwrite(c, start, ':');
code.update(c, start, ':');
}
}
}
@ -204,7 +204,7 @@ class Atrule {
code.remove(c, this.node.block.start);
} else if (this.node.name === 'supports') {
let c = this.node.start + 9;
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' ');
if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
this.node.prelude.children.forEach((query: CssNode) => {
// TODO minify queries
c = query.end;
@ -213,7 +213,7 @@ class Atrule {
} else {
let c = this.node.start + this.node.name.length + 1;
if (this.node.prelude) {
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' ');
if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
c = this.node.prelude.end;
}
if (this.node.block && this.node.block.start - c > 0) {
@ -255,7 +255,7 @@ class Atrule {
});
});
} else {
code.overwrite(start, end, keyframes.get(name));
code.update(start, end, keyframes.get(name));
}
}
});

@ -24,7 +24,8 @@ const read_only_media_attributes = new Set([
'videoHeight',
'videoWidth',
'naturalWidth',
'naturalHeight'
'naturalHeight',
'readyState'
]);
export default class Binding extends Node {

@ -82,11 +82,15 @@ const a11y_nested_implicit_semantics = new Map([
const a11y_implicit_semantics = new Map([
['a', 'link'],
['area', 'link'],
['article', 'article'],
['aside', 'complementary'],
['body', 'document'],
['button', 'button'],
['datalist', 'listbox'],
['dd', 'definition'],
['dfn', 'term'],
['dialog', 'dialog'],
['details', 'group'],
['dt', 'term'],
['fieldset', 'group'],
@ -98,10 +102,14 @@ const a11y_implicit_semantics = new Map([
['h5', 'heading'],
['h6', 'heading'],
['hr', 'separator'],
['img', 'img'],
['li', 'listitem'],
['link', 'link'],
['menu', 'list'],
['meter', 'progressbar'],
['nav', 'navigation'],
['ol', 'list'],
['option', 'option'],
['optgroup', 'group'],
['output', 'status'],
['progress', 'progressbar'],
@ -115,11 +123,67 @@ const a11y_implicit_semantics = new Map([
['ul', 'list']
]);
const menuitem_type_to_implicit_role = new Map([
['command', 'menuitem'],
['checkbox', 'menuitemcheckbox'],
['radio', 'menuitemradio']
]);
const input_type_to_implicit_role = new Map([
['button', 'button'],
['image', 'button'],
['reset', 'button'],
['submit', 'button'],
['checkbox', 'checkbox'],
['radio', 'radio'],
['range', 'slider'],
['number', 'spinbutton'],
['email', 'textbox'],
['search', 'searchbox'],
['tel', 'textbox'],
['text', 'textbox'],
['url', 'textbox']
]);
const combobox_if_list = new Set(['email', 'search', 'tel', 'text', 'url']);
function input_implicit_role(attribute_map: Map<string, Attribute>) {
const type_attribute = attribute_map.get('type');
if (!type_attribute || !type_attribute.is_static) return;
const type = type_attribute.get_static_value() as string;
const list_attribute_exists = attribute_map.has('list');
if (list_attribute_exists && combobox_if_list.has(type)) {
return 'combobox';
}
return input_type_to_implicit_role.get(type);
}
function menuitem_implicit_role(attribute_map: Map<string, Attribute>) {
const type_attribute = attribute_map.get('type');
if (!type_attribute || !type_attribute.is_static) return;
const type = type_attribute.get_static_value() as string;
return menuitem_type_to_implicit_role.get(type);
}
function get_implicit_role(name: string, attribute_map: Map<string, Attribute>) : (string | undefined) {
if (name === 'menuitem') {
return menuitem_implicit_role(attribute_map);
} else if (name === 'input') {
return input_implicit_role(attribute_map);
} else {
return a11y_implicit_semantics.get(name);
}
}
const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set([
'preventDefault',
'stopPropagation',
'stopImmediatePropagation',
'capture',
'once',
'passive',
@ -487,8 +551,8 @@ export default class Element extends Node {
}
// aria-activedescendant-has-tabindex
if (name === 'aria-activedescendant' && !is_interactive_element(this.name, attribute_map) && !attribute_map.has('tabindex')) {
component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex);
if (name === 'aria-activedescendant' && !this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !attribute_map.has('tabindex')) {
component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex);
}
}
@ -511,7 +575,7 @@ export default class Element extends Node {
}
// no-redundant-roles
const has_redundant_role = current_role === a11y_implicit_semantics.get(this.name);
const has_redundant_role = current_role === get_implicit_role(this.name, attribute_map);
if (this.name === current_role || has_redundant_role) {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role));
@ -527,7 +591,7 @@ export default class Element extends Node {
}
// role-has-required-aria-props
if (!is_semantic_role_element(current_role, this.name, attribute_map)) {
if (!this.is_dynamic_element && !is_semantic_role_element(current_role, this.name, attribute_map)) {
const role = roles.get(current_role);
if (role) {
const required_role_props = Object.keys(role.requiredProps);
@ -559,7 +623,7 @@ export default class Element extends Node {
}
// scope
if (name === 'scope' && this.name !== 'th') {
if (name === 'scope' && !this.is_dynamic_element && this.name !== 'th') {
component.warn(attribute, compiler_warnings.a11y_misplaced_scope);
}
@ -579,6 +643,7 @@ export default class Element extends Node {
const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefintionKey);
if (
!this.is_dynamic_element &&
!is_hidden_from_screen_reader(this.name, attribute_map) &&
(!role || is_non_presentation_role) &&
!is_interactive_element(this.name, attribute_map) &&
@ -592,19 +657,36 @@ export default class Element extends Node {
if (!has_key_event) {
component.warn(
this,
compiler_warnings.a11y_click_events_have_key_events()
compiler_warnings.a11y_click_events_have_key_events
);
}
}
}
// no-noninteractive-tabindex
if (!is_interactive_element(this.name, attribute_map) && !is_interactive_roles(attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey)) {
if (!this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !is_interactive_roles(attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey)) {
const tab_index = attribute_map.get('tabindex');
if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex);
}
}
// role-supports-aria-props
const role = attribute_map.get('role');
const role_value = (role ? role.get_static_value() : get_implicit_role(this.name, attribute_map)) as ARIARoleDefintionKey;
if (typeof role_value === 'string' && roles.has(role_value)) {
const { props } = roles.get(role_value);
const invalid_aria_props = new Set(aria.keys().filter(attribute => !(attribute in props)));
const is_implicit = role_value && role === undefined;
attributes
.filter(prop => prop.type !== 'Spread')
.forEach(prop => {
if (invalid_aria_props.has(prop.name as ARIAProperty)) {
component.warn(prop, compiler_warnings.a11y_role_supports_aria_props(prop.name, role_value, is_implicit, this.name));
}
});
}
}
validate_special_cases() {
@ -898,7 +980,8 @@ export default class Element extends Node {
name === 'muted' ||
name === 'playbackRate' ||
name === 'seeking' ||
name === 'ended'
name === 'ended' ||
name === 'readyState'
) {
if (this.name !== 'audio' && this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name));

@ -1,4 +1,4 @@
import Renderer from './Renderer';
import Renderer, { BindingGroup } from './Renderer';
import Wrapper from './wrappers/shared/Wrapper';
import { b, x } from 'code-red';
import { Node, Identifier, ArrayPattern } from 'estree';
@ -40,6 +40,7 @@ export default class Block {
bindings: Map<string, Bindings>;
binding_group_initialised: Set<string> = new Set();
binding_groups: Set<BindingGroup> = new Set();
chunks: {
declarations: Array<Node | Node[]>;
@ -249,6 +250,7 @@ export default class Block {
}
}
this.render_binding_groups();
this.render_listeners();
const properties: Record<string, any> = {};
@ -500,4 +502,10 @@ export default class Block {
}
}
}
render_binding_groups() {
for (const binding_group of this.binding_groups) {
binding_group.render();
}
}
}

@ -22,6 +22,15 @@ type BitMasks = Array<{
names: string[];
}>;
export interface BindingGroup {
binding_group: (to_reference?: boolean) => Node;
contexts: string[];
list_dependencies: Set<string>;
keypath: string;
elements: Identifier[];
render: () => void;
}
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
@ -33,7 +42,7 @@ export default class Renderer {
blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: Map<string, { binding_group: (to_reference?: boolean) => Node; is_context: boolean; contexts: string[]; index: number; keypath: string }> = new Map();
binding_groups: Map<string, BindingGroup> = new Map();
block: Block;
fragment: FragmentWrapper;
@ -64,10 +73,6 @@ export default class Renderer {
this.add_to_context('#slots');
}
if (this.binding_groups.size > 0) {
this.add_to_context('$$binding_groups');
}
// main block
this.block = new Block({
renderer: this,

@ -448,7 +448,9 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
if (${iterations}[#i]) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
}
`);
@ -542,7 +544,9 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
if (${iterations}[#i]) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
}
`);

@ -9,7 +9,7 @@ import Text from '../../../nodes/Text';
import handle_select_value_binding from './handle_select_value_binding';
import { Identifier, Node } from 'estree';
import { namespaces } from '../../../../utils/namespaces';
import { boolean_attributes } from '../../../../../shared/boolean_attributes';
import { BooleanAttributes, boolean_attributes } from '../../../../../shared/boolean_attributes';
import { regex_double_quotes } from '../../../../utils/patterns';
const non_textlike_input_types = new Set([
@ -85,6 +85,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (node.name === 'value') {
handle_select_value_binding(this, node.dependencies);
this.parent.has_dynamic_value = true;
}
}
@ -180,6 +181,17 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
`;
}
if (this.node.name === 'value' && dependencies.length > 0) {
if (this.parent.bindings.some(binding => binding.node.name === 'group')) {
this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed');
block.add_variable(this.parent.dynamic_value_condition, x`false`);
updater = b`
${updater}
${this.parent.dynamic_value_condition} = true;
`;
}
}
if (dependencies.length > 0) {
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
@ -324,7 +336,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
}
// source: https://html.spec.whatwg.org/multipage/indices.html
const attribute_lookup = {
type AttributeMetadata = { property_name?: string, applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [key in string]: AttributeMetadata } = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
async: { applies_to: ['script'] },
@ -349,7 +362,9 @@ const attribute_lookup = {
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
hidden: {},
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'] },

@ -5,7 +5,7 @@ import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object';
import Block from '../../Block';
import Renderer from '../../Renderer';
import Renderer, { BindingGroup } from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference';
import { Node, Identifier } from 'estree';
import add_to_set from '../../../utils/add_to_set';
@ -26,6 +26,7 @@ export default class BindingWrapper {
snippet: Node;
is_readonly: boolean;
needs_lock: boolean;
binding_group: BindingGroup;
constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) {
this.node = node;
@ -45,6 +46,10 @@ export default class BindingWrapper {
this.object = get_object(this.node.expression.node).name;
if (this.node.name === 'group') {
this.binding_group = get_binding_group(parent.renderer, this, block);
}
// view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression);
@ -67,6 +72,10 @@ export default class BindingWrapper {
}
});
if (this.binding_group) {
this.binding_group.list_dependencies.forEach(dep => dependencies.add(dep));
}
return dependencies;
}
@ -105,6 +114,7 @@ export default class BindingWrapper {
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
let update_or_condition: any = null;
const dependency_array = Array.from(this.get_dependencies());
@ -142,33 +152,12 @@ export default class BindingWrapper {
switch (this.node.name) {
case 'group':
{
const { binding_group, is_context, contexts, index, keypath } = get_binding_group(parent.renderer, this.node, block);
block.renderer.add_to_context('$$binding_groups');
this.binding_group.elements.push(this.parent.var);
if (is_context && !block.binding_group_initialised.has(keypath)) {
if (contexts.length > 1) {
let binding_group = x`${block.renderer.reference('$$binding_groups')}[${index}]`;
for (const name of contexts.slice(0, -1)) {
binding_group = x`${binding_group}[${block.renderer.reference(name)}]`;
block.chunks.init.push(
b`${binding_group} = ${binding_group} || [];`
);
}
}
block.chunks.init.push(
b`${binding_group(true)} = [];`
);
block.binding_group_initialised.add(keypath);
if ((this.parent as ElementWrapper).has_dynamic_value) {
update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition;
}
block.chunks.hydrate.push(
b`${binding_group(true)}.push(${parent.var});`
);
block.chunks.destroy.push(
b`${binding_group(true)}.splice(${binding_group(true)}.indexOf(${parent.var}), 1);`
);
break;
}
@ -214,7 +203,8 @@ export default class BindingWrapper {
if (update_dom) {
if (update_conditions.length > 0) {
const condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
let condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
if (update_or_condition) condition = x`${update_or_condition} || (${condition})`;
block.chunks.update.push(b`
if (${condition}) {
@ -266,7 +256,7 @@ function get_dom_updater(
const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox'
? x`~${binding.snippet}.indexOf(${element.var}.__value)`
? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`;
return b`${element.var}.checked = ${condition};`;
@ -279,7 +269,8 @@ function get_dom_updater(
return b`${element.var}.${binding.node.name} = ${binding.snippet};`;
}
function get_binding_group(renderer: Renderer, value: Binding, block: Block) {
function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: Block) {
const value = binding.node;
const { parts } = flatten_reference(value.raw_expression);
let keypath = parts.join('.');
@ -314,41 +305,75 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) {
contexts.push(name);
}
// create a global binding_group across blocks
if (!renderer.binding_groups.has(keypath)) {
const index = renderer.binding_groups.size;
// the bind:group depends on the list in the {#each} block as well
// as reordering (removing and adding back to the DOM) may affect the value
const list_dependencies = new Set<string>();
let parent = value.parent;
while (parent) {
if (parent.type === 'EachBlock') {
for (const dep of parent.expression.dynamic_dependencies()) {
list_dependencies.add(dep);
}
}
parent = parent.parent;
}
const elements = [];
contexts.forEach(context => {
renderer.add_to_context(context, true);
});
renderer.binding_groups.set(keypath, {
binding_group: (to_reference: boolean = false) => {
let binding_group = '$$binding_groups';
let _secondary_indexes = contexts;
binding_group: () => {
let obj = x`$$binding_groups[${index}]`;
if (to_reference) {
binding_group = block.renderer.reference(binding_group);
_secondary_indexes = _secondary_indexes.map(name => block.renderer.reference(name));
}
if (_secondary_indexes.length > 0) {
let obj = x`${binding_group}[${index}]`;
_secondary_indexes.forEach(secondary_index => {
if (contexts.length > 0) {
contexts.forEach(secondary_index => {
obj = x`${obj}[${secondary_index}]`;
});
return obj;
} else {
return x`${binding_group}[${index}]`;
}
return obj;
},
is_context: contexts.length > 0,
contexts,
index,
keypath
list_dependencies,
keypath,
elements,
render() {
const local_name = block.get_unique_name('binding_group');
const binding_group = block.renderer.reference('$$binding_groups');
block.add_variable(local_name);
if (contexts.length > 0) {
const indexes = { type: 'ArrayExpression', elements: contexts.map(name => block.renderer.reference(name)) };
block.chunks.init.push(
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`
);
block.chunks.update.push(
b`if (${block.renderer.dirty(Array.from(list_dependencies))}) ${local_name}.u(${indexes})`
);
} else {
block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])`
);
}
block.chunks.hydrate.push(
b`${local_name}.p(${elements})`
);
block.chunks.destroy.push(
b`${local_name}.r()`
);
}
});
}
return renderer.binding_groups.get(keypath);
// register the binding_group for the block
const binding_group = renderer.binding_groups.get(keypath);
block.binding_groups.add(binding_group);
return binding_group;
}
function get_event_handler(
@ -386,7 +411,7 @@ function get_event_handler(
}
}
const value = get_value_from_dom(renderer, binding.parent, binding, block, contextual_dependencies);
const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies);
const mutation = b`
${lhs} = ${value};
@ -402,10 +427,9 @@ function get_event_handler(
}
function get_value_from_dom(
renderer: Renderer,
_renderer: Renderer,
element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper,
block: Block,
contextual_dependencies: Set<string>
) {
const { node } = element;
@ -427,7 +451,7 @@ function get_value_from_dom(
// <input type='checkbox' bind:group='foo'>
if (name === 'group') {
if (type === 'checkbox') {
const { binding_group, contexts } = get_binding_group(renderer, binding.node, block);
const { binding_group, contexts } = binding.binding_group;
add_to_set(contextual_dependencies, contexts);
return x`@get_binding_group_value(${binding_group()}, this.__value, this.checked)`;
}

@ -41,6 +41,7 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('stopImmediatePropagation')) snippet = x`@stop_immediate_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
@ -64,6 +65,7 @@ export default class EventHandlerWrapper {
if (block.renderer.options.dev) {
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
}
block.event_listeners.push(

@ -63,13 +63,11 @@ const events = [
filter: (node: Element, _name: string) =>
node.name === 'input' && node.get_static_attribute_value('type') === 'range'
},
{
event_names: ['elementresize'],
filter: (_node: Element, name: string) =>
regex_dimensions.test(name)
},
// media events
{
event_names: ['timeupdate'],
@ -131,7 +129,14 @@ const events = [
node.is_media_node() &&
(name === 'videoHeight' || name === 'videoWidth')
},
{
// from https://html.spec.whatwg.org/multipage/media.html#ready-states
// and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
event_names: ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'readyState'
},
// details event
{
event_names: ['toggle'],
@ -155,8 +160,11 @@ export default class ElementWrapper extends Wrapper {
bindings: Binding[];
event_handlers: EventHandler[];
class_dependencies: string[];
has_dynamic_attribute: boolean;
select_binding_dependencies?: Set<string>;
has_dynamic_value: boolean;
dynamic_value_condition: any;
var: any;
void: boolean;
@ -223,6 +231,7 @@ export default class ElementWrapper extends Wrapper {
}
return new AttributeWrapper(this, block, attribute);
});
this.has_dynamic_attribute = !!this.attributes.find(attr => attr.node.get_dependencies().length > 0);
// ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings,
@ -291,9 +300,8 @@ export default class ElementWrapper extends Wrapper {
(x`#nodes` as unknown) as Identifier
);
const previous_tag = block.get_unique_name('previous_tag');
const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0;
const tag = this.node.tag_expr.manipulate(block);
block.add_variable(previous_tag, tag);
block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
@ -315,44 +323,72 @@ export default class ElementWrapper extends Wrapper {
if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});
`);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
block.chunks.update.push(b`
if (${tag}) {
if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c();
${has_transitions && b`@transition_in(${this.var})`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c();
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
if (is_tag_dynamic) {
const previous_tag = block.get_unique_name('previous_tag');
block.add_variable(previous_tag, tag);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
const tag_will_be_removed = block.get_unique_name('tag_will_be_removed');
if (has_transitions) {
block.add_variable(tag_will_be_removed, x`false`);
}
block.chunks.update.push(b`
if (${tag}) {
if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`@transition_in(${this.var})`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${has_transitions
? b`
${tag_will_be_removed} = true;
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
${previous_tag} = ${tag};
${tag_will_be_removed} = false;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
${previous_tag} = ${tag};
`
}
}
`);
} else {
block.chunks.update.push(b`
if (${tag}) {
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${has_transitions
? b`
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
`
}
}
${previous_tag} = ${tag};
`);
`);
}
if (this.child_dynamic_element_block.has_intros) {
block.chunks.intro.push(b`@transition_in(${this.var});`);
@ -491,7 +527,11 @@ export default class ElementWrapper extends Wrapper {
block.maintain_context = true;
}
this.add_attributes(block);
if (this.node.is_dynamic_element) {
this.add_dynamic_element_attributes(block);
} else {
this.add_attributes(block);
}
this.add_directives_in_order(block);
this.add_transitions(block);
this.add_animation(block);
@ -769,7 +809,7 @@ export default class ElementWrapper extends Wrapper {
}
});
if (this.node.attributes.some(attr => attr.is_spread) || this.node.is_dynamic_element) {
if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block);
return;
}
@ -818,29 +858,18 @@ export default class ElementWrapper extends Wrapper {
}
`);
const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`;
const fn =
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
if (this.node.is_dynamic_element) {
// call attribute bindings for custom element if tag is custom element
const tag = this.node.tag_expr.manipulate(block);
const attr_update = this.node.namespace === namespaces.svg
? b`${fn}(${this.var}, ${data});`
: b`
if (/-/.test(${tag})) {
@set_custom_element_data_map(${this.var}, ${data});
} else {
${fn}(${this.var}, ${data});
}`;
block.chunks.hydrate.push(attr_update);
block.chunks.update.push(b`
${data} = @get_spread_update(${levels}, [${updates}]);
${attr_update}`
);
} else {
block.chunks.hydrate.push(
b`${fn}(${this.var}, ${data});`
);
block.chunks.hydrate.push(
b`${fn}(${this.var}, ${data});`
);
if (this.has_dynamic_attribute) {
block.chunks.update.push(b`
${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [
${updates}
@ -858,8 +887,9 @@ export default class ElementWrapper extends Wrapper {
}
block.chunks.mount.push(b`
(${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value);
'value' in ${data} && (${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value);
`);
block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${data}) (${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value);
`);
@ -867,7 +897,9 @@ export default class ElementWrapper extends Wrapper {
const type = this.node.get_static_attribute_value('type');
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') {
block.chunks.mount.push(b`
${this.var}.value = ${data}.value;
if ('value' in ${data}) {
${this.var}.value = ${data}.value;
}
`);
block.chunks.update.push(b`
if ('value' in ${data}) {
@ -884,6 +916,35 @@ export default class ElementWrapper extends Wrapper {
}
}
add_dynamic_element_attributes(block: Block) {
if (this.attributes.length === 0) return;
if (this.has_dynamic_attribute) {
this.add_spread_attributes(block);
return;
}
const static_attributes = [];
this.attributes.forEach((attr) => {
if (attr instanceof SpreadAttributeWrapper) {
static_attributes.push({ type: 'SpreadElement', argument: attr.node.expression.node });
} else {
const name = attr.property_name || attr.name;
static_attributes.push(p`${name}: ${attr.get_value(block)}`);
}
});
const fn =
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
block.chunks.hydrate.push(
b`${fn}(${this.var}, {${static_attributes}});`
);
}
add_transitions(block: Block) {
const { intro, outro } = this.node;
if (!intro && !outro) return;
@ -1081,7 +1142,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.hydrate.push(updater);
if (has_spread || this.node.is_dynamic_element) {
if ((this.node.is_dynamic_element || has_spread) && this.has_dynamic_attribute) {
block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);

@ -21,6 +21,7 @@ import SlotTemplate from '../../../nodes/SlotTemplate';
import { is_head } from '../shared/is_head';
import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces';
import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
@ -111,9 +112,12 @@ export default class InlineComponentWrapper extends Wrapper {
return;
}
const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
}
this.renderer.component.pop_ignores();
}
render(
@ -414,6 +418,7 @@ export default class InlineComponentWrapper extends Wrapper {
const switch_props = block.get_unique_name('switch_props');
const snippet = this.node.expression.manipulate(block);
const dependencies = this.node.expression.dynamic_dependencies();
if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
@ -464,8 +469,13 @@ export default class InlineComponentWrapper extends Wrapper {
? b`@insert(${tmp_anchor}.parentNode, ${css_custom_properties_wrapper}, ${tmp_anchor});`
: b`@insert(${parent_node}, ${css_custom_properties_wrapper}, ${tmp_anchor});`);
let update_condition = x`${switch_value} !== (${switch_value} = ${snippet})`;
if (dependencies.length > 0) {
update_condition = x`${block.renderer.dirty(dependencies)} && ${update_condition}`;
}
block.chunks.update.push(b`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${update_condition}) {
if (${name}) {
@group_outros();
const old_component = ${name};

@ -16,6 +16,7 @@ import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces';
import { Expression, TemplateLiteral, Identifier } from 'estree';
import { collapse_template_literal } from '../utils/collapse_template_literal';
import { escape_template } from '../utils/stringify';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
@ -106,6 +107,9 @@ export default class Renderer {
this.current = last.current;
}
// Optimize the TemplateLiteral to remove unnecessary nodes
collapse_template_literal(popped.literal);
return popped.literal;
}

@ -13,4 +13,10 @@ describe('get_name_from_filename', () => {
it('handles Windows filenames', () => {
assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget');
});
it('handles special characters in filenames', () => {
assert.equal(get_name_from_filename('@.svelte'), '_');
assert.equal(get_name_from_filename('&.svelte'), '_');
assert.equal(get_name_from_filename('~.svelte'), '_');
});
});

@ -0,0 +1,34 @@
import { TemplateLiteral } from 'estree';
import { escape_template } from './stringify';
/**
* Collapse string literals together
*/
export function collapse_template_literal(literal: TemplateLiteral) {
if (!literal.quasis.length) return;
const collapsed_quasis = [];
const collapsed_expressions = [];
let cur_quasi = literal.quasis[0];
// An expression always follows a quasi and vice versa, ending with a quasi
for (let i = 0; i < literal.quasis.length; i++) {
const expr = literal.expressions[i];
const next_quasi = literal.quasis[i + 1];
// If an expression is a simple string literal, combine it with its preceding
// and following quasi
if (next_quasi && expr && expr.type === 'Literal' && typeof expr.value === 'string') {
cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw;
} else {
if (expr) {
collapsed_expressions.push(expr);
}
collapsed_quasis.push(cur_quasi);
cur_quasi = next_quasi;
}
}
literal.quasis = collapsed_quasis;
literal.expressions = collapsed_expressions;
}

@ -1,9 +1,8 @@
import { regex_starts_with_underscore, regex_ends_with_underscore } from '../../utils/patterns';
const regex_percentage_characters = /%/g;
const regex_file_ending = /\.[^.]+$/;
const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g;
const regex_starts_with_digit = /^(\d)/;
const regex_may_starts_or_ends_with_underscore = /^_?(.+?)_?$/;
export default function get_name_from_filename(filename: string) {
if (!filename) return null;
@ -18,12 +17,12 @@ export default function get_name_from_filename(filename: string) {
}
}
const base = parts.pop()
const base = parts
.pop()
.replace(regex_percentage_characters, 'u')
.replace(regex_file_ending, '')
.replace(regex_repeated_invalid_variable_identifier_characters, '_')
.replace(regex_starts_with_underscore, '')
.replace(regex_ends_with_underscore, '')
.replace(regex_may_starts_or_ends_with_underscore, '$1')
.replace(regex_starts_with_digit, '_$1');
if (!base) {

@ -519,7 +519,7 @@ function read_sequence(parser: Parser, done: () => boolean, location: string): T
function flush(end: number) {
if (current_chunk.raw) {
current_chunk.data = decode_character_references(current_chunk.raw);
current_chunk.data = decode_character_references(current_chunk.raw, true);
current_chunk.end = end;
chunks.push(current_chunk);
}

@ -19,7 +19,7 @@ export default function text(parser: Parser) {
end: parser.index,
type: 'Text',
raw: data,
data: decode_character_references(data)
data: decode_character_references(data, false)
};
parser.current().children.push(node);

File diff suppressed because it is too large Load Diff

@ -35,12 +35,32 @@ const windows_1252 = [
376
];
const entity_pattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`,
'g'
);
function reg_exp_entity(entity_name: string, is_attribute_value: boolean) {
// https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
// doesn't decode the html entity which not ends with ; and next character is =, number or alphabet in attribute value.
if (is_attribute_value && !entity_name.endsWith(';')) {
return `${entity_name}\\b(?!=)`;
}
return entity_name;
}
function get_entity_pattern(is_attribute_value: boolean) {
const reg_exp_num = '#(?:x[a-fA-F\\d]+|\\d+)(?:;)?';
const reg_exp_entities = Object.keys(entities).map(entity_name => reg_exp_entity(entity_name, is_attribute_value));
const entity_pattern = new RegExp(
`&(${reg_exp_num}|${reg_exp_entities.join('|')})`,
'g'
);
return entity_pattern;
}
const entity_pattern_content = get_entity_pattern(false);
const entity_pattern_attr_value = get_entity_pattern(true);
export function decode_character_references(html: string) {
export function decode_character_references(html: string, is_attribute_value: boolean) {
const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content;
return html.replace(entity_pattern, (match, entity) => {
let code;

@ -1,6 +1,7 @@
import { TemplateNode } from '../interfaces';
import { flatten } from './flatten';
import { regex_whitespace } from './patterns';
import { INode } from '../compile/nodes/interfaces';
const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
@ -33,3 +34,25 @@ export function extract_ignores_above_position(position: number, template_nodes:
return [];
}
export function extract_ignores_above_node(node: INode): string[] {
/**
* This utilizes the fact that node has a prev and a next attribute
* which means that it can find svelte-ignores along
* the nodes on the same level as itself who share the same parent.
*/
let cur_node = node.prev;
while (cur_node) {
if (cur_node.type !== 'Comment' && cur_node.type !== 'Text') {
return [];
}
if (cur_node.type === 'Comment' && cur_node.ignores.length) {
return cur_node.ignores;
}
cur_node = cur_node.prev;
}
return [];
}

@ -49,10 +49,11 @@ export function detach_after_dev(before: Node) {
}
}
export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean) {
export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean, has_stop_immediate_propagation?: boolean) {
const modifiers = options === true ? [ 'capture' ] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
if (has_stop_immediate_propagation) modifiers.push('stopImmediatePropagation');
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });

@ -13,7 +13,7 @@ export function end_hydrating() {
type NodeEx = Node & {
claim_order?: number,
hydrate_init? : true,
hydrate_init?: true,
actual_end_child?: NodeEx,
childNodes: NodeListOf<NodeEx>,
};
@ -35,7 +35,7 @@ function init_hydrate(target: NodeEx) {
if (target.hydrate_init) return;
target.hydrate_init = true;
type NodeEx2 = NodeEx & {claim_order: number};
type NodeEx2 = NodeEx & { claim_order: number };
// We know that all children have claim_order values since the unclaimed have been detached if target is not <head>
let children: ArrayLike<NodeEx2> = target.childNodes as NodeListOf<NodeEx2>;
@ -260,7 +260,7 @@ export function listen(node: EventTarget, event: string, handler: EventListenerO
}
export function prevent_default(fn) {
return function(event) {
return function (event) {
event.preventDefault();
// @ts-ignore
return fn.call(this, event);
@ -268,22 +268,30 @@ export function prevent_default(fn) {
}
export function stop_propagation(fn) {
return function(event) {
return function (event) {
event.stopPropagation();
// @ts-ignore
return fn.call(this, event);
};
}
export function stop_immediate_propagation(fn) {
return function (event) {
event.stopImmediatePropagation();
// @ts-ignore
return fn.call(this, event);
};
}
export function self(fn) {
return function(event) {
return function (event) {
// @ts-ignore
if (event.target === this) fn.call(this, event);
};
}
export function trusted(fn) {
return function(event) {
return function (event) {
// @ts-ignore
if (event.isTrusted) fn.call(this, event);
};
@ -332,6 +340,10 @@ export function set_custom_element_data(node, prop, value) {
}
}
export function set_dynamic_element_data(tag: string) {
return (/-/.test(tag)) ? set_custom_element_data_map : set_attributes;
}
export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
}
@ -347,6 +359,53 @@ export function get_binding_group_value(group, __value, checked) {
return Array.from(value);
}
export function init_binding_group(group) {
let _inputs: HTMLInputElement[];
return {
/* push */ p(...inputs: HTMLInputElement[]) {
_inputs = inputs;
_inputs.forEach(input => group.push(input));
},
/* remove */ r() {
_inputs.forEach(input => group.splice(group.indexOf(input), 1));
}
};
}
export function init_binding_group_dynamic(group, indexes: number[]) {
let _group: HTMLInputElement[] = get_binding_group(group);
let _inputs: HTMLInputElement[];
function get_binding_group(group) {
for (let i = 0; i < indexes.length; i++) {
group = group[indexes[i]] = group[indexes[i]] || [];
}
return group;
}
function push() {
_inputs.forEach(input => _group.push(input));
}
function remove() {
_inputs.forEach(input => _group.splice(_group.indexOf(input), 1));
}
return {
/* update */ u(new_indexes: number[]) {
indexes = new_indexes;
const new_group = get_binding_group(group);
if (new_group !== _group) {
remove();
_group = new_group;
push();
}
},
/* push */ p(...inputs: HTMLInputElement[]) {
_inputs = inputs;
push();
},
/* remove */ r: remove
};
}
export function to_number(value) {
return value === '' ? null : +value;
}
@ -380,7 +439,7 @@ export function children(element: Element) {
function init_claim_info(nodes: ChildNodeArray) {
if (nodes.claim_info === undefined) {
nodes.claim_info = {last_index: 0, total_claimed: 0};
nodes.claim_info = { last_index: 0, total_claimed: 0 };
}
}
@ -567,8 +626,16 @@ export function select_options(select, value) {
}
}
function first_enabled_option(select) {
for (const option of select.options) {
if (!option.disabled) {
return option;
}
}
}
export function select_value(select) {
const selected_option = select.querySelector(':checked') || select.options[0];
const selected_option = select.querySelector(':checked') || first_enabled_option(select);
return selected_option && selected_option.__value;
}
@ -648,7 +715,7 @@ export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name);
}
export function custom_event<T=any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> {
export function custom_event<T = any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> {
const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, bubbles, cancelable, detail);
return e;
@ -686,7 +753,7 @@ export class HtmlTag {
// html tag nodes
n: ChildNode[];
// target
t: HTMLElement | SVGElement;
t: HTMLElement | SVGElement | DocumentFragment;
// anchor
a: HTMLElement | SVGElement;
@ -706,8 +773,9 @@ export class HtmlTag {
) {
if (!this.e) {
if (this.is_svg) this.e = svg_element(target.nodeName as keyof SVGElementTagNameMap);
else this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
this.t = target;
/** #7364 target for <template> may be provided as #document-fragment(11) */
else this.e = element((target.nodeType === 11 ? 'TEMPLATE' : target.nodeName) as keyof HTMLElementTagNameMap);
this.t = target.tagName !== 'TEMPLATE' ? target : (target as HTMLTemplateElement).content;
this.c(html);
}
@ -716,7 +784,7 @@ export class HtmlTag {
h(html: string) {
this.e.innerHTML = html;
this.n = Array.from(this.e.childNodes);
this.n = Array.from(this.e.nodeName === 'TEMPLATE' ? (this.e as HTMLTemplateElement).content.childNodes : this.e.childNodes);
}
i(anchor) {

@ -1,4 +1,5 @@
import { transition_in, transition_out } from './transitions';
import { run_all } from './utils';
export function destroy_block(block, lookup) {
block.d(1);
@ -32,6 +33,7 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
const new_blocks = [];
const new_lookup = new Map();
const deltas = new Map();
const updates = [];
i = n;
while (i--) {
@ -43,7 +45,8 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
block = create_each_block(key, child_ctx);
block.c();
} else if (dynamic) {
block.p(child_ctx, dirty);
// defer updates until all the DOM shuffling is done
updates.push(() => block.p(child_ctx, dirty));
}
new_lookup.set(key, new_blocks[i] = block);
@ -99,6 +102,8 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
while (n) insert(new_blocks[n - 1]);
run_all(updates);
return new_blocks;
}

@ -98,23 +98,27 @@ export interface SlideParams {
delay?: number;
duration?: number;
easing?: EasingFunction;
axis?: 'x' | 'y';
}
export function slide(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut
easing = cubicOut,
axis = 'y'
}: SlideParams = {}): TransitionConfig {
const style = getComputedStyle(node);
const opacity = +style.opacity;
const height = parseFloat(style.height);
const padding_top = parseFloat(style.paddingTop);
const padding_bottom = parseFloat(style.paddingBottom);
const margin_top = parseFloat(style.marginTop);
const margin_bottom = parseFloat(style.marginBottom);
const border_top_width = parseFloat(style.borderTopWidth);
const border_bottom_width = parseFloat(style.borderBottomWidth);
const primary_property = axis === 'y' ? 'height' : 'width';
const primary_property_value = parseFloat(style[primary_property]);
const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
const capitalized_secondary_properties = secondary_properties.map((e) => `${e[0].toUpperCase()}${e.slice(1)}`);
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
const border_width_start_value = parseFloat(style[`border${capitalized_secondary_properties[0]}Width`]);
const border_width_end_value = parseFloat(style[`border${capitalized_secondary_properties[1]}Width`]);
return {
delay,
duration,
@ -122,13 +126,13 @@ export function slide(node: Element, {
css: t =>
'overflow: hidden;' +
`opacity: ${Math.min(t * 20, 1) * opacity};` +
`height: ${t * height}px;` +
`padding-top: ${t * padding_top}px;` +
`padding-bottom: ${t * padding_bottom}px;` +
`margin-top: ${t * margin_top}px;` +
`margin-bottom: ${t * margin_bottom}px;` +
`border-top-width: ${t * border_top_width}px;` +
`border-bottom-width: ${t * border_bottom_width}px;`
`${primary_property}: ${t * primary_property_value}px;` +
`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`
};
}

@ -1,5 +1,4 @@
// source: https://html.spec.whatwg.org/multipage/indices.html
export const boolean_attributes = new Set([
const _boolean_attributes = [
'allowfullscreen',
'allowpaymentrequest',
'async',
@ -26,4 +25,12 @@ export const boolean_attributes = new Set([
'required',
'reversed',
'selected'
]);
] as const;
export type BooleanAttributes = typeof _boolean_attributes[number];
/**
* List of HTML boolean attributes (e.g. `<input disabled>`).
* Source: https://html.spec.whatwg.org/multipage/indices.html
*/
export const boolean_attributes: Set<string> = new Set([..._boolean_attributes]);

@ -0,0 +1,6 @@
export default {
options: {
generate: 'ssr',
dev: true
}
};

@ -0,0 +1,25 @@
/* generated by Svelte vX.Y.Z */
import { add_attribute, create_ssr_component, escape } from "svelte/internal";
const const1 = 1;
const const2 = 'const2';
function foo() {
return '';
}
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `
<div class="class1 class2" style="color:red;">-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div class="${"class1 " + escape('class2', true)}">-</div>
<div class="${"class1 " + escape(const2, true)}">-</div>
<div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
});
export default Component;

@ -0,0 +1,20 @@
<script>
const const1 = 1;
const const2 = 'const2';
function foo() {
return '';
}
</script>
<!-- canonical case - only markup -->
<div class="class1 class2" style="color:red;">-</div>
<!-- various forms of variable syntax -->
<div class="{const1}">-</div>
<div class={const1}>-</div>
<!-- mixed static string + expressions -->
<div class="class1 {'class2'}">-</div>
<div class="class1 {const2}">-</div>
<div class="class1 {const2}" style={foo()}>-</div>

@ -114,7 +114,9 @@ function create_fragment(ctx) {
},
m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert_dev(target, t0, anchor);

@ -108,7 +108,9 @@ function create_fragment(ctx) {
},
m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert_dev(target, t0, anchor);

@ -86,7 +86,9 @@ function create_fragment(ctx) {
},
m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert_dev(target, each_1_anchor, anchor);

@ -63,7 +63,9 @@ function create_fragment(ctx) {
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert(target, each_1_anchor, anchor);

@ -63,7 +63,9 @@ function create_fragment(ctx) {
},
m(target, anchor) {
for (let i = 0; i < 5; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert(target, each_1_anchor, anchor);

@ -104,7 +104,9 @@ function create_fragment(ctx) {
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert(target, t0, anchor);
@ -170,4 +172,4 @@ class Component extends SvelteComponent {
}
}
export default Component;
export default Component;

@ -87,7 +87,9 @@ function create_fragment(ctx) {
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert(target, each_1_anchor, anchor);

@ -72,7 +72,9 @@ function create_fragment(ctx) {
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
}
insert(target, each_1_anchor, anchor);

@ -12,6 +12,7 @@ import {
run_all,
safe_not_equal,
space,
stop_immediate_propagation,
stop_propagation
} from "svelte/internal";
@ -24,6 +25,8 @@ function create_fragment(ctx) {
let button1;
let t5;
let button2;
let t7;
let button3;
let mounted;
let dispose;
@ -41,6 +44,9 @@ function create_fragment(ctx) {
t5 = space();
button2 = element("button");
button2.textContent = "or me!";
t7 = space();
button3 = element("button");
button3.textContent = "or me!";
},
m(target, anchor) {
insert(target, div1, anchor);
@ -51,6 +57,8 @@ function create_fragment(ctx) {
append(div1, button1);
append(div1, t5);
append(div1, button2);
append(div1, t7);
append(div1, button3);
if (!mounted) {
dispose = [
@ -58,6 +66,8 @@ function create_fragment(ctx) {
listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true),
listen(button3, "click", stop_immediate_propagation(handleClick)),
listen(button3, "click", handleTouchstart),
listen(div1, "touchstart", handleTouchstart, { passive: true })
];

@ -13,4 +13,9 @@
<button on:click|stopPropagation|preventDefault={handleClick}>click me</button>
<button on:click|once|capture={handleClick}>or me</button>
<button on:click|capture={handleClick}>or me!</button>
<button
on:click|stopImmediatePropagation={handleClick}
on:click={handleTouchstart}>
or me!
</button>
</div>

@ -30,18 +30,19 @@ function create_fragment(ctx) {
audio_updating = true;
}
/*audio_timeupdate_handler*/ ctx[13].call(audio);
/*audio_timeupdate_handler*/ ctx[14].call(audio);
}
return {
c() {
audio = element("audio");
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[11].call(audio));
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[12].call(audio));
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[12].call(audio));
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[13].call(audio));
if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[10] === void 0) add_render_callback(audio_timeupdate_handler);
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[14].call(audio));
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[18].call(audio));
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[19].call(audio));
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[15].call(audio));
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[19].call(audio));
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[20].call(audio));
if (/*readyState*/ ctx[11] === void 0) add_render_callback(() => /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21].call(audio));
},
m(target, anchor) {
insert(target, audio, anchor);
@ -58,17 +59,24 @@ function create_fragment(ctx) {
if (!mounted) {
dispose = [
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]),
listen(audio, "progress", /*audio_progress_handler*/ ctx[12]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[13]),
listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[19])
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[15]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[16]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[16]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[17]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[18]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[19]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[19]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[20]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "loadeddata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "canplay", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "canplaythrough", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "playing", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "waiting", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "emptied", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21])
];
mounted = true;
@ -119,6 +127,7 @@ function instance($$self, $$props, $$invalidate) {
let { playbackRate } = $$props;
let { seeking } = $$props;
let { ended } = $$props;
let { readyState } = $$props;
function audio_progress_handler() {
buffered = time_ranges_to_array(this.buffered);
@ -173,6 +182,11 @@ function instance($$self, $$props, $$invalidate) {
$$invalidate(10, ended);
}
function audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
readyState = this.readyState;
$$invalidate(11, readyState);
}
$$self.$$set = $$props => {
if ('buffered' in $$props) $$invalidate(0, buffered = $$props.buffered);
if ('seekable' in $$props) $$invalidate(1, seekable = $$props.seekable);
@ -185,6 +199,7 @@ function instance($$self, $$props, $$invalidate) {
if ('playbackRate' in $$props) $$invalidate(8, playbackRate = $$props.playbackRate);
if ('seeking' in $$props) $$invalidate(9, seeking = $$props.seeking);
if ('ended' in $$props) $$invalidate(10, ended = $$props.ended);
if ('readyState' in $$props) $$invalidate(11, readyState = $$props.readyState);
};
return [
@ -199,6 +214,7 @@ function instance($$self, $$props, $$invalidate) {
playbackRate,
seeking,
ended,
readyState,
audio_progress_handler,
audio_loadedmetadata_handler,
audio_timeupdate_handler,
@ -207,7 +223,8 @@ function instance($$self, $$props, $$invalidate) {
audio_volumechange_handler,
audio_ratechange_handler,
audio_seeking_seeked_handler,
audio_ended_handler
audio_ended_handler,
audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
];
}
@ -226,9 +243,10 @@ class Component extends SvelteComponent {
muted: 7,
playbackRate: 8,
seeking: 9,
ended: 10
ended: 10,
readyState: 11
});
}
}
export default Component;
export default Component;

@ -10,6 +10,7 @@
export let playbackRate;
export let seeking;
export let ended;
export let readyState;
</script>
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended/>
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended bind:readyState/>

@ -2,20 +2,16 @@
import {
SvelteComponent,
append,
assign,
bubble,
detach,
element,
empty,
get_spread_update,
init,
insert,
listen,
noop,
run_all,
safe_not_equal,
set_attributes,
set_custom_element_data_map
set_dynamic_element_data
} from "svelte/internal";
function create_dynamic_element(ctx) {
@ -23,36 +19,13 @@ function create_dynamic_element(ctx) {
let svelte_element0;
let mounted;
let dispose;
let svelte_element0_levels = [{ class: "inner" }];
let svelte_element0_data = {};
for (let i = 0; i < svelte_element0_levels.length; i += 1) {
svelte_element0_data = assign(svelte_element0_data, svelte_element0_levels[i]);
}
let svelte_element1_levels = [{ class: "outer" }];
let svelte_element1_data = {};
for (let i = 0; i < svelte_element1_levels.length; i += 1) {
svelte_element1_data = assign(svelte_element1_data, svelte_element1_levels[i]);
}
return {
c() {
svelte_element1 = element(a);
svelte_element0 = element(span);
if ((/-/).test(span)) {
set_custom_element_data_map(svelte_element0, svelte_element0_data);
} else {
set_attributes(svelte_element0, svelte_element0_data);
}
if ((/-/).test(a)) {
set_custom_element_data_map(svelte_element1, svelte_element1_data);
} else {
set_attributes(svelte_element1, svelte_element1_data);
}
set_dynamic_element_data(span)(svelte_element0, { class: "inner" });
set_dynamic_element_data(a)(svelte_element1, { class: "outer" });
},
m(target, anchor) {
insert(target, svelte_element1, anchor);
@ -69,23 +42,7 @@ function create_dynamic_element(ctx) {
mounted = true;
}
},
p(ctx, dirty) {
svelte_element0_data = get_spread_update(svelte_element0_levels, [{ class: "inner" }]);
if ((/-/).test(span)) {
set_custom_element_data_map(svelte_element0, svelte_element0_data);
} else {
set_attributes(svelte_element0, svelte_element0_data);
}
svelte_element1_data = get_spread_update(svelte_element1_levels, [{ class: "outer" }]);
if ((/-/).test(a)) {
set_custom_element_data_map(svelte_element1, svelte_element1_data);
} else {
set_attributes(svelte_element1, svelte_element1_data);
}
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element1);
mounted = false;
@ -95,44 +52,23 @@ function create_dynamic_element(ctx) {
}
function create_fragment(ctx) {
let previous_tag = a;
let svelte_element_anchor;
let svelte_element = a && create_dynamic_element(ctx);
return {
c() {
if (svelte_element) svelte_element.c();
svelte_element_anchor = empty();
},
m(target, anchor) {
if (svelte_element) svelte_element.m(target, anchor);
insert(target, svelte_element_anchor, anchor);
},
p(ctx, [dirty]) {
if (a) {
if (!previous_tag) {
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, a)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
svelte_element.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
svelte_element.p(ctx, dirty);
}
previous_tag = a;
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(svelte_element_anchor);
if (svelte_element) svelte_element.d(detaching);
}
};

@ -2,10 +2,7 @@
import {
SvelteComponent,
append,
assign,
detach,
empty,
get_spread_update,
init,
insert,
noop,
@ -17,37 +14,19 @@ import {
function create_dynamic_element(ctx) {
let svelte_element1;
let svelte_element0;
let svelte_element0_levels = [{ xmlns: "http://www.w3.org/2000/svg" }];
let svelte_element0_data = {};
for (let i = 0; i < svelte_element0_levels.length; i += 1) {
svelte_element0_data = assign(svelte_element0_data, svelte_element0_levels[i]);
}
let svelte_element1_levels = [{ xmlns: "http://www.w3.org/2000/svg" }];
let svelte_element1_data = {};
for (let i = 0; i < svelte_element1_levels.length; i += 1) {
svelte_element1_data = assign(svelte_element1_data, svelte_element1_levels[i]);
}
return {
c() {
svelte_element1 = svg_element(/*tag*/ ctx[0].svg);
svelte_element0 = svg_element(/*tag*/ ctx[0].path);
set_svg_attributes(svelte_element0, svelte_element0_data);
set_svg_attributes(svelte_element1, svelte_element1_data);
set_svg_attributes(svelte_element0, { xmlns: "http://www.w3.org/2000/svg" });
set_svg_attributes(svelte_element1, { xmlns: "http://www.w3.org/2000/svg" });
},
m(target, anchor) {
insert(target, svelte_element1, anchor);
append(svelte_element1, svelte_element0);
},
p(ctx, dirty) {
svelte_element0_data = get_spread_update(svelte_element0_levels, [{ xmlns: "http://www.w3.org/2000/svg" }]);
set_svg_attributes(svelte_element0, svelte_element0_data);
svelte_element1_data = get_spread_update(svelte_element1_levels, [{ xmlns: "http://www.w3.org/2000/svg" }]);
set_svg_attributes(svelte_element1, svelte_element1_data);
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element1);
}
@ -55,44 +34,23 @@ function create_dynamic_element(ctx) {
}
function create_fragment(ctx) {
let previous_tag = /*tag*/ ctx[0].svg;
let svelte_element_anchor;
let svelte_element = /*tag*/ ctx[0].svg && create_dynamic_element(ctx);
return {
c() {
if (svelte_element) svelte_element.c();
svelte_element_anchor = empty();
},
m(target, anchor) {
if (svelte_element) svelte_element.m(target, anchor);
insert(target, svelte_element_anchor, anchor);
},
p(ctx, [dirty]) {
if (/*tag*/ ctx[0].svg) {
if (!previous_tag) {
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, /*tag*/ ctx[0].svg)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
svelte_element.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
svelte_element.p(ctx, dirty);
}
previous_tag = /*tag*/ ctx[0].svg;
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(svelte_element_anchor);
if (svelte_element) svelte_element.d(detaching);
}
};

@ -0,0 +1,244 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
assign,
detach,
element,
empty,
get_spread_update,
init,
insert,
noop,
safe_not_equal,
set_dynamic_element_data,
space,
toggle_class
} from "svelte/internal";
function create_dynamic_element_3(ctx) {
let svelte_element;
return {
c() {
svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, { static_value, ...static_obj });
toggle_class(svelte_element, "foo", static_value);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (10:0) <svelte:element this={dynamic_value} {static_value} {...static_obj} class:foo={static_value} />
function create_dynamic_element_2(ctx) {
let svelte_element;
return {
c() {
svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, ...static_obj });
toggle_class(svelte_element, "foo", static_value);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (12:0) <svelte:element this={static_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
function create_dynamic_element_1(ctx) {
let svelte_element;
let svelte_element_levels = [{ dynamic_value: /*dynamic_value*/ ctx[0] }, /*dynamic_obj*/ ctx[1]];
let svelte_element_data = {};
for (let i = 0; i < svelte_element_levels.length; i += 1) {
svelte_element_data = assign(svelte_element_data, svelte_element_levels[i]);
}
return {
c() {
svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, svelte_element_data);
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p(ctx, dirty) {
set_dynamic_element_data(static_value)(svelte_element, svelte_element_data = get_spread_update(svelte_element_levels, [
dirty & /*dynamic_value*/ 1 && { dynamic_value: /*dynamic_value*/ ctx[0] },
dirty & /*dynamic_obj*/ 2 && /*dynamic_obj*/ ctx[1]
]));
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (14:0) <svelte:element this={dynamic_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
function create_dynamic_element(ctx) {
let svelte_element;
let svelte_element_levels = [{ dynamic_value: /*dynamic_value*/ ctx[0] }, /*dynamic_obj*/ ctx[1]];
let svelte_element_data = {};
for (let i = 0; i < svelte_element_levels.length; i += 1) {
svelte_element_data = assign(svelte_element_data, svelte_element_levels[i]);
}
return {
c() {
svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, svelte_element_data);
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p(ctx, dirty) {
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, svelte_element_data = get_spread_update(svelte_element_levels, [
dirty & /*dynamic_value*/ 1 && { dynamic_value: /*dynamic_value*/ ctx[0] },
dirty & /*dynamic_obj*/ 2 && /*dynamic_obj*/ ctx[1]
]));
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
function create_fragment(ctx) {
let t0;
let previous_tag = /*dynamic_value*/ ctx[0];
let t1;
let t2;
let previous_tag_1 = /*dynamic_value*/ ctx[0];
let svelte_element3_anchor;
let svelte_element0 = static_value && create_dynamic_element_3(ctx);
let svelte_element1 = /*dynamic_value*/ ctx[0] && create_dynamic_element_2(ctx);
let svelte_element2 = static_value && create_dynamic_element_1(ctx);
let svelte_element3 = /*dynamic_value*/ ctx[0] && create_dynamic_element(ctx);
return {
c() {
if (svelte_element0) svelte_element0.c();
t0 = space();
if (svelte_element1) svelte_element1.c();
t1 = space();
if (svelte_element2) svelte_element2.c();
t2 = space();
if (svelte_element3) svelte_element3.c();
svelte_element3_anchor = empty();
},
m(target, anchor) {
if (svelte_element0) svelte_element0.m(target, anchor);
insert(target, t0, anchor);
if (svelte_element1) svelte_element1.m(target, anchor);
insert(target, t1, anchor);
if (svelte_element2) svelte_element2.m(target, anchor);
insert(target, t2, anchor);
if (svelte_element3) svelte_element3.m(target, anchor);
insert(target, svelte_element3_anchor, anchor);
},
p(ctx, [dirty]) {
if (static_value) {
svelte_element0.p(ctx, dirty);
}
if (/*dynamic_value*/ ctx[0]) {
if (!previous_tag) {
svelte_element1 = create_dynamic_element_2(ctx);
previous_tag = /*dynamic_value*/ ctx[0];
svelte_element1.c();
svelte_element1.m(t1.parentNode, t1);
} else if (safe_not_equal(previous_tag, /*dynamic_value*/ ctx[0])) {
svelte_element1.d(1);
svelte_element1 = create_dynamic_element_2(ctx);
previous_tag = /*dynamic_value*/ ctx[0];
svelte_element1.c();
svelte_element1.m(t1.parentNode, t1);
} else {
svelte_element1.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element1.d(1);
svelte_element1 = null;
previous_tag = /*dynamic_value*/ ctx[0];
}
if (static_value) {
svelte_element2.p(ctx, dirty);
}
if (/*dynamic_value*/ ctx[0]) {
if (!previous_tag_1) {
svelte_element3 = create_dynamic_element(ctx);
previous_tag_1 = /*dynamic_value*/ ctx[0];
svelte_element3.c();
svelte_element3.m(svelte_element3_anchor.parentNode, svelte_element3_anchor);
} else if (safe_not_equal(previous_tag_1, /*dynamic_value*/ ctx[0])) {
svelte_element3.d(1);
svelte_element3 = create_dynamic_element(ctx);
previous_tag_1 = /*dynamic_value*/ ctx[0];
svelte_element3.c();
svelte_element3.m(svelte_element3_anchor.parentNode, svelte_element3_anchor);
} else {
svelte_element3.p(ctx, dirty);
}
} else if (previous_tag_1) {
svelte_element3.d(1);
svelte_element3 = null;
previous_tag_1 = /*dynamic_value*/ ctx[0];
}
},
i: noop,
o: noop,
d(detaching) {
if (svelte_element0) svelte_element0.d(detaching);
if (detaching) detach(t0);
if (svelte_element1) svelte_element1.d(detaching);
if (detaching) detach(t1);
if (svelte_element2) svelte_element2.d(detaching);
if (detaching) detach(t2);
if (detaching) detach(svelte_element3_anchor);
if (svelte_element3) svelte_element3.d(detaching);
}
};
}
let static_value = 'a';
function instance($$self, $$props, $$invalidate) {
let { dynamic_value } = $$props;
let { dynamic_obj } = $$props;
let static_obj = {};
$$self.$$set = $$props => {
if ('dynamic_value' in $$props) $$invalidate(0, dynamic_value = $$props.dynamic_value);
if ('dynamic_obj' in $$props) $$invalidate(1, dynamic_obj = $$props.dynamic_obj);
};
return [dynamic_value, dynamic_obj, static_obj];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, { dynamic_value: 0, dynamic_obj: 1 });
}
}
export default Component;

@ -0,0 +1,14 @@
<script>
export let dynamic_value;
export let dynamic_obj;
let static_value = 'a';
let static_obj = {};
</script>
<svelte:element this={static_value} {static_value} {...static_obj} class:foo={static_value} />
<svelte:element this={dynamic_value} {static_value} {...static_obj} class:foo={static_value} />
<svelte:element this={static_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
<svelte:element this={dynamic_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />

@ -30,23 +30,31 @@ function create_fragment(ctx) {
video_updating = true;
}
/*video_timeupdate_handler*/ ctx[4].call(video);
/*video_timeupdate_handler*/ ctx[5].call(video);
}
return {
c() {
video = element("video");
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video));
add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video));
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[6].call(video));
add_render_callback(() => /*video_elementresize_handler*/ ctx[7].call(video));
if (/*readyState*/ ctx[4] === void 0) add_render_callback(() => /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8].call(video));
},
m(target, anchor) {
insert(target, video, anchor);
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video));
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[7].bind(video));
if (!mounted) {
dispose = [
listen(video, "timeupdate", video_timeupdate_handler),
listen(video, "resize", /*video_resize_handler*/ ctx[5])
listen(video, "resize", /*video_resize_handler*/ ctx[6]),
listen(video, "loadedmetadata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "loadeddata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "canplay", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "canplaythrough", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "playing", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "waiting", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "emptied", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8])
];
mounted = true;
@ -75,6 +83,7 @@ function instance($$self, $$props, $$invalidate) {
let { videoHeight } = $$props;
let { videoWidth } = $$props;
let { offsetWidth } = $$props;
let { readyState } = $$props;
function video_timeupdate_handler() {
currentTime = this.currentTime;
@ -93,11 +102,17 @@ function instance($$self, $$props, $$invalidate) {
$$invalidate(3, offsetWidth);
}
function video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
readyState = this.readyState;
$$invalidate(4, readyState);
}
$$self.$$set = $$props => {
if ('currentTime' in $$props) $$invalidate(0, currentTime = $$props.currentTime);
if ('videoHeight' in $$props) $$invalidate(1, videoHeight = $$props.videoHeight);
if ('videoWidth' in $$props) $$invalidate(2, videoWidth = $$props.videoWidth);
if ('offsetWidth' in $$props) $$invalidate(3, offsetWidth = $$props.offsetWidth);
if ('readyState' in $$props) $$invalidate(4, readyState = $$props.readyState);
};
return [
@ -105,9 +120,11 @@ function instance($$self, $$props, $$invalidate) {
videoHeight,
videoWidth,
offsetWidth,
readyState,
video_timeupdate_handler,
video_resize_handler,
video_elementresize_handler
video_elementresize_handler,
video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
];
}
@ -119,9 +136,10 @@ class Component extends SvelteComponent {
currentTime: 0,
videoHeight: 1,
videoWidth: 2,
offsetWidth: 3
offsetWidth: 3,
readyState: 4
});
}
}
export default Component;
export default Component;

@ -3,6 +3,7 @@
export let videoHeight;
export let videoWidth;
export let offsetWidth;
export let readyState;
</script>
<video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth/>
<video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth bind:readyState/>

@ -0,0 +1,3 @@
export default {
html: '<p>2, 1</p>'
};

@ -0,0 +1,6 @@
<script>
const arr = [1, 2];
[arr[0], arr[1]] = [arr[1], arr[0]];
</script>
<p>{arr[0]}, {arr[1]}</p>

@ -0,0 +1,3 @@
export default {
html: '<p>[{"a":2},100]</p>'
};

@ -0,0 +1,7 @@
<script>
const a = 100;
const arr = [{ a: 1 }, 2];
[arr[0].a, arr[1] = a] = [arr[1]];
</script>
<p>{JSON.stringify(arr)}</p>

@ -9,10 +9,6 @@ export default {
html: "<input type='checkbox'>",
// somehow ssr will render indeterminate=""
// the hydrated html will still contain that attribute
ssrHtml: "<input type='checkbox' indeterminate=''>",
test({ assert, component, target }) {
const input = target.querySelector('input');

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

@ -0,0 +1,5 @@
<script>
export let inert;
</script>
<div {inert}>some div <button>click</button></div>

@ -0,0 +1,11 @@
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);
}
};

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

@ -0,0 +1,52 @@
// https://github.com/sveltejs/svelte/issues/7633
export default {
async test({ assert, target, component, window }) {
let inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
component.moveDown(0);
component.moveDown(1);
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`
<div class="item">
b <label><input name="current" type="radio" value="b"> current</label>
</div>
<div class="item">
c <label><input name="current" type="radio" value="c"> current</label>
</div>
<div class="item">
a <label><input name="current" type="radio" value="a"> current</label>
</div>
`
);
// after shifting order, should still keep the correct radio checked
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, true);
(component.current = 'b');
await Promise.resolve();
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
component.moveDown(1);
await Promise.resolve();
// after shifting order, should still keep the correct radio checked
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
}
};

@ -0,0 +1,36 @@
<script>
export let list = [
{ name: "a", text: "This is a test." },
{ name: "b", text: "This is another test." },
{ name: "c", text: "This is also a test." },
];
export let current = "a";
export function moveUp(i) {
list = [
...list.slice(0, Math.max(i - 1, 0)),
list[i],
list[i - 1],
...list.slice(i + 1),
];
}
export function moveDown(i) {
moveUp(i + 1);
}
</script>
{#each list as item (item.name)}
<div class="item">
{item.name}
{#if true}
<label
><input
type="radio"
name="current"
bind:group={current}
value={item.name}
/> current</label
>
{/if}
</div>
{/each}

@ -0,0 +1,88 @@
// https://github.com/sveltejs/svelte/issues/6112
export default {
async test({ assert, target, component, window }) {
let inputs = target.querySelectorAll('input');
const check = (set) => {
for (let i = 0; i < inputs.length; i++) {
assert.equal(inputs[i].checked, set.has(i));
}
};
assert.htmlEqual(
target.innerHTML,
`
<div>1</div>
<div>2
<div class="arg">
<input type="radio" value="a">
<input type="radio" value="b">
</div>
<div class="arg">
<input type="radio" value="c">
<input type="radio" value="d">
</div>
</div>
<div>3
<div class="arg">
<input type="radio" value="a">
<input type="radio" value="b">
</div>
<div class="arg">
<input type="radio" value="c">
<input type="radio" value="d">
</div>
</div>
`
);
check(new Set([0, 2, 5, 6]));
const event = new window.Event('change');
// dom to value
inputs[3].checked = true;
await inputs[3].dispatchEvent(event);
check(new Set([0, 3, 5, 6]));
assert.equal(component.pipelineOperations[1].operation.args[1].value, 'd');
// remove item
component.pipelineOperations = component.pipelineOperations.slice(1);
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`
<div>2
<div class="arg">
<input type="radio" value="a">
<input type="radio" value="b">
</div>
<div class="arg">
<input type="radio" value="c">
<input type="radio" value="d">
</div>
</div>
<div>3
<div class="arg">
<input type="radio" value="a">
<input type="radio" value="b">
</div>
<div class="arg">
<input type="radio" value="c">
<input type="radio" value="d">
</div>
</div>
`
);
inputs = target.querySelectorAll('input');
check(new Set([0, 3, 5, 6]));
inputs[2].checked = true;
await inputs[2].dispatchEvent(event);
check(new Set([0, 2, 5, 6]));
}
};

@ -0,0 +1,60 @@
<script>
export let pipelineOperations = [
{
operation: {
name: "foo",
args: [],
},
id: 1,
},
{
operation: {
name: "bar",
args: [
{
name: "bar_1",
value: "a",
options: [{ value: "a" }, { value: "b" }],
},
{
name: "bar_2",
value: "c",
options: [{ value: "c" }, { value: "d" }],
},
],
},
id: 2,
},
{
operation: {
name: "baz",
args: [
{
name: "baz_1",
value: "b",
options: [{ value: "a" }, { value: "b" }],
},
{
name: "baz_2",
value: "c",
options: [{ value: "c" }, { value: "d" }],
},
],
},
id: 3,
},
];
</script>
{#each pipelineOperations as { operation, id } (id)}
<div>
{id}
{#each operation.args as arg}
<div class="arg">
{#each arg.options as { value }}
<input type="radio" bind:group={arg.value} {value} />
{/each}
</div>
{/each}
</div>
{/each}

@ -0,0 +1,89 @@
// https://github.com/sveltejs/svelte/issues/6112
export default {
async test({ assert, target, component, window }) {
let inputs = target.querySelectorAll('input');
const check = (set) => {
for (let i = 0; i < inputs.length; i++) {
assert.equal(inputs[i].checked, set.has(i));
}
};
assert.htmlEqual(
target.innerHTML,
`
<div>1</div>
<div>2
<div class="arg">
<input type="checkbox" value="a">
<input type="checkbox" value="b">
</div>
<div class="arg">
<input type="checkbox" value="c">
<input type="checkbox" value="d">
</div>
</div>
<div>3
<div class="arg">
<input type="checkbox" value="a">
<input type="checkbox" value="b">
</div>
<div class="arg">
<input type="checkbox" value="c">
<input type="checkbox" value="d">
</div>
</div>
`
);
check(new Set([0, 2]));
const event = new window.Event('change');
// dom to value
inputs[3].checked = true;
await inputs[3].dispatchEvent(event);
check(new Set([0, 2, 3]));
assert.deepEqual(component.pipelineOperations[1].operation.args[1].value, ['c', 'd']);
// remove item
component.pipelineOperations = component.pipelineOperations.slice(1);
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`
<div>2
<div class="arg">
<input type="checkbox" value="a">
<input type="checkbox" value="b">
</div>
<div class="arg">
<input type="checkbox" value="c">
<input type="checkbox" value="d">
</div>
</div>
<div>3
<div class="arg">
<input type="checkbox" value="a">
<input type="checkbox" value="b">
</div>
<div class="arg">
<input type="checkbox" value="c">
<input type="checkbox" value="d">
</div>
</div>
`
);
inputs = target.querySelectorAll('input');
check(new Set([0, 2, 3]));
inputs[5].checked = true;
await inputs[5].dispatchEvent(event);
check(new Set([0, 2, 3, 5]));
assert.deepEqual(component.pipelineOperations[1].operation.args[0].value, ['b']);
}
};

@ -0,0 +1,60 @@
<script>
export let pipelineOperations = [
{
operation: {
name: "foo",
args: [],
},
id: 1,
},
{
operation: {
name: "bar",
args: [
{
name: "bar_1",
value: ["a"],
options: [{ value: "a" }, { value: "b" }],
},
{
name: "bar_2",
value: ["c"],
options: [{ value: "c" }, { value: "d" }],
},
],
},
id: 2,
},
{
operation: {
name: "baz",
args: [
{
name: "baz_1",
value: [],
options: [{ value: "a" }, { value: "b" }],
},
{
name: "baz_2",
value: [],
options: [{ value: "c" }, { value: "d" }],
},
],
},
id: 3,
},
];
</script>
{#each pipelineOperations as { operation, id } (id)}
<div>
{id}
{#each operation.args as arg}
<div class="arg">
{#each arg.options as { value }}
<input type="checkbox" bind:group={arg.value} {value} />
{/each}
</div>
{/each}
</div>
{/each}

@ -0,0 +1,30 @@
export default {
async test({ assert, target, window }) {
const [input1, input2] = target.querySelectorAll('input[type=text]');
const radio = target.querySelector('input[type=radio]');
assert.equal(radio.checked, false);
const event = new window.Event('input');
input1.value = 'world';
await input1.dispatchEvent(event);
assert.equal(radio.checked, true);
input2.value = 'foo';
await input2.dispatchEvent(event);
assert.equal(radio.checked, false);
input1.value = 'foo';
await input1.dispatchEvent(event);
assert.equal(radio.checked, true);
input1.value = 'bar';
await input1.dispatchEvent(event);
assert.equal(radio.checked, false);
input2.value = 'bar';
await input2.dispatchEvent(event);
assert.equal(radio.checked, true);
}
};

@ -0,0 +1,10 @@
<script>
let name = 'world';
let current = '';
</script>
<input type="radio" name="current" bind:group={current} value={name}>
<input type="text" bind:value={current} />
<input type="text" bind:value={name} />

@ -0,0 +1,78 @@
// https://github.com/sveltejs/svelte/issues/7884
export default {
async test({ assert, target, component, window }) {
let inputs = target.querySelectorAll('input');
assert.htmlEqual(target.innerHTML, `
<p>{"foo":[],"bar":[]}</p>
<h2>foo</h2>
<ul>
<li><label><input name="foo" type="checkbox" value="1"> 1</label></li>
<li><label><input name="foo" type="checkbox" value="2"> 2</label></li>
<li><label><input name="foo" type="checkbox" value="3"> 3</label></li>
</ul>
<h2>bar</h2>
<ul>
<li><label><input name="bar" type="checkbox" value="1"> 1</label></li>
<li><label><input name="bar" type="checkbox" value="2"> 2</label></li>
<li><label><input name="bar" type="checkbox" value="3"> 3</label></li>
</ul>
`);
const event = new window.Event('change');
inputs[0].checked = true;
await inputs[0].dispatchEvent(event);
inputs[2].checked = true;
await inputs[2].dispatchEvent(event);
inputs[3].checked = true;
await inputs[3].dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<p>{"foo":[1,3],"bar":[1]}</p>
<h2>foo</h2>
<ul>
<li><label><input name="foo" type="checkbox" value="1"> 1</label></li>
<li><label><input name="foo" type="checkbox" value="2"> 2</label></li>
<li><label><input name="foo" type="checkbox" value="3"> 3</label></li>
</ul>
<h2>bar</h2>
<ul>
<li><label><input name="bar" type="checkbox" value="1"> 1</label></li>
<li><label><input name="bar" type="checkbox" value="2"> 2</label></li>
<li><label><input name="bar" type="checkbox" value="3"> 3</label></li>
</ul>
`);
await component.update();
assert.htmlEqual(target.innerHTML, `
<p>{"foo":[1,3],"bar":[1],"qux":[]}</p>
<h2>qux</h2>
<ul>
<li><label><input name="qux" type="checkbox" value="4"> 4</label></li>
<li><label><input name="qux" type="checkbox" value="5"> 5</label></li>
<li><label><input name="qux" type="checkbox" value="6"> 6</label></li>
</ul>
`);
inputs = target.querySelectorAll('input');
inputs[0].checked = true;
await inputs[0].dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<p>{"foo":[1,3],"bar":[1],"qux":[4]}</p>
<h2>qux</h2>
<ul>
<li><label><input name="qux" type="checkbox" value="4"> 4</label></li>
<li><label><input name="qux" type="checkbox" value="5"> 5</label></li>
<li><label><input name="qux" type="checkbox" value="6"> 6</label></li>
</ul>
`);
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
}
};

@ -0,0 +1,36 @@
<script>
let keys = ["foo", "bar"];
let values = [1, 2, 3];
const object = {};
$: keys.forEach((key) => {
// Make sure Svelte has an array to bind to
if (!object[key]) {
object[key] = [];
}
});
export function update() {
keys = ["qux"];
values = [4, 5, 6];
}
</script>
<p>
{JSON.stringify(object)}
</p>
{#each keys as key (key)}
<h2>{key}</h2>
<ul>
{#each values as value (value)}
<li>
<label>
<input type="checkbox" name={key} {value} bind:group={object[key]} />
{value}
</label>
</li>
{/each}
</ul>
{/each}

@ -0,0 +1,49 @@
// https://github.com/sveltejs/svelte/issues/7633
export default {
async test({ assert, target, component, window }) {
let inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
await component.moveDown(0);
await component.moveDown(1);
assert.htmlEqual(
target.innerHTML,
`
<div class="item">
b <label><input name="current" type="radio" value="b"> current</label>
</div>
<div class="item">
c <label><input name="current" type="radio" value="c"> current</label>
</div>
<div class="item">
a <label><input name="current" type="radio" value="a"> current</label>
</div>
`
);
// after shifting order, should still keep the correct radio checked
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, true);
await (component.current = 'b');
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
await component.moveDown(1);
// after shifting order, should still keep the correct radio checked
inputs = target.querySelectorAll('input');
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
}
};

@ -0,0 +1,36 @@
<script>
export let list = [
{ name: "a", text: "This is a test." },
{ name: "b", text: "This is another test." },
{ name: "c", text: "This is also a test." },
];
export let current = "a";
export function moveUp(i) {
list = [
...list.slice(0, Math.max(i - 1, 0)),
list[i],
list[i - 1],
...list.slice(i + 1),
];
}
export function moveDown(i) {
moveUp(i + 1);
}
</script>
{#each list as item}
<div class="item">
{item.name}
{#if true}
<label
><input
type="radio"
name="current"
bind:group={current}
value={item.name}
/> current</label
>
{/if}
</div>
{/each}

@ -0,0 +1,25 @@
export default {
async test({ assert, target, component, window }) {
const [input1, input2, input3] = target.querySelectorAll('input');
const event = new window.Event('change');
function validate_inputs(v1, v2, v3) {
assert.equal(input1.checked, v1);
assert.equal(input2.checked, v2);
assert.equal(input3.checked, v3);
}
assert.deepEqual(component.values.inner, []);
validate_inputs(false, false, false);
component.values = { inner: undefined };
assert.deepEqual(component.values.inner, undefined);
validate_inputs(false, false, false);
input1.checked = true;
await input1.dispatchEvent(event);
assert.deepEqual(component.values.inner, ['first']);
validate_inputs(true, false, false);
}
};

@ -0,0 +1,15 @@
<script>
export let values = {
inner: []
};
</script>
<input type='checkbox' value='first' bind:group={values.inner} />
<input type='checkbox' value='second' bind:group={values.inner} />
<input type='checkbox' value='third' bind:group={values.inner} />
<div>
{#each ['first', 'second', 'third'] as k}
<span>{k}</span>
{/each}
</div>

@ -5,6 +5,7 @@ export default {
<p>selected: a</p>
<select>
<option disabled='' value='x'>x</option>
<option value='a'>a</option>
<option value='b'>b</option>
<option value='c'>c</option>
@ -18,7 +19,8 @@ export default {
const select = target.querySelector('select');
const options = [...target.querySelectorAll('option')];
// first enabled option should be selected
assert.equal(select.value, 'a');
assert.ok(options[0].selected);
assert.ok(options[1].selected);
}
};

@ -5,6 +5,7 @@
<p>selected: {selected}</p>
<select bind:value={selected}>
<option disabled>x</option>
<option>a</option>
<option>b</option>
<option>c</option>

@ -56,5 +56,40 @@ export default {
<p>selected: d</p>
`);
component.selected = 'b'; // second option should now be selected
assert.equal(select.value, 'b');
assert.ok(options[1].selected);
assert.htmlEqual(target.innerHTML, `
<p>selected: b</p>
<select>
<option value='a'>a</option>
<option value='b'>b</option>
<option value='c'>c</option>
</select>
<p>selected: b</p>
`);
component.selected = undefined; // also doesn't match an option
// now no option should be selected again
assert.equal(select.value, '');
assert.equal(select.selectedIndex, -1);
assert.ok(!options[0].selected);
assert.htmlEqual(target.innerHTML, `
<p>selected: undefined</p>
<select>
<option value='a'>a</option>
<option value='b'>b</option>
<option value='c'>c</option>
</select>
<p>selected: undefined</p>
`);
}
};

@ -1,5 +1,5 @@
<script>
let primary = true;
export let primary = true;
let elem;
</script>

@ -0,0 +1,21 @@
<script>
import InnerChild from './InnerChild.svelte';
export let id = 1;
export let count;
export let increment;
let list;
$: {
list = [];
for (let i = 0; i < count; ++i) {
list.push(i);
}
}
</script>
<div data-id={id}>
{#each list as item (item)}
<InnerChild val={item} {increment} />
{/each}
</div>

@ -0,0 +1,14 @@
<script>
import { afterUpdate } from 'svelte';
export let val = 1;
export let increment;
afterUpdate(() => {
increment();
});
</script>
<inner>
{val}
</inner>

@ -0,0 +1,53 @@
export default {
html: `
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
</div>
`,
ssrHtml: `
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
`,
async test({ assert, component, target }) {
await component.done;
assert.equal(component.getCounter(), 13);
assert.htmlEqual(target.innerHTML, `
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
</div>
`);
}
};

@ -0,0 +1,35 @@
<script>
import { onMount } from 'svelte';
import Child from './Child.svelte';
let updateCounter = 0;
let promiseResolve;
export const done = new Promise(resolve => {
promiseResolve = resolve;
});
export const getCounter = () => {
return updateCounter;
};
let vals = [1, 2, 3];
const instances = [];
let count = 3;
const increment = () => {
++updateCounter;
};
onMount(() => {
count = 2;
setTimeout(() => {
vals = vals.reverse();
setTimeout(promiseResolve);
});
});
</script>
{#each vals as val, index (val)}
<Child bind:this={instances[index]} id={val} {count} {increment} />
{/each}

@ -0,0 +1,21 @@
<script>
import InnerChild from './InnerChild.svelte';
export let id = 1;
export let count;
export let increment;
let list;
$: {
list = [];
for (let i = 0; i < count; ++i) {
list.push(i);
}
}
</script>
<div data-id={id}>
{#each list as item}
<InnerChild val={item} {increment} />
{/each}
</div>

@ -0,0 +1,14 @@
<script>
import { afterUpdate } from 'svelte';
export let val = 1;
export let increment;
afterUpdate(() => {
increment();
});
</script>
<inner>
{val}
</inner>

@ -0,0 +1,53 @@
export default {
html: `
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
</div>
`,
ssrHtml: `
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
<inner>2</inner>
</div>
`,
async test({ assert, component, target }) {
await component.done;
assert.equal(component.getCounter(), 13);
assert.htmlEqual(target.innerHTML, `
<div data-id="3">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="2">
<inner>0</inner>
<inner>1</inner>
</div>
<div data-id="1">
<inner>0</inner>
<inner>1</inner>
</div>
`);
}
};

@ -0,0 +1,35 @@
<script>
import { onMount } from 'svelte';
import Child from './Child.svelte';
let updateCounter = 0;
let promiseResolve;
export const done = new Promise(resolve => {
promiseResolve = resolve;
});
export const getCounter = () => {
return updateCounter;
};
let vals = [1, 2, 3];
const instances = [];
let count = 3;
const increment = () => {
++updateCounter;
};
onMount(() => {
count = 2;
setTimeout(() => {
vals = vals.reverse();
setTimeout(promiseResolve);
});
});
</script>
{#each vals as val, index (val)}
<Child bind:this={instances[index]} id={val} {count} {increment} />
{/each}

@ -0,0 +1,19 @@
export default {
async test({ assert, target, raf }) {
const button = target.querySelector('#button');
const container = target.querySelector('#container');
// Multiple click on button
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
await button.dispatchEvent(new window.MouseEvent('click'));
assert.equal(container.children.length, 1);
raf.tick(501);
assert.equal(container.children.length, 0);
}
};

@ -0,0 +1,13 @@
<script>
import { slide } from 'svelte/transition';
let tag = 'div';
function toggle() {
tag = (tag) ? null : 'div';
}
</script>
<button id="button" on:click={toggle}>toggle</button> TAG={tag}
<div id="container">
<svelte:element this={tag} transition:slide={{duration:500}}>CONTENT</svelte:element>
</div>

@ -0,0 +1,24 @@
const calls = [];
export default {
props: {
calls
},
before_test() {
calls.length = 0;
},
async test({ assert, component, target, window }) {
const buttons = target.querySelector('button');
assert.deepEqual(calls.length, 1);
const event = new window.MouseEvent('click');
await buttons.dispatchEvent(event);
assert.deepEqual(calls.length, 1);
component.current_path = 'bar';
assert.deepEqual(calls.length, 2);
}
};

@ -0,0 +1,16 @@
<script>
export let current_path = 'foo';
export let calls;
let i = 0;
function getComponent(path) {
calls.push(path);
return null;
}
function onClick() {
i = i + 1;
}
</script>
<svelte:component this={getComponent(current_path)} />
<button on:click={onClick}>click me</button>
{i}

@ -0,0 +1,17 @@
export default {
async test({ assert, component, target, window }) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');
await button.dispatchEvent(event);
assert.deepEqual(component.logs, ['click_1', 'click_2']);
component.click_2 = () => component.logs.push('22');
await button.dispatchEvent(event);
assert.deepEqual(component.logs, ['click_1', 'click_2', 'click_1', '22']);
component.click_1 = () => component.logs.push('11');
await button.dispatchEvent(event);
assert.deepEqual(component.logs, ['click_1', 'click_2', 'click_1', '22', '11', '22']);
}
};

@ -0,0 +1,22 @@
<script>
export let logs = [];
export let click_1 = () => {
logs.push('click_1');
}
export let click_2 = () => {
logs.push('click_2');
}
export let click_3 = () => {
logs.push('click_3');
}
</script>
<button
on:click={click_1}
on:click|stopImmediatePropagation={click_2}
on:click={click_3}>
click me
</button>

@ -0,0 +1,19 @@
export default {
html: `
<span data-xxx="&amp;copy=value" style="&amp;copy=value"></span>
<span data-xxx="&amp;copy=value" style="&amp;copy=value"></span>
<span data-xxx="©" style="©"></span>
<span data-xxx="©=value" style="©=value"></span>
<span data-xxx="&amp;copyotherstring=value" style="&amp;copyotherstring=value"></span>
<span data-xxx="&amp;copy123=value" style="&amp;copy123=value"></span>
<span data-xxx="&amp;rect=value" style="&amp;rect=value"></span>
<span data-xxx="▭=value" style="▭=value"></span>
`
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save