|
|
|
|
@ -21,7 +21,6 @@ const non_textlike_input_types = new Set([
|
|
|
|
|
]);
|
|
|
|
|
/** */
|
|
|
|
|
export class BaseAttributeWrapper {
|
|
|
|
|
|
|
|
|
|
/** @type {import('../../../nodes/Attribute.js').default} */
|
|
|
|
|
node;
|
|
|
|
|
|
|
|
|
|
@ -36,14 +35,13 @@ export class BaseAttributeWrapper {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @param {import('../../Block.js').default} _block */
|
|
|
|
|
render(_block) { }
|
|
|
|
|
render(_block) {}
|
|
|
|
|
}
|
|
|
|
|
const regex_minus_sign = /-/;
|
|
|
|
|
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
|
|
|
|
|
|
|
|
|
|
/** @extends BaseAttributeWrapper */
|
|
|
|
|
export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
|
|
|
|
|
/** @type {import('../../../nodes/Attribute.js').default} */
|
|
|
|
|
node;
|
|
|
|
|
|
|
|
|
|
@ -107,8 +105,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
this.property_name = null;
|
|
|
|
|
this.is_select_value_attribute = false;
|
|
|
|
|
this.is_input_value = false;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
} else {
|
|
|
|
|
this.name = fix_attribute_casing(this.node.name);
|
|
|
|
|
this.metadata = this.get_metadata();
|
|
|
|
|
this.is_indirectly_bound_value = is_indirectly_bound_value(this);
|
|
|
|
|
@ -137,7 +134,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
: name.slice(0, 6) === 'xlink:'
|
|
|
|
|
? '@xlink_attr'
|
|
|
|
|
: '@attr';
|
|
|
|
|
const is_legacy_input_type = element.renderer.component.compile_options.legacy &&
|
|
|
|
|
const is_legacy_input_type =
|
|
|
|
|
element.renderer.component.compile_options.legacy &&
|
|
|
|
|
name === 'type' &&
|
|
|
|
|
this.parent.node.name === 'input';
|
|
|
|
|
const dependencies = this.get_dependencies();
|
|
|
|
|
@ -147,40 +145,37 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
let updater;
|
|
|
|
|
const init = this.get_init(block, value);
|
|
|
|
|
if (is_legacy_input_type) {
|
|
|
|
|
block.chunks.hydrate.push(b `@set_input_type(${element.var}, ${init});`);
|
|
|
|
|
updater = b `@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
|
|
|
|
|
}
|
|
|
|
|
else if (this.is_select_value_attribute) {
|
|
|
|
|
block.chunks.hydrate.push(b`@set_input_type(${element.var}, ${init});`);
|
|
|
|
|
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
|
|
|
|
|
} else if (this.is_select_value_attribute) {
|
|
|
|
|
// annoying special case
|
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
|
if (is_multiple_select) {
|
|
|
|
|
updater = b `@select_options(${element.var}, ${value});`;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
updater = b `@select_option(${element.var}, ${value});`;
|
|
|
|
|
updater = b`@select_options(${element.var}, ${value});`;
|
|
|
|
|
} else {
|
|
|
|
|
updater = b`@select_option(${element.var}, ${value});`;
|
|
|
|
|
}
|
|
|
|
|
block.chunks.mount.push(b `
|
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
|
${updater}
|
|
|
|
|
`);
|
|
|
|
|
}
|
|
|
|
|
else if (this.is_src) {
|
|
|
|
|
block.chunks.hydrate.push(b `if (!@src_url_equal(${element.var}.src, ${init})) ${method}(${element.var}, "${name}", ${this.last});`);
|
|
|
|
|
updater = b `${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
|
}
|
|
|
|
|
else if (property_name) {
|
|
|
|
|
block.chunks.hydrate.push(b `${element.var}.${property_name} = ${init};`);
|
|
|
|
|
} else if (this.is_src) {
|
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
|
b`if (!@src_url_equal(${element.var}.src, ${init})) ${method}(${element.var}, "${name}", ${this.last});`
|
|
|
|
|
);
|
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
|
} else if (property_name) {
|
|
|
|
|
block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
|
|
|
|
|
updater = block.renderer.options.dev
|
|
|
|
|
? b `@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
|
|
|
|
|
: b `${element.var}.${property_name} = ${should_cache ? this.last : value};`;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
block.chunks.hydrate.push(b `${method}(${element.var}, "${name}", ${init});`);
|
|
|
|
|
updater = b `${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
|
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
|
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
|
|
|
|
|
} else {
|
|
|
|
|
block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`);
|
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
|
}
|
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
|
|
const update_value = b `@set_input_value(${element.var}, ${element.var}.__value);`;
|
|
|
|
|
const update_value = b`@set_input_value(${element.var}, ${element.var}.__value);`;
|
|
|
|
|
block.chunks.hydrate.push(update_value);
|
|
|
|
|
updater = b `
|
|
|
|
|
updater = b`
|
|
|
|
|
${updater}
|
|
|
|
|
${update_value};
|
|
|
|
|
`;
|
|
|
|
|
@ -188,8 +183,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
if (this.node.name === 'value' && dependencies.length > 0) {
|
|
|
|
|
if (this.parent.bindings.some((binding) => binding.node.name === 'group')) {
|
|
|
|
|
this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed');
|
|
|
|
|
block.add_variable(this.parent.dynamic_value_condition, x `false`);
|
|
|
|
|
updater = b `
|
|
|
|
|
block.add_variable(this.parent.dynamic_value_condition, x`false`);
|
|
|
|
|
updater = b`
|
|
|
|
|
${updater}
|
|
|
|
|
${this.parent.dynamic_value_condition} = true;
|
|
|
|
|
`;
|
|
|
|
|
@ -197,7 +192,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
}
|
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
|
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
|
|
|
|
|
block.chunks.update.push(b `
|
|
|
|
|
block.chunks.update.push(b`
|
|
|
|
|
if (${condition}) {
|
|
|
|
|
${updater}
|
|
|
|
|
}`);
|
|
|
|
|
@ -218,10 +213,14 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
get_init(block, value) {
|
|
|
|
|
this.last =
|
|
|
|
|
this.should_cache &&
|
|
|
|
|
block.get_unique_name(`${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value`);
|
|
|
|
|
if (this.should_cache)
|
|
|
|
|
block.add_variable(this.last);
|
|
|
|
|
return this.should_cache ? x `${this.last} = ${value}` : value;
|
|
|
|
|
block.get_unique_name(
|
|
|
|
|
`${this.parent.var.name}_${this.name.replace(
|
|
|
|
|
regex_invalid_variable_identifier_characters,
|
|
|
|
|
'_'
|
|
|
|
|
)}_value`
|
|
|
|
|
);
|
|
|
|
|
if (this.should_cache) block.add_variable(this.last);
|
|
|
|
|
return this.should_cache ? x`${this.last} = ${value}` : value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -235,17 +234,19 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
let condition = dependency_condition;
|
|
|
|
|
if (should_cache) {
|
|
|
|
|
condition = this.is_src
|
|
|
|
|
? x `${condition} && (!@src_url_equal(${element.var}.src, (${last} = ${value})))`
|
|
|
|
|
: x `${condition} && (${last} !== (${last} = ${value}))`;
|
|
|
|
|
? x`${condition} && (!@src_url_equal(${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 !== true && !non_textlike_input_types.has(type)) {
|
|
|
|
|
condition = x `${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
|
|
|
|
|
condition = x`${condition} && ${element.var}.${property_name} !== ${
|
|
|
|
|
should_cache ? last : value
|
|
|
|
|
}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (block.has_outros) {
|
|
|
|
|
condition = x `!#current || ${condition}`;
|
|
|
|
|
condition = x`!#current || ${condition}`;
|
|
|
|
|
}
|
|
|
|
|
return condition;
|
|
|
|
|
}
|
|
|
|
|
@ -263,8 +264,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
return Array.from(dependencies);
|
|
|
|
|
}
|
|
|
|
|
get_metadata() {
|
|
|
|
|
if (this.parent.node.namespace)
|
|
|
|
|
return null;
|
|
|
|
|
if (this.parent.node.namespace) return null;
|
|
|
|
|
const metadata = attribute_lookup[this.name];
|
|
|
|
|
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name))
|
|
|
|
|
return null;
|
|
|
|
|
@ -275,25 +275,29 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
get_value(block) {
|
|
|
|
|
if (this.node.is_true) {
|
|
|
|
|
if (this.metadata && boolean_attributes.has(this.metadata.property_name.toLowerCase())) {
|
|
|
|
|
return x `true`;
|
|
|
|
|
return x`true`;
|
|
|
|
|
}
|
|
|
|
|
return x `""`;
|
|
|
|
|
return x`""`;
|
|
|
|
|
}
|
|
|
|
|
if (this.node.chunks.length === 0)
|
|
|
|
|
return x `""`;
|
|
|
|
|
if (this.node.chunks.length === 0) return x`""`;
|
|
|
|
|
// TODO some of this code is repeated in Tag.ts — would be good to
|
|
|
|
|
// DRY it out if that's possible without introducing crazy indirection
|
|
|
|
|
if (this.node.chunks.length === 1) {
|
|
|
|
|
return this.node.chunks[0].type === 'Text'
|
|
|
|
|
? string_literal(( /** @type {import('../../../nodes/Text.js').default} */(this.node.chunks[0])).data)
|
|
|
|
|
: ( /** @type {import('../../../nodes/shared/Expression.js').default} */(this.node.chunks[0])).manipulate(block);
|
|
|
|
|
}
|
|
|
|
|
let value = this.node.name === 'class'
|
|
|
|
|
? string_literal(
|
|
|
|
|
/** @type {import('../../../nodes/Text.js').default} */ (this.node.chunks[0]).data
|
|
|
|
|
)
|
|
|
|
|
: /** @type {import('../../../nodes/shared/Expression.js').default} */ (
|
|
|
|
|
this.node.chunks[0]
|
|
|
|
|
).manipulate(block);
|
|
|
|
|
}
|
|
|
|
|
let value =
|
|
|
|
|
this.node.name === 'class'
|
|
|
|
|
? this.get_class_name_text(block)
|
|
|
|
|
: this.render_chunks(block).reduce((lhs, rhs) => x `${lhs} + ${rhs}`);
|
|
|
|
|
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
|
|
|
// '{foo} {bar}' — treat as string concatenation
|
|
|
|
|
if (this.node.chunks[0].type !== 'Text') {
|
|
|
|
|
value = x `"" + ${value}`;
|
|
|
|
|
value = x`"" + ${value}`;
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
@ -304,9 +308,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
const rendered = this.render_chunks(block);
|
|
|
|
|
if (scoped_css && rendered.length === 2) {
|
|
|
|
|
// we have a situation like class={possiblyUndefined}
|
|
|
|
|
rendered[0] = x `@null_to_empty(${rendered[0]})`;
|
|
|
|
|
rendered[0] = x`@null_to_empty(${rendered[0]})`;
|
|
|
|
|
}
|
|
|
|
|
return rendered.reduce((lhs, rhs) => x `${lhs} + ${rhs}`);
|
|
|
|
|
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @param {import('../../Block.js').default} block */
|
|
|
|
|
@ -319,11 +323,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
stringify() {
|
|
|
|
|
if (this.node.is_true)
|
|
|
|
|
return '';
|
|
|
|
|
if (this.node.is_true) return '';
|
|
|
|
|
const value = this.node.chunks;
|
|
|
|
|
if (value.length === 0)
|
|
|
|
|
return '=""';
|
|
|
|
|
if (value.length === 0) return '=""';
|
|
|
|
|
return `="${value
|
|
|
|
|
.map((chunk) => {
|
|
|
|
|
return chunk.type === 'Text'
|
|
|
|
|
@ -392,8 +394,7 @@ const attribute_lookup = {
|
|
|
|
|
};
|
|
|
|
|
Object.keys(attribute_lookup).forEach((name) => {
|
|
|
|
|
const metadata = attribute_lookup[name];
|
|
|
|
|
if (!metadata.property_name)
|
|
|
|
|
metadata.property_name = name;
|
|
|
|
|
if (!metadata.property_name) metadata.property_name = name;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** @param {AttributeWrapper} attribute */
|
|
|
|
|
@ -405,12 +406,14 @@ const regex_contains_checked_or_group = /checked|group/;
|
|
|
|
|
/** @param {AttributeWrapper} attribute */
|
|
|
|
|
function is_indirectly_bound_value(attribute) {
|
|
|
|
|
const element = attribute.parent;
|
|
|
|
|
return (attribute.name === 'value' &&
|
|
|
|
|
return (
|
|
|
|
|
attribute.name === 'value' &&
|
|
|
|
|
(element.node.name === 'option' || // TODO check it's actually bound
|
|
|
|
|
(element.node.name === 'input' &&
|
|
|
|
|
element.node.bindings.some((binding) => regex_contains_checked_or_group.test(binding.name)))));
|
|
|
|
|
element.node.bindings.some((binding) =>
|
|
|
|
|
regex_contains_checked_or_group.test(binding.name)
|
|
|
|
|
)))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @typedef {{ property_name?: string; applies_to?: string[] }} AttributeMetadata */
|
|
|
|
|
|
|
|
|
|
|