|
|
@ -7,8 +7,9 @@ import { b, x } from 'code-red';
|
|
|
|
import Expression from '../../../nodes/shared/Expression';
|
|
|
|
import Expression from '../../../nodes/shared/Expression';
|
|
|
|
import Text from '../../../nodes/Text';
|
|
|
|
import Text from '../../../nodes/Text';
|
|
|
|
import handle_select_value_binding from './handle_select_value_binding';
|
|
|
|
import handle_select_value_binding from './handle_select_value_binding';
|
|
|
|
|
|
|
|
import { Identifier, Node } from 'estree';
|
|
|
|
|
|
|
|
|
|
|
|
export default class AttributeWrapper {
|
|
|
|
export class BaseAttributeWrapper {
|
|
|
|
node: Attribute;
|
|
|
|
node: Attribute;
|
|
|
|
parent: ElementWrapper;
|
|
|
|
parent: ElementWrapper;
|
|
|
|
|
|
|
|
|
|
|
@ -21,7 +22,29 @@ export default class AttributeWrapper {
|
|
|
|
parent.not_static_content();
|
|
|
|
parent.not_static_content();
|
|
|
|
|
|
|
|
|
|
|
|
block.add_dependencies(node.dependencies);
|
|
|
|
block.add_dependencies(node.dependencies);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render(_block: Block) {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
|
|
|
node: Attribute;
|
|
|
|
|
|
|
|
parent: ElementWrapper;
|
|
|
|
|
|
|
|
metadata: any;
|
|
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
property_name: string;
|
|
|
|
|
|
|
|
is_indirectly_bound_value: boolean;
|
|
|
|
|
|
|
|
is_src: boolean;
|
|
|
|
|
|
|
|
is_select_value_attribute: boolean;
|
|
|
|
|
|
|
|
is_input_value: boolean;
|
|
|
|
|
|
|
|
should_cache: boolean;
|
|
|
|
|
|
|
|
last: Identifier;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(parent: ElementWrapper, block: Block, node: Attribute) {
|
|
|
|
|
|
|
|
super(parent, block, node);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (node.dependencies.size > 0) {
|
|
|
|
// special case — <option value={foo}> — see below
|
|
|
|
// special case — <option value={foo}> — see below
|
|
|
|
if (this.parent.node.name === 'option' && node.name === 'value') {
|
|
|
|
if (this.parent.node.name === 'option' && node.name === 'value') {
|
|
|
|
let select: ElementWrapper = this.parent;
|
|
|
|
let select: ElementWrapper = this.parent;
|
|
|
@ -42,31 +65,22 @@ export default class AttributeWrapper {
|
|
|
|
handle_select_value_binding(this, node.dependencies);
|
|
|
|
handle_select_value_binding(this, node.dependencies);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is_indirectly_bound_value() {
|
|
|
|
this.name = fix_attribute_casing(this.node.name);
|
|
|
|
const element = this.parent;
|
|
|
|
this.metadata = this.get_metadata();
|
|
|
|
const name = fix_attribute_casing(this.node.name);
|
|
|
|
this.is_indirectly_bound_value = is_indirectly_bound_value(this);
|
|
|
|
return name === 'value' &&
|
|
|
|
this.property_name = this.is_indirectly_bound_value
|
|
|
|
(element.node.name === 'option' || // TODO check it's actually bound
|
|
|
|
? '__value'
|
|
|
|
(element.node.name === 'input' &&
|
|
|
|
: this.metadata && this.metadata.property_name;
|
|
|
|
element.node.bindings.some(
|
|
|
|
this.is_src = this.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
|
|
|
|
(binding) =>
|
|
|
|
this.is_select_value_attribute = this.name === 'value' && this.parent.node.name === 'select';
|
|
|
|
/checked|group/.test(binding.name)
|
|
|
|
this.is_input_value = this.name === 'value' && this.parent.node.name === 'input';
|
|
|
|
)));
|
|
|
|
this.should_cache = should_cache(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
render(block: Block) {
|
|
|
|
render(block: Block) {
|
|
|
|
const element = this.parent;
|
|
|
|
const element = this.parent;
|
|
|
|
const name = fix_attribute_casing(this.node.name);
|
|
|
|
const { name, property_name, should_cache, is_indirectly_bound_value } = this;
|
|
|
|
|
|
|
|
|
|
|
|
const metadata = this.get_metadata();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const is_indirectly_bound_value = this.is_indirectly_bound_value();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const property_name = is_indirectly_bound_value
|
|
|
|
|
|
|
|
? '__value'
|
|
|
|
|
|
|
|
: metadata && metadata.property_name;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// xlink is a special case... we could maybe extend this to generic
|
|
|
|
// xlink is a special case... we could maybe extend this to generic
|
|
|
|
// namespaced attributes but I'm not sure that's applicable in
|
|
|
|
// namespaced attributes but I'm not sure that's applicable in
|
|
|
@ -82,29 +96,15 @@ export default class AttributeWrapper {
|
|
|
|
const dependencies = this.get_dependencies();
|
|
|
|
const dependencies = this.get_dependencies();
|
|
|
|
const value = this.get_value(block);
|
|
|
|
const value = this.get_value(block);
|
|
|
|
|
|
|
|
|
|
|
|
const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
|
|
|
|
|
|
|
|
const is_select_value_attribute =
|
|
|
|
|
|
|
|
name === 'value' && element.node.name === 'select';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const is_input_value = name === 'value' && element.node.name === 'input';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const should_cache = is_src || this.node.should_cache();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const last = should_cache && block.get_unique_name(
|
|
|
|
|
|
|
|
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (should_cache) block.add_variable(last);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let updater;
|
|
|
|
let updater;
|
|
|
|
const init = should_cache ? x`${last} = ${value}` : value;
|
|
|
|
const init = this.get_init(block, value);
|
|
|
|
|
|
|
|
|
|
|
|
if (is_legacy_input_type) {
|
|
|
|
if (is_legacy_input_type) {
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
b`@set_input_type(${element.var}, ${init});`
|
|
|
|
b`@set_input_type(${element.var}, ${init});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
|
|
|
|
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
|
|
|
|
} else if (is_select_value_attribute) {
|
|
|
|
} else if (this.is_select_value_attribute) {
|
|
|
|
// annoying special case
|
|
|
|
// annoying special case
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
|
|
|
|
|
|
|
@ -117,45 +117,37 @@ export default class AttributeWrapper {
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
${updater}
|
|
|
|
${updater}
|
|
|
|
`);
|
|
|
|
`);
|
|
|
|
} else if (is_src) {
|
|
|
|
} else if (this.is_src) {
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
|
|
|
|
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
} else if (property_name) {
|
|
|
|
} else if (property_name) {
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
b`${element.var}.${property_name} = ${init};`
|
|
|
|
b`${element.var}.${property_name} = ${init};`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
updater = block.renderer.options.dev
|
|
|
|
updater = block.renderer.options.dev
|
|
|
|
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
|
|
|
|
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
b`${method}(${element.var}, "${name}", ${init});`
|
|
|
|
b`${method}(${element.var}, "${name}", ${init});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
|
let condition = block.renderer.dirty(dependencies);
|
|
|
|
const update_value = b`${element.var}.value = ${element.var}.__value;`;
|
|
|
|
|
|
|
|
block.chunks.hydrate.push(update_value);
|
|
|
|
if (should_cache) {
|
|
|
|
|
|
|
|
condition = is_src
|
|
|
|
|
|
|
|
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
|
|
|
|
|
|
|
|
: x`${condition} && (${last} !== (${last} = ${value}))`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (is_input_value) {
|
|
|
|
|
|
|
|
const type = element.node.get_static_attribute_value('type');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
|
|
|
|
updater = b`
|
|
|
|
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
|
|
|
|
${updater}
|
|
|
|
}
|
|
|
|
${update_value};
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (block.has_outros) {
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
condition = x`!#current || ${condition}`;
|
|
|
|
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.chunks.update.push(b`
|
|
|
|
block.chunks.update.push(b`
|
|
|
|
if (${condition}) {
|
|
|
|
if (${condition}) {
|
|
|
@ -167,13 +159,44 @@ export default class AttributeWrapper {
|
|
|
|
if (this.node.is_true && name === 'autofocus') {
|
|
|
|
if (this.node.is_true && name === 'autofocus') {
|
|
|
|
block.autofocus = element.var;
|
|
|
|
block.autofocus = element.var;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
|
get_init(block: Block, value) {
|
|
|
|
const update_value = b`${element.var}.value = ${element.var}.__value;`;
|
|
|
|
this.last = this.should_cache && block.get_unique_name(
|
|
|
|
|
|
|
|
`${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
block.chunks.hydrate.push(update_value);
|
|
|
|
if (this.should_cache) block.add_variable(this.last);
|
|
|
|
if (dependencies.length > 0) block.chunks.update.push(update_value);
|
|
|
|
|
|
|
|
|
|
|
|
return this.should_cache ? x`${this.last} = ${value}` : value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
get_dom_update_conditions(block: Block, dependency_condition: Node) {
|
|
|
|
|
|
|
|
const { property_name, should_cache, last } = this;
|
|
|
|
|
|
|
|
const element = this.parent;
|
|
|
|
|
|
|
|
const value = this.get_value(block);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let condition = dependency_condition;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (should_cache) {
|
|
|
|
|
|
|
|
condition = this.is_src
|
|
|
|
|
|
|
|
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
|
|
|
|
|
|
|
|
: x`${condition} && (${last} !== (${last} = ${value}))`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.is_input_value) {
|
|
|
|
|
|
|
|
const type = element.node.get_static_attribute_value('type');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
|
|
|
|
|
|
|
|
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (block.has_outros) {
|
|
|
|
|
|
|
|
condition = x`!#current || ${condition}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return condition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get_dependencies() {
|
|
|
|
get_dependencies() {
|
|
|
@ -194,15 +217,14 @@ export default class AttributeWrapper {
|
|
|
|
|
|
|
|
|
|
|
|
get_metadata() {
|
|
|
|
get_metadata() {
|
|
|
|
if (this.parent.node.namespace) return null;
|
|
|
|
if (this.parent.node.namespace) return null;
|
|
|
|
const metadata = attribute_lookup[fix_attribute_casing(this.node.name)];
|
|
|
|
const metadata = attribute_lookup[this.name];
|
|
|
|
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
|
|
|
|
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
|
|
|
|
return metadata;
|
|
|
|
return metadata;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get_value(block) {
|
|
|
|
get_value(block) {
|
|
|
|
if (this.node.is_true) {
|
|
|
|
if (this.node.is_true) {
|
|
|
|
const metadata = this.get_metadata();
|
|
|
|
if (this.metadata && boolean_attribute.has(this.metadata.property_name.toLowerCase())) {
|
|
|
|
if (metadata && boolean_attribute.has(metadata.property_name.toLowerCase())) {
|
|
|
|
|
|
|
|
return x`true`;
|
|
|
|
return x`true`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return x`""`;
|
|
|
|
return x`""`;
|
|
|
@ -350,4 +372,19 @@ const boolean_attribute = new Set([
|
|
|
|
'required',
|
|
|
|
'required',
|
|
|
|
'reversed',
|
|
|
|
'reversed',
|
|
|
|
'selected'
|
|
|
|
'selected'
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function should_cache(attribute: AttributeWrapper) {
|
|
|
|
|
|
|
|
return attribute.is_src || attribute.node.should_cache();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function is_indirectly_bound_value(attribute: AttributeWrapper) {
|
|
|
|
|
|
|
|
const element = attribute.parent;
|
|
|
|
|
|
|
|
return attribute.name === 'value' &&
|
|
|
|
|
|
|
|
(element.node.name === 'option' || // TODO check it's actually bound
|
|
|
|
|
|
|
|
(element.node.name === 'input' &&
|
|
|
|
|
|
|
|
element.node.bindings.some(
|
|
|
|
|
|
|
|
(binding) =>
|
|
|
|
|
|
|
|
/checked|group/.test(binding.name)
|
|
|
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
}
|