diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94d7326eea..2849115008 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# 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))
diff --git a/package-lock.json b/package-lock.json
index 8a28bf9997..0aef0fa227 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.5.3",
+ "version": "3.5.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 9047af529e..96a1dd8f99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.5.3",
+ "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..ceaa19a398 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}
```
---
@@ -502,6 +510,14 @@ When the value of an `` matches its text content, the attribute can be o
```
+---
+
+Elements with the `contenteditable` attribute support `innerHTML` and `textContent` bindings.
+
+```html
+
+```
+
##### Media element bindings
---
@@ -1241,7 +1257,7 @@ It cannot appear at the top level of your markup; it must be inside an if or eac
### ``
```sv
-
+
```
---
@@ -1318,7 +1334,7 @@ As with ``, this element allows you to add listeners to events on
### ``
```sv
-
+...
```
---
@@ -1335,7 +1351,7 @@ This element makes it possible to insert elements into `document.head`. During s
### ``
```sv
-
+
```
---
@@ -1351,4 +1367,4 @@ The `` element provides a place to specify per-component compile
```html
-```
\ No newline at end of file
+```
diff --git a/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-a/App.svelte b/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-a/App.svelte
new file mode 100644
index 0000000000..55d699959c
--- /dev/null
+++ b/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-a/App.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+{html}
+
+
\ No newline at end of file
diff --git a/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-b/App.svelte b/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-b/App.svelte
new file mode 100644
index 0000000000..ee97cf19c0
--- /dev/null
+++ b/site/content/tutorial/06-bindings/08-contenteditable-bindings/app-b/App.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+{html}
+
+
\ No newline at end of file
diff --git a/site/content/tutorial/06-bindings/08-contenteditable-bindings/text.md b/site/content/tutorial/06-bindings/08-contenteditable-bindings/text.md
new file mode 100644
index 0000000000..a73b07c0ba
--- /dev/null
+++ b/site/content/tutorial/06-bindings/08-contenteditable-bindings/text.md
@@ -0,0 +1,12 @@
+---
+title: Contenteditable bindings
+---
+
+Elements with a `contenteditable="true"` attribute support `textContent` and `innerHTML` bindings:
+
+```html
+
+```
\ No newline at end of file
diff --git a/site/content/tutorial/06-bindings/08-each-block-bindings/app-a/App.svelte b/site/content/tutorial/06-bindings/09-each-block-bindings/app-a/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/08-each-block-bindings/app-a/App.svelte
rename to site/content/tutorial/06-bindings/09-each-block-bindings/app-a/App.svelte
diff --git a/site/content/tutorial/06-bindings/08-each-block-bindings/app-b/App.svelte b/site/content/tutorial/06-bindings/09-each-block-bindings/app-b/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/08-each-block-bindings/app-b/App.svelte
rename to site/content/tutorial/06-bindings/09-each-block-bindings/app-b/App.svelte
diff --git a/site/content/tutorial/06-bindings/08-each-block-bindings/text.md b/site/content/tutorial/06-bindings/09-each-block-bindings/text.md
similarity index 100%
rename from site/content/tutorial/06-bindings/08-each-block-bindings/text.md
rename to site/content/tutorial/06-bindings/09-each-block-bindings/text.md
diff --git a/site/content/tutorial/06-bindings/09-media-elements/app-a/App.svelte b/site/content/tutorial/06-bindings/10-media-elements/app-a/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/09-media-elements/app-a/App.svelte
rename to site/content/tutorial/06-bindings/10-media-elements/app-a/App.svelte
diff --git a/site/content/tutorial/06-bindings/09-media-elements/app-b/App.svelte b/site/content/tutorial/06-bindings/10-media-elements/app-b/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/09-media-elements/app-b/App.svelte
rename to site/content/tutorial/06-bindings/10-media-elements/app-b/App.svelte
diff --git a/site/content/tutorial/06-bindings/09-media-elements/text.md b/site/content/tutorial/06-bindings/10-media-elements/text.md
similarity index 100%
rename from site/content/tutorial/06-bindings/09-media-elements/text.md
rename to site/content/tutorial/06-bindings/10-media-elements/text.md
diff --git a/site/content/tutorial/06-bindings/10-dimensions/app-a/App.svelte b/site/content/tutorial/06-bindings/11-dimensions/app-a/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/10-dimensions/app-a/App.svelte
rename to site/content/tutorial/06-bindings/11-dimensions/app-a/App.svelte
diff --git a/site/content/tutorial/06-bindings/10-dimensions/app-b/App.svelte b/site/content/tutorial/06-bindings/11-dimensions/app-b/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/10-dimensions/app-b/App.svelte
rename to site/content/tutorial/06-bindings/11-dimensions/app-b/App.svelte
diff --git a/site/content/tutorial/06-bindings/10-dimensions/text.md b/site/content/tutorial/06-bindings/11-dimensions/text.md
similarity index 100%
rename from site/content/tutorial/06-bindings/10-dimensions/text.md
rename to site/content/tutorial/06-bindings/11-dimensions/text.md
diff --git a/site/content/tutorial/06-bindings/11-bind-this/app-a/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/11-bind-this/app-a/App.svelte
rename to site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte
diff --git a/site/content/tutorial/06-bindings/11-bind-this/app-b/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/11-bind-this/app-b/App.svelte
rename to site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte
diff --git a/site/content/tutorial/06-bindings/11-bind-this/text.md b/site/content/tutorial/06-bindings/12-bind-this/text.md
similarity index 100%
rename from site/content/tutorial/06-bindings/11-bind-this/text.md
rename to site/content/tutorial/06-bindings/12-bind-this/text.md
diff --git a/site/content/tutorial/06-bindings/12-component-bindings/app-a/App.svelte b/site/content/tutorial/06-bindings/13-component-bindings/app-a/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/12-component-bindings/app-a/App.svelte
rename to site/content/tutorial/06-bindings/13-component-bindings/app-a/App.svelte
diff --git a/site/content/tutorial/06-bindings/12-component-bindings/app-a/Keypad.svelte b/site/content/tutorial/06-bindings/13-component-bindings/app-a/Keypad.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/12-component-bindings/app-a/Keypad.svelte
rename to site/content/tutorial/06-bindings/13-component-bindings/app-a/Keypad.svelte
diff --git a/site/content/tutorial/06-bindings/12-component-bindings/app-b/App.svelte b/site/content/tutorial/06-bindings/13-component-bindings/app-b/App.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/12-component-bindings/app-b/App.svelte
rename to site/content/tutorial/06-bindings/13-component-bindings/app-b/App.svelte
diff --git a/site/content/tutorial/06-bindings/12-component-bindings/app-b/Keypad.svelte b/site/content/tutorial/06-bindings/13-component-bindings/app-b/Keypad.svelte
similarity index 100%
rename from site/content/tutorial/06-bindings/12-component-bindings/app-b/Keypad.svelte
rename to site/content/tutorial/06-bindings/13-component-bindings/app-b/Keypad.svelte
diff --git a/site/content/tutorial/06-bindings/12-component-bindings/text.md b/site/content/tutorial/06-bindings/13-component-bindings/text.md
similarity index 100%
rename from site/content/tutorial/06-bindings/12-component-bindings/text.md
rename to site/content/tutorial/06-bindings/13-component-bindings/text.md
diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte
index 98a2696f64..86f3e1ce33 100644
--- a/site/src/routes/_components/WhosUsingSvelte.svelte
+++ b/site/src/routes/_components/WhosUsingSvelte.svelte
@@ -52,6 +52,8 @@
+
+
diff --git a/site/static/organisations/nonkosi.svg b/site/static/organisations/nonkosi.svg
new file mode 100644
index 0000000000..8904cdae9d
--- /dev/null
+++ b/site/static/organisations/nonkosi.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
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/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index 3e883200a7..a3030f52a5 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -608,8 +608,8 @@ export default class Element extends Node {
});
}
} else if (
- name === 'text' ||
- name === 'html'
+ name === 'textContent' ||
+ name === 'innerHTML'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
@@ -618,7 +618,7 @@ export default class Element extends Node {
if (!contenteditable) {
component.error(binding, {
code: `missing-contenteditable-attribute`,
- message: `'contenteditable' attribute is required for text and html two-way bindings`
+ message: `'contenteditable' attribute is required for textContent and innerHTML two-way bindings`
});
} else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, {
diff --git a/src/compiler/compile/render-dom/index.ts b/src/compiler/compile/render-dom/index.ts
index 17b6fd86f2..e643b40a33 100644
--- a/src/compiler/compile/render-dom/index.ts
+++ b/src/compiler/compile/render-dom/index.ts
@@ -82,7 +82,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/Element/Binding.ts b/src/compiler/compile/render-dom/wrappers/Element/Binding.ts
index 5b1fe6dee1..1b58751e9f 100644
--- a/src/compiler/compile/render-dom/wrappers/Element/Binding.ts
+++ b/src/compiler/compile/render-dom/wrappers/Element/Binding.ts
@@ -133,6 +133,14 @@ export default class BindingWrapper {
break;
}
+ case 'textContent':
+ update_conditions.push(`${this.snippet} !== ${parent.var}.textContent`);
+ break;
+
+ case 'innerHTML':
+ update_conditions.push(`${this.snippet} !== ${parent.var}.innerHTML`);
+ break;
+
case 'currentTime':
case 'playbackRate':
case 'volume':
@@ -162,7 +170,9 @@ export default class BindingWrapper {
);
}
- if (!/(currentTime|paused)/.test(this.node.name)) {
+ if (this.node.name === 'innerHTML' || this.node.name === 'textContent') {
+ block.builders.mount.add_block(`if (${this.snippet} !== void 0) ${update_dom}`);
+ } else if (!/(currentTime|paused)/.test(this.node.name)) {
block.builders.mount.add_block(update_dom);
}
}
@@ -198,14 +208,6 @@ function get_dom_updater(
return `${element.var}.checked = ${condition};`;
}
- if (binding.node.name === 'text') {
- return `if (${binding.snippet} !== ${element.var}.textContent) ${element.var}.textContent = ${binding.snippet};`;
- }
-
- if (binding.node.name === 'html') {
- return `if (${binding.snippet} !== ${element.var}.innerHTML) ${element.var}.innerHTML = ${binding.snippet};`;
- }
-
return `${element.var}.${binding.node.name} = ${binding.snippet};`;
}
@@ -318,14 +320,6 @@ function get_value_from_dom(
return `@time_ranges_to_array(this.${name})`;
}
- if (name === 'text') {
- return `this.textContent`;
- }
-
- if (name === 'html') {
- return `this.innerHTML`;
- }
-
// everything else
return `this.${name}`;
}
diff --git a/src/compiler/compile/render-dom/wrappers/Element/index.ts b/src/compiler/compile/render-dom/wrappers/Element/index.ts
index 390196160d..842201381c 100644
--- a/src/compiler/compile/render-dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render-dom/wrappers/Element/index.ts
@@ -30,7 +30,7 @@ const events = [
{
event_names: ['input'],
filter: (node: Element, name: string) =>
- (name === 'text' || name === 'html') &&
+ (name === 'textContent' || name === 'innerHTML') &&
node.attributes.some(attribute => attribute.name === 'contenteditable')
},
{
@@ -510,7 +510,19 @@ export default class ElementWrapper extends Wrapper {
.map(binding => `${binding.snippet} === void 0`)
.join(' || ');
- if (this.node.name === 'select' || group.bindings.find(binding => binding.node.name === 'indeterminate' || binding.is_readonly_media_attribute())) {
+ const should_initialise = (
+ 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) {
const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`;
block.builders.hydrate.add_line(
`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
diff --git a/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render-dom/wrappers/InlineComponent/index.ts
index 8d5c751add..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`);
diff --git a/src/compiler/compile/render-ssr/handlers/Element.ts b/src/compiler/compile/render-ssr/handlers/Element.ts
index 825e7e5594..27cf858f1f 100644
--- a/src/compiler/compile/render-ssr/handlers/Element.ts
+++ b/src/compiler/compile/render-ssr/handlers/Element.ts
@@ -53,7 +53,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
slot_scopes: Map;
}) {
let opening_tag = `<${node.name}`;
- let node_contents; // awkward special case
+
+ // awkward special case
+ let node_contents;
+ let value;
+
const contenteditable = (
node.name !== 'textarea' &&
node.name !== 'input' &&
@@ -150,33 +154,34 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (name === 'group') {
// TODO server-render group bindings
- } else if (contenteditable && (name === 'text' || name === 'html')) {
- const snippet = snip(expression);
- if (name == 'text') {
- node_contents = '${@escape(' + snippet + ')}';
- } else {
- // Do not escape HTML content
- node_contents = '${' + snippet + '}';
- }
+ } else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) {
+ node_contents = snip(expression);
+ value = name === 'textContent' ? '@escape($$value)' : '$$value';
} else if (binding.name === 'value' && node.name === 'textarea') {
const snippet = snip(expression);
- node_contents='${(' + snippet + ') || ""}';
+ node_contents = '${(' + snippet + ') || ""}';
} else {
const snippet = snip(expression);
- opening_tag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + @_JSON.stringify(v))) : "")(' + snippet + ')}';
+ opening_tag += '${@add_attribute("' + name + '", ' + snippet + ')}';
}
});
if (add_class_attribute) {
- opening_tag += `\${((v) => v ? ' class="' + v + '"' : '')([${class_expression}].join(' ').trim())}`;
+ opening_tag += `\${@add_classes([${class_expression}].join(' ').trim())}`;
}
opening_tag += '>';
renderer.append(opening_tag);
- if ((node.name === 'textarea' || contenteditable) && node_contents !== undefined) {
- renderer.append(node_contents);
+ if (node_contents !== undefined) {
+ if (contenteditable) {
+ renderer.append('${($$value => $$value === void 0 ? `');
+ renderer.render(node.children, options);
+ renderer.append('` : ' + value + ')(' + node_contents + ')}');
+ } else {
+ renderer.append(node_contents);
+ }
} else {
renderer.render(node.children, options);
}
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/compile/utils/scope.ts b/src/compiler/compile/utils/scope.ts
index 6bf3f9eaf4..75e44f0adf 100644
--- a/src/compiler/compile/utils/scope.ts
+++ b/src/compiler/compile/utils/scope.ts
@@ -2,6 +2,7 @@ import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import { Node } from '../../interfaces';
import { Node as ESTreeNode } from 'estree';
+import get_object from './get_object';
export function create_scopes(expression: Node) {
const map = new WeakMap();
@@ -114,6 +115,10 @@ const extractors = {
nodes.push(param);
},
+ MemberExpression(nodes: Node[], param: Node) {
+ nodes.push(get_object(param));
+ },
+
ObjectPattern(nodes: Node[], param: Node) {
param.properties.forEach((prop: Node) => {
if (prop.type === 'RestElement') {
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/ssr.ts b/src/runtime/internal/ssr.ts
index c80b4d9f45..91d6e452fe 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -119,3 +119,12 @@ export function get_store_value(store: Readable): T | undefined {
store.subscribe(_ => value = _)();
return value;
}
+
+export function add_attribute(name, value) {
+ if (!value) return '';
+ return ` ${name}${value === true ? '' : `=${JSON.stringify(value)}`}`;
+}
+
+export function add_classes(classes) {
+ return classes ? ` class="${classes}"` : ``;
+}
\ No newline at end of file
diff --git a/test/parser/samples/attribute-escaped/input.svelte b/test/parser/samples/attribute-escaped/input.svelte
index 82186dcee4..4c9cd5ff68 100644
--- a/test/parser/samples/attribute-escaped/input.svelte
+++ b/test/parser/samples/attribute-escaped/input.svelte
@@ -1 +1 @@
-
+
diff --git a/test/parser/samples/attribute-escaped/output.json b/test/parser/samples/attribute-escaped/output.json
index e60a8c2531..a8498bd574 100644
--- a/test/parser/samples/attribute-escaped/output.json
+++ b/test/parser/samples/attribute-escaped/output.json
@@ -1,27 +1,27 @@
{
"html": {
"start": 0,
- "end": 41,
+ "end": 83,
"type": "Fragment",
"children": [
{
"start": 0,
- "end": 41,
+ "end": 83,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 5,
- "end": 34,
+ "end": 76,
"type": "Attribute",
"name": "data-foo",
"value": [
{
"start": 15,
- "end": 33,
+ "end": 75,
"type": "Text",
- "raw": ""quoted"",
- "data": "\"quoted\""
+ "raw": "semi:"space:" letter:"e number:"1 end:"",
+ "data": "semi:\"space:\" letter:"e number:"1 end:\""
}
]
}
diff --git a/test/runtime/samples/binding-contenteditable-html-initial/_config.js b/test/runtime/samples/binding-contenteditable-html-initial/_config.js
new file mode 100644
index 0000000000..9eac2c9b17
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-html-initial/_config.js
@@ -0,0 +1,40 @@
+export default {
+ html: `
+ world
+ hello world
+ `,
+
+ ssrHtml: `
+ world
+ hello undefined
+ `,
+
+ async test({ assert, component, target, window }) {
+ assert.equal(component.name, 'world ');
+
+ const el = target.querySelector('editor');
+
+ el.innerHTML = 'everybody ';
+
+ // No updates to data yet
+ assert.htmlEqual(target.innerHTML, `
+ everybody
+ hello world
+ `);
+
+ // Handle user input
+ const event = new window.Event('input');
+ await el.dispatchEvent(event);
+ assert.htmlEqual(target.innerHTML, `
+ everybody
+ hello everybody
+ `);
+
+ component.name = 'goodbye ';
+ assert.equal(el.innerHTML, 'goodbye ');
+ assert.htmlEqual(target.innerHTML, `
+ goodbye
+ hello goodbye
+ `);
+ },
+};
diff --git a/test/runtime/samples/binding-contenteditable-html-initial/main.svelte b/test/runtime/samples/binding-contenteditable-html-initial/main.svelte
new file mode 100644
index 0000000000..119b41b52d
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-html-initial/main.svelte
@@ -0,0 +1,8 @@
+
+
+
+ world
+
+hello {@html name}
\ No newline at end of file
diff --git a/test/runtime/samples/contenteditable-html/_config.js b/test/runtime/samples/binding-contenteditable-html/_config.js
similarity index 90%
rename from test/runtime/samples/contenteditable-html/_config.js
rename to test/runtime/samples/binding-contenteditable-html/_config.js
index 285512b6c9..ceb6a75c70 100644
--- a/test/runtime/samples/contenteditable-html/_config.js
+++ b/test/runtime/samples/binding-contenteditable-html/_config.js
@@ -8,11 +8,6 @@ export default {
hello world
`,
- ssrHtml: `
- world
- hello world
- `,
-
async test({ assert, component, target, window }) {
const el = target.querySelector('editor');
assert.equal(el.innerHTML, 'world ');
diff --git a/test/runtime/samples/binding-contenteditable-html/main.svelte b/test/runtime/samples/binding-contenteditable-html/main.svelte
new file mode 100644
index 0000000000..f576cf1868
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-html/main.svelte
@@ -0,0 +1,6 @@
+
+
+
+hello {@html name}
\ No newline at end of file
diff --git a/test/runtime/samples/binding-contenteditable-text-initial/_config.js b/test/runtime/samples/binding-contenteditable-text-initial/_config.js
new file mode 100644
index 0000000000..4899f30f12
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-text-initial/_config.js
@@ -0,0 +1,34 @@
+export default {
+ html: `
+ world
+ hello world
+ `,
+
+ ssrHtml: `
+ world
+ hello undefined
+ `,
+
+ async test({ assert, component, target, window }) {
+ assert.equal(component.name, 'world');
+
+ const el = target.querySelector('editor');
+
+ const event = new window.Event('input');
+
+ el.textContent = 'everybody';
+ await el.dispatchEvent(event);
+
+ assert.htmlEqual(target.innerHTML, `
+ everybody
+ hello everybody
+ `);
+
+ component.name = 'goodbye';
+ assert.equal(el.textContent, 'goodbye');
+ assert.htmlEqual(target.innerHTML, `
+ goodbye
+ hello goodbye
+ `);
+ },
+};
diff --git a/test/runtime/samples/binding-contenteditable-text-initial/main.svelte b/test/runtime/samples/binding-contenteditable-text-initial/main.svelte
new file mode 100644
index 0000000000..b0938d4d0b
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-text-initial/main.svelte
@@ -0,0 +1,8 @@
+
+
+
+ world
+
+hello {name}
\ No newline at end of file
diff --git a/test/runtime/samples/contenteditable-text/_config.js b/test/runtime/samples/binding-contenteditable-text/_config.js
similarity index 89%
rename from test/runtime/samples/contenteditable-text/_config.js
rename to test/runtime/samples/binding-contenteditable-text/_config.js
index 059cda7cfe..9f8645724d 100644
--- a/test/runtime/samples/contenteditable-text/_config.js
+++ b/test/runtime/samples/binding-contenteditable-text/_config.js
@@ -8,11 +8,6 @@ export default {
hello world
`,
- ssrHtml: `
- world
- hello world
- `,
-
async test({ assert, component, target, window }) {
const el = target.querySelector('editor');
assert.equal(el.textContent, 'world');
diff --git a/test/runtime/samples/binding-contenteditable-text/main.svelte b/test/runtime/samples/binding-contenteditable-text/main.svelte
new file mode 100644
index 0000000000..70f65e8824
--- /dev/null
+++ b/test/runtime/samples/binding-contenteditable-text/main.svelte
@@ -0,0 +1,6 @@
+
+
+
+hello {name}
\ No newline at end of file
diff --git a/test/runtime/samples/binding-this-component-computed-key/Foo.svelte b/test/runtime/samples/binding-this-component-computed-key/Foo.svelte
new file mode 100644
index 0000000000..066a48b65e
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-computed-key/Foo.svelte
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/test/runtime/samples/binding-this-component-computed-key/_config.js b/test/runtime/samples/binding-this-component-computed-key/_config.js
new file mode 100644
index 0000000000..415d2e641c
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-computed-key/_config.js
@@ -0,0 +1,8 @@
+export default {
+ skip_if_ssr: true,
+
+ html: `
+ foo
+ has foo: true
+ `
+};
diff --git a/test/runtime/samples/binding-this-component-computed-key/main.svelte b/test/runtime/samples/binding-this-component-computed-key/main.svelte
new file mode 100644
index 0000000000..af450ea1fe
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-computed-key/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+ has foo: {!!foo.computed}
+
diff --git a/test/runtime/samples/binding-this-component-each-block-value/Foo.svelte b/test/runtime/samples/binding-this-component-each-block-value/Foo.svelte
new file mode 100644
index 0000000000..066a48b65e
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block-value/Foo.svelte
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/test/runtime/samples/binding-this-component-each-block-value/_config.js b/test/runtime/samples/binding-this-component-each-block-value/_config.js
new file mode 100644
index 0000000000..2b5df8c595
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block-value/_config.js
@@ -0,0 +1,12 @@
+export default {
+ skip_if_ssr: true,
+
+ html: `
+ foo
+ first has foo: true
+ foo
+ second has foo: true
+ foo
+ third has foo: true
+ `
+};
diff --git a/test/runtime/samples/binding-this-component-each-block-value/main.svelte b/test/runtime/samples/binding-this-component-each-block-value/main.svelte
new file mode 100644
index 0000000000..5093eecd3f
--- /dev/null
+++ b/test/runtime/samples/binding-this-component-each-block-value/main.svelte
@@ -0,0 +1,11 @@
+
+
+{#each ["first", "second", "third"] as value}
+
+
+ {value} has foo: {!!foo[value]}
+
+{/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/contenteditable-html/main.svelte b/test/runtime/samples/contenteditable-html/main.svelte
deleted file mode 100644
index 53b4e81c88..0000000000
--- a/test/runtime/samples/contenteditable-html/main.svelte
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-hello {@html name}
\ No newline at end of file
diff --git a/test/runtime/samples/contenteditable-text/main.svelte b/test/runtime/samples/contenteditable-text/main.svelte
deleted file mode 100644
index a71d9f0c5b..0000000000
--- a/test/runtime/samples/contenteditable-text/main.svelte
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-hello {name}
\ No newline at end of file
diff --git a/test/runtime/samples/destructuring-assignment-array/_config.js b/test/runtime/samples/destructuring-assignment-array/_config.js
new file mode 100644
index 0000000000..ede4552803
--- /dev/null
+++ b/test/runtime/samples/destructuring-assignment-array/_config.js
@@ -0,0 +1,23 @@
+export default {
+ html: `
+
+ Gruyere
+ Compté
+ Beaufort
+ Abondance
+
+ `,
+
+ async test({ assert, component, target }) {
+ await component.swap(0, 1);
+
+ assert.htmlEqual(target.innerHTML, `
+
+ Compté
+ Gruyere
+ Beaufort
+ Abondance
+
+ `);
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/destructuring-assignment-array/main.svelte b/test/runtime/samples/destructuring-assignment-array/main.svelte
new file mode 100644
index 0000000000..aebbfe204c
--- /dev/null
+++ b/test/runtime/samples/destructuring-assignment-array/main.svelte
@@ -0,0 +1,18 @@
+
+
+
+ {#each cheese as cheese}
+ {cheese}
+ {/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/validator/samples/contenteditable-dynamic/input.svelte b/test/validator/samples/contenteditable-dynamic/input.svelte
index 97d2c9228c..8dfa91a354 100644
--- a/test/validator/samples/contenteditable-dynamic/input.svelte
+++ b/test/validator/samples/contenteditable-dynamic/input.svelte
@@ -3,4 +3,4 @@
let toggle = false;
-
+
diff --git a/test/validator/samples/contenteditable-missing/errors.json b/test/validator/samples/contenteditable-missing/errors.json
index 9cadb20629..53d5af4928 100644
--- a/test/validator/samples/contenteditable-missing/errors.json
+++ b/test/validator/samples/contenteditable-missing/errors.json
@@ -1,6 +1,6 @@
[{
"code": "missing-contenteditable-attribute",
- "message": "'contenteditable' attribute is required for text and html two-way bindings",
+ "message": "'contenteditable' attribute is required for textContent and innerHTML two-way bindings",
"start": {
"line": 4,
"column": 8,
@@ -8,8 +8,8 @@
},
"end": {
"line": 4,
- "column": 24,
- "character": 64
+ "column": 31,
+ "character": 71
},
"pos": 48
}]
\ No newline at end of file
diff --git a/test/validator/samples/contenteditable-missing/input.svelte b/test/validator/samples/contenteditable-missing/input.svelte
index 47f125894a..dae1088994 100644
--- a/test/validator/samples/contenteditable-missing/input.svelte
+++ b/test/validator/samples/contenteditable-missing/input.svelte
@@ -1,4 +1,4 @@
-
+