Merge branch 'master' into a11y-no-noninteractive-element-to-interactive-role

pull/8167/head
Nguyen Tran 4 years ago
commit 30b2e8e33d

@ -1,44 +1,45 @@
# Svelte changelog
## Unreleased
## 3.56.0
* Add `|stopImmediatePropagation` event modifier ([#5085](https://github.com/sveltejs/svelte/issues/5085))
* Add `axis` parameter to `slide` transition ([#6182](https://github.com/sveltejs/svelte/issues/6182))
* Add `readonly` utility to convert `writable` store to readonly ([#6518](https://github.com/sveltejs/svelte/pull/6518))
* Add `readyState` binding for media elements ([#6666](https://github.com/sveltejs/svelte/issues/6666))
* Generate valid automatic component names when the filename contains only special characters ([#7143](https://github.com/sveltejs/svelte/issues/7143))
* Add `naturalWidth` and `naturalHeight` bindings ([#7771](https://github.com/sveltejs/svelte/issues/7771))
* Support `<!-- svelte-ignore ... -->` on components ([#8082](https://github.com/sveltejs/svelte/issues/8082))
* Add a11y warnings:
* `aria-activedescendant-has-tabindex`: elements with `aria-activedescendant` need to have a `tabindex` ([#8172](https://github.com/sveltejs/svelte/pull/8172))
* `aria-activedescendant-has-tabindex`: checks that elements with `aria-activedescendant` 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))
* Don't throw when calling `unsubscribe` twice ([#8186](https://github.com/sveltejs/svelte/pull/8186))
* Detect unused empty attribute CSS selectors ([#8042](https://github.com/sveltejs/svelte/issues/8042))
* Simpler output for reactive statements if dependencies are all static ([#7942](https://github.com/sveltejs/svelte/pull/7942))
* Flush remaining `afterUpdate` calls before `onDestroy` ([#7476](https://github.com/sveltejs/svelte/issues/7476))
* Check value equality for `input` types `url` and `search` ([#7027](https://github.com/sveltejs/svelte/issues/7027))
* 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))
* Compute node dimensions immediately before crossfading ([#4111](https://github.com/sveltejs/svelte/issues/4111))
* Fix potential infinite invalidate loop with `<svelte:component>` ([#4129](https://github.com/sveltejs/svelte/issues/4129))
* 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))
* Validate component `:global()` selectors ([#6272](https://github.com/sveltejs/svelte/issues/6272))
* Improve warnings:
* Make `noreferrer` warning less zealous ([#6289](https://github.com/sveltejs/svelte/issues/6289))
* Omit a11y warnings on `<video aria-hidden="true">` ([#7874](https://github.com/sveltejs/svelte/issues/7874))
* Omit a11y warnings on `<svelte:element>` ([#7939](https://github.com/sveltejs/svelte/issues/7939))
* Detect unused empty attribute CSS selectors ([#8042](https://github.com/sveltejs/svelte/issues/8042))
* Omit "no child content" warning on elements with `aria-label` ([#8296](https://github.com/sveltejs/svelte/issues/8296))
* Check value equality for `<input type="search">` and `<input type="url">` ([#7027](https://github.com/sveltejs/svelte/issues/7027))
* Do not select a disabled `<option>` by default when the initial bound value is undefined ([#7041](https://github.com/sveltejs/svelte/issues/7041))
* 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))
* Ensure `afterUpdate` is not called after `onDestroy` ([#7476](https://github.com/sveltejs/svelte/issues/7476))
* Improve handling of `inert` attribute ([#7500](https://github.com/sveltejs/svelte/issues/7500))
* Reduce use of template literals in SSR output for better performance ([#7539](https://github.com/sveltejs/svelte/pull/7539))
* Ensure `<input>` value persists when swapping elements with spread attributes in an `{#each}` block ([#7578](https://github.com/sveltejs/svelte/issues/7578))
* Simplify generated code for reactive statements if dependencies are all static ([#7942](https://github.com/sveltejs/svelte/pull/7942))
* Fix race condition on `<svelte:element>` with transitions ([#7948](https://github.com/sveltejs/svelte/issues/7948))
* Allow assigning to a property of a `const` when destructuring ([#7964](https://github.com/sveltejs/svelte/issues/7964))
* Match browser behavior for decoding malformed HTML entities ([#8026](https://github.com/sveltejs/svelte/issues/8026))
* Ensure `trusted-types` CSP compatibility for Web Components ([#8134](https://github.com/sveltejs/svelte/issues/8134))
* Optimise `<svelte:element>` output code for static tag and static attribute ([#8161](https://github.com/sveltejs/svelte/pull/8161))
* Don't throw when calling unsubscribing from a store twice ([#8186](https://github.com/sveltejs/svelte/pull/8186))
* Clear inputs when `bind:group` value is set to `undefined` ([#8214](https://github.com/sveltejs/svelte/issues/8214))
* Fix handling of nested arrays with keyed `{#each}` containing a non-keyed `{#each}` ([#8282](https://github.com/sveltejs/svelte/issues/8282))
## 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;
@ -866,9 +869,9 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
}
export interface HTMLMetaAttributes extends HTMLAttributes<HTMLMetaElement> {
charSet?: string | undefined | null;
charset?: string | undefined | null;
content?: string | undefined | null;
httpequiv?: string | undefined | null;
'http-equiv'?: string | undefined | null;
name?: string | undefined | null;
media?: string | undefined | null;
}

1330
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.55.1",
"version": "3.56.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -122,41 +122,41 @@
"@ampproject/remapping": "^0.3.0",
"@jridgewell/sourcemap-codec": "^1.4.14",
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0",
"@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.0",
"@types/aria-query": "^5.0.1",
"@types/mocha": "^7.0.0",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"acorn": "^8.8.1",
"agadoo": "^2.0.0",
"aria-query": "^5.1.1",
"agadoo": "^3.0.0",
"aria-query": "^5.1.3",
"axobject-query": "^3.1.1",
"code-red": "^0.2.5",
"css-tree": "^2.2.1",
"eslint": "^8.26.0",
"eslint-plugin-import": "^2.26.0",
"code-red": "^1.0.0",
"css-tree": "^2.3.1",
"eslint": "^8.35.0",
"eslint-plugin-import": "^2.27.0",
"eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.1",
"is-reference": "^3.0.0",
"estree-walker": "^3.0.3",
"is-reference": "^3.0.1",
"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",
"periscopic": "^3.1.0",
"puppeteer": "^2.0.0",
"rollup": "^1.27.14",
"source-map": "^0.7.4",
"source-map-support": "^0.5.21",
"tiny-glob": "^0.2.9",
"tslib": "^2.4.1",
"tslib": "^2.5.0",
"typescript": "^3.7.5",
"util": "^0.12.5"
}

@ -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`

@ -951,7 +951,7 @@ export default class Component {
});
}
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string, scope: Scope) {
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string | number | symbol, scope: Scope) {
if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&

@ -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);
}
@ -189,7 +189,7 @@ export default class Selector {
index !== 0 &&
selector.children &&
selector.children.length > 0 &&
!/[.:#\s]/.test(selector.children[0].value)
!/[.:#\s]/.test(selector.children[0].value[0])
) {
component.error(selector, compiler_errors.css_invalid_global_selector_position);
}

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

@ -23,7 +23,7 @@ import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
import { ARIARoleDefinitionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element, is_abstract_role } from '../utils/a11y';
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
@ -565,7 +565,7 @@ export default class Element extends Node {
const value = attribute.get_static_value();
if (typeof value === 'string') {
value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefintionKey) => {
value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefinitionKey) => {
if (current_role && is_abstract_role(current_role)) {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role));
} else if (current_role && !aria_role_set.has(current_role)) {
@ -643,7 +643,7 @@ export default class Element extends Node {
// click-events-have-key-events
if (handlers_map.has('click')) {
const role = attribute_map.get('role');
const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefintionKey);
const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefinitionKey);
if (
!this.is_dynamic_element &&
@ -667,7 +667,7 @@ export default class Element extends Node {
}
// no-noninteractive-tabindex
if (!this.is_dynamic_element && !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 ARIARoleDefinitionKey)) {
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);
@ -676,7 +676,7 @@ export default class Element extends Node {
// 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;
const role_value = (role ? role.get_static_value() : get_implicit_role(this.name, attribute_map)) as ARIARoleDefinitionKey;
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)));

@ -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,

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

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

@ -163,6 +163,8 @@ export default class ElementWrapper extends Wrapper {
has_dynamic_attribute: boolean;
select_binding_dependencies?: Set<string>;
has_dynamic_value: boolean;
dynamic_value_condition: any;
var: any;
void: boolean;

@ -418,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);
@ -468,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};

@ -1,5 +1,5 @@
import {
ARIARoleDefintionKey,
ARIARoleDefinitionKey,
roles as roles_map,
elementRoles,
ARIARoleRelationConcept
@ -34,11 +34,11 @@ const interactive_roles = new Set(
non_abstract_roles.filter((name) => !non_interactive_roles.has(name))
);
export function is_non_interactive_roles(role: ARIARoleDefintionKey) {
export function is_non_interactive_roles(role: ARIARoleDefinitionKey) {
return non_interactive_roles.has(role);
}
export function is_interactive_roles(role: ARIARoleDefintionKey) {
export function is_interactive_roles(role: ARIARoleDefinitionKey) {
return interactive_roles.has(role);
}
@ -48,7 +48,7 @@ export function is_abstract_role(role: ARIARoleDefintionKey) {
const presentation_roles = new Set(['presentation', 'none']);
export function is_presentation_role(role: ARIARoleDefintionKey) {
export function is_presentation_role(role: ARIARoleDefinitionKey) {
return presentation_roles.has(role);
}
@ -186,7 +186,7 @@ export function is_static_element(tag_name: string, attribute_map: Map<string, A
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static;
}
export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) {
export function is_semantic_role_element(role: ARIARoleDefinitionKey, tag_name: string, attribute_map: Map<string, Attribute>) {
for (const [schema, ax_object] of elementAXObjects.entries()) {
if (schema.name === tag_name && (!schema.attributes || schema.attributes.every(
(attr) => attribute_map.has(attr.name) && attribute_map.get(attr.name).get_static_value() === attr.value

@ -3,12 +3,12 @@ import * as code_red from 'code-red';
export const parse = (source: string): Node => code_red.parse(source, {
sourceType: 'module',
ecmaVersion: 12,
ecmaVersion: 13,
locations: true
});
export const parse_expression_at = (source: string, index: number): Node => code_red.parseExpressionAt(source, index, {
sourceType: 'module',
ecmaVersion: 12,
ecmaVersion: 13,
locations: true
});

@ -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,7 +268,7 @@ 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);
@ -284,14 +284,14 @@ export function stop_immediate_propagation(fn) {
}
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);
};
@ -359,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;
}
@ -392,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 };
}
}
@ -668,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;

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

@ -8,7 +8,7 @@ export const binding_callbacks = [];
let render_callbacks = [];
const flush_callbacks = [];
const resolved_promise = Promise.resolve();
const resolved_promise = /* @__PURE__ */ Promise.resolve();
let update_scheduled = false;
export function schedule_update() {

@ -0,0 +1,25 @@
/* generated by Svelte vX.Y.Z */
import { SvelteComponent, init, safe_not_equal } from "svelte/internal";
function instance($$self) {
class A {
p1;
p2 = 1;
#p3 = 2;
#getP3() {
return this.#p3;
}
}
return [];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, null, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,12 @@
<script>
class A {
p1;
p2 = 1;
#p3 = 2;
#getP3() {
return this.#p3;
}
}
</script>

@ -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,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}

@ -18,10 +18,13 @@ export async function test({ assert, css, js }) {
// TODO make util fn + move to test index.js
const sourcefile = 'input.svelte';
[
// TODO how to get line + column numbers?
// TODO: get line and col num from input.svelte rather than hardcoding here
[css, '--keep-me', 13, 2],
[css, '--done-replace-once', 6, 5],
[css, '--done-replace-twice', 9, 5]
// TODO: these should be 7, 2 and 10, 2
// we use locate_1 which means lines are 1-indexed and cols are 0-indexed
// each tab is 1 col
[css, '--done-replace-once', 6, 4],
[css, '--done-replace-twice', 9, 4]
]
.forEach(([where, content, line, column]) => {
assert.deepEqual(

@ -0,0 +1,12 @@
[{
"code": "css-invalid-global-selector-position",
"message": ":global(...) not at the start of a selector sequence should not contain type or universal selectors",
"start": {
"line": 2,
"column": 5
},
"end": {
"line": 2,
"column": 21
}
}]

@ -0,0 +1,7 @@
<style>
.foo:global(div.bar) {
color: red;
}
</style>
<div class="foo" />
Loading…
Cancel
Save