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.
pull/2341/head
Jacob Wright 7 years ago
parent a824a6dd6f
commit fad119e3ac

@ -0,0 +1,3 @@
import Tag from './shared/Tag';
export default class StrictMustacheTag extends Tag {}

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

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

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

@ -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();

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

@ -30,6 +30,7 @@ const handlers: Record<string, Handler> = {
MustacheTag: Tag, // TODO MustacheTag is an anachronism
Options: noop,
RawMustacheTag: HtmlTag,
StrictMustacheTag: Tag,
Slot,
Text,
Title,

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

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

@ -0,0 +1,49 @@
export default {
skip_if_ssr: true,
props: {
text: 'test'
},
html: '<div>beforetestafter</div>',
test({ assert, component, target }) {
const text = component.container.childNodes[1];
text.data += 'ing';
assert.equal(target.innerHTML, '<div>beforetestingafter</div>');
// 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, '');
}
};

@ -0,0 +1,8 @@
<script>
export let text;
export let container = null;
</script>
<div bind:this={container}>
before{@strict text}after
</div>
Loading…
Cancel
Save