Merge remote-tracking branch 'upstream/master'

pull/4522/head
Pontus Lundin 5 years ago
commit 55559e6523

@ -2,7 +2,6 @@
**/expected.js **/expected.js
_output _output
test/*/samples/*/output.js test/*/samples/*/output.js
node_modules
# automatically generated # automatically generated
internal_exports.ts internal_exports.ts

@ -1,65 +1,6 @@
module.exports = { module.exports = {
root: true, root: true,
rules: { extends: '@sveltejs',
indent: 'off',
'no-unused-vars': 'off',
semi: [2, 'always'],
'keyword-spacing': [2, { before: true, after: true }],
'space-before-blocks': [2, 'always'],
'no-mixed-spaces-and-tabs': [2, 'smart-tabs'],
'no-cond-assign': 0,
'object-shorthand': [2, 'always'],
'no-const-assign': 2,
'no-class-assign': 2,
'no-this-before-super': 2,
'no-var': 2,
'no-unreachable': 2,
'valid-typeof': 2,
'quote-props': [2, 'as-needed'],
'one-var': [2, 'never'],
'prefer-arrow-callback': 2,
'prefer-const': [2, { destructuring: 'all' }],
'arrow-spacing': 2,
'no-inner-declarations': 0,
'require-atomic-updates': 'off',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', 'array-simple'],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-object-literal-type-assertion': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-interface': 'off'
},
globals: {
globalThis: false
},
env: {
es6: true,
browser: true,
node: true,
mocha: true
},
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 9,
sourceType: 'module'
},
plugins: ['svelte3'],
settings: { settings: {
'import/core-modules': [ 'import/core-modules': [
'svelte', 'svelte',
@ -69,20 +10,5 @@ module.exports = {
'estree' 'estree'
], ],
'svelte3/compiler': require('./compiler') 'svelte3/compiler': require('./compiler')
}, }
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off'
}
},
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
rules: {
'@typescript-eslint/indent': 'off'
}
}
]
}; };

@ -1,5 +1,14 @@
# Svelte changelog # Svelte changelog
## 3.23.1
* Fix checkbox `bind:group` when multiple options have the same value ([#4397](https://github.com/sveltejs/svelte/issues/4397))
* Fix `bind:this` to the value of an `{#each}` block ([#4517](https://github.com/sveltejs/svelte/issues/4517))
* Fix reactivity when assigning to contextual `{#each}` variable ([#4574](https://github.com/sveltejs/svelte/issues/4574), [#4744](https://github.com/sveltejs/svelte/issues/4744))
* Fix binding to contextual `{#each}` values that shadow outer names ([#4757](https://github.com/sveltejs/svelte/issues/4757))
* Work around EdgeHTML DOM issue when removing attributes during hydration ([#4911](https://github.com/sveltejs/svelte/pull/4911))
* Throw CSS parser error when `:global()` does not contain a selector ([#4930](https://github.com/sveltejs/svelte/issues/4930))
## 3.23.0 ## 3.23.0
* Update `<select>` with `bind:value` when the available `<option>`s change ([#1764](https://github.com/sveltejs/svelte/issues/1764)) * Update `<select>` with `bind:value` when the available `<option>`s change ([#1764](https://github.com/sveltejs/svelte/issues/1764))

1169
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.23.0", "version": "3.23.1",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -63,18 +63,19 @@
"@rollup/plugin-sucrase": "^3.0.0", "@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0", "@rollup/plugin-virtual": "^2.0.0",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v0.0.1",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "^8.10.53", "@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^2.1.0", "@typescript-eslint/parser": "^3.0.2",
"acorn": "^7.1.0", "acorn": "^7.1.0",
"agadoo": "^1.1.0", "agadoo": "^1.1.0",
"c8": "^5.0.1", "c8": "^5.0.1",
"code-red": "0.1.1", "code-red": "0.1.1",
"codecov": "^3.5.0", "codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0", "eslint": "^7.1.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0", "estree-walker": "^1.0.0",
"is-reference": "^1.1.4", "is-reference": "^1.1.4",

@ -601,18 +601,18 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — six
* `duration` (readonly) — the total duration of the video, in seconds * `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects * `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto
* `played` (readonly) — ditto * `played` (readonly) — ditto
* `seekable` (readonly) — ditto
* `seeking` (readonly) — boolean * `seeking` (readonly) — boolean
* `ended` (readonly) — boolean * `ended` (readonly) — boolean
...and five *two-way* bindings: ...and five *two-way* bindings:
* `currentTime` — the current point in the video, in seconds * `currentTime` — the current playback time in the video, in seconds
* `playbackRate` — how fast to play the video, where 1 is 'normal' * `playbackRate` — how fast or slow to play the video, where 1 is 'normal'
* `paused` — this one should be self-explanatory * `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1 * `volume` — a value between 0 and 1
* `muted` — a boolean value where true is muted * `muted` — a boolean value where `true` is muted
Videos additionally have readonly `videoWidth` and `videoHeight` bindings. Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
@ -621,11 +621,12 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
src={clip} src={clip}
bind:duration bind:duration
bind:buffered bind:buffered
bind:played
bind:seekable bind:seekable
bind:seeking bind:seeking
bind:played
bind:ended bind:ended
bind:currentTime bind:currentTime
bind:playbackRate
bind:paused bind:paused
bind:volume bind:volume
bind:muted bind:muted

@ -2,4 +2,10 @@
question: What about Typescript support? question: What about Typescript support?
--- ---
You need to install a [community supported preprocessor](https://github.com/sveltejs/integrations#preprocessors) such as [svelte-preprocess](https://github.com/kaisermann/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83). You can also run type checking from command line with [svelte-check](https://www.npmjs.com/package/svelte-check). You need to install a [community supported preprocessor](https://github.com/sveltejs/integrations#preprocessors) such as [svelte-preprocess](https://github.com/kaisermann/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83). You can also run type checking from the command line with [svelte-check](https://www.npmjs.com/package/svelte-check).
To declare the type of a reactive variable in a Svelte template, you can use the following syntax:
```
let x: number;
$: x = count + 1;
```

@ -101,9 +101,6 @@
.BLM a { .BLM a {
white-space: nowrap; white-space: nowrap;
} }
:global(header){
filter: grayscale(100%) /* BLM */
}
main { main {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
@ -127,8 +124,4 @@
padding-bottom: 75px; /* BLM */ padding-bottom: 75px; /* BLM */
} }
} }
main > :global(*) {
filter: grayscale(100%) /* BLM */
}
</style> </style>

@ -14,6 +14,8 @@ import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression, Identifier } from 'estree'; import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
import { is_reserved_keyword } from '../../utils/reserved_keywords'; import { is_reserved_keyword } from '../../utils/reserved_keywords';
import replace_object from '../../utils/replace_object';
import EachBlock from '../EachBlock';
type Owner = Wrapper | TemplateNode; type Owner = Wrapper | TemplateNode;
@ -134,6 +136,8 @@ export default class Expression {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}); });
const each_block = template_scope.get_owner(name);
(each_block as EachBlock).has_binding = true;
} else { } else {
component.add_reference(name); component.add_reference(name);
@ -311,6 +315,10 @@ export default class Expression {
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const object_name = get_object(assignee).name;
if (scope.has(object_name)) return;
// normally (`a = 1`, `b.c = 2`), there'll be a single name // normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there // (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones // may be more, in which case we need to tack the extra ones
@ -327,6 +335,23 @@ export default class Expression {
} }
}); });
const context = block.bindings.get(object_name);
if (context) {
// for `{#each array as item}`
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
// rather than mutating the local `item` variable
const { snippet, object, property } = context;
const replaced: any = replace_object(assignee, snippet);
if (node.type === 'AssignmentExpression') {
node.left = replaced;
} else {
node.argument = replaced;
}
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
this.replace(invalidate(block.renderer, scope, node, traced)); this.replace(invalidate(block.renderer, scope, node, traced));
} }
} }

@ -26,6 +26,7 @@ export default class Renderer {
options: CompileOptions; options: CompileOptions;
context: ContextMember[] = []; context: ContextMember[] = [];
initial_context: ContextMember[] = [];
context_lookup: Map<string, ContextMember> = new Map(); context_lookup: Map<string, ContextMember> = new Map();
context_overflow: boolean; context_overflow: boolean;
blocks: Array<Block | Node | Node[]> = []; blocks: Array<Block | Node | Node[]> = [];
@ -110,8 +111,12 @@ export default class Renderer {
// these determine whether variable is included in initial context // these determine whether variable is included in initial context
// array, so must have the highest priority // array, so must have the highest priority
if (variable.export_name) member.priority += 8; if (variable.export_name) member.priority += 16;
if (variable.referenced) member.priority += 16; if (variable.referenced) member.priority += 32;
} else if (member.is_non_contextual) {
// determine whether variable is included in initial context
// array, so must have the highest priority
member.priority += 8;
} }
if (!member.is_contextual) { if (!member.is_contextual) {
@ -121,6 +126,17 @@ export default class Renderer {
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number))); this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number)));
this.context.forEach((member, i) => member.index.value = i); this.context.forEach((member, i) => member.index.value = i);
let i = this.context.length;
while (i--) {
const member = this.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
}
this.initial_context = this.context.slice(0, i + 1);
} }
add_to_context(name: string, contextual = false) { add_to_context(name: string, contextual = false) {

@ -301,24 +301,13 @@ export default function dom(
const instance_javascript = component.extract_javascript(component.ast.instance); const instance_javascript = component.extract_javascript(component.ast.instance);
let i = renderer.context.length;
while (i--) {
const member = renderer.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
}
const initial_context = renderer.context.slice(0, i + 1);
const has_definition = ( const has_definition = (
component.compile_options.dev || component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) || (instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 || filtered_props.length > 0 ||
uses_props || uses_props ||
component.partly_hoisted.length > 0 || component.partly_hoisted.length > 0 ||
initial_context.length > 0 || renderer.initial_context.length > 0 ||
component.reactive_declarations.length > 0 || component.reactive_declarations.length > 0 ||
capture_state || capture_state ||
inject_state inject_state
@ -404,7 +393,7 @@ export default function dom(
const return_value = { const return_value = {
type: 'ArrayExpression', type: 'ArrayExpression',
elements: initial_context.map(member => ({ elements: renderer.initial_context.map(member => ({
type: 'Identifier', type: 'Identifier',
name: member.name name: member.name
}) as Expression) }) as Expression)

@ -1,7 +1,9 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding'; import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element'; import ElementWrapper from '../Element';
import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object'; import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object';
import Block from '../../Block'; import Block from '../../Block';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
@ -10,20 +12,20 @@ import { Node, Identifier } from 'estree';
export default class BindingWrapper { export default class BindingWrapper {
node: Binding; node: Binding;
parent: ElementWrapper; parent: ElementWrapper | InlineComponentWrapper;
object: string; object: string;
handler: { handler: {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: (Node | Node[]);
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
snippet?: Node; lhs?: Node;
}; };
snippet: Node; snippet: Node;
is_readonly: boolean; is_readonly: boolean;
needs_lock: boolean; needs_lock: boolean;
constructor(block: Block, node: Binding, parent: ElementWrapper) { constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) {
this.node = node; this.node = node;
this.parent = parent; this.parent = parent;
@ -33,7 +35,7 @@ export default class BindingWrapper {
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`? // TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') { if (parent.node.name === 'select') {
parent.select_binding_dependencies = dependencies; (parent as ElementWrapper).select_binding_dependencies = dependencies;
dependencies.forEach((prop: string) => { dependencies.forEach((prop: string) => {
parent.renderer.component.indirect_dependencies.set(prop, new Set()); parent.renderer.component.indirect_dependencies.set(prop, new Set());
}); });
@ -207,7 +209,7 @@ export default class BindingWrapper {
} }
function get_dom_updater( function get_dom_updater(
element: ElementWrapper, element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper binding: BindingWrapper
) { ) {
const { node } = element; const { node } = element;
@ -270,21 +272,17 @@ function get_event_handler(
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
lhs?: Node; lhs?: Node;
} { } {
const value = get_value_from_dom(renderer, binding.parent, binding); const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);
const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
const context = block.bindings.get(name); const context = block.bindings.get(name);
let set_store; let set_store;
if (context) { if (context) {
const { object, property, modifier, store } = context; const { object, property, store, snippet } = context;
lhs = replace_object(lhs, snippet);
if (lhs.type === 'Identifier') { contextual_dependencies.add(object.name);
lhs = modifier(x`${object}[${property}]`); contextual_dependencies.add(property.name);
contextual_dependencies.delete(name);
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
if (store) { if (store) {
set_store = b`${store}.set(${`$${store}`});`; set_store = b`${store}.set(${`$${store}`});`;
@ -297,6 +295,8 @@ function get_event_handler(
} }
} }
const value = get_value_from_dom(renderer, binding.parent, binding);
const mutation = b` const mutation = b`
${lhs} = ${value}; ${lhs} = ${value};
${set_store} ${set_store}
@ -305,20 +305,21 @@ function get_event_handler(
return { return {
uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
mutation, mutation,
contextual_dependencies contextual_dependencies,
lhs,
}; };
} }
function get_value_from_dom( function get_value_from_dom(
renderer: Renderer, renderer: Renderer,
element: ElementWrapper, element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper binding: BindingWrapper
) { ) {
const { node } = element; const { node } = element;
const { name } = binding.node; const { name } = binding.node;
if (name === 'this') { if (name === 'this') {
return x`$$node`; return x`$$value`;
} }
// <select bind:value='selected> // <select bind:value='selected>
@ -334,7 +335,7 @@ function get_value_from_dom(
if (name === 'group') { if (name === 'group') {
const binding_group = get_binding_group(renderer, binding.node.expression.node); const binding_group = get_binding_group(renderer, binding.node.expression.node);
if (type === 'checkbox') { if (type === 'checkbox') {
return x`@get_binding_group_value($$binding_groups[${binding_group}])`; return x`@get_binding_group_value($$binding_groups[${binding_group}], this.__value, this.checked)`;
} }
return x`this.__value`; return x`this.__value`;

@ -27,6 +27,11 @@ import EventHandler from './EventHandler';
import { extract_names } from 'periscopic'; import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
interface BindingGroup {
events: string[];
bindings: Binding[];
}
const events = [ const events = [
{ {
event_names: ['input'], event_names: ['input'],
@ -436,14 +441,9 @@ export default class ElementWrapper extends Wrapper {
} }
add_directives_in_order (block: Block) { add_directives_in_order (block: Block) {
interface BindingGroup {
events: string[];
bindings: Binding[];
}
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action; type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const bindingGroups = events const binding_groups = events
.map(event => ({ .map(event => ({
events: event.event_names, events: event.event_names,
bindings: this.bindings bindings: this.bindings
@ -467,7 +467,7 @@ export default class ElementWrapper extends Wrapper {
} }
([ ([
...bindingGroups, ...binding_groups,
...this.event_handlers, ...this.event_handlers,
this_binding, this_binding,
...this.node.actions ...this.node.actions
@ -487,144 +487,141 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
add_bindings(block: Block, bindingGroup) { add_bindings(block: Block, binding_group: BindingGroup) {
const { renderer } = this; const { renderer } = this;
if (bindingGroup.bindings.length === 0) return; if (binding_group.bindings.length === 0) return;
renderer.component.has_reactive_assignments = true; renderer.component.has_reactive_assignments = true;
const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ? const lock = binding_group.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var.name}_updating`) : block.get_unique_name(`${this.var.name}_updating`) :
null; null;
if (lock) block.add_variable(lock, x`false`); if (lock) block.add_variable(lock, x`false`);
[bindingGroup].forEach(group => { const handler = renderer.component.get_unique_name(`${this.var.name}_${binding_group.events.join('_')}_handler`);
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); renderer.add_to_context(handler.name);
renderer.add_to_context(handler.name);
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock); const needs_lock = binding_group.bindings.some(binding => binding.needs_lock);
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set(); const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach(binding => { binding_group.bindings.forEach(binding => {
// TODO this is a mess // TODO this is a mess
add_to_set(dependencies, binding.get_dependencies()); add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies); add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
binding.render(block, lock); binding.render(block, lock);
}); });
// media bindings — awkward special case. The native timeupdate events // media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our // fire too infrequently, so we need to take matters into our
// own hands // own hands
let animation_frame; let animation_frame;
if (group.events[0] === 'timeupdate') { if (binding_group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`); animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame); block.add_variable(animation_frame);
} }
const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame; const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
let callee = renderer.reference(handler); let callee = renderer.reference(handler);
// TODO dry this out — similar code for event handlers and component bindings // TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) { if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name)); const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// need to create a block-local function that calls an instance-level function // need to create a block-local function that calls an instance-level function
if (animation_frame) { if (animation_frame) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${handler}() { function ${handler}() {
@_cancelAnimationFrame(${animation_frame}); @_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) { if (!${this.var}.paused) {
${animation_frame} = @raf(${handler}); ${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
}
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && b`${lock} = true;`} ${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
} }
`); ${callee}.call(${this.var}, ${args});
} }
`);
callee = handler; } else {
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
}
`);
} }
const params = Array.from(contextual_dependencies).map(name => ({ callee = handler;
type: 'Identifier', }
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);
group.events.forEach(name => {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);
block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
block.chunks.destroy.push( const params = Array.from(contextual_dependencies).map(name => ({
b`${resize_listener}();` type: 'Identifier',
); name
} else { }));
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})` this.renderer.component.partly_hoisted.push(b`
); function ${handler}(${params}) {
} ${binding_group.bindings.map(b => b.handler.mutation)}
}); ${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);
const some_initial_state_is_undefined = group.bindings binding_group.events.forEach(name => {
.map(binding => x`${binding.snippet} === void 0`) if (name === 'elementresize') {
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); // special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
const should_initialise = ( block.add_variable(resize_listener);
this.node.name === 'select' ||
group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);
if (should_initialise) { block.chunks.mount.push(
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`; b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
); );
}
if (group.events[0] === 'elementresize') { block.chunks.destroy.push(
block.chunks.hydrate.push( b`${resize_listener}();`
b`@add_render_callback(() => ${callee}.call(${this.var}));` );
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
); );
} }
}); });
const some_initial_state_is_undefined = binding_group.bindings
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = (
this.node.name === 'select' ||
binding_group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);
if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}
if (binding_group.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}
if (lock) { if (lock) {
block.chunks.update.push(b`${lock} = false;`); block.chunks.update.push(b`${lock} = false;`);
} }
@ -635,7 +632,7 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true; renderer.component.has_reactive_assignments = true;
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); const binding_callback = bind_this(renderer.component, block, this_binding, this.var);
block.chunks.mount.push(binding_callback); block.chunks.mount.push(binding_callback);
} }

@ -1,4 +1,5 @@
import Wrapper from '../shared/Wrapper'; import Wrapper from '../shared/Wrapper';
import BindingWrapper from '../Element/Binding';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import Block from '../../Block'; import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent'; import InlineComponent from '../../../nodes/InlineComponent';
@ -309,7 +310,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (binding.name === 'this') { if (binding.name === 'this') {
return bind_this(component, block, binding, this.var); return bind_this(component, block, new BindingWrapper(block, binding, this), this.var);
} }
const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`); const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`);

@ -1,50 +1,33 @@
import flatten_reference from '../../../utils/flatten_reference';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Component from '../../../Component'; import Component from '../../../Component';
import Block from '../../Block'; import Block from '../../Block';
import Binding from '../../../nodes/Binding'; import BindingWrapper from '../Element/Binding';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) { export default function bind_this(component: Component, block: Block, binding: BindingWrapper, variable: Identifier) {
const fn = component.get_unique_name(`${variable.name}_binding`); const fn = component.get_unique_name(`${variable.name}_binding`);
block.renderer.add_to_context(fn.name); block.renderer.add_to_context(fn.name);
const callee = block.renderer.reference(fn.name); const callee = block.renderer.reference(fn.name);
let lhs; const { contextual_dependencies, mutation, lhs } = binding.handler;
let object; const dependencies = binding.get_dependencies();
let body;
const body = b`
if (binding.is_contextual && binding.raw_expression.type === 'Identifier') { ${mutation}
// bind:x={y} — we can't just do `y = x`, we need to ${Array.from(dependencies)
// to `array[index] = x; .filter(dep => dep[0] !== '$')
const { name } = binding.raw_expression; .filter(dep => !contextual_dependencies.has(dep))
const { snippet } = block.bindings.get(name); .map(dep => b`${block.renderer.invalidate(dep)};`)}
lhs = snippet; `;
body = b`${lhs} = $$value`; // TODO we need to invalidate... something if (contextual_dependencies.size) {
} else { const params: Identifier[] = Array.from(contextual_dependencies).map(name => ({
object = flatten_reference(binding.raw_expression).name; type: 'Identifier',
lhs = binding.raw_expression; name
}));
body = binding.raw_expression.type === 'Identifier'
? b`
${block.renderer.invalidate(object, x`${lhs} = $$value`)};
`
: b`
${lhs} = $$value;
${block.renderer.invalidate(object)};
`;
}
const contextual_dependencies: Identifier[] = Array.from(binding.expression.contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
if (contextual_dependencies.length) {
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${fn}($$value, ${contextual_dependencies}) { function ${fn}($$value, ${params}) {
if (${lhs} === $$value) return; if (${lhs} === $$value) return;
@binding_callbacks[$$value ? 'unshift' : 'push'](() => { @binding_callbacks[$$value ? 'unshift' : 'push'](() => {
${body} ${body}
@ -53,7 +36,7 @@ export default function bind_this(component: Component, block: Block, binding: B
`); `);
const args = []; const args = [];
for (const id of contextual_dependencies) { for (const id of params) {
args.push(id); args.push(id);
if (block.variables.has(id.name)) { if (block.variables.has(id.name)) {
if (block.renderer.context_lookup.get(id.name).is_contextual) continue; if (block.renderer.context_lookup.get(id.name).is_contextual) continue;
@ -69,7 +52,7 @@ export default function bind_this(component: Component, block: Block, binding: B
const ${unassign} = () => ${callee}(null, ${args}); const ${unassign} = () => ${callee}(null, ${args});
`); `);
const condition = Array.from(contextual_dependencies) const condition = Array.from(params)
.map(name => x`${name} !== ${block.renderer.reference(name.name)}`) .map(name => x`${name} !== ${block.renderer.reference(name.name)}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);

@ -0,0 +1,14 @@
import { Node } from 'estree';
export default function replace_object(node: Node, replacement: Node) {
if (node.type === 'Identifier') return replacement;
const ancestor = node;
let parent;
while (node.type === 'MemberExpression') {
parent = node;
node = node.object;
}
parent.object = replacement;
return ancestor;
}

@ -54,6 +54,13 @@ export default function read_style(parser: Parser, start: number, attributes: No
}, node.start); }, node.start);
} }
if (node.type === 'PseudoClassSelector' && node.name === 'global' && node.children === null) {
parser.error({
code: `css-syntax-error`,
message: `:global() must contain a selector`
}, node.loc.start.offset);
}
if (node.loc) { if (node.loc) {
node.start = node.loc.start.offset; node.start = node.loc.start.offset;
node.end = node.loc.end.offset; node.end = node.loc.end.offset;

@ -19,7 +19,6 @@ interface Fragment {
/* outro */ o: (local: any) => void; /* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0|1) => void; /* destroy */ d: (detaching: 0|1) => void;
} }
// eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ { interface T$$ {
dirty: number[]; dirty: number[];
ctx: null|any; ctx: null|any;

@ -126,12 +126,15 @@ export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
} }
export function get_binding_group_value(group) { export function get_binding_group_value(group, __value, checked) {
const value = []; const value = new Set();
for (let i = 0; i < group.length; i += 1) { for (let i = 0; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value); if (group[i].checked) value.add(group[i].__value);
} }
return value; if (!checked) {
value.delete(__value);
}
return Array.from(value);
} }
export function to_number(value) { export function to_number(value) {
@ -155,14 +158,16 @@ export function claim_element(nodes, name, attributes, svg) {
const node = nodes[i]; const node = nodes[i];
if (node.nodeName === name) { if (node.nodeName === name) {
let j = 0; let j = 0;
const remove = [];
while (j < node.attributes.length) { while (j < node.attributes.length) {
const attribute = node.attributes[j]; const attribute = node.attributes[j++];
if (attributes[attribute.name]) { if (!attributes[attribute.name]) {
j++; remove.push(attribute.name);
} else {
node.removeAttribute(attribute.name);
} }
} }
for (let k = 0; k < remove.length; k++) {
node.removeAttribute(remove[k]);
}
return nodes.splice(i, 1)[0]; return nodes.splice(i, 1)[0];
} }
} }

@ -0,0 +1,59 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal
} from "svelte/internal";
function create_fragment(ctx) {
let button;
let mounted;
let dispose;
return {
c() {
button = element("button");
},
m(target, anchor) {
insert(target, button, anchor);
if (!mounted) {
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(button);
mounted = false;
dispose();
}
};
}
function instance($$self, $$props, $$invalidate) {
let foo;
function unreferenced() {
$$invalidate(0, foo = 1);
}
const click_handler = () => $$invalidate(0, foo = 2);
return [foo, click_handler];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,7 @@
<script>
let foo;
function unreferenced () {
foo = 1;
}
</script>
<button on:click={() => foo = 2}></button>

@ -116,7 +116,8 @@ function instance($$self, $$props, $$invalidate) {
function div_binding($$value) { function div_binding($$value) {
binding_callbacks[$$value ? "unshift" : "push"](() => { binding_callbacks[$$value ? "unshift" : "push"](() => {
$$invalidate(0, node = $$value); node = $$value;
$$invalidate(0, node);
}); });
} }

@ -0,0 +1,10 @@
{
"code": "css-syntax-error",
"message": ":global() must contain a selector",
"start": {
"line": 2,
"column": 1,
"character": 9
},
"pos": 9
}

@ -0,0 +1,81 @@
export default {
html: `
<p>Checked: </p>
<hr>
<input type='checkbox' value='a'>a<br>
<input type='checkbox' value='b'>b<br>
<input type='checkbox' value='c'>c<br>
<input type='checkbox' value='d'>d<br>
<hr>
<input type='checkbox' value='a'>a<br>
<input type='checkbox' value='b'>b<br>
<input type='checkbox' value='c'>c<br>
<input type='checkbox' value='d'>d<br>
`,
async test({ assert, component, target, window }) {
const inputs = target.querySelectorAll("input");
const p = target.querySelector("p");
assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
assert.equal(inputs[3].checked, false);
assert.equal(inputs[4].checked, false);
assert.equal(inputs[5].checked, false);
assert.equal(inputs[6].checked, false);
assert.equal(inputs[7].checked, false);
const event = new window.Event("change");
inputs[0].checked = true;
await inputs[0].dispatchEvent(event);
assert.htmlEqual(p.innerHTML, `Checked: a`);
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
assert.equal(inputs[3].checked, false);
assert.equal(inputs[4].checked, true);
assert.equal(inputs[5].checked, false);
assert.equal(inputs[6].checked, false);
assert.equal(inputs[7].checked, false);
inputs[3].checked = true;
await inputs[3].dispatchEvent(event);
assert.htmlEqual(p.innerHTML, `Checked: a,d`);
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
assert.equal(inputs[3].checked, true);
assert.equal(inputs[4].checked, true);
assert.equal(inputs[5].checked, false);
assert.equal(inputs[6].checked, false);
assert.equal(inputs[7].checked, true);
inputs[4].checked = false;
await inputs[4].dispatchEvent(event);
assert.htmlEqual(p.innerHTML, `Checked: d`);
assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false);
assert.equal(inputs[3].checked, true);
assert.equal(inputs[4].checked, false);
assert.equal(inputs[5].checked, false);
assert.equal(inputs[6].checked, false);
assert.equal(inputs[7].checked, true);
},
};

@ -0,0 +1,19 @@
<script>
let foo = [];
</script>
<p>Checked: {foo}</p>
<hr>
<input type='checkbox' bind:group={foo} value='a'>a<br>
<input type='checkbox' bind:group={foo} value='b'>b<br>
<input type='checkbox' bind:group={foo} value='c'>c<br>
<input type='checkbox' bind:group={foo} value='d'>d<br>
<hr>
<input type='checkbox' bind:group={foo} value='a'>a<br>
<input type='checkbox' bind:group={foo} value='b'>b<br>
<input type='checkbox' bind:group={foo} value='c'>c<br>
<input type='checkbox' bind:group={foo} value='d'>d<br>

@ -0,0 +1,23 @@
export default {
html: `
Hello
<input />
`,
ssrHtml: `
Hello
<input value="Hello"/>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
input.value = "abcd";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
abcd
<input />
`
);
},
};

@ -0,0 +1,10 @@
<script>
let a = [
{ a: 'Hello' }
];
</script>
{#each a as { a }}
{a}
<input bind:value={a} />
{/each}

@ -0,0 +1,105 @@
export default {
html: `
<div>
Hello World
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`,
ssrHtml: `
<div>
Hello World
<input value="Hello"/>
<input value="World"/>
</div>
<div>
Sapper App
<input value="Sapper"/>
<input value="App"/>
</div>
`,
async test({ assert, target, window }) {
const [input1, input2, input3, input4] = target.querySelectorAll("input");
input1.value = "Awesome";
await input1.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome World
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`
);
input2.value = "Svelte";
await input2.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`
);
input3.value = "Foo";
await input3.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Foo App
<input />
<input />
</div>
`
);
input4.value = "Bar";
await input4.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Foo Bar
<input />
<input />
</div>
`
);
},
};

@ -0,0 +1,14 @@
<script>
let a = [
['Hello', 'World'],
['Sapper', 'App'],
]
</script>
{#each a as a}
<div>
{a[0]} {a[1]}
<input bind:value={a[0]}>
<input bind:value={a[1]}>
</div>
{/each}

@ -0,0 +1,64 @@
export default {
html: `
<div>
b: Hello
<input />
</div>
<button>Button</button>
`,
ssrHtml: `
<div>
b: Hello
<input value="Hello" />
</div>
<button>Button</button>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
const button = target.querySelector("button");
input.value = "Awesome";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
b: Awesome
<input />
</div>
<button>Button</button>
`
);
await button.dispatchEvent(new window.MouseEvent("click"));
assert.htmlEqual(
target.innerHTML,
`
<div>
c: World
<input />
</div>
<button>Button</button>
`
);
assert.equal(input.value, 'World');
input.value = "Svelte";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
c: Svelte
<input />
</div>
<button>Button</button>
`
);
},
};

@ -0,0 +1,14 @@
<script>
let a = [
{ a: { b: 'Hello', c: 'World' }, key: 'b' },
];
</script>
{#each a as { a, key }}
<div>
{key}: {a[key]}
<input bind:value={a[key]}>
</div>
{/each}
<button on:click={() => a[0].key = 'c'}>Button</button>

@ -0,0 +1,23 @@
export default {
html: `
Hello
<input />
`,
ssrHtml: `
Hello
<input value="Hello"/>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
input.value = "abcd";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
abcd
<input />
`
);
},
};

@ -0,0 +1,10 @@
<script>
let a = [
'Hello'
];
</script>
{#each a as a}
{a}
<input bind:value={a} />
{/each}

@ -0,0 +1,20 @@
export default {
html: `
<span class="content">foo</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
const button = target.querySelector("button");
const clickEvent = new window.MouseEvent("click");
await button.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<span class="content">bar</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,12 @@
<script>
let obj = {
prop: "foo"
};
let arr = [obj]
</script>
{#each arr as o}
<span class="content">{o.prop}</span>
<button on:click={ () => o = { ...o, prop: "bar" } }>Test</button>
{/each}

@ -0,0 +1,97 @@
export default {
html: `
<button>Add</button>
<span class="content">1</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
let [incrementBtn, ...buttons] = target.querySelectorAll("button");
const clickEvent = new window.MouseEvent("click");
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[2].dispatchEvent(clickEvent);
await buttons[2].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
`
);
await incrementBtn.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">4</span>
<button>Test</button>
`
);
[incrementBtn, ...buttons] = target.querySelectorAll("button");
await buttons[3].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">8</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,13 @@
<script>
let obj = {
prop: "foo"
};
let arr = [1, 2, 3]
</script>
<button on:click={() => arr = [...arr, arr.length + 1]}>Add</button>
{#each arr as o}
<span class="content">{o}</span>
<button on:click={() => { o *= 2; }}>Test</button>
{/each}
Loading…
Cancel
Save