initialise text/html bindings if necessary

pull/2996/head
Richard Harris 6 years ago
parent 8bff4517b5
commit e3de705cb8

@ -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`

@ -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}`;
}

@ -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});`

@ -53,7 +53,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
slot_scopes: Map<any, any>;
}) {
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);
}

@ -0,0 +1,40 @@
export default {
html: `
<editor><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>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>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>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:html={name}>
<b>world</b>
</editor>
<p>hello {@html name}</p>

@ -19,14 +19,14 @@ export default {
el.innerHTML = 'every<span>body</span>';
// No updates to data yet
// No updates to data yet
assert.htmlEqual(target.innerHTML, `
<editor>every<span>body</span></editor>
<p>hello <b>world</b></p>
`);
// 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, `
<editor>every<span>body</span></editor>

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

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

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

@ -1,5 +1,5 @@
<script>
export let name;
export let name;
</script>
<editor contenteditable="true" bind:text={name}></editor>
Loading…
Cancel
Save