From fad119e3ac2eae4dddec4c946bcf14ce118bdb92 Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Fri, 29 Mar 2019 16:42:25 -0600 Subject: [PATCH] Adds strict text updates `{@strict text}` will only update the text node if the text node's value is not equal. This is only needed (from what I can tell) in contenteditable situations where the user can update the text and the text must catch up. See https://v3.svelte.technology/repl?version=3.0.0-beta.22&gist=93c7a0c8399348d8e9a86964aef9f8fb for a bit more about this. --- src/compile/nodes/StrictMustacheTag.ts | 3 ++ src/compile/nodes/shared/map_children.ts | 2 + src/compile/render-dom/wrappers/Fragment.ts | 2 + .../render-dom/wrappers/StrictMustacheTag.ts | 28 +++++++++++ src/compile/render-dom/wrappers/shared/Tag.ts | 5 +- .../render-dom/wrappers/shared/Wrapper.ts | 3 +- src/compile/render-ssr/Renderer.ts | 1 + src/internal/dom.js | 4 ++ src/parse/state/mustache.ts | 15 ++++++ .../samples/strict-mustaches/_config.js | 49 +++++++++++++++++++ .../samples/strict-mustaches/main.svelte | 8 +++ 11 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/compile/nodes/StrictMustacheTag.ts create mode 100644 src/compile/render-dom/wrappers/StrictMustacheTag.ts create mode 100644 test/runtime/samples/strict-mustaches/_config.js create mode 100644 test/runtime/samples/strict-mustaches/main.svelte diff --git a/src/compile/nodes/StrictMustacheTag.ts b/src/compile/nodes/StrictMustacheTag.ts new file mode 100644 index 0000000000..8cbee08aa0 --- /dev/null +++ b/src/compile/nodes/StrictMustacheTag.ts @@ -0,0 +1,3 @@ +import Tag from './shared/Tag'; + +export default class StrictMustacheTag extends Tag {} \ No newline at end of file diff --git a/src/compile/nodes/shared/map_children.ts b/src/compile/nodes/shared/map_children.ts index b903853016..d674dfc1b9 100644 --- a/src/compile/nodes/shared/map_children.ts +++ b/src/compile/nodes/shared/map_children.ts @@ -9,6 +9,7 @@ import InlineComponent from '../InlineComponent'; import MustacheTag from '../MustacheTag'; import Options from '../Options'; import RawMustacheTag from '../RawMustacheTag'; +import StrictMustacheTag from '../StrictMustacheTag'; import DebugTag from '../DebugTag'; import Slot from '../Slot'; import Text from '../Text'; @@ -29,6 +30,7 @@ function get_constructor(type): typeof Node { case 'MustacheTag': return MustacheTag; case 'Options': return Options; case 'RawMustacheTag': return RawMustacheTag; + case 'StrictMustacheTag': return StrictMustacheTag; case 'DebugTag': return DebugTag; case 'Slot': return Slot; case 'Text': return Text; diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts index 8d955b6d01..809d3c37da 100644 --- a/src/compile/render-dom/wrappers/Fragment.ts +++ b/src/compile/render-dom/wrappers/Fragment.ts @@ -9,6 +9,7 @@ import IfBlock from './IfBlock'; import InlineComponent from './InlineComponent/index'; import MustacheTag from './MustacheTag'; import RawMustacheTag from './RawMustacheTag'; +import StrictMustacheTag from './StrictMustacheTag'; import Slot from './Slot'; import Text from './Text'; import Title from './Title'; @@ -32,6 +33,7 @@ const wrappers = { MustacheTag, Options: null, RawMustacheTag, + StrictMustacheTag, Slot, Text, Title, diff --git a/src/compile/render-dom/wrappers/StrictMustacheTag.ts b/src/compile/render-dom/wrappers/StrictMustacheTag.ts new file mode 100644 index 0000000000..7273dc56fc --- /dev/null +++ b/src/compile/render-dom/wrappers/StrictMustacheTag.ts @@ -0,0 +1,28 @@ +import Renderer from '../Renderer'; +import Block from '../Block'; +import Node from '../../nodes/shared/Node'; +import Tag from './shared/Tag'; +import Wrapper from './shared/Wrapper'; + +export default class StrictMustacheTagWrapper extends Tag { + var = 't'; + + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { + super(renderer, block, parent, node); + this.cannot_use_innerhtml(); + } + + render(block: Block, parent_node: string, parent_nodes: string) { + const { init } = this.rename_this_method( + block, + value => `@set_data_strict(${this.var}, ${value});` + ); + + block.add_element( + this.var, + `@text(${init})`, + parent_nodes && `@claim_text(${parent_nodes}, ${init})`, + parent_node + ); + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/shared/Tag.ts b/src/compile/render-dom/wrappers/shared/Tag.ts index 27e5f1f03f..89c3efe22f 100644 --- a/src/compile/render-dom/wrappers/shared/Tag.ts +++ b/src/compile/render-dom/wrappers/shared/Tag.ts @@ -3,11 +3,12 @@ import Renderer from '../../Renderer'; import Block from '../../Block'; import MustacheTag from '../../../nodes/MustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag'; +import StrictMustacheTag from '../../../nodes/StrictMustacheTag'; export default class Tag extends Wrapper { - node: MustacheTag | RawMustacheTag; + node: MustacheTag | RawMustacheTag | StrictMustacheTag; - constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag | StrictMustacheTag) { super(renderer, block, parent, node); this.cannot_use_innerhtml(); diff --git a/src/compile/render-dom/wrappers/shared/Wrapper.ts b/src/compile/render-dom/wrappers/shared/Wrapper.ts index 2a9394415f..c091ce38cc 100644 --- a/src/compile/render-dom/wrappers/shared/Wrapper.ts +++ b/src/compile/render-dom/wrappers/shared/Wrapper.ts @@ -72,7 +72,8 @@ export default class Wrapper { return ( this.node.type === 'Element' || this.node.type === 'Text' || - this.node.type === 'MustacheTag' + this.node.type === 'MustacheTag' || + this.node.type === 'StrictMustacheTag' ); } } \ No newline at end of file diff --git a/src/compile/render-ssr/Renderer.ts b/src/compile/render-ssr/Renderer.ts index 73f89dba22..bcbfc4178f 100644 --- a/src/compile/render-ssr/Renderer.ts +++ b/src/compile/render-ssr/Renderer.ts @@ -30,6 +30,7 @@ const handlers: Record = { MustacheTag: Tag, // TODO MustacheTag is an anachronism Options: noop, RawMustacheTag: HtmlTag, + StrictMustacheTag: Tag, Slot, Text, Title, diff --git a/src/internal/dom.js b/src/internal/dom.js index 2d661b5958..ed61e73dd5 100644 --- a/src/internal/dom.js +++ b/src/internal/dom.js @@ -157,6 +157,10 @@ export function set_data(text, data) { text.data = '' + data; } +export function set_data_strict(text, data) { + if (text.data !== '' + data) text.data = '' + data; +} + export function set_input_type(input, type) { try { input.type = type; diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 1acae36c9c..ac24b3a3db 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -329,6 +329,21 @@ export default function mustache(parser: Parser) { type: 'RawMustacheTag', expression, }); + } else if (parser.eat('@strict')) { + // {@equal content} tag + parser.require_whitespace(); + + const expression = read_expression(parser); + + parser.allow_whitespace(); + parser.eat('}', true); + + parser.current().children.push({ + start, + end: parser.index, + type: 'StrictMustacheTag', + expression, + }); } else if (parser.eat('@debug')) { let identifiers; diff --git a/test/runtime/samples/strict-mustaches/_config.js b/test/runtime/samples/strict-mustaches/_config.js new file mode 100644 index 0000000000..ae6c8f6f66 --- /dev/null +++ b/test/runtime/samples/strict-mustaches/_config.js @@ -0,0 +1,49 @@ +export default { + skip_if_ssr: true, + + props: { + text: 'test' + }, + + html: '
beforetestafter
', + + test({ assert, component, target }) { + const text = component.container.childNodes[1]; + text.data += 'ing'; + + assert.equal(target.innerHTML, '
beforetestingafter
'); + + // Track when .data is set on text + let get, set, proto = text.__proto__; + while (proto) { + const descriptor = Object.getOwnPropertyDescriptor(proto, 'data'); + if (descriptor) { + get = descriptor.get; + set = descriptor.set; + break; + } else { + proto = proto.__proto__; + } + } + if (!get || !set) throw new Error('Could not get the getter/setter for data'); + let setValue; + + Object.defineProperty(text, 'data', { + get, + set(value) { + console.log('SETTING VALUE:', value); + setValue = value; + set.call(this, value); + } + }); + + component.text += 'ing'; + assert.equal(setValue, undefined); + + component.text += '!'; + assert.equal(setValue, 'testing!'); + + component.$destroy(); + assert.equal(target.innerHTML, ''); + } +}; diff --git a/test/runtime/samples/strict-mustaches/main.svelte b/test/runtime/samples/strict-mustaches/main.svelte new file mode 100644 index 0000000000..c959ddf74c --- /dev/null +++ b/test/runtime/samples/strict-mustaches/main.svelte @@ -0,0 +1,8 @@ + + +
+ before{@strict text}after +