Skip to content

Commit 3e930a0

Browse files
committed
spread condition for input element
1 parent 1c05785 commit 3e930a0

File tree

8 files changed

+241
-84
lines changed

8 files changed

+241
-84
lines changed

src/compiler/compile/render_dom/wrappers/Element/Attribute.ts

Lines changed: 110 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { string_literal } from '../../../utils/stringify';
66
import { b, x } from 'code-red';
77
import Expression from '../../../nodes/shared/Expression';
88
import Text from '../../../nodes/Text';
9+
import { Identifier, Node } from 'estree';
910

10-
export default class AttributeWrapper {
11+
export class BaseAttributeWrapper {
1112
node: Attribute;
1213
parent: ElementWrapper;
1314

@@ -20,7 +21,29 @@ export default class AttributeWrapper {
2021
parent.not_static_content();
2122

2223
block.add_dependencies(node.dependencies);
24+
}
25+
}
26+
27+
render(_block: Block) {}
28+
}
2329

30+
export default class AttributeWrapper extends BaseAttributeWrapper {
31+
node: Attribute;
32+
parent: ElementWrapper;
33+
metadata: any;
34+
name: string;
35+
property_name: string;
36+
is_indirectly_bound_value: boolean;
37+
is_src: boolean;
38+
is_select_value_attribute: boolean;
39+
is_input_value: boolean;
40+
should_cache: boolean;
41+
last: Identifier;
42+
43+
constructor(parent: ElementWrapper, block: Block, node: Attribute) {
44+
super(parent, block, node);
45+
46+
if (node.dependencies.size > 0) {
2447
// special case — <option value={foo}> — see below
2548
if (this.parent.node.name === 'option' && node.name === 'value') {
2649
let select: ElementWrapper = this.parent;
@@ -37,31 +60,22 @@ export default class AttributeWrapper {
3760
}
3861
}
3962
}
40-
}
4163

42-
is_indirectly_bound_value() {
43-
const element = this.parent;
44-
const name = fix_attribute_casing(this.node.name);
45-
return name === 'value' &&
46-
(element.node.name === 'option' || // TODO check it's actually bound
47-
(element.node.name === 'input' &&
48-
element.node.bindings.some(
49-
(binding) =>
50-
/checked|group/.test(binding.name)
51-
)));
64+
this.name = fix_attribute_casing(this.node.name);
65+
this.metadata = this.get_metadata();
66+
this.is_indirectly_bound_value = is_indirectly_bound_value(this);
67+
this.property_name = this.is_indirectly_bound_value
68+
? '__value'
69+
: this.metadata && this.metadata.property_name;
70+
this.is_src = this.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
71+
this.is_select_value_attribute = this.name === 'value' && this.parent.node.name === 'select';
72+
this.is_input_value = this.name === 'value' && this.parent.node.name === 'input';
73+
this.should_cache = should_cache(this);
5274
}
5375

5476
render(block: Block) {
5577
const element = this.parent;
56-
const name = fix_attribute_casing(this.node.name);
57-
58-
const metadata = this.get_metadata();
59-
60-
const is_indirectly_bound_value = this.is_indirectly_bound_value();
61-
62-
const property_name = is_indirectly_bound_value
63-
? '__value'
64-
: metadata && metadata.property_name;
78+
const { name, property_name, should_cache, is_indirectly_bound_value } = this;
6579

6680
// xlink is a special case... we could maybe extend this to generic
6781
// namespaced attributes but I'm not sure that's applicable in
@@ -77,81 +91,59 @@ export default class AttributeWrapper {
7791
const dependencies = this.node.get_dependencies();
7892
const value = this.get_value(block);
7993

80-
const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
81-
const is_select_value_attribute =
82-
name === 'value' && element.node.name === 'select';
83-
84-
const is_input_value = name === 'value' && element.node.name === 'input';
85-
86-
const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
87-
88-
const last = should_cache && block.get_unique_name(
89-
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
90-
);
91-
92-
if (should_cache) block.add_variable(last);
93-
9494
let updater;
95-
const init = should_cache ? x`${last} = ${value}` : value;
95+
const init = this.get_init(block, value);
9696

9797
if (is_legacy_input_type) {
9898
block.chunks.hydrate.push(
9999
b`@set_input_type(${element.var}, ${init});`
100100
);
101-
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
102-
} else if (is_select_value_attribute) {
101+
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
102+
} else if (this.is_select_value_attribute) {
103103
// annoying special case
104104
const is_multiple_select = element.node.get_static_attribute_value('multiple');
105105

106106
if (is_multiple_select) {
107-
updater = b`@select_options(${element.var}, ${last});`;
107+
updater = b`@select_options(${element.var}, ${this.last});`;
108108
} else {
109-
updater = b`@select_option(${element.var}, ${last});`;
109+
updater = b`@select_option(${element.var}, ${this.last});`;
110110
}
111111

112112
block.chunks.mount.push(b`
113-
${last} = ${value};
113+
${this.last} = ${value};
114114
${updater}
115115
`);
116-
} else if (is_src) {
116+
} else if (this.is_src) {
117117
block.chunks.hydrate.push(
118-
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
118+
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});`
119119
);
120-
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
120+
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
121121
} else if (property_name) {
122122
block.chunks.hydrate.push(
123123
b`${element.var}.${property_name} = ${init};`
124124
);
125125
updater = block.renderer.options.dev
126-
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
127-
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
126+
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
127+
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
128128
} else {
129129
block.chunks.hydrate.push(
130130
b`${method}(${element.var}, "${name}", ${init});`
131131
);
132-
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
132+
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
133133
}
134134

135-
if (dependencies.length > 0) {
136-
let condition = block.renderer.dirty(dependencies);
137-
138-
if (should_cache) {
139-
condition = is_src
140-
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
141-
: x`${condition} && (${last} !== (${last} = ${value}))`;
142-
}
143-
144-
if (is_input_value) {
145-
const type = element.node.get_static_attribute_value('type');
135+
if (is_indirectly_bound_value) {
136+
const update_value = b`${element.var}.value = ${element.var}.__value;`;
137+
block.chunks.hydrate.push(update_value);
146138

147-
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
148-
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
149-
}
150-
}
139+
updater = b`
140+
${updater}
141+
${update_value};
142+
`;
143+
}
151144

152-
if (block.has_outros) {
153-
condition = x`!#current || ${condition}`;
154-
}
145+
if (dependencies.length > 0) {
146+
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
155147

156148
block.chunks.update.push(b`
157149
if (${condition}) {
@@ -163,26 +155,56 @@ export default class AttributeWrapper {
163155
if (this.node.is_true && name === 'autofocus') {
164156
block.autofocus = element.var;
165157
}
158+
}
166159

167-
if (is_indirectly_bound_value) {
168-
const update_value = b`${element.var}.value = ${element.var}.__value;`;
160+
get_init(block: Block, value: Node) {
161+
this.last = this.should_cache && block.get_unique_name(
162+
`${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value`
163+
);
169164

170-
block.chunks.hydrate.push(update_value);
171-
if (this.node.get_dependencies().length > 0) block.chunks.update.push(update_value);
165+
if (this.should_cache) block.add_variable(this.last);
166+
167+
return this.should_cache ? x`${this.last} = ${value}` : value;
168+
}
169+
170+
get_dom_update_conditions(block: Block, dependency_condition: Node) {
171+
const { property_name, should_cache, last } = this;
172+
const element = this.parent;
173+
const value = this.get_value(block);
174+
175+
let condition = dependency_condition;
176+
177+
if (should_cache) {
178+
condition = this.is_src
179+
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
180+
: x`${condition} && (${last} !== (${last} = ${value}))`;
181+
}
182+
183+
if (this.is_input_value) {
184+
const type = element.node.get_static_attribute_value('type');
185+
186+
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
187+
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
188+
}
189+
}
190+
191+
if (block.has_outros) {
192+
condition = x`!#current || ${condition}`;
172193
}
194+
195+
return condition;
173196
}
174197

175198
get_metadata() {
176199
if (this.parent.node.namespace) return null;
177-
const metadata = attribute_lookup[fix_attribute_casing(this.node.name)];
200+
const metadata = attribute_lookup[this.name];
178201
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
179202
return metadata;
180203
}
181204

182205
get_value(block) {
183206
if (this.node.is_true) {
184-
const metadata = this.get_metadata();
185-
if (metadata && boolean_attribute.has(metadata.property_name.toLowerCase())) {
207+
if (this.metadata && boolean_attribute.has(this.metadata.property_name.toLowerCase())) {
186208
return x`true`;
187209
}
188210
return x`""`;
@@ -330,4 +352,19 @@ const boolean_attribute = new Set([
330352
'required',
331353
'reversed',
332354
'selected'
333-
]);
355+
]);
356+
357+
function should_cache(attribute: AttributeWrapper) {
358+
return attribute.is_src || attribute.node.should_cache() || attribute.is_select_value_attribute; // TODO is this necessary?
359+
}
360+
361+
function is_indirectly_bound_value(attribute: AttributeWrapper) {
362+
const element = attribute.parent;
363+
return attribute.name === 'value' &&
364+
(element.node.name === 'option' || // TODO check it's actually bound
365+
(element.node.name === 'input' &&
366+
element.node.bindings.some(
367+
(binding) =>
368+
/checked|group/.test(binding.name)
369+
)));
370+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { BaseAttributeWrapper } from "./Attribute";
2+
3+
export default class SpreadAttributeWrapper extends BaseAttributeWrapper {}

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { b, x, p } from 'code-red';
1212
import { namespaces } from '../../../../utils/namespaces';
1313
import AttributeWrapper from './Attribute';
1414
import StyleAttributeWrapper from './StyleAttribute';
15+
import SpreadAttributeWrapper from './SpreadAttribute';
1516
import { dimensions } from '../../../../utils/patterns';
1617
import Binding from './Binding';
1718
import InlineComponentWrapper from '../InlineComponent';
@@ -136,7 +137,7 @@ const events = [
136137
export default class ElementWrapper extends Wrapper {
137138
node: Element;
138139
fragment: FragmentWrapper;
139-
attributes: AttributeWrapper[];
140+
attributes: Array<AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper>;
140141
bindings: Binding[];
141142
event_handlers: EventHandler[];
142143
class_dependencies: string[];
@@ -220,6 +221,9 @@ export default class ElementWrapper extends Wrapper {
220221
if (attribute.name === 'style') {
221222
return new StyleAttributeWrapper(this, block, attribute);
222223
}
224+
if (attribute.type === 'Spread') {
225+
return new SpreadAttributeWrapper(this, block, attribute);
226+
}
223227
return new AttributeWrapper(this, block, attribute);
224228
});
225229

@@ -664,25 +668,24 @@ export default class ElementWrapper extends Wrapper {
664668

665669
this.attributes
666670
.forEach(attr => {
667-
const condition = attr.node.dependencies.size > 0
668-
? block.renderer.dirty(Array.from(attr.node.dependencies))
671+
const dependencies = attr.node.get_dependencies();
672+
673+
const condition = dependencies.length > 0
674+
? block.renderer.dirty(dependencies)
669675
: null;
670676

671-
if (attr.node.is_spread) {
677+
if (attr instanceof SpreadAttributeWrapper) {
672678
const snippet = attr.node.expression.manipulate(block);
673679

674680
initial_props.push(snippet);
675681

676682
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
677683
} else {
678-
const metadata = attr.get_metadata();
679-
const name = attr.is_indirectly_bound_value()
680-
? '__value'
681-
: (metadata && metadata.property_name) || fix_attribute_casing(attr.node.name);
682-
const snippet = x`{ ${name}: ${attr.get_value(block)} }`;
683-
initial_props.push(snippet);
684+
const name = attr.property_name || attr.name;
685+
initial_props.push(x`{ ${name}: ${attr.get_init(block, attr.get_value(block))} }`);
686+
const snippet = x`{ ${name}: ${attr.should_cache ? attr.last : attr.get_value(block)} }`;
684687

685-
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
688+
updates.push(condition ? x`${attr.get_dom_update_conditions(block, condition)} && ${snippet}` : snippet);
686689
}
687690
});
688691

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
import { omit } from './utils.js';
3+
4+
export let value;
5+
6+
function onInput(e) {
7+
value = e.target.value;
8+
}
9+
10+
$: props = omit($$props, 'value');
11+
</script>
12+
13+
<input
14+
type="text"
15+
{...props}
16+
on:input={onInput}
17+
{value}
18+
/>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
import { omit } from './utils.js';
3+
4+
export let value;
5+
6+
function onInput(e) {
7+
value = e.target.value;
8+
}
9+
10+
$: props = omit($$props, 'value', 'minlength');
11+
</script>
12+
13+
<input
14+
type="text"
15+
minlength="10"
16+
value={value}
17+
{...props}
18+
on:input={onInput}
19+
/>

0 commit comments

Comments
 (0)