|
|
@ -1,13 +1,13 @@
|
|
|
|
import Attribute from "../../../nodes/Attribute";
|
|
|
|
import Attribute from '../../../nodes/Attribute';
|
|
|
|
import Block from "../../Block";
|
|
|
|
import Block from '../../Block';
|
|
|
|
import fix_attribute_casing from "./fix_attribute_casing";
|
|
|
|
import fix_attribute_casing from './fix_attribute_casing';
|
|
|
|
import ElementWrapper from "./index";
|
|
|
|
import ElementWrapper from './index';
|
|
|
|
import { string_literal } from "../../../utils/stringify";
|
|
|
|
import { string_literal } from '../../../utils/stringify';
|
|
|
|
import { b, x } from "code-red";
|
|
|
|
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";
|
|
|
|
import { Identifier, Node } from 'estree';
|
|
|
|
|
|
|
|
|
|
|
|
export class BaseAttributeWrapper {
|
|
|
|
export class BaseAttributeWrapper {
|
|
|
|
node: Attribute;
|
|
|
|
node: Attribute;
|
|
|
@ -46,27 +46,22 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
|
|
|
|
|
|
|
if (node.dependencies.size > 0) {
|
|
|
|
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;
|
|
|
|
while (
|
|
|
|
while (select && (select.node.type !== 'Element' || select.node.name !== 'select'))
|
|
|
|
select &&
|
|
|
|
|
|
|
|
(select.node.type !== "Element" || select.node.name !== "select")
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
// @ts-ignore todo: doublecheck this, but looks to be correct
|
|
|
|
// @ts-ignore todo: doublecheck this, but looks to be correct
|
|
|
|
select = select.parent;
|
|
|
|
select = select.parent;
|
|
|
|
|
|
|
|
|
|
|
|
if (select && select.select_binding_dependencies) {
|
|
|
|
if (select && select.select_binding_dependencies) {
|
|
|
|
select.select_binding_dependencies.forEach((prop) => {
|
|
|
|
select.select_binding_dependencies.forEach(prop => {
|
|
|
|
this.node.dependencies.forEach((dependency: string) => {
|
|
|
|
this.node.dependencies.forEach((dependency: string) => {
|
|
|
|
this.parent.renderer.component.indirect_dependencies
|
|
|
|
this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
|
|
|
|
.get(prop)
|
|
|
|
|
|
|
|
.add(dependency);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (node.name === "value") {
|
|
|
|
if (node.name === 'value') {
|
|
|
|
handle_select_value_binding(this, node.dependencies);
|
|
|
|
handle_select_value_binding(this, node.dependencies);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -75,38 +70,28 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
this.metadata = this.get_metadata();
|
|
|
|
this.metadata = this.get_metadata();
|
|
|
|
this.is_indirectly_bound_value = is_indirectly_bound_value(this);
|
|
|
|
this.is_indirectly_bound_value = is_indirectly_bound_value(this);
|
|
|
|
this.property_name = this.is_indirectly_bound_value
|
|
|
|
this.property_name = this.is_indirectly_bound_value
|
|
|
|
? "__value"
|
|
|
|
? '__value'
|
|
|
|
: this.metadata && this.metadata.property_name;
|
|
|
|
: this.metadata && this.metadata.property_name;
|
|
|
|
this.is_src = this.name === "src"; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
|
|
|
|
this.is_src = this.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
|
|
|
|
this.is_select_value_attribute =
|
|
|
|
this.is_select_value_attribute = this.name === 'value' && this.parent.node.name === 'select';
|
|
|
|
this.name === "value" && this.parent.node.name === "select";
|
|
|
|
this.is_input_value = this.name === 'value' && this.parent.node.name === 'input';
|
|
|
|
this.is_input_value =
|
|
|
|
|
|
|
|
this.name === "value" && this.parent.node.name === "input";
|
|
|
|
|
|
|
|
this.should_cache = should_cache(this);
|
|
|
|
this.should_cache = should_cache(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
render(block: Block) {
|
|
|
|
render(block: Block) {
|
|
|
|
const element = this.parent;
|
|
|
|
const element = this.parent;
|
|
|
|
const {
|
|
|
|
const { name, property_name, should_cache, is_indirectly_bound_value } = this;
|
|
|
|
name,
|
|
|
|
|
|
|
|
property_name,
|
|
|
|
|
|
|
|
should_cache,
|
|
|
|
|
|
|
|
is_indirectly_bound_value,
|
|
|
|
|
|
|
|
} = this;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// HTML5?
|
|
|
|
// HTML5?
|
|
|
|
const method = /-/.test(element.node.name)
|
|
|
|
const method = /-/.test(element.node.name)
|
|
|
|
? "@set_custom_element_data"
|
|
|
|
? '@set_custom_element_data'
|
|
|
|
: name.slice(0, 6) === "xlink:"
|
|
|
|
: name.slice(0, 6) === 'xlink:'
|
|
|
|
? "@xlink_attr"
|
|
|
|
? '@xlink_attr'
|
|
|
|
: "@attr";
|
|
|
|
: '@attr';
|
|
|
|
|
|
|
|
|
|
|
|
const is_legacy_input_type =
|
|
|
|
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
|
|
|
|
element.renderer.component.compile_options.legacy &&
|
|
|
|
|
|
|
|
name === "type" &&
|
|
|
|
|
|
|
|
this.parent.node.name === "input";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dependencies = this.get_dependencies();
|
|
|
|
const dependencies = this.get_dependencies();
|
|
|
|
const value = this.get_value(block);
|
|
|
|
const value = this.get_value(block);
|
|
|
@ -114,21 +99,14 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
let updater;
|
|
|
|
let updater;
|
|
|
|
const init = this.get_init(block, value);
|
|
|
|
const init = this.get_init(block, value);
|
|
|
|
|
|
|
|
|
|
|
|
// Set inputs value default to '' if undefined
|
|
|
|
|
|
|
|
if (name == "value") {
|
|
|
|
|
|
|
|
block.chunks.mount.push(b`@set_input_value(${element.var}, ${value});`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (is_legacy_input_type) {
|
|
|
|
if (is_legacy_input_type) {
|
|
|
|
block.chunks.hydrate.push(b`@set_input_type(${element.var}, ${init});`);
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
updater = b`@set_input_type(${element.var}, ${
|
|
|
|
b`@set_input_type(${element.var}, ${init});`
|
|
|
|
should_cache ? this.last : value
|
|
|
|
);
|
|
|
|
});`;
|
|
|
|
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
|
|
|
|
} else if (this.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(
|
|
|
|
const is_multiple_select = element.node.get_static_attribute_value('multiple');
|
|
|
|
"multiple"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (is_multiple_select) {
|
|
|
|
if (is_multiple_select) {
|
|
|
|
updater = b`@select_options(${element.var}, ${value});`;
|
|
|
|
updater = b`@select_options(${element.var}, ${value});`;
|
|
|
@ -136,6 +114,11 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
updater = b`@select_option(${element.var}, ${value});`;
|
|
|
|
updater = b`@select_option(${element.var}, ${value});`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set inputs value default to '' if undefined
|
|
|
|
|
|
|
|
if (name == 'value') {
|
|
|
|
|
|
|
|
block.chunks.mount.push(b`@set_input_value(${element.var}, ${value});`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
block.chunks.mount.push(b`
|
|
|
|
${updater}
|
|
|
|
${updater}
|
|
|
|
`);
|
|
|
|
`);
|
|
|
@ -143,25 +126,19 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});`
|
|
|
|
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
should_cache ? this.last : value
|
|
|
|
|
|
|
|
});`;
|
|
|
|
|
|
|
|
} else if (property_name) {
|
|
|
|
} else if (property_name) {
|
|
|
|
block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
|
|
|
|
block.chunks.hydrate.push(
|
|
|
|
|
|
|
|
b`${element.var}.${property_name} = ${init};`
|
|
|
|
|
|
|
|
);
|
|
|
|
updater = block.renderer.options.dev
|
|
|
|
updater = block.renderer.options.dev
|
|
|
|
? b`@prop_dev(${element.var}, "${property_name}", ${
|
|
|
|
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
|
|
|
|
should_cache ? this.last : value
|
|
|
|
: b`${element.var}.${property_name} = ${should_cache ? this.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}", ${
|
|
|
|
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
|
|
|
|
should_cache ? this.last : value
|
|
|
|
|
|
|
|
});`;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
|
if (is_indirectly_bound_value) {
|
|
|
@ -175,10 +152,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
if (dependencies.length > 0) {
|
|
|
|
const condition = this.get_dom_update_conditions(
|
|
|
|
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
|
|
|
|
block,
|
|
|
|
|
|
|
|
block.renderer.dirty(dependencies)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.chunks.update.push(b`
|
|
|
|
block.chunks.update.push(b`
|
|
|
|
if (${condition}) {
|
|
|
|
if (${condition}) {
|
|
|
@ -187,19 +161,14 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// special case – autofocus. has to be handled in a bit of a weird way
|
|
|
|
// special case – autofocus. has to be handled in a bit of a weird way
|
|
|
|
if (this.node.is_true && name === "autofocus") {
|
|
|
|
if (this.node.is_true && name === 'autofocus') {
|
|
|
|
block.autofocus = element.var;
|
|
|
|
block.autofocus = element.var;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get_init(block: Block, value) {
|
|
|
|
get_init(block: Block, value) {
|
|
|
|
this.last =
|
|
|
|
this.last = this.should_cache && block.get_unique_name(
|
|
|
|
this.should_cache &&
|
|
|
|
`${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value`
|
|
|
|
block.get_unique_name(
|
|
|
|
|
|
|
|
`${this.parent.var.name}_${this.name.replace(
|
|
|
|
|
|
|
|
/[^a-zA-Z_$]/g,
|
|
|
|
|
|
|
|
"_"
|
|
|
|
|
|
|
|
)}_value`
|
|
|
|
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.should_cache) block.add_variable(this.last);
|
|
|
|
if (this.should_cache) block.add_variable(this.last);
|
|
|
@ -221,18 +190,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.is_input_value) {
|
|
|
|
if (this.is_input_value) {
|
|
|
|
const type = element.node.get_static_attribute_value("type");
|
|
|
|
const type = element.node.get_static_attribute_value('type');
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
|
|
|
|
type === null ||
|
|
|
|
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
|
|
|
|
type === "" ||
|
|
|
|
|
|
|
|
type === "text" ||
|
|
|
|
|
|
|
|
type === "email" ||
|
|
|
|
|
|
|
|
type === "password"
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
condition = x`${condition} && ${element.var}.${property_name} !== ${
|
|
|
|
|
|
|
|
should_cache ? last : value
|
|
|
|
|
|
|
|
}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -248,11 +209,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
const dependencies = new Set(node_dependencies);
|
|
|
|
const dependencies = new Set(node_dependencies);
|
|
|
|
|
|
|
|
|
|
|
|
node_dependencies.forEach((prop: string) => {
|
|
|
|
node_dependencies.forEach((prop: string) => {
|
|
|
|
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(
|
|
|
|
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
|
|
|
|
prop
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
if (indirect_dependencies) {
|
|
|
|
if (indirect_dependencies) {
|
|
|
|
indirect_dependencies.forEach((indirect_dependency) => {
|
|
|
|
indirect_dependencies.forEach(indirect_dependency => {
|
|
|
|
dependencies.add(indirect_dependency);
|
|
|
|
dependencies.add(indirect_dependency);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -264,21 +223,13 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
get_metadata() {
|
|
|
|
get_metadata() {
|
|
|
|
if (this.parent.node.namespace) return null;
|
|
|
|
if (this.parent.node.namespace) return null;
|
|
|
|
const metadata = attribute_lookup[this.name];
|
|
|
|
const metadata = attribute_lookup[this.name];
|
|
|
|
if (
|
|
|
|
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
|
|
|
|
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) {
|
|
|
|
if (
|
|
|
|
if (this.metadata && boolean_attribute.has(this.metadata.property_name.toLowerCase())) {
|
|
|
|
this.metadata &&
|
|
|
|
|
|
|
|
boolean_attribute.has(this.metadata.property_name.toLowerCase())
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
return x`true`;
|
|
|
|
return x`true`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return x`""`;
|
|
|
|
return x`""`;
|
|
|
@ -288,18 +239,17 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
// TODO some of this code is repeated in Tag.ts — would be good to
|
|
|
|
// 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
|
|
|
|
// DRY it out if that's possible without introducing crazy indirection
|
|
|
|
if (this.node.chunks.length === 1) {
|
|
|
|
if (this.node.chunks.length === 1) {
|
|
|
|
return this.node.chunks[0].type === "Text"
|
|
|
|
return this.node.chunks[0].type === 'Text'
|
|
|
|
? string_literal((this.node.chunks[0] as Text).data)
|
|
|
|
? string_literal((this.node.chunks[0] as Text).data)
|
|
|
|
: (this.node.chunks[0] as Expression).manipulate(block);
|
|
|
|
: (this.node.chunks[0] as Expression).manipulate(block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let value =
|
|
|
|
let value = this.node.name === 'class'
|
|
|
|
this.node.name === "class"
|
|
|
|
|
|
|
|
? this.get_class_name_text(block)
|
|
|
|
? 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
|
|
|
|
// '{foo} {bar}' — treat as string concatenation
|
|
|
|
if (this.node.chunks[0].type !== "Text") {
|
|
|
|
if (this.node.chunks[0].type !== 'Text') {
|
|
|
|
value = x`"" + ${value}`;
|
|
|
|
value = x`"" + ${value}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -320,7 +270,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
|
|
|
|
|
|
|
|
render_chunks(block: Block) {
|
|
|
|
render_chunks(block: Block) {
|
|
|
|
return this.node.chunks.map((chunk) => {
|
|
|
|
return this.node.chunks.map((chunk) => {
|
|
|
|
if (chunk.type === "Text") {
|
|
|
|
if (chunk.type === 'Text') {
|
|
|
|
return string_literal(chunk.data);
|
|
|
|
return string_literal(chunk.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -329,114 +279,104 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stringify() {
|
|
|
|
stringify() {
|
|
|
|
if (this.node.is_true) return "";
|
|
|
|
if (this.node.is_true) return '';
|
|
|
|
|
|
|
|
|
|
|
|
const value = this.node.chunks;
|
|
|
|
const value = this.node.chunks;
|
|
|
|
if (value.length === 0) return `=""`;
|
|
|
|
if (value.length === 0) return `=""`;
|
|
|
|
|
|
|
|
|
|
|
|
return `="${value
|
|
|
|
return `="${value.map(chunk => {
|
|
|
|
.map((chunk) => {
|
|
|
|
return chunk.type === 'Text'
|
|
|
|
return chunk.type === "Text"
|
|
|
|
|
|
|
|
? chunk.data.replace(/"/g, '\\"')
|
|
|
|
? chunk.data.replace(/"/g, '\\"')
|
|
|
|
: `\${${chunk.manipulate()}}`;
|
|
|
|
: `\${${chunk.manipulate()}}`;
|
|
|
|
})
|
|
|
|
}).join('')}"`;
|
|
|
|
.join("")}"`;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// source: https://html.spec.whatwg.org/multipage/indices.html
|
|
|
|
// source: https://html.spec.whatwg.org/multipage/indices.html
|
|
|
|
const attribute_lookup = {
|
|
|
|
const attribute_lookup = {
|
|
|
|
allowfullscreen: { property_name: "allowFullscreen", applies_to: ["iframe"] },
|
|
|
|
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
|
|
|
|
allowpaymentrequest: {
|
|
|
|
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
|
|
|
|
property_name: "allowPaymentRequest",
|
|
|
|
async: { applies_to: ['script'] },
|
|
|
|
applies_to: ["iframe"],
|
|
|
|
autofocus: { applies_to: ['button', 'input', 'keygen', 'select', 'textarea'] },
|
|
|
|
},
|
|
|
|
autoplay: { applies_to: ['audio', 'video'] },
|
|
|
|
async: { applies_to: ["script"] },
|
|
|
|
checked: { applies_to: ['input'] },
|
|
|
|
autofocus: {
|
|
|
|
controls: { applies_to: ['audio', 'video'] },
|
|
|
|
applies_to: ["button", "input", "keygen", "select", "textarea"],
|
|
|
|
default: { applies_to: ['track'] },
|
|
|
|
},
|
|
|
|
defer: { applies_to: ['script'] },
|
|
|
|
autoplay: { applies_to: ["audio", "video"] },
|
|
|
|
|
|
|
|
checked: { applies_to: ["input"] },
|
|
|
|
|
|
|
|
controls: { applies_to: ["audio", "video"] },
|
|
|
|
|
|
|
|
default: { applies_to: ["track"] },
|
|
|
|
|
|
|
|
defer: { applies_to: ["script"] },
|
|
|
|
|
|
|
|
disabled: {
|
|
|
|
disabled: {
|
|
|
|
applies_to: [
|
|
|
|
applies_to: [
|
|
|
|
"button",
|
|
|
|
'button',
|
|
|
|
"fieldset",
|
|
|
|
'fieldset',
|
|
|
|
"input",
|
|
|
|
'input',
|
|
|
|
"keygen",
|
|
|
|
'keygen',
|
|
|
|
"optgroup",
|
|
|
|
'optgroup',
|
|
|
|
"option",
|
|
|
|
'option',
|
|
|
|
"select",
|
|
|
|
'select',
|
|
|
|
"textarea",
|
|
|
|
'textarea'
|
|
|
|
],
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
|
|
|
formnovalidate: {
|
|
|
|
|
|
|
|
property_name: "formNoValidate",
|
|
|
|
|
|
|
|
applies_to: ["button", "input"],
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
|
|
|
|
hidden: {},
|
|
|
|
hidden: {},
|
|
|
|
indeterminate: { applies_to: ["input"] },
|
|
|
|
indeterminate: { applies_to: ['input'] },
|
|
|
|
ismap: { property_name: "isMap", applies_to: ["img"] },
|
|
|
|
ismap: { property_name: 'isMap', applies_to: ['img'] },
|
|
|
|
loop: { applies_to: ["audio", "bgsound", "video"] },
|
|
|
|
loop: { applies_to: ['audio', 'bgsound', 'video'] },
|
|
|
|
multiple: { applies_to: ["input", "select"] },
|
|
|
|
multiple: { applies_to: ['input', 'select'] },
|
|
|
|
muted: { applies_to: ["audio", "video"] },
|
|
|
|
muted: { applies_to: ['audio', 'video'] },
|
|
|
|
nomodule: { property_name: "noModule", applies_to: ["script"] },
|
|
|
|
nomodule: { property_name: 'noModule', applies_to: ['script'] },
|
|
|
|
novalidate: { property_name: "noValidate", applies_to: ["form"] },
|
|
|
|
novalidate: { property_name: 'noValidate', applies_to: ['form'] },
|
|
|
|
open: { applies_to: ["details", "dialog"] },
|
|
|
|
open: { applies_to: ['details', 'dialog'] },
|
|
|
|
playsinline: { property_name: "playsInline", applies_to: ["video"] },
|
|
|
|
playsinline: { property_name: 'playsInline', applies_to: ['video'] },
|
|
|
|
readonly: { property_name: "readOnly", applies_to: ["input", "textarea"] },
|
|
|
|
readonly: { property_name: 'readOnly', applies_to: ['input', 'textarea'] },
|
|
|
|
required: { applies_to: ["input", "select", "textarea"] },
|
|
|
|
required: { applies_to: ['input', 'select', 'textarea'] },
|
|
|
|
reversed: { applies_to: ["ol"] },
|
|
|
|
reversed: { applies_to: ['ol'] },
|
|
|
|
selected: { applies_to: ["option"] },
|
|
|
|
selected: { applies_to: ['option'] },
|
|
|
|
value: {
|
|
|
|
value: {
|
|
|
|
applies_to: [
|
|
|
|
applies_to: [
|
|
|
|
"button",
|
|
|
|
'button',
|
|
|
|
"option",
|
|
|
|
'option',
|
|
|
|
"input",
|
|
|
|
'input',
|
|
|
|
"li",
|
|
|
|
'li',
|
|
|
|
"meter",
|
|
|
|
'meter',
|
|
|
|
"progress",
|
|
|
|
'progress',
|
|
|
|
"param",
|
|
|
|
'param',
|
|
|
|
"select",
|
|
|
|
'select',
|
|
|
|
"textarea",
|
|
|
|
'textarea'
|
|
|
|
],
|
|
|
|
]
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Object.keys(attribute_lookup).forEach((name) => {
|
|
|
|
Object.keys(attribute_lookup).forEach(name => {
|
|
|
|
const metadata = attribute_lookup[name];
|
|
|
|
const metadata = attribute_lookup[name];
|
|
|
|
if (!metadata.property_name) metadata.property_name = name;
|
|
|
|
if (!metadata.property_name) metadata.property_name = name;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// source: https://html.spec.whatwg.org/multipage/indices.html
|
|
|
|
// source: https://html.spec.whatwg.org/multipage/indices.html
|
|
|
|
const boolean_attribute = new Set([
|
|
|
|
const boolean_attribute = new Set([
|
|
|
|
"allowfullscreen",
|
|
|
|
'allowfullscreen',
|
|
|
|
"allowpaymentrequest",
|
|
|
|
'allowpaymentrequest',
|
|
|
|
"async",
|
|
|
|
'async',
|
|
|
|
"autofocus",
|
|
|
|
'autofocus',
|
|
|
|
"autoplay",
|
|
|
|
'autoplay',
|
|
|
|
"checked",
|
|
|
|
'checked',
|
|
|
|
"controls",
|
|
|
|
'controls',
|
|
|
|
"default",
|
|
|
|
'default',
|
|
|
|
"defer",
|
|
|
|
'defer',
|
|
|
|
"disabled",
|
|
|
|
'disabled',
|
|
|
|
"formnovalidate",
|
|
|
|
'formnovalidate',
|
|
|
|
"hidden",
|
|
|
|
'hidden',
|
|
|
|
"ismap",
|
|
|
|
'ismap',
|
|
|
|
"itemscope",
|
|
|
|
'itemscope',
|
|
|
|
"loop",
|
|
|
|
'loop',
|
|
|
|
"multiple",
|
|
|
|
'multiple',
|
|
|
|
"muted",
|
|
|
|
'muted',
|
|
|
|
"nomodule",
|
|
|
|
'nomodule',
|
|
|
|
"novalidate",
|
|
|
|
'novalidate',
|
|
|
|
"open",
|
|
|
|
'open',
|
|
|
|
"playsinline",
|
|
|
|
'playsinline',
|
|
|
|
"readonly",
|
|
|
|
'readonly',
|
|
|
|
"required",
|
|
|
|
'required',
|
|
|
|
"reversed",
|
|
|
|
'reversed',
|
|
|
|
"selected",
|
|
|
|
'selected'
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
function should_cache(attribute: AttributeWrapper) {
|
|
|
|
function should_cache(attribute: AttributeWrapper) {
|
|
|
@ -445,12 +385,11 @@ function should_cache(attribute: AttributeWrapper) {
|
|
|
|
|
|
|
|
|
|
|
|
function is_indirectly_bound_value(attribute: AttributeWrapper) {
|
|
|
|
function is_indirectly_bound_value(attribute: AttributeWrapper) {
|
|
|
|
const element = attribute.parent;
|
|
|
|
const element = attribute.parent;
|
|
|
|
return (
|
|
|
|
return attribute.name === 'value' &&
|
|
|
|
attribute.name === "value" &&
|
|
|
|
(element.node.name === 'option' || // TODO check it's actually bound
|
|
|
|
(element.node.name === "option" || // TODO check it's actually bound
|
|
|
|
(element.node.name === 'input' &&
|
|
|
|
(element.node.name === "input" &&
|
|
|
|
element.node.bindings.some(
|
|
|
|
element.node.bindings.some((binding) =>
|
|
|
|
(binding) =>
|
|
|
|
/checked|group/.test(binding.name)
|
|
|
|
/checked|group/.test(binding.name)
|
|
|
|
)))
|
|
|
|
)));
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|