diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1adaf0b76..2849115008 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,27 @@
# Svelte changelog
+## 3.5.4
+
+* Preserve whitespace at the boundaries of `{#each}` blocks ([#713](https://github.com/sveltejs/svelte/issues/713))
+* Fix dynamic `bind:this` on components ([#2333](https://github.com/sveltejs/svelte/issues/2333))
+* Fix binding to values in a component when it uses `$$props` ([#2725](https://github.com/sveltejs/svelte/issues/2725))
+* Fix parsing ambiguous HTML entities ([#3071](https://github.com/sveltejs/svelte/pull/3071))
+
+## 3.5.3
+
+* Don't double-destroy keyed each blocks with outros ([#3055](https://github.com/sveltejs/svelte/issues/3055))
+
+## 3.5.2
+
+* Prevent duplicated outros causing errors ([#3001](https://github.com/sveltejs/svelte/issues/3001))
+* Fix automatic name generation ([#2843](https://github.com/sveltejs/svelte/issues/2843))
+* Fix .d.ts stubs ([#3009](https://github.com/sveltejs/svelte/pull/3009))
+* Don't strip non-breaking spaces ([#3014](https://github.com/sveltejs/svelte/issues/3014))
+* Fix `requestAnimationFrame` context ([#2933](https://github.com/sveltejs/svelte/issues/2933))
+* Allow space before attribute value ([#3026](https://github.com/sveltejs/svelte/issues/3026))
+* Remove null/undefined attributes ([#1434](https://github.com/sveltejs/svelte/issues/1434))
+* Fix whitespace in static markup ([#3030](https://github.com/sveltejs/svelte/pull/3030))
+
## 3.5.1
* Accommodate webpack idiosyncracies
diff --git a/package-lock.json b/package-lock.json
index aea08689b1..0aef0fa227 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.5.1",
+ "version": "3.5.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -253,7 +253,7 @@
},
"array-equal": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+ "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
@@ -676,7 +676,7 @@
},
"commander": {
"version": "2.15.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+ "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true
},
@@ -1876,7 +1876,7 @@
},
"is-builtin-module": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true,
"requires": {
@@ -2357,7 +2357,7 @@
},
"minimist": {
"version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
@@ -2384,7 +2384,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
@@ -2790,7 +2790,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
@@ -3527,7 +3527,7 @@
},
"safe-regex": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
diff --git a/package.json b/package.json
index 37bd6f75e7..96a1dd8f99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.5.1",
+ "version": "3.5.4",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
diff --git a/site/README.md b/site/README.md
index 59380feffc..259e956003 100644
--- a/site/README.md
+++ b/site/README.md
@@ -4,7 +4,10 @@ Set up the project:
```bash
git clone https://github.com/sveltejs/svelte.git
-cd svelte/site
+cd svelte
+npm ci
+PUBLISH=1 npm run build
+cd site
npm ci
npm run update
```
@@ -17,7 +20,7 @@ By default, the REPL will fetch the most recent version of Svelte from https://u
To produce the proper browser-compatible UMD build of the compiler, you will need to run `npm run build` (or `npm run dev`) in the root of this repository with the `PUBLISH` environment variable set to any non-empty string.
-Then visit the REPL at [localhost:3000/repl?version=local](http://localhost:3000/repl?version=local).
+Then visit the REPL at [localhost:3000/repl?version=local](http://localhost:3000/repl?version=local). Please note that the local REPL only works with `npm run dev` and not when building the site for production usage.
## REPL GitHub integration
@@ -32,6 +35,13 @@ In order for the REPL's GitHub integration to work properly when running locally
GITHUB_CLIENT_SECRET=[your app's Client Secret]
BASEURL=http://localhost:3000
```
+## Building the site
+
+To build the website, run `npm run sapper`. The output can be found in `__sapper__/build`.
+
+## Testing
+
+Tests can be run using `npm run test`.
## Translating the API docs
diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md
index a6e17f8bc2..682012d88b 100644
--- a/site/content/docs/02-template-syntax.md
+++ b/site/content/docs/02-template-syntax.md
@@ -188,12 +188,20 @@ If a *key* expression is provided — which must uniquely identify each list ite
---
-You can freely use destructuring patterns in each blocks.
+You can freely use destructuring and rest patterns in each blocks.
```html
{#each items as { id, name, qty }, i (id)}
{i + 1}: {name} x {qty}
{/each}
+
+{#each objects as { id, ...rest }}
+
{id}
+{/each}
+
+{#each items as [id, ...rest]}
+
{id}
+{/each}
```
---
@@ -1241,7 +1249,7 @@ It cannot appear at the top level of your markup; it must be inside an if or eac
### ``
```sv
-
+
```
---
@@ -1318,7 +1326,7 @@ As with ``, this element allows you to add listeners to events on
### ``
```sv
-
+...
```
---
@@ -1335,7 +1343,7 @@ This element makes it possible to insert elements into `document.head`. During s
### ``
```sv
-
+
```
---
@@ -1351,4 +1359,4 @@ The `` element provides a place to specify per-component compile
```html
-```
\ No newline at end of file
+```
diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte
index fc6725e894..04b4de5cf2 100644
--- a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte
+++ b/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte
@@ -7,7 +7,7 @@
- {#each [0, 1, 2, 3, 4, 5, 6, 7, 8] as layer}
+ {#each layers as layer}
- {#each [0, 1, 2, 3, 4, 5, 6, 7, 8] as layer}
+ {#each layers as layer}
=10.0.0"
diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte
index 98a2696f64..918c07b1bf 100644
--- a/site/src/routes/_components/WhosUsingSvelte.svelte
+++ b/site/src/routes/_components/WhosUsingSvelte.svelte
@@ -52,6 +52,7 @@
+
diff --git a/site/static/organisations/nzz.svg b/site/static/organisations/nzz.svg
new file mode 100644
index 0000000000..70f9486263
--- /dev/null
+++ b/site/static/organisations/nzz.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/compiler/compile/render-dom/index.ts b/src/compiler/compile/render-dom/index.ts
index 46aa705bfb..9a49bc0c35 100644
--- a/src/compiler/compile/render-dom/index.ts
+++ b/src/compiler/compile/render-dom/index.ts
@@ -80,7 +80,7 @@ export default function dom(
${$$props} => {
${uses_props && component.invalidate('$$props', `$$props = @assign(@assign({}, $$props), $$new_props)`)}
${writable_props.map(prop =>
- `if ('${prop.export_name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = $$props.${prop.export_name}`)};`
+ `if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.export_name}`)};`
)}
${component.slots.size > 0 &&
`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', `$$scope = ${$$props}.$$scope`)};`}
diff --git a/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts
index c7af29a073..346dbe9ca0 100644
--- a/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts
+++ b/src/compiler/compile/render-dom/wrappers/AwaitBlock.ts
@@ -177,7 +177,7 @@ export default class AwaitBlockWrapper extends Wrapper {
`);
if (has_transitions) {
- block.builders.intro.add_line(`${info}.block.i();`);
+ block.builders.intro.add_line(`@transition_in(${info}.block);`);
}
const conditions = [];
@@ -216,7 +216,7 @@ export default class AwaitBlockWrapper extends Wrapper {
block.builders.outro.add_block(deindent`
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
- if (block) block.o();
+ @transition_out(block);
}
`);
}
diff --git a/src/compiler/compile/render-dom/wrappers/EachBlock.ts b/src/compiler/compile/render-dom/wrappers/EachBlock.ts
index a1a875a12f..c26b58c356 100644
--- a/src/compiler/compile/render-dom/wrappers/EachBlock.ts
+++ b/src/compiler/compile/render-dom/wrappers/EachBlock.ts
@@ -204,7 +204,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_intro_method || this.block.has_outro_method) {
block.builders.intro.add_block(deindent`
- for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) ${this.vars.iterations}[#i].i();
+ for (var #i = 0; #i < ${this.vars.data_length}; #i += 1) @transition_in(${this.vars.iterations}[#i]);
`);
}
@@ -362,7 +362,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
- for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].o();
+ for (#i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);
`);
}
@@ -425,24 +425,6 @@ export default class EachBlockWrapper extends Wrapper {
all_dependencies.add(dependency);
});
- const outro_block = this.block.has_outros && block.get_unique_name('outro_block');
- if (outro_block) {
- block.builders.init.add_block(deindent`
- function ${outro_block}(i, detaching, local) {
- if (${iterations}[i]) {
- if (detaching) {
- @on_outro(() => {
- ${iterations}[i].d(detaching);
- ${iterations}[i] = null;
- });
- }
-
- ${iterations}[i].o(local);
- }
- }
- `);
- }
-
const condition = Array.from(all_dependencies)
.map(dependency => `changed.${dependency}`)
.join(' || ');
@@ -454,18 +436,18 @@ export default class EachBlockWrapper extends Wrapper {
? deindent`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
- ${has_transitions && `${iterations}[#i].i(1);`}
+ ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
} else {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
- ${has_transitions && `${iterations}[#i].i(1);`}
+ ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
${iterations}[#i].m(${update_mount_node}, ${anchor});
}
`
: deindent`
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
- ${has_transitions && `${iterations}[#i].i(1);`}
+ ${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
${iterations}[#i].m(${update_mount_node}, ${anchor});
`;
@@ -474,9 +456,16 @@ export default class EachBlockWrapper extends Wrapper {
let remove_old_blocks;
if (this.block.has_outros) {
+ const out = block.get_unique_name('out');
+
+ block.builders.init.add_block(deindent`
+ const ${out} = i => @transition_out(${iterations}[i], 1, () => {
+ ${iterations}[i] = null;
+ });
+ `);
remove_old_blocks = deindent`
@group_outros();
- for (; #i < ${view_length}; #i += 1) ${outro_block}(#i, 1, 1);
+ for (; #i < ${view_length}; #i += 1) ${out}(#i);
@check_outros();
`;
} else {
@@ -507,10 +496,10 @@ export default class EachBlockWrapper extends Wrapper {
`);
}
- if (outro_block) {
+ if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
${iterations} = ${iterations}.filter(Boolean);
- for (let #i = 0; #i < ${view_length}; #i += 1) ${outro_block}(#i, 0, 0);`
+ for (let #i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);`
);
}
diff --git a/src/compiler/compile/render-dom/wrappers/Element/index.ts b/src/compiler/compile/render-dom/wrappers/Element/index.ts
index 527a101f6a..2062ba1f14 100644
--- a/src/compiler/compile/render-dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render-dom/wrappers/Element/index.ts
@@ -335,6 +335,8 @@ export default class ElementWrapper extends Wrapper {
function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
+ if (wrapper.node.use_space) return ' ';
+
const parent = wrapper.node.parent as Element;
const raw = parent && (
@@ -342,9 +344,9 @@ export default class ElementWrapper extends Wrapper {
parent.name === 'style'
);
- return raw
+ return (raw
? wrapper.node.data
- : escape_html(wrapper.node.data)
+ : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
diff --git a/src/compiler/compile/render-dom/wrappers/Fragment.ts b/src/compiler/compile/render-dom/wrappers/Fragment.ts
index f2e2ab7a7a..bd20c9107f 100644
--- a/src/compiler/compile/render-dom/wrappers/Fragment.ts
+++ b/src/compiler/compile/render-dom/wrappers/Fragment.ts
@@ -42,6 +42,13 @@ function link(next: Wrapper, prev: Wrapper) {
if (next) next.prev = prev;
}
+function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
+ // Whitespace is trimmable if one of the following is true:
+ // The child and its sibling share a common nearest each block (not at an each block boundary)
+ // The next sibling's previous node is an each block
+ return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock';
+}
+
export default class FragmentWrapper {
nodes: Wrapper[];
@@ -85,7 +92,7 @@ export default class FragmentWrapper {
if (this.nodes.length === 0) {
const should_trim = (
// @ts-ignore todo: probably error, should it be next_sibling.node.data?
- next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock')
+ next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
);
if (should_trim) {
diff --git a/src/compiler/compile/render-dom/wrappers/IfBlock.ts b/src/compiler/compile/render-dom/wrappers/IfBlock.ts
index 46f17653a1..aa0a7aef99 100644
--- a/src/compiler/compile/render-dom/wrappers/IfBlock.ts
+++ b/src/compiler/compile/render-dom/wrappers/IfBlock.ts
@@ -160,7 +160,7 @@ export default class IfBlockWrapper extends Wrapper {
if (has_outros) {
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching);
- block.builders.outro.add_line(`if (${name}) ${name}.o();`);
+ block.builders.outro.add_line(`@transition_out(${name});`);
} else {
this.render_compound(block, parent_node, parent_nodes, dynamic, vars, detaching);
}
@@ -168,7 +168,7 @@ export default class IfBlockWrapper extends Wrapper {
this.render_simple(block, parent_node, parent_nodes, dynamic, vars, detaching);
if (has_outros) {
- block.builders.outro.add_line(`if (${name}) ${name}.o();`);
+ block.builders.outro.add_line(`@transition_out(${name});`);
}
}
@@ -181,7 +181,7 @@ export default class IfBlockWrapper extends Wrapper {
}
if (has_intros || has_outros) {
- block.builders.intro.add_line(`if (${name}) ${name}.i();`);
+ block.builders.intro.add_line(`@transition_in(${name});`);
}
if (needs_anchor) {
@@ -238,7 +238,7 @@ export default class IfBlockWrapper extends Wrapper {
${name} = ${current_block_type_and}${current_block_type}(ctx);
if (${name}) {
${name}.c();
- ${has_transitions && `${name}.i(1);`}
+ ${has_transitions && `@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
}
`;
@@ -326,11 +326,9 @@ export default class IfBlockWrapper extends Wrapper {
const destroy_old_block = deindent`
@group_outros();
- @on_outro(() => {
- ${if_blocks}[${previous_block_index}].d(1);
+ @transition_out(${if_blocks}[${previous_block_index}], 1, () => {
${if_blocks}[${previous_block_index}] = null;
});
- ${name}.o(1);
@check_outros();
`;
@@ -340,7 +338,7 @@ export default class IfBlockWrapper extends Wrapper {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
${name}.c();
}
- ${has_transitions && `${name}.i(1);`}
+ ${has_transitions && `@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
`;
@@ -414,11 +412,11 @@ export default class IfBlockWrapper extends Wrapper {
? deindent`
if (${name}) {
${name}.p(changed, ctx);
- ${has_transitions && `${name}.i(1);`}
+ ${has_transitions && `@transition_in(${name}, 1);`}
} else {
${name} = ${branch.block.name}(ctx);
${name}.c();
- ${has_transitions && `${name}.i(1);`}
+ ${has_transitions && `@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
}
`
@@ -426,38 +424,37 @@ export default class IfBlockWrapper extends Wrapper {
if (!${name}) {
${name} = ${branch.block.name}(ctx);
${name}.c();
- ${has_transitions && `${name}.i(1);`}
+ ${has_transitions && `@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
${has_transitions && `} else {
- ${name}.i(1);`}
+ @transition_in(${name}, 1);`}
}
`;
// no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
- const exit = branch.block.has_outro_method
- ? deindent`
- @group_outros();
- @on_outro(() => {
+ if (branch.block.has_outro_method) {
+ block.builders.update.add_block(deindent`
+ if (${branch.condition}) {
+ ${enter}
+ } else if (${name}) {
+ @group_outros();
+ @transition_out(${name}, 1, () => {
+ ${name} = null;
+ });
+ @check_outros();
+ }
+ `);
+ } else {
+ block.builders.update.add_block(deindent`
+ if (${branch.condition}) {
+ ${enter}
+ } else if (${name}) {
${name}.d(1);
${name} = null;
- });
-
- ${name}.o(1);
- @check_outros();
- `
- : deindent`
- ${name}.d(1);
- ${name} = null;
- `;
-
- block.builders.update.add_block(deindent`
- if (${branch.condition}) {
- ${enter}
- } else if (${name}) {
- ${exit}
- }
- `);
+ }
+ `);
+ }
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
}
diff --git a/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts
index 696fff51c0..46b3df4c31 100644
--- a/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts
+++ b/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts
@@ -276,15 +276,17 @@ export default class InlineComponentWrapper extends Wrapper {
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
}
+ const contextual_dependencies = [...binding.expression.contextual_dependencies];
+
component.partly_hoisted.push(deindent`
- function ${fn}($$component) {
+ function ${fn}(${['$$component', ...contextual_dependencies].join(', ')}) {
${lhs} = $$component;
${object && component.invalidate(object)}
}
`);
block.builders.destroy.add_line(`ctx.${fn}(null);`);
- return `@add_binding_callback(() => ctx.${fn}(${this.var}));`;
+ return `@add_binding_callback(() => ctx.${fn}(${[this.var, ...contextual_dependencies.map(name => `ctx.${name}`)].join(', ')}));`;
}
const name = component.get_unique_name(`${this.var}_${binding.name}_binding`);
@@ -423,10 +425,9 @@ export default class InlineComponentWrapper extends Wrapper {
if (${name}) {
@group_outros();
const old_component = ${name};
- @on_outro(() => {
- old_component.$destroy();
+ @transition_out(old_component.$$.fragment, 1, () => {
+ @destroy_component(old_component);
});
- old_component.$$.fragment.o(1);
@check_outros();
}
@@ -437,7 +438,7 @@ export default class InlineComponentWrapper extends Wrapper {
${munged_handlers}
${name}.$$.fragment.c();
- ${name}.$$.fragment.i(1);
+ @transition_in(${name}.$$.fragment, 1);
@mount_component(${name}, ${update_mount_node}, ${anchor});
} else {
${name} = null;
@@ -446,7 +447,7 @@ export default class InlineComponentWrapper extends Wrapper {
`);
block.builders.intro.add_block(deindent`
- if (${name}) ${name}.$$.fragment.i(#local);
+ @transition_in(${name}.$$.fragment, #local);
`);
if (updates.length) {
@@ -458,10 +459,10 @@ export default class InlineComponentWrapper extends Wrapper {
}
block.builders.outro.add_line(
- `if (${name}) ${name}.$$.fragment.o(#local);`
+ `if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
- block.builders.destroy.add_line(`if (${name}) ${name}.$destroy(${parent_node ? '' : 'detaching'});`);
+ block.builders.destroy.add_line(`if (${name}) @destroy_component(${name}, ${parent_node ? '' : 'detaching'});`);
} else {
const expression = this.node.name === 'svelte:self'
? '__svelte:self__' // TODO conflict-proof this
@@ -490,7 +491,7 @@ export default class InlineComponentWrapper extends Wrapper {
);
block.builders.intro.add_block(deindent`
- ${name}.$$.fragment.i(#local);
+ @transition_in(${name}.$$.fragment, #local);
`);
if (updates.length) {
@@ -501,11 +502,11 @@ export default class InlineComponentWrapper extends Wrapper {
}
block.builders.destroy.add_block(deindent`
- ${name}.$destroy(${parent_node ? '' : 'detaching'});
+ @destroy_component(${name}, ${parent_node ? '' : 'detaching'});
`);
block.builders.outro.add_line(
- `${name}.$$.fragment.o(#local);`
+ `@transition_out(${name}.$$.fragment, #local);`
);
}
}
diff --git a/src/compiler/compile/render-dom/wrappers/Slot.ts b/src/compiler/compile/render-dom/wrappers/Slot.ts
index 5347b87de4..3376a797b8 100644
--- a/src/compiler/compile/render-dom/wrappers/Slot.ts
+++ b/src/compiler/compile/render-dom/wrappers/Slot.ts
@@ -142,11 +142,11 @@ export default class SlotWrapper extends Wrapper {
`);
block.builders.intro.add_line(
- `if (${slot} && ${slot}.i) ${slot}.i(#local);`
+ `@transition_in(${slot}, #local);`
);
block.builders.outro.add_line(
- `if (${slot} && ${slot}.o) ${slot}.o(#local);`
+ `@transition_out(${slot}, #local);`
);
let update_conditions = [...this.dependencies].map(name => `changed.${name}`).join(' || ');
diff --git a/src/compiler/compile/utils/flatten_reference.ts b/src/compiler/compile/utils/flatten_reference.ts
index e7d66d01ca..460cd2f1e9 100644
--- a/src/compiler/compile/utils/flatten_reference.ts
+++ b/src/compiler/compile/utils/flatten_reference.ts
@@ -7,10 +7,11 @@ export default function flatten_reference(node: Node) {
const prop_end = node.end;
while (node.type === 'MemberExpression') {
- if (node.computed) return null;
-
nodes.unshift(node.property);
- parts.unshift(node.property.name);
+
+ if (!node.computed) {
+ parts.unshift(node.property.name);
+ }
node = node.object;
}
@@ -20,10 +21,11 @@ export default function flatten_reference(node: Node) {
? node.name
: node.type === 'ThisExpression' ? 'this' : null;
- if (!name) return null;
-
- parts.unshift(name);
nodes.unshift(node);
+ if (!node.computed) {
+ parts.unshift(name);
+ }
+
return { name, nodes, parts, keypath: `${name}[✂${prop_start}-${prop_end}✂]` };
}
diff --git a/src/compiler/parse/utils/html.ts b/src/compiler/parse/utils/html.ts
index b49989eacd..13ff553a3f 100644
--- a/src/compiler/parse/utils/html.ts
+++ b/src/compiler/parse/utils/html.ts
@@ -36,7 +36,7 @@ const windows_1252 = [
];
const entity_pattern = new RegExp(
- `&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}));?`,
+ `&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`,
'g'
);
diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts
index 7624d5c829..753d1c8a98 100644
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -2,6 +2,7 @@ import { add_render_callback, flush, schedule_update, dirty_components } from '.
import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_function, run, run_all, noop } from './utils';
import { children } from './dom';
+import { transition_in } from './transitions';
// eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ {
@@ -49,10 +50,11 @@ export function mount_component(component, target, anchor) {
after_render.forEach(add_render_callback);
}
-function destroy(component, detaching) {
- if (component.$$) {
+export function destroy_component(component, detaching) {
+ if (component.$$.fragment) {
run_all(component.$$.on_destroy);
- component.$$.fragment.d(detaching);
+
+ if (detaching) component.$$.fragment.d(1);
// TODO null out other refs, including component.$$ (but need to
// preserve final state?)
@@ -123,7 +125,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
$$.fragment!.c();
}
- if (options.intro && component.$$.fragment.i) component.$$.fragment.i();
+ if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
flush();
}
@@ -153,7 +155,7 @@ if (typeof HTMLElement !== 'undefined') {
}
$destroy() {
- destroy(this, true);
+ destroy_component(this, 1);
this.$destroy = noop;
}
@@ -178,7 +180,7 @@ export class SvelteComponent {
$$: T$$;
$destroy() {
- destroy(this, true);
+ destroy_component(this, 1);
this.$destroy = noop;
}
diff --git a/src/runtime/internal/await-block.ts b/src/runtime/internal/await-block.ts
index 9527b000ca..a3313497ff 100644
--- a/src/runtime/internal/await-block.ts
+++ b/src/runtime/internal/await-block.ts
@@ -1,5 +1,5 @@
import { assign, is_promise } from './utils';
-import { check_outros, group_outros, on_outro } from './transitions';
+import { check_outros, group_outros, transition_in, transition_out } from './transitions';
import { flush } from '../internal/scheduler';
export function handle_promise(promise, info) {
@@ -18,11 +18,9 @@ export function handle_promise(promise, info) {
info.blocks.forEach((block, i) => {
if (i !== index && block) {
group_outros();
- on_outro(() => {
- block.d(1);
+ transition_out(block, 1, () => {
info.blocks[i] = null;
});
- block.o(1);
check_outros();
}
});
@@ -31,7 +29,7 @@ export function handle_promise(promise, info) {
}
block.c();
- if (block.i) block.i(1);
+ transition_in(block, 1);
block.m(info.mount(), info.anchor);
flush();
diff --git a/src/runtime/internal/keyed-each.ts b/src/runtime/internal/keyed-each.ts
index f13c858897..e18ae61770 100644
--- a/src/runtime/internal/keyed-each.ts
+++ b/src/runtime/internal/keyed-each.ts
@@ -1,4 +1,4 @@
-import { on_outro } from './transitions';
+import { transition_in, transition_out } from './transitions';
export function destroy_block(block, lookup) {
block.d(1);
@@ -6,11 +6,9 @@ export function destroy_block(block, lookup) {
}
export function outro_and_destroy_block(block, lookup) {
- on_outro(() => {
- destroy_block(block, lookup);
+ transition_out(block, 1, () => {
+ lookup.delete(block.key);
});
-
- block.o(1);
}
export function fix_and_destroy_block(block, lookup) {
@@ -57,7 +55,7 @@ export function update_keyed_each(old_blocks, changed, get_key, dynamic, ctx, li
const did_move = new Set();
function insert(block) {
- if (block.i) block.i(1);
+ transition_in(block, 1);
block.m(node, next);
lookup.set(block.key, block);
next = block.first;
diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts
index 80c76ffec5..bbb24ba9ee 100644
--- a/src/runtime/internal/transitions.ts
+++ b/src/runtime/internal/transitions.ts
@@ -23,6 +23,7 @@ function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') {
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));
}
+const outroing = new Set();
let outros;
export function group_outros() {
@@ -38,9 +39,30 @@ export function check_outros() {
}
}
-export function on_outro(callback) {
- outros.callbacks.push(callback);
+export function transition_in(block, local?: 0 | 1) {
+ if (block && block.i) {
+ outroing.delete(block);
+ block.i(local);
+ }
}
+
+export function transition_out(block, local: 0 | 1, callback) {
+ if (block && block.o) {
+ if (outroing.has(block)) return;
+ outroing.add(block);
+
+ outros.callbacks.push(() => {
+ outroing.delete(block);
+ if (callback) {
+ block.d(1);
+ callback();
+ }
+ });
+
+ block.o(local);
+ }
+}
+
type TransitionFn = (node: Element, params: any) => TransitionConfig;
export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
diff --git a/test/js/samples/component-static-array/expected.js b/test/js/samples/component-static-array/expected.js
index 875c741076..6997f431dd 100644
--- a/test/js/samples/component-static-array/expected.js
+++ b/test/js/samples/component-static-array/expected.js
@@ -1,10 +1,13 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
init,
mount_component,
noop,
- safe_not_equal
+ safe_not_equal,
+ transition_in,
+ transition_out
} from "svelte/internal";
function create_fragment(ctx) {
@@ -26,18 +29,18 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- nested.$$.fragment.i(local);
+ transition_in(nested.$$.fragment, local);
current = true;
},
o(local) {
- nested.$$.fragment.o(local);
+ transition_out(nested.$$.fragment, local);
current = false;
},
d(detaching) {
- nested.$destroy(detaching);
+ destroy_component(nested, detaching);
}
};
}
diff --git a/test/js/samples/component-static-immutable/expected.js b/test/js/samples/component-static-immutable/expected.js
index 6612a7f1b6..4b33d537ca 100644
--- a/test/js/samples/component-static-immutable/expected.js
+++ b/test/js/samples/component-static-immutable/expected.js
@@ -1,10 +1,13 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
init,
mount_component,
noop,
- not_equal
+ not_equal,
+ transition_in,
+ transition_out
} from "svelte/internal";
function create_fragment(ctx) {
@@ -26,18 +29,18 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- nested.$$.fragment.i(local);
+ transition_in(nested.$$.fragment, local);
current = true;
},
o(local) {
- nested.$$.fragment.o(local);
+ transition_out(nested.$$.fragment, local);
current = false;
},
d(detaching) {
- nested.$destroy(detaching);
+ destroy_component(nested, detaching);
}
};
}
diff --git a/test/js/samples/component-static-immutable2/expected.js b/test/js/samples/component-static-immutable2/expected.js
index 6612a7f1b6..4b33d537ca 100644
--- a/test/js/samples/component-static-immutable2/expected.js
+++ b/test/js/samples/component-static-immutable2/expected.js
@@ -1,10 +1,13 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
init,
mount_component,
noop,
- not_equal
+ not_equal,
+ transition_in,
+ transition_out
} from "svelte/internal";
function create_fragment(ctx) {
@@ -26,18 +29,18 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- nested.$$.fragment.i(local);
+ transition_in(nested.$$.fragment, local);
current = true;
},
o(local) {
- nested.$$.fragment.o(local);
+ transition_out(nested.$$.fragment, local);
current = false;
},
d(detaching) {
- nested.$destroy(detaching);
+ destroy_component(nested, detaching);
}
};
}
diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js
index 12ebeb28b7..5a031a32a4 100644
--- a/test/js/samples/component-static/expected.js
+++ b/test/js/samples/component-static/expected.js
@@ -1,10 +1,13 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
init,
mount_component,
noop,
- safe_not_equal
+ safe_not_equal,
+ transition_in,
+ transition_out
} from "svelte/internal";
function create_fragment(ctx) {
@@ -26,18 +29,18 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- nested.$$.fragment.i(local);
+ transition_in(nested.$$.fragment, local);
current = true;
},
o(local) {
- nested.$$.fragment.o(local);
+ transition_out(nested.$$.fragment, local);
current = false;
},
d(detaching) {
- nested.$destroy(detaching);
+ destroy_component(nested, detaching);
}
};
}
diff --git a/test/js/samples/dynamic-import/expected.js b/test/js/samples/dynamic-import/expected.js
index 2d51bcf60d..9a6a67bcd9 100644
--- a/test/js/samples/dynamic-import/expected.js
+++ b/test/js/samples/dynamic-import/expected.js
@@ -1,10 +1,13 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
init,
mount_component,
noop,
- safe_not_equal
+ safe_not_equal,
+ transition_in,
+ transition_out
} from "svelte/internal";
import LazyLoad from "./LazyLoad.svelte";
@@ -27,18 +30,18 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- lazyload.$$.fragment.i(local);
+ transition_in(lazyload.$$.fragment, local);
current = true;
},
o(local) {
- lazyload.$$.fragment.o(local);
+ transition_out(lazyload.$$.fragment, local);
current = false;
},
d(detaching) {
- lazyload.$destroy(detaching);
+ destroy_component(lazyload, detaching);
}
};
}
diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js
index 20ac461c76..42a21aa496 100644
--- a/test/js/samples/non-imported-component/expected.js
+++ b/test/js/samples/non-imported-component/expected.js
@@ -1,13 +1,16 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
+ destroy_component,
detach,
init,
insert,
mount_component,
noop,
safe_not_equal,
- space
+ space,
+ transition_in,
+ transition_out
} from "svelte/internal";
import Imported from "Imported.svelte";
@@ -36,27 +39,27 @@ function create_fragment(ctx) {
i(local) {
if (current) return;
- imported.$$.fragment.i(local);
+ transition_in(imported.$$.fragment, local);
- nonimported.$$.fragment.i(local);
+ transition_in(nonimported.$$.fragment, local);
current = true;
},
o(local) {
- imported.$$.fragment.o(local);
- nonimported.$$.fragment.o(local);
+ transition_out(imported.$$.fragment, local);
+ transition_out(nonimported.$$.fragment, local);
current = false;
},
d(detaching) {
- imported.$destroy(detaching);
+ destroy_component(imported, detaching);
if (detaching) {
detach(t);
}
- nonimported.$destroy(detaching);
+ destroy_component(nonimported, detaching);
}
};
}
diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js
index 2df99a4c87..53bce5f5d0 100644
--- a/test/js/samples/transition-local/expected.js
+++ b/test/js/samples/transition-local/expected.js
@@ -9,7 +9,8 @@ import {
init,
insert,
noop,
- safe_not_equal
+ safe_not_equal,
+ transition_in
} from "svelte/internal";
// (8:0) {#if x}
@@ -34,10 +35,10 @@ function create_if_block(ctx) {
if (!if_block) {
if_block = create_if_block_1(ctx);
if_block.c();
- if_block.i(1);
+ transition_in(if_block, 1);
if_block.m(if_block_anchor.parentNode, if_block_anchor);
} else {
- if_block.i(1);
+ transition_in(if_block, 1);
}
} else if (if_block) {
if_block.d(1);
diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js
new file mode 100644
index 0000000000..959bd47201
--- /dev/null
+++ b/test/js/samples/transition-repeated-outro/expected.js
@@ -0,0 +1,129 @@
+/* generated by Svelte vX.Y.Z */
+import {
+ SvelteComponent,
+ check_outros,
+ create_out_transition,
+ detach,
+ element,
+ empty,
+ group_outros,
+ init,
+ insert,
+ safe_not_equal,
+ transition_in,
+ transition_out
+} from "svelte/internal";
+import { fade } from "svelte/transition";
+
+// (7:0) {#if num < 5}
+function create_if_block(ctx) {
+ var div, div_outro, current;
+
+ return {
+ c() {
+ div = element("div");
+ div.innerHTML = `
+{/each}
diff --git a/test/runtime/samples/binding-this-component-each-block/Foo.svelte b/test/runtime/samples/binding-this-component-each-block/Foo.svelte
new file mode 100644
index 0000000000..066a48b65e
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block/Foo.svelte
@@ -0,0 +1 @@
+
foo
\ No newline at end of file
diff --git a/test/runtime/samples/binding-this-component-each-block/_config.js b/test/runtime/samples/binding-this-component-each-block/_config.js
new file mode 100644
index 0000000000..358c5b5afc
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block/_config.js
@@ -0,0 +1,12 @@
+export default {
+ skip_if_ssr: true,
+
+ html: `
+
foo
+
0 has foo: true
+
foo
+
1 has foo: true
+
foo
+
2 has foo: true
+ `
+};
diff --git a/test/runtime/samples/binding-this-component-each-block/main.svelte b/test/runtime/samples/binding-this-component-each-block/main.svelte
new file mode 100644
index 0000000000..49d1d76172
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block/main.svelte
@@ -0,0 +1,11 @@
+
+
+{#each Array(3) as _, i}
+
+
+ {i} has foo: {!!foo[i]}
+
+{/each}
diff --git a/test/runtime/samples/binding-using-props/TextInput.svelte b/test/runtime/samples/binding-using-props/TextInput.svelte
new file mode 100644
index 0000000000..da9e7e4a0e
--- /dev/null
+++ b/test/runtime/samples/binding-using-props/TextInput.svelte
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/test/runtime/samples/binding-using-props/_config.js b/test/runtime/samples/binding-using-props/_config.js
new file mode 100644
index 0000000000..dcb34c4357
--- /dev/null
+++ b/test/runtime/samples/binding-using-props/_config.js
@@ -0,0 +1,14 @@
+export default {
+ async test({ assert, target, window }) {
+ const input = target.querySelector('input');
+
+ const event = new window.Event('input');
+ input.value = 'changed';
+ await input.dispatchEvent(event);
+
+ assert.htmlEqual(target.innerHTML, `
+
+
changed
+ `);
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/binding-using-props/main.svelte b/test/runtime/samples/binding-using-props/main.svelte
new file mode 100644
index 0000000000..543bd28d49
--- /dev/null
+++ b/test/runtime/samples/binding-using-props/main.svelte
@@ -0,0 +1,7 @@
+
+
+
+
{actualValue}
\ No newline at end of file
diff --git a/test/runtime/samples/each-block-keyed-shift/Nested.svelte b/test/runtime/samples/each-block-keyed-shift/Nested.svelte
new file mode 100644
index 0000000000..82df711742
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-shift/Nested.svelte
@@ -0,0 +1,5 @@
+
+
+
{title}
\ No newline at end of file
diff --git a/test/runtime/samples/each-block-keyed-shift/_config.js b/test/runtime/samples/each-block-keyed-shift/_config.js
new file mode 100644
index 0000000000..44ca8447f0
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-shift/_config.js
@@ -0,0 +1,27 @@
+export default {
+ props: {
+ titles: [{ name: 'a', }, { name: 'b' }, { name: 'c' }]
+ },
+
+ html: `
+
+ `);
+
+ }
+};
diff --git a/test/runtime/samples/each-block-keyed-shift/main.svelte b/test/runtime/samples/each-block-keyed-shift/main.svelte
new file mode 100644
index 0000000000..b8954e17fd
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-shift/main.svelte
@@ -0,0 +1,9 @@
+
+
+{#each titles as title (title.name)}
+
+{/each}
\ No newline at end of file
diff --git a/test/runtime/samples/fragment-trailing-whitespace/_config.js b/test/runtime/samples/fragment-trailing-whitespace/_config.js
new file mode 100644
index 0000000000..2251d6dae8
--- /dev/null
+++ b/test/runtime/samples/fragment-trailing-whitespace/_config.js
@@ -0,0 +1,16 @@
+const message = "the quick brown fox jumps over the lazy dog";
+const expected = [...message].map(c => `${c + " "}`).join("");
+
+export default {
+ props: {
+ message
+ },
+
+ async test({ assert, target }) {
+ const firstSpanList = target.children[0];
+ assert.equal(firstSpanList.innerHTML, expected);
+
+ const secondSpanList = target.children[1];
+ assert.equal(secondSpanList.innerHTML, expected);
+ }
+};
diff --git a/test/runtime/samples/fragment-trailing-whitespace/main.svelte b/test/runtime/samples/fragment-trailing-whitespace/main.svelte
new file mode 100644
index 0000000000..f36f2694a6
--- /dev/null
+++ b/test/runtime/samples/fragment-trailing-whitespace/main.svelte
@@ -0,0 +1,11 @@
+
+
+
+ {#each message as char}
+ {char}
+ {/each}
+
+
+
{#each message as char}{char} {/each}
\ No newline at end of file
diff --git a/test/runtime/samples/html-entities/_config.js b/test/runtime/samples/html-entities/_config.js
index 1591f31a53..c8c2de8403 100644
--- a/test/runtime/samples/html-entities/_config.js
+++ b/test/runtime/samples/html-entities/_config.js
@@ -9,6 +9,6 @@ export default {
A€
- ¬anentity;
+ ¬anentity;
`
};
\ No newline at end of file
diff --git a/test/runtime/samples/script-style-non-top-level/_config.js b/test/runtime/samples/script-style-non-top-level/_config.js
index 1aade72239..86eddf4611 100644
--- a/test/runtime/samples/script-style-non-top-level/_config.js
+++ b/test/runtime/samples/script-style-non-top-level/_config.js
@@ -2,7 +2,7 @@ export default {
html: `
-
+
`
};
\ No newline at end of file
diff --git a/test/runtime/samples/script-style-non-top-level/main.svelte b/test/runtime/samples/script-style-non-top-level/main.svelte
index 94cf72e4bd..73b0dfcf38 100644
--- a/test/runtime/samples/script-style-non-top-level/main.svelte
+++ b/test/runtime/samples/script-style-non-top-level/main.svelte
@@ -1,4 +1,4 @@