Merge remote-tracking branch 'origin/master' into Issue-2806

pull/3099/head
Primo Sabatini 6 years ago
commit 375fc0fe15

@ -39,6 +39,9 @@
}], }],
"@typescript-eslint/no-unused-vars": "off" "@typescript-eslint/no-unused-vars": "off"
}, },
"globals": {
"globalThis": false
},
"env": { "env": {
"es6": true, "es6": true,
"browser": true, "browser": true,

2
.gitignore vendored

@ -3,7 +3,7 @@
.nyc_output .nyc_output
node_modules node_modules
*.map *.map
/src/compiler/compile/internal-exports.ts /src/compiler/compile/internal_exports.ts
/compiler.d.ts /compiler.d.ts
/compiler.*js /compiler.*js
/index.*js /index.*js

@ -30,7 +30,7 @@
"coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html",
"codecov": "codecov", "codecov": "codecov",
"precodecov": "npm run coverage", "precodecov": "npm run coverage",
"build": "rollup -c && npm run tsd", "build": "rollup -c && npm run lint && npm run tsd",
"prepare": "npm run build", "prepare": "npm run build",
"dev": "rollup -cw", "dev": "rollup -cw",
"pretest": "npm run build", "pretest": "npm run build",

@ -66,7 +66,7 @@ export default [
if (dir === 'internal') { if (dir === 'internal') {
const mod = bundle['index.mjs']; const mod = bundle['index.mjs'];
if (mod) { if (mod) {
fs.writeFileSync('src/compiler/compile/internal-exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`); fs.writeFileSync('src/compiler/compile/internal_exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
} }
} }

@ -510,6 +510,14 @@ When the value of an `<option>` matches its text content, the attribute can be o
</select> </select>
``` ```
---
Elements with the `contenteditable` attribute support `innerHTML` and `textContent` bindings.
```html
<div contenteditable="true" bind:innerHTML={html}></div>
```
##### Media element bindings ##### Media element bindings
--- ---

@ -0,0 +1,15 @@
<script>
let html = '<p>Write some text!</p>';
</script>
<div contenteditable="true"></div>
<pre>{html}</pre>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>

@ -0,0 +1,18 @@
<script>
let html = '<p>Write some text!</p>';
</script>
<div
contenteditable="true"
bind:innerHTML={html}
></div>
<pre>{html}</pre>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>

@ -0,0 +1,12 @@
---
title: Contenteditable bindings
---
Elements with a `contenteditable="true"` attribute support `textContent` and `innerHTML` bindings:
```html
<div
contenteditable="true"
bind:innerHTML={html}
></div>
```

@ -52,6 +52,7 @@
<a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo"></a> <a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo"></a>
<a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo"></a> <a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo"></a>
<a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo"></a> <a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo"></a>
<a target="_blank" rel="noopener" href="https://www.nonkositelecoms.com"><img src="organisations/nonkosi.svg" alt="Nonkosi Telecoms logo"></a>
<a target="_blank" rel="noopener" href="https://www.nzz.ch"><img src="organisations/nzz.svg" alt="Neue Zürcher Zeitung logo"></a> <a target="_blank" rel="noopener" href="https://www.nzz.ch"><img src="organisations/nzz.svg" alt="Neue Zürcher Zeitung logo"></a>
<a target="_blank" rel="noopener" href="https://nytimes.com"><img src="organisations/nyt.svg" alt="The New York Times logo"></a> <a target="_blank" rel="noopener" href="https://nytimes.com"><img src="organisations/nyt.svg" alt="The New York Times logo"></a>
<a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo"></a> <a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo"></a>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

@ -10,7 +10,7 @@ import { create_scopes, extract_names, Scope, extract_identifiers } from './util
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import internal_exports from './internal-exports'; import internal_exports from './internal_exports';
import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces'; import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame'; import get_code_frame from '../utils/get_code_frame';
@ -101,7 +101,8 @@ export default class Component {
reactive_declaration_nodes: Set<Node> = new Set(); reactive_declaration_nodes: Set<Node> = new Set();
has_reactive_assignments = false; has_reactive_assignments = false;
injected_reactive_declaration_vars: Set<string> = new Set(); injected_reactive_declaration_vars: Set<string> = new Set();
helpers: Set<string> = new Set(); helpers: Map<string, string> = new Map();
globals: Map<string, string> = new Map();
indirect_dependencies: Map<string, Set<string>> = new Map(); indirect_dependencies: Map<string, Set<string>> = new Map();
@ -233,8 +234,15 @@ export default class Component {
} }
helper(name: string) { helper(name: string) {
this.helpers.add(name); const alias = this.alias(name);
return this.alias(name); this.helpers.set(name, alias);
return alias;
}
global(name: string) {
const alias = this.alias(name);
this.globals.set(name, alias);
return alias;
} }
generate(result: string) { generate(result: string) {
@ -251,23 +259,29 @@ export default class Component {
.replace(/__svelte:self__/g, this.name) .replace(/__svelte:self__/g, this.name)
.replace(compile_options.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (_match: string, sigil: string, name: string) => { .replace(compile_options.generate === 'ssr' ? /(@+|#+)(\w*(?:-\w*)?)/g : /(@+)(\w*(?:-\w*)?)/g, (_match: string, sigil: string, name: string) => {
if (sigil === '@') { if (sigil === '@') {
if (internal_exports.has(name)) { if (name[0] === '_') {
if (compile_options.dev && internal_exports.has(`${name}Dev`)) name = `${name}Dev`; return this.global(name.slice(1));
this.helpers.add(name); }
if (!internal_exports.has(name)) {
throw new Error(`compiler error: this shouldn't happen! generated code is trying to use inexistent internal '${name}'`);
}
if (compile_options.dev && internal_exports.has(`${name}Dev`)) {
name = `${name}Dev`;
} }
return this.alias(name); return this.helper(name);
} }
return sigil.slice(1) + name; return sigil.slice(1) + name;
}); });
const imported_helpers = Array.from(this.helpers) const referenced_globals = Array.from(this.globals, ([name, alias]) => name !== alias && ({ name, alias })).filter(Boolean);
.sort() if (referenced_globals.length) {
.map(name => { this.helper('globals');
const alias = this.alias(name); }
return { name, alias }; const imported_helpers = Array.from(this.helpers, ([name, alias]) => ({ name, alias }));
});
const module = create_module( const module = create_module(
result, result,
@ -276,6 +290,7 @@ export default class Component {
banner, banner,
compile_options.sveltePath, compile_options.sveltePath,
imported_helpers, imported_helpers,
referenced_globals,
this.imports, this.imports,
this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({ this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({
name: variable.name, name: variable.name,

@ -17,6 +17,7 @@ export default function create_module(
banner: string, banner: string,
sveltePath = 'svelte', sveltePath = 'svelte',
helpers: Array<{ name: string; alias: string }>, helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[], imports: Node[],
module_exports: Export[], module_exports: Export[],
source: string source: string
@ -24,10 +25,10 @@ export default function create_module(
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
if (format === 'esm') { if (format === 'esm') {
return esm(code, name, banner, sveltePath, internal_path, helpers, imports, module_exports, source); return esm(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, source);
} }
if (format === 'cjs') return cjs(code, name, banner, sveltePath, internal_path, helpers, imports, module_exports); if (format === 'cjs') return cjs(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
@ -45,6 +46,7 @@ function esm(
sveltePath: string, sveltePath: string,
internal_path: string, internal_path: string,
helpers: Array<{ name: string; alias: string }>, helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[], imports: Node[],
module_exports: Export[], module_exports: Export[],
source: string source: string
@ -52,6 +54,9 @@ function esm(
const internal_imports = helpers.length > 0 && ( const internal_imports = helpers.length > 0 && (
`import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};` `import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};`
); );
const internal_globals = globals.length > 0 && (
`const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};`
);
const user_imports = imports.length > 0 && ( const user_imports = imports.length > 0 && (
imports imports
@ -70,6 +75,7 @@ function esm(
return deindent` return deindent`
${banner} ${banner}
${internal_imports} ${internal_imports}
${internal_globals}
${user_imports} ${user_imports}
${code} ${code}
@ -85,6 +91,7 @@ function cjs(
sveltePath: string, sveltePath: string,
internal_path: string, internal_path: string,
helpers: Array<{ name: string; alias: string }>, helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[], imports: Node[],
module_exports: Export[] module_exports: Export[]
) { ) {
@ -93,6 +100,9 @@ function cjs(
const internal_imports = helpers.length > 0 && ( const internal_imports = helpers.length > 0 && (
`const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n` `const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n`
); );
const internal_globals = globals.length > 0 && (
`const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};`
);
const requires = imports.map(node => { const requires = imports.map(node => {
let lhs; let lhs;
@ -127,6 +137,7 @@ function cjs(
"use strict"; "use strict";
${internal_imports} ${internal_imports}
${internal_globals}
${requires} ${requires}
${code} ${code}

@ -1,8 +1,8 @@
import { assign } from '../../runtime/internal/utils'; import { assign } from '../../runtime/internal/utils';
import Stats from '../Stats'; import Stats from '../Stats';
import parse from '../parse/index'; import parse from '../parse/index';
import render_dom from './render-dom/index'; import render_dom from './render_dom/index';
import render_ssr from './render-ssr/index'; import render_ssr from './render_ssr/index';
import { CompileOptions, Warning } from '../interfaces'; import { CompileOptions, Warning } from '../interfaces';
import Component from './Component'; import Component from './Component';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';

@ -608,8 +608,8 @@ export default class Element extends Node {
}); });
} }
} else if ( } else if (
name === 'text' || name === 'textContent' ||
name === 'html' name === 'innerHTML'
) { ) {
const contenteditable = this.attributes.find( const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable' (attribute: Attribute) => attribute.name === 'contenteditable'
@ -618,7 +618,7 @@ export default class Element extends Node {
if (!contenteditable) { if (!contenteditable) {
component.error(binding, { component.error(binding, {
code: `missing-contenteditable-attribute`, 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) { } else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, { component.error(contenteditable, {

@ -2,7 +2,7 @@ import Node from './shared/Node';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import deindent from '../utils/deindent'; import deindent from '../utils/deindent';
import Block from '../render-dom/Block'; import Block from '../render_dom/Block';
export default class EventHandler extends Node { export default class EventHandler extends Node {
type: 'EventHandler'; type: 'EventHandler';

@ -1,7 +1,7 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Component from '../Component'; import Component from '../Component';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import Block from '../render-dom/Block'; import Block from '../render_dom/Block';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';

@ -1,4 +1,4 @@
import Block from '../../render-dom/Block'; import Block from '../../render_dom/Block';
import Component from '../../Component'; import Component from '../../Component';
import Node from './Node'; import Node from './Node';
import { INode } from '../interfaces'; import { INode } from '../interfaces';

@ -6,12 +6,12 @@ import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import { globals , sanitize } from '../../../utils/names'; import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import Wrapper from '../../render-dom/wrappers/shared/Wrapper'; import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope'; import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match'; import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block'; import Block from '../../render_dom/Block';
import { INode } from '../interfaces'; import { INode } from '../interfaces';
const binary_operators: Record<string, number> = { const binary_operators: Record<string, number> = {

@ -164,7 +164,7 @@ export default class Block {
if (parent_node) { if (parent_node) {
this.builders.mount.add_line(`@append(${parent_node}, ${name});`); this.builders.mount.add_line(`@append(${parent_node}, ${name});`);
if (parent_node === 'document.head' && !no_detach) this.builders.destroy.add_line(`@detach(${name});`); if (parent_node === '@_document.head' && !no_detach) this.builders.destroy.add_line(`@detach(${name});`);
} else { } else {
this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`); this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`);
if (!no_detach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`); if (!no_detach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`);

@ -36,13 +36,15 @@ export default function dom(
`${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` :
css.code, { only_escape_at_symbol: true }); css.code, { only_escape_at_symbol: true });
const add_css = component.get_unique_name('add_css');
if (styles && component.compile_options.css !== false && !options.customElement) { if (styles && component.compile_options.css !== false && !options.customElement) {
builder.add_block(deindent` builder.add_block(deindent`
function @add_css() { function ${add_css}() {
var style = @element("style"); var style = @element("style");
style.id = '${component.stylesheet.id}-style'; style.id = '${component.stylesheet.id}-style';
style.textContent = ${styles}; style.textContent = ${styles};
@append(document.head, style); @append(@_document.head, style);
} }
`); `);
} }
@ -57,7 +59,7 @@ export default function dom(
if (options.dev && !options.hydratable) { if (options.dev && !options.hydratable) {
block.builders.claim.add_line( block.builders.claim.add_line(
'throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");' 'throw new @_Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");'
); );
} }
@ -106,7 +108,7 @@ export default function dom(
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
body.push(deindent` body.push(deindent`
get ${x.export_name}() { get ${x.export_name}() {
throw new Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
} }
`); `);
} }
@ -122,14 +124,14 @@ export default function dom(
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
body.push(deindent` body.push(deindent`
set ${x.export_name}(value) { set ${x.export_name}(value) {
throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'"); throw new @_Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'");
} }
`); `);
} }
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
body.push(deindent` body.push(deindent`
set ${x.export_name}(value) { set ${x.export_name}(value) {
throw new Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'"); throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
} }
`); `);
} }
@ -145,7 +147,7 @@ export default function dom(
const props = ${options.customElement ? `this.attributes` : `options.props || {}`}; const props = ${options.customElement ? `this.attributes` : `options.props || {}`};
${expected.map(prop => deindent` ${expected.map(prop => deindent`
if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) { if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) {
console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`)}
`; `;
} }
@ -402,8 +404,8 @@ export default function dom(
if (component.compile_options.dev && !component.var_lookup.has('$$props') && writable_props.length) { if (component.compile_options.dev && !component.var_lookup.has('$$props') && writable_props.length) {
unknown_props_check = deindent` unknown_props_check = deindent`
const writable_props = [${writable_props.map(prop => `'${prop.export_name}'`).join(', ')}]; const writable_props = [${writable_props.map(prop => `'${prop.export_name}'`).join(', ')}];
Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!writable_props.includes(key) && !key.startsWith('$$')) console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`); if (!writable_props.includes(key) && !key.startsWith('$$')) @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
}); });
`; `;
} }
@ -481,7 +483,7 @@ export default function dom(
if (component.tag != null) { if (component.tag != null) {
builder.add_block(deindent` builder.add_block(deindent`
customElements.define("${component.tag}", ${name}); @_customElements.define("${component.tag}", ${name});
`); `);
} }
} else { } else {
@ -491,7 +493,7 @@ export default function dom(
class ${name} extends @${superclass} { class ${name} extends @${superclass} {
constructor(options) { constructor(options) {
super(${options.dev && `options`}); super(${options.dev && `options`});
${should_add_css && `if (!document.getElementById("${component.stylesheet.id}-style")) @add_css();`} ${should_add_css && `if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names}); @init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names});
${dev_props_check} ${dev_props_check}

@ -142,7 +142,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this.catch.block.name && `catch: ${this.catch.block.name}`, this.catch.block.name && `catch: ${this.catch.block.name}`,
this.then.block.name && `value: '${this.node.value}'`, this.then.block.name && `value: '${this.node.value}'`,
this.catch.block.name && `error: '${this.node.error}'`, this.catch.block.name && `error: '${this.node.error}'`,
this.pending.block.has_outro_method && `blocks: Array(3)` this.pending.block.has_outro_method && `blocks: [,,,]`
].filter(Boolean); ].filter(Boolean);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`

@ -11,11 +11,11 @@ export default class BodyWrapper extends Wrapper {
const snippet = handler.render(block); const snippet = handler.render(block);
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
document.body.addEventListener("${handler.name}", ${snippet}); @_document.body.addEventListener("${handler.name}", ${snippet});
`); `);
block.builders.destroy.add_block(deindent` block.builders.destroy.add_block(deindent`
document.body.removeEventListener("${handler.name}", ${snippet}); @_document.body.removeEventListener("${handler.name}", ${snippet});
`); `);
}); });
} }

@ -62,7 +62,7 @@ export default class DebugTagWrapper extends Wrapper {
block.builders.update.add_block(deindent` block.builders.update.add_block(deindent`
if (${condition}) { if (${condition}) {
const { ${ctx_identifiers} } = ctx; const { ${ctx_identifiers} } = ctx;
console.${log}({ ${logged_identifiers} }); @_console.${log}({ ${logged_identifiers} });
debugger; debugger;
} }
`); `);
@ -70,7 +70,7 @@ export default class DebugTagWrapper extends Wrapper {
block.builders.create.add_block(deindent` block.builders.create.add_block(deindent`
{ {
const { ${ctx_identifiers} } = ctx; const { ${ctx_identifiers} } = ctx;
console.${log}({ ${logged_identifiers} }); @_console.${log}({ ${logged_identifiers} });
debugger; debugger;
} }
`); `);

@ -190,7 +190,7 @@ export default class EachBlockWrapper extends Wrapper {
renderer.blocks.push(deindent` renderer.blocks.push(deindent`
function ${this.vars.get_each_context}(ctx, list, i) { function ${this.vars.get_each_context}(ctx, list, i) {
const child_ctx = Object.create(ctx); const child_ctx = @_Object.create(ctx);
${this.context_props} ${this.context_props}
return child_ctx; return child_ctx;
} }
@ -296,7 +296,7 @@ export default class EachBlockWrapper extends Wrapper {
const lookup = block.get_unique_name(`${this.var}_lookup`); const lookup = block.get_unique_name(`${this.var}_lookup`);
block.add_variable(iterations, '[]'); block.add_variable(iterations, '[]');
block.add_variable(lookup, `new Map()`); block.add_variable(lookup, `new @_Map()`);
if (this.fragment.nodes[0].is_dom_node()) { if (this.fragment.nodes[0].is_dom_node()) {
this.block.first = this.fragment.nodes[0].var; this.block.first = this.fragment.nodes[0].var;
@ -498,7 +498,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.block.has_outros) { if (this.block.has_outros) {
block.builders.outro.add_block(deindent` block.builders.outro.add_block(deindent`
${iterations} = ${iterations}.filter(Boolean); ${iterations} = ${iterations}.filter(@_Boolean);
for (let #i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);` for (let #i = 0; #i < ${view_length}; #i += 1) @transition_out(${iterations}[#i]);`
); );
} }

@ -133,10 +133,18 @@ export default class BindingWrapper {
break; 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 'currentTime':
case 'playbackRate': case 'playbackRate':
case 'volume': case 'volume':
update_conditions.push(`!isNaN(${this.snippet})`); update_conditions.push(`!@_isNaN(${this.snippet})`);
break; break;
case 'paused': case 'paused':
@ -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); block.builders.mount.add_block(update_dom);
} }
} }
@ -198,14 +208,6 @@ function get_dom_updater(
return `${element.var}.checked = ${condition};`; 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};`; return `${element.var}.${binding.node.name} = ${binding.snippet};`;
} }
@ -318,14 +320,6 @@ function get_value_from_dom(
return `@time_ranges_to_array(this.${name})`; return `@time_ranges_to_array(this.${name})`;
} }
if (name === 'text') {
return `this.textContent`;
}
if (name === 'html') {
return `this.innerHTML`;
}
// everything else // everything else
return `this.${name}`; return `this.${name}`;
} }

@ -30,7 +30,7 @@ const events = [
{ {
event_names: ['input'], event_names: ['input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
(name === 'text' || name === 'html') && (name === 'textContent' || name === 'innerHTML') &&
node.attributes.some(attribute => attribute.name === 'contenteditable') node.attributes.some(attribute => attribute.name === 'contenteditable')
}, },
{ {
@ -270,7 +270,7 @@ export default class ElementWrapper extends Wrapper {
`@append(${parent_node}, ${node});` `@append(${parent_node}, ${node});`
); );
if (parent_node === 'document.head') { if (parent_node === '@_document.head') {
block.builders.destroy.add_line(`@detach(${node});`); block.builders.destroy.add_line(`@detach(${node});`);
} }
} else { } else {
@ -344,12 +344,10 @@ export default class ElementWrapper extends Wrapper {
parent.name === 'style' parent.name === 'style'
); );
return (raw return (raw ? wrapper.node.data : escape_html(wrapper.node.data))
? wrapper.node.data .replace(/\\/g, '\\\\')
: escape_html(wrapper.node.data)) .replace(/`/g, '\\`')
.replace(/\\/g, '\\\\') .replace(/\$/g, '\\$');
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
} }
if (wrapper.node.name === 'noscript') return ''; if (wrapper.node.name === 'noscript') return '';
@ -381,7 +379,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (namespace) { if (namespace) {
return `document.createElementNS("${namespace}", "${name}")`; return `@_document.createElementNS("${namespace}", "${name}")`;
} }
return `@element("${name}")`; return `@element("${name}")`;
@ -467,7 +465,7 @@ export default class ElementWrapper extends Wrapper {
block.builders.init.add_block(deindent` block.builders.init.add_block(deindent`
function ${handler}() { function ${handler}() {
${animation_frame && deindent` ${animation_frame && deindent`
cancelAnimationFrame(${animation_frame}); @_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = @raf(${handler});`} if (!${this.var}.paused) ${animation_frame} = @raf(${handler});`}
${needs_lock && `${lock} = true;`} ${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''}); ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
@ -510,7 +508,19 @@ export default class ElementWrapper extends Wrapper {
.map(binding => `${binding.snippet} === void 0`) .map(binding => `${binding.snippet} === void 0`)
.join(' || '); .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})`; const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`;
block.builders.hydrate.add_line( block.builders.hydrate.add_line(
`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});` `if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`

@ -30,6 +30,6 @@ export default class HeadWrapper extends Wrapper {
} }
render(block: Block, _parent_node: string, _parent_nodes: string) { render(block: Block, _parent_node: string, _parent_nodes: string) {
this.fragment.render(block, 'document.head', 'nodes'); this.fragment.render(block, '@_document.head', 'nodes');
} }
} }

@ -154,7 +154,7 @@ export default class IfBlockWrapper extends Wrapper {
const vars = { name, anchor, if_name, has_else, has_transitions }; const vars = { name, anchor, if_name, has_else, has_transitions };
const detaching = (parent_node && parent_node !== 'document.head') ? '' : 'detaching'; const detaching = (parent_node && parent_node !== '@_document.head') ? '' : 'detaching';
if (this.node.else) { if (this.node.else) {
if (has_outros) { if (has_outros) {

@ -22,7 +22,7 @@ export default class RawMustacheTagWrapper extends Tag {
render(block: Block, parent_node: string, parent_nodes: string) { render(block: Block, parent_node: string, parent_nodes: string) {
const name = this.var; const name = this.var;
const in_head = parent_node === 'document.head'; const in_head = parent_node === '@_document.head';
const needs_anchors = !parent_node || in_head; const needs_anchors = !parent_node || in_head;
// if in head always needs anchors // if in head always needs anchors

@ -68,9 +68,9 @@ export default class TitleWrapper extends Wrapper {
const init = this.node.should_cache ? `${last} = ${value}` : value; const init = this.node.should_cache ? `${last} = ${value}` : value;
block.builders.init.add_line( block.builders.init.add_line(
`document.title = ${init};` `@_document.title = ${init};`
); );
const updater = `document.title = ${this.node.should_cache ? last : value};`; const updater = `@_document.title = ${this.node.should_cache ? last : value};`;
if (all_dependencies.size) { if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies); const dependencies = Array.from(all_dependencies);
@ -95,7 +95,7 @@ export default class TitleWrapper extends Wrapper {
? stringify((this.node.children[0] as Text).data) ? stringify((this.node.children[0] as Text).data)
: '""'; : '""';
block.builders.hydrate.add_line(`document.title = ${value};`); block.builders.hydrate.add_line(`@_document.title = ${value};`);
} }
} }
} }

@ -44,8 +44,8 @@ export default class WindowWrapper extends Wrapper {
const events = {}; const events = {};
const bindings: Record<string, string> = {}; const bindings: Record<string, string> = {};
add_actions(component, block, 'window', this.node.actions); add_actions(component, block, '@_window', this.node.actions);
add_event_handlers(block, 'window', this.node.handlers); add_event_handlers(block, '@_window', this.node.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to // in dev mode, throw if read-only values are written to
@ -92,29 +92,29 @@ export default class WindowWrapper extends Wrapper {
renderer.meta_bindings.add_block(deindent` renderer.meta_bindings.add_block(deindent`
if (${condition}) { if (${condition}) {
window.scrollTo(${x || 'window.pageXOffset'}, ${y || 'window.pageYOffset'}); @_scrollTo(${x || '@_window.pageXOffset'}, ${y || '@_window.pageYOffset'});
} }
${x && `${x} = window.pageXOffset;`} ${x && `${x} = @_window.pageXOffset;`}
${y && `${y} = window.pageYOffset;`} ${y && `${y} = @_window.pageYOffset;`}
`); `);
block.event_listeners.push(deindent` block.event_listeners.push(deindent`
@listen(window, "${event}", () => { @listen(@_window, "${event}", () => {
${scrolling} = true; ${scrolling} = true;
clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
${scrolling_timeout} = setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
ctx.${handler_name}(); ctx.${handler_name}();
}) })
`); `);
} else { } else {
props.forEach(prop => { props.forEach(prop => {
renderer.meta_bindings.add_line( renderer.meta_bindings.add_line(
`this._state.${prop.name} = window.${prop.value};` `this._state.${prop.name} = @_window.${prop.value};`
); );
}); });
block.event_listeners.push(deindent` block.event_listeners.push(deindent`
@listen(window, "${event}", ctx.${handler_name}) @listen(@_window, "${event}", ctx.${handler_name})
`); `);
} }
@ -126,7 +126,7 @@ export default class WindowWrapper extends Wrapper {
component.partly_hoisted.push(deindent` component.partly_hoisted.push(deindent`
function ${handler_name}() { function ${handler_name}() {
${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)} ${props.map(prop => `${prop.name} = @_window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)}
} }
`); `);
@ -146,13 +146,13 @@ export default class WindowWrapper extends Wrapper {
).join(' || ') ).join(' || ')
} && !${scrolling}) { } && !${scrolling}) {
${scrolling} = true; ${scrolling} = true;
clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
window.scrollTo(${ @_scrollTo(${
bindings.scrollX ? `ctx.${bindings.scrollX}` : `window.pageXOffset` bindings.scrollX ? `ctx.${bindings.scrollX}` : `@_window.pageXOffset`
}, ${ }, ${
bindings.scrollY ? `ctx.${bindings.scrollY}` : `window.pageYOffset` bindings.scrollY ? `ctx.${bindings.scrollY}` : `@_window.pageYOffset`
}); });
${scrolling_timeout} = setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
} }
`); `);
} }
@ -170,7 +170,7 @@ export default class WindowWrapper extends Wrapper {
component.partly_hoisted.push(deindent` component.partly_hoisted.push(deindent`
function ${handler_name}() { function ${handler_name}() {
${name} = navigator.onLine; $$invalidate('${name}', ${name}); ${name} = @_navigator.onLine; $$invalidate('${name}', ${name});
} }
`); `);
@ -179,8 +179,8 @@ export default class WindowWrapper extends Wrapper {
`); `);
block.event_listeners.push( block.event_listeners.push(
`@listen(window, "online", ctx.${handler_name})`, `@listen(@_window, "online", ctx.${handler_name})`,
`@listen(window, "offline", ctx.${handler_name})` `@listen(@_window, "offline", ctx.${handler_name})`
); );
component.has_reactive_assignments = true; component.has_reactive_assignments = true;

@ -53,7 +53,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
}) { }) {
let opening_tag = `<${node.name}`; let opening_tag = `<${node.name}`;
let node_contents; // awkward special case
// awkward special case
let node_contents;
let value;
const contenteditable = ( const contenteditable = (
node.name !== 'textarea' && node.name !== 'textarea' &&
node.name !== 'input' && node.name !== 'input' &&
@ -150,33 +154,34 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (name === 'group') { if (name === 'group') {
// TODO server-render group bindings // TODO server-render group bindings
} else if (contenteditable && (name === 'text' || name === 'html')) { } else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) {
const snippet = snip(expression); node_contents = snip(expression);
if (name == 'text') { value = name === 'textContent' ? '@escape($$value)' : '$$value';
node_contents = '${@escape(' + snippet + ')}';
} else {
// Do not escape HTML content
node_contents = '${' + snippet + '}';
}
} else if (binding.name === 'value' && node.name === 'textarea') { } else if (binding.name === 'value' && node.name === 'textarea') {
const snippet = snip(expression); const snippet = snip(expression);
node_contents='${(' + snippet + ') || ""}'; node_contents = '${(' + snippet + ') || ""}';
} else { } else {
const snippet = snip(expression); 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) { if (add_class_attribute) {
opening_tag += `\${((v) => v ? ' class="' + v + '"' : '')([${class_expression}].join(' ').trim())}`; opening_tag += `\${@add_classes([${class_expression}].join(' ').trim())}`;
} }
opening_tag += '>'; opening_tag += '>';
renderer.append(opening_tag); renderer.append(opening_tag);
if ((node.name === 'textarea' || contenteditable) && node_contents !== undefined) { if (node_contents !== undefined) {
renderer.append(node_contents); if (contenteditable) {
renderer.append('${($$value => $$value === void 0 ? `');
renderer.render(node.children, options);
renderer.append('` : ' + value + ')(' + node_contents + ')}');
} else {
renderer.append(node_contents);
}
} else { } else {
renderer.render(node.children, options); renderer.render(node.children, options);
} }

@ -52,7 +52,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
let props; let props;
if (uses_spread) { if (uses_spread) {
props = `Object.assign(${ props = `@_Object.assign(${
node.attributes node.attributes
.map(attribute => { .map(attribute => {
if (attribute.is_spread) { if (attribute.is_spread) {

@ -2,6 +2,7 @@ import { walk } from 'estree-walker';
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
import get_object from './get_object';
export function create_scopes(expression: Node) { export function create_scopes(expression: Node) {
const map = new WeakMap(); const map = new WeakMap();
@ -114,6 +115,10 @@ const extractors = {
nodes.push(param); nodes.push(param);
}, },
MemberExpression(nodes: Node[], param: Node) {
nodes.push(get_object(param));
},
ObjectPattern(nodes: Node[], param: Node) { ObjectPattern(nodes: Node[], param: Node) {
param.properties.forEach((prop: Node) => { param.properties.forEach((prop: Node) => {
if (prop.type === 'RestElement') { if (prop.type === 'RestElement') {

@ -1,6 +1,6 @@
import { assign, is_promise } from './utils'; import { assign, is_promise } from './utils';
import { check_outros, group_outros, transition_in, transition_out } from './transitions'; import { check_outros, group_outros, transition_in, transition_out } from './transitions';
import { flush } from '../internal/scheduler'; import { flush } from './scheduler';
export function handle_promise(promise, info) { export function handle_promise(promise, info) {
const token = info.token = {}; const token = info.token = {};

@ -0,0 +1,3 @@
declare const global: any;
export const globals = (typeof window !== 'undefined' ? window : global) as unknown as typeof globalThis;

@ -1,8 +1,9 @@
export * from './animations'; export * from './animations';
export * from './await-block'; export * from './await_block';
export * from './dom'; export * from './dom';
export * from './environment'; export * from './environment';
export * from './keyed-each'; export * from './globals';
export * from './keyed_each';
export * from './lifecycle'; export * from './lifecycle';
export * from './loop'; export * from './loop';
export * from './scheduler'; export * from './scheduler';

@ -119,3 +119,12 @@ export function get_store_value<T>(store: Readable<T>): T | undefined {
store.subscribe(_ => value = _)(); store.subscribe(_ => value = _)();
return 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}"` : ``;
}

@ -28,7 +28,7 @@ export function exists(path) {
export function tryToLoadJson(file) { export function tryToLoadJson(file) {
try { try {
return JSON.parse(fs.readFileSync(file)); return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') throw err; if (err.code !== 'ENOENT') throw err;
return null; return null;
@ -44,14 +44,21 @@ export function tryToReadFile(file) {
} }
} }
export const virtualConsole = new jsdom.VirtualConsole(); const virtualConsole = new jsdom.VirtualConsole();
const { window } = new jsdom.JSDOM('<main></main>', {virtualConsole}); virtualConsole.sendTo(console);
global.window = new jsdom.JSDOM('<main></main>', {virtualConsole}).window;
global.document = window.document; global.document = window.document;
global.getComputedStyle = window.getComputedStyle; global.requestAnimationFrame = null; // placeholder, filled in using set_raf
global.navigator = {userAgent: 'fake'};
// add missing ecmascript globals to window
for (const key of Object.getOwnPropertyNames(global)) {
window[key] = window[key] || global[key];
}
export function env() { export function env() {
window._svelteTransitionManager = null; window._svelteTransitionManager = null;
window.document.title = '';
window.document.body.innerHTML = '<main></main>'; window.document.body.innerHTML = '<main></main>';
return window; return window;
@ -120,7 +127,7 @@ export function normalizeHtml(window, html) {
.replace(/<!--.*?-->/g, '') .replace(/<!--.*?-->/g, '')
.replace(/>[\s\r\n]+</g, '><') .replace(/>[\s\r\n]+</g, '><')
.trim(); .trim();
cleanChildren(node, ''); cleanChildren(node);
return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
} catch (err) { } catch (err) {
throw new Error(`Failed to normalize HTML:\n${html}`); throw new Error(`Failed to normalize HTML:\n${html}`);

@ -42,7 +42,7 @@ function create_fragment(ctx) {
if (changed.y && !scrolling) { if (changed.y && !scrolling) {
scrolling = true; scrolling = true;
clearTimeout(scrolling_timeout); clearTimeout(scrolling_timeout);
window.scrollTo(window.pageXOffset, ctx.y); scrollTo(window.pageXOffset, ctx.y);
scrolling_timeout = setTimeout(clear_scrolling, 100); scrolling_timeout = setTimeout(clear_scrolling, 100);
} }

@ -3,7 +3,7 @@ import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { rollup } from 'rollup'; import { rollup } from 'rollup';
import * as virtual from 'rollup-plugin-virtual'; import * as virtual from 'rollup-plugin-virtual';
import { clear_loops, set_now, set_raf } from "../../internal"; import { clear_loops, flush, set_now, set_raf } from "../../internal";
import { import {
showOutput, showOutput,
@ -20,7 +20,6 @@ let compileOptions = null;
let compile = null; let compile = null;
const sveltePath = process.cwd().split('\\').join('/'); const sveltePath = process.cwd().split('\\').join('/');
const internal = `${sveltePath}/internal`;
describe("runtime", () => { describe("runtime", () => {
before(() => { before(() => {
@ -47,8 +46,6 @@ describe("runtime", () => {
function runTest(dir, hydrate) { function runTest(dir, hydrate) {
if (dir[0] === ".") return; if (dir[0] === ".") return;
const { flush } = require(internal);
const config = loadConfig(`./runtime/samples/${dir}/_config.js`); const config = loadConfig(`./runtime/samples/${dir}/_config.js`);
if (hydrate && config.skip_if_hydrate) return; if (hydrate && config.skip_if_hydrate) return;
@ -66,7 +63,6 @@ describe("runtime", () => {
compile = (config.preserveIdentifiers ? svelte : svelte$).compile; compile = (config.preserveIdentifiers ? svelte : svelte$).compile;
const cwd = path.resolve(`test/runtime/samples/${dir}`); const cwd = path.resolve(`test/runtime/samples/${dir}`);
global.document.title = '';
compileOptions = config.compileOptions || {}; compileOptions = config.compileOptions || {};
compileOptions.sveltePath = sveltePath; compileOptions.sveltePath = sveltePath;
@ -119,8 +115,6 @@ describe("runtime", () => {
throw err; throw err;
} }
global.window = window;
if (config.before_test) config.before_test(); if (config.before_test) config.before_test();
// Put things we need on window for testing // Put things we need on window for testing
@ -221,6 +215,7 @@ describe("runtime", () => {
'main.js': js.code 'main.js': js.code
}), }),
{ {
name: 'svelte-packages',
resolveId: (importee, importer) => { resolveId: (importee, importer) => {
if (importee.startsWith('svelte/')) { if (importee.startsWith('svelte/')) {
return importee.replace('svelte', process.cwd()) + '/index.mjs'; return importee.replace('svelte', process.cwd()) + '/index.mjs';

@ -0,0 +1,40 @@
export default {
html: `
<editor contenteditable="true"><b>world</b></editor>
<p>hello <b>world</b></p>
`,
ssrHtml: `
<editor contenteditable="true"><b>world</b></editor>
<p>hello undefined</p>
`,
async test({ assert, component, target, window }) {
assert.equal(component.name, '<b>world</b>');
const el = target.querySelector('editor');
el.innerHTML = 'every<span>body</span>';
// No updates to data yet
assert.htmlEqual(target.innerHTML, `
<editor contenteditable="true">every<span>body</span></editor>
<p>hello <b>world</b></p>
`);
// Handle user input
const event = new window.Event('input');
await el.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, `
<editor contenteditable="true">every<span>body</span></editor>
<p>hello every<span>body</span></p>
`);
component.name = 'good<span>bye</span>';
assert.equal(el.innerHTML, 'good<span>bye</span>');
assert.htmlEqual(target.innerHTML, `
<editor contenteditable="true">good<span>bye</span></editor>
<p>hello good<span>bye</span></p>
`);
},
};

@ -0,0 +1,8 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:innerHTML={name}>
<b>world</b>
</editor>
<p>hello {@html name}</p>

@ -8,11 +8,6 @@ export default {
<p>hello <b>world</b></p> <p>hello <b>world</b></p>
`, `,
ssrHtml: `
<editor contenteditable="true"><b>world</b></editor>
<p>hello <b>world</b></p>
`,
async test({ assert, component, target, window }) { async test({ assert, component, target, window }) {
const el = target.querySelector('editor'); const el = target.querySelector('editor');
assert.equal(el.innerHTML, '<b>world</b>'); assert.equal(el.innerHTML, '<b>world</b>');

@ -0,0 +1,6 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:innerHTML={name}></editor>
<p>hello {@html name}</p>

@ -0,0 +1,34 @@
export default {
html: `
<editor contenteditable="true"><b>world</b></editor>
<p>hello world</p>
`,
ssrHtml: `
<editor contenteditable="true"><b>world</b></editor>
<p>hello undefined</p>
`,
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, `
<editor contenteditable="true">everybody</editor>
<p>hello everybody</p>
`);
component.name = 'goodbye';
assert.equal(el.textContent, 'goodbye');
assert.htmlEqual(target.innerHTML, `
<editor contenteditable="true">goodbye</editor>
<p>hello goodbye</p>
`);
},
};

@ -0,0 +1,8 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:textContent={name}>
<b>world</b>
</editor>
<p>hello {name}</p>

@ -8,11 +8,6 @@ export default {
<p>hello world</p> <p>hello world</p>
`, `,
ssrHtml: `
<editor contenteditable="true">world</editor>
<p>hello world</p>
`,
async test({ assert, component, target, window }) { async test({ assert, component, target, window }) {
const el = target.querySelector('editor'); const el = target.querySelector('editor');
assert.equal(el.textContent, 'world'); assert.equal(el.textContent, 'world');

@ -0,0 +1,6 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:textContent={name}></editor>
<p>hello {name}</p>

@ -1,6 +0,0 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:html={name}></editor>
<p>hello {@html name}</p>

@ -1,6 +0,0 @@
<script>
export let name;
</script>
<editor contenteditable="true" bind:text={name}></editor>
<p>hello {name}</p>

@ -0,0 +1,14 @@
export default {
preserveIdentifiers: true,
compileOptions: {
name: 'window'
},
html: `
<p>I hereby declare Svelte the bestest framework.</p>
<p>nintendo sixty four</p>
<p>Woops.</p>
<p>42</p>
<p>false</p>
`
};

@ -0,0 +1,20 @@
<script>
const document = 'I hereby declare Svelte the bestest framework.';
const console = 'nintendo sixty four';
const Error = 'Woops.';
const Object = 42;
const Map = false;
const everyone = [document, console, Error, Object, Map];
</script>
<svelte:head>
<title>Cute test</title>
</svelte:head>
<svelte:window on:click></svelte:window>
<svelte:body on:mouseenter></svelte:body>
{#each everyone as someone (someone)}
<p>{someone}</p>
{/each}

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

Loading…
Cancel
Save