diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index c37ea5020a..3e883200a7 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -610,7 +610,7 @@ export default class Element extends Node { } else if ( name === 'text' || name === 'html' - ){ + ) { const contenteditable = this.attributes.find( (attribute: Attribute) => attribute.name === 'contenteditable' ); @@ -626,7 +626,7 @@ export default class Element extends Node { message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding` }); } - } else if (name !== 'this') { + } else if (name !== 'this') { component.error(binding, { code: `invalid-binding`, message: `'${binding.name}' is not a valid binding` diff --git a/src/compiler/compile/render-dom/wrappers/Element/Binding.ts b/src/compiler/compile/render-dom/wrappers/Element/Binding.ts index 22f1f682e4..7de661e3d1 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 'text': + update_conditions.push(`${this.snippet} !== ${parent.var}.textContent`); + break; + + case 'html': + 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 === 'html' || this.node.name === 'text') { + 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};`; - } - if (binding.node.name === 'text') { return `${element.var}.textContent = ${binding.snippet};`; } @@ -334,14 +336,6 @@ function get_value_from_dom( return `this.innerHTML`; } - 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 c63ebf1693..527a101f6a 100644 --- a/src/compiler/compile/render-dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render-dom/wrappers/Element/index.ts @@ -33,12 +33,6 @@ const events = [ (name === 'text' || name === 'html') && node.attributes.some(attribute => attribute.name === 'contenteditable') }, - { - event_names: ['change'], - filter: (node: Element, name: string) => - (name === 'text' || name === 'html') && - node.attributes.some(attribute => attribute.name === 'contenteditable') - }, { event_names: ['change'], filter: (node: Element, _name: string) => @@ -514,7 +508,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 === 'text' || + binding.node.name === 'html' || + 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-ssr/handlers/Element.ts b/src/compiler/compile/render-ssr/handlers/Element.ts index 681e0d4c7b..fb9b935f9a 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' && @@ -151,16 +155,16 @@ 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) + node_contents = snip(expression); if (name == 'text') { - node_contents = '${@escape(' + snippet + ')}' + value = '@escape($$value)'; } else { // Do not escape HTML content - node_contents = '${' + snippet + '}' + 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 + ')}'; @@ -175,8 +179,14 @@ export default function(node: Element, renderer: Renderer, options: RenderOption 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/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..0b1f656a54 --- /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..1c05a0950d --- /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 89% rename from test/runtime/samples/contenteditable-html/_config.js rename to test/runtime/samples/binding-contenteditable-html/_config.js index cd2a822655..013fa30f39 100644 --- a/test/runtime/samples/contenteditable-html/_config.js +++ b/test/runtime/samples/binding-contenteditable-html/_config.js @@ -19,14 +19,14 @@ export default { el.innerHTML = 'everybody'; - // No updates to data yet + // No updates to data yet assert.htmlEqual(target.innerHTML, ` everybody

hello world

`); - // Handle user input - const event = new window.Event('input'); + // Handle user input + const event = new window.Event('input'); await el.dispatchEvent(event); assert.htmlEqual(target.innerHTML, ` everybody diff --git a/test/runtime/samples/contenteditable-html/main.svelte b/test/runtime/samples/binding-contenteditable-html/main.svelte similarity index 62% rename from test/runtime/samples/contenteditable-html/main.svelte rename to test/runtime/samples/binding-contenteditable-html/main.svelte index 53b4e81c88..09195ed558 100644 --- a/test/runtime/samples/contenteditable-html/main.svelte +++ b/test/runtime/samples/binding-contenteditable-html/main.svelte @@ -1,5 +1,5 @@ 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..7345687d29 --- /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..633d268f43 --- /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 100% rename from test/runtime/samples/contenteditable-text/_config.js rename to test/runtime/samples/binding-contenteditable-text/_config.js diff --git a/test/runtime/samples/contenteditable-text/main.svelte b/test/runtime/samples/binding-contenteditable-text/main.svelte similarity index 66% rename from test/runtime/samples/contenteditable-text/main.svelte rename to test/runtime/samples/binding-contenteditable-text/main.svelte index a71d9f0c5b..c47f5ee477 100644 --- a/test/runtime/samples/contenteditable-text/main.svelte +++ b/test/runtime/samples/binding-contenteditable-text/main.svelte @@ -1,5 +1,5 @@