|
|
|
@ -7,7 +7,6 @@ import { b, x } from 'code-red';
|
|
|
|
|
import Expression from '../../../nodes/shared/Expression';
|
|
|
|
|
import Text from '../../../nodes/Text';
|
|
|
|
|
import { changed } from '../shared/changed';
|
|
|
|
|
import { Literal } from 'estree';
|
|
|
|
|
|
|
|
|
|
export default class AttributeWrapper {
|
|
|
|
|
node: Attribute;
|
|
|
|
@ -72,85 +71,69 @@ export default class AttributeWrapper {
|
|
|
|
|
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
|
|
|
|
|
|
|
|
|
|
const dependencies = this.node.get_dependencies();
|
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
|
let value;
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
// single {tag} — may be a non-string
|
|
|
|
|
value = (this.node.chunks[0] as Expression).manipulate(block);
|
|
|
|
|
} else {
|
|
|
|
|
value = this.node.name === 'class'
|
|
|
|
|
? this.get_class_name_text()
|
|
|
|
|
: this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
|
|
|
|
|
|
|
|
// '{foo} {bar}' — treat as string concatenation
|
|
|
|
|
if (this.node.chunks[0].type !== 'Text') {
|
|
|
|
|
value = x`"" + ${value}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const value = this.get_value(block);
|
|
|
|
|
|
|
|
|
|
const is_select_value_attribute =
|
|
|
|
|
name === 'value' && element.node.name === 'select';
|
|
|
|
|
const is_select_value_attribute =
|
|
|
|
|
name === 'value' && element.node.name === 'select';
|
|
|
|
|
|
|
|
|
|
const should_cache = is_select_value_attribute; // TODO is this necessary?
|
|
|
|
|
const should_cache = is_select_value_attribute; // TODO is this necessary?
|
|
|
|
|
|
|
|
|
|
const last = should_cache && block.get_unique_name(
|
|
|
|
|
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
|
|
|
|
|
);
|
|
|
|
|
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;
|
|
|
|
|
const init = should_cache ? x`${last} = ${value}` : 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 ? last : value});`;
|
|
|
|
|
} else if (is_select_value_attribute) {
|
|
|
|
|
// annoying special case
|
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
|
const i = block.get_unique_name('i');
|
|
|
|
|
const option = block.get_unique_name('option');
|
|
|
|
|
|
|
|
|
|
const if_statement = is_multiple_select
|
|
|
|
|
? b`
|
|
|
|
|
${option}.selected = ~${last}.indexOf(${option}.__value);`
|
|
|
|
|
: b`
|
|
|
|
|
if (${option}.__value === ${last}) {
|
|
|
|
|
${option}.selected = true;
|
|
|
|
|
${{ type: 'BreakStatement' }};
|
|
|
|
|
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
|
|
|
|
|
|
|
|
|
|
updater = b`
|
|
|
|
|
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
|
|
|
|
|
var ${option} = ${element.var}.options[${i}];
|
|
|
|
|
|
|
|
|
|
${if_statement}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
|
${last} = ${value};
|
|
|
|
|
${updater}
|
|
|
|
|
`);
|
|
|
|
|
} 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 ? last : value});`
|
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
|
|
|
|
|
} else {
|
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
|
b`${method}(${element.var}, "${name}", ${init});`
|
|
|
|
|
);
|
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
|
|
|
|
|
}
|
|
|
|
|
if (should_cache) block.add_variable(last);
|
|
|
|
|
|
|
|
|
|
let updater;
|
|
|
|
|
const init = should_cache ? x`${last} = ${value}` : 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 ? last : value});`;
|
|
|
|
|
} else if (is_select_value_attribute) {
|
|
|
|
|
// annoying special case
|
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
|
const i = block.get_unique_name('i');
|
|
|
|
|
const option = block.get_unique_name('option');
|
|
|
|
|
|
|
|
|
|
const if_statement = is_multiple_select
|
|
|
|
|
? b`
|
|
|
|
|
${option}.selected = ~${last}.indexOf(${option}.__value);`
|
|
|
|
|
: b`
|
|
|
|
|
if (${option}.__value === ${last}) {
|
|
|
|
|
${option}.selected = true;
|
|
|
|
|
${{ type: 'BreakStatement' }};
|
|
|
|
|
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
|
|
|
|
|
|
|
|
|
|
updater = b`
|
|
|
|
|
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
|
|
|
|
|
var ${option} = ${element.var}.options[${i}];
|
|
|
|
|
|
|
|
|
|
${if_statement}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
|
${last} = ${value};
|
|
|
|
|
${updater}
|
|
|
|
|
`);
|
|
|
|
|
} 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 ? last : value});`
|
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
|
|
|
|
|
} else {
|
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
|
b`${method}(${element.var}, "${name}", ${init});`
|
|
|
|
|
);
|
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
|
let condition = changed(dependencies);
|
|
|
|
|
|
|
|
|
|
if (should_cache) {
|
|
|
|
@ -165,23 +148,11 @@ export default class AttributeWrapper {
|
|
|
|
|
if (${condition}) {
|
|
|
|
|
${updater}
|
|
|
|
|
}`);
|
|
|
|
|
} else {
|
|
|
|
|
const value = this.node.get_value(block);
|
|
|
|
|
|
|
|
|
|
const statement = (
|
|
|
|
|
is_legacy_input_type
|
|
|
|
|
? b`@set_input_type(${element.var}, ${value});`
|
|
|
|
|
: property_name
|
|
|
|
|
? b`${element.var}.${property_name} = ${value};`
|
|
|
|
|
: b`${method}(${element.var}, "${name}", ${value.type === 'Literal' && (value as Literal).value === true ? x`""` : value});`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
block.chunks.hydrate.push(statement);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// special case – autofocus. has to be handled in a bit of a weird way
|
|
|
|
|
if (this.node.is_true && name === 'autofocus') {
|
|
|
|
|
block.autofocus = element.var;
|
|
|
|
|
}
|
|
|
|
|
// special case – autofocus. has to be handled in a bit of a weird way
|
|
|
|
|
if (this.node.is_true && name === 'autofocus') {
|
|
|
|
|
block.autofocus = element.var;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
|
@ -199,6 +170,36 @@ export default class AttributeWrapper {
|
|
|
|
|
return metadata;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_value(block) {
|
|
|
|
|
if (this.node.is_true) {
|
|
|
|
|
const metadata = this.get_metadata();
|
|
|
|
|
if (metadata && boolean_attribute.has(metadata.property_name.toLowerCase())) {
|
|
|
|
|
return x`true`;
|
|
|
|
|
}
|
|
|
|
|
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((this.node.chunks[0] as Text).data)
|
|
|
|
|
: (this.node.chunks[0] as Expression).manipulate(block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let value = this.node.name === 'class'
|
|
|
|
|
? this.get_class_name_text()
|
|
|
|
|
: this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
|
|
|
|
|
|
|
|
|
|
// '{foo} {bar}' — treat as string concatenation
|
|
|
|
|
if (this.node.chunks[0].type !== 'Text') {
|
|
|
|
|
value = x`"" + ${value}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_class_name_text() {
|
|
|
|
|
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
|
|
|
|
|
const rendered = this.render_chunks();
|
|
|
|
@ -292,3 +293,32 @@ Object.keys(attribute_lookup).forEach(name => {
|
|
|
|
|
const metadata = attribute_lookup[name];
|
|
|
|
|
if (!metadata.property_name) metadata.property_name = name;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// source: https://html.spec.whatwg.org/multipage/indices.html
|
|
|
|
|
const boolean_attribute = new Set([
|
|
|
|
|
'allowfullscreen',
|
|
|
|
|
'allowpaymentrequest',
|
|
|
|
|
'async',
|
|
|
|
|
'autofocus',
|
|
|
|
|
'autoplay',
|
|
|
|
|
'checked',
|
|
|
|
|
'controls',
|
|
|
|
|
'default',
|
|
|
|
|
'defer',
|
|
|
|
|
'disabled',
|
|
|
|
|
'formnovalidate',
|
|
|
|
|
'hidden',
|
|
|
|
|
'ismap',
|
|
|
|
|
'itemscope',
|
|
|
|
|
'loop',
|
|
|
|
|
'multiple',
|
|
|
|
|
'muted',
|
|
|
|
|
'nomodule',
|
|
|
|
|
'novalidate',
|
|
|
|
|
'open',
|
|
|
|
|
'playsinline',
|
|
|
|
|
'readonly',
|
|
|
|
|
'required',
|
|
|
|
|
'reversed',
|
|
|
|
|
'selected'
|
|
|
|
|
]);
|