Convert src/compiler/compile/render_dom/wrappers/Element/Binding.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent 1eb0941f0c
commit 29a97b6cf7

@ -5,6 +5,7 @@ import handle_select_value_binding from './handle_select_value_binding.js';
import { namespaces } from '../../../../utils/namespaces.js'; import { namespaces } from '../../../../utils/namespaces.js';
import { boolean_attributes } from '../../../../../shared/boolean_attributes.js'; import { boolean_attributes } from '../../../../../shared/boolean_attributes.js';
import { regex_double_quotes } from '../../../../utils/patterns.js'; import { regex_double_quotes } from '../../../../utils/patterns.js';
const non_textlike_input_types = new Set([ const non_textlike_input_types = new Set([
'button', 'button',
'checkbox', 'checkbox',
@ -19,13 +20,19 @@ const non_textlike_input_types = new Set([
'reset', 'reset',
'submit' 'submit'
]); ]);
/** */
export class BaseAttributeWrapper { export class BaseAttributeWrapper {
/** @type {import('../../../nodes/Attribute.js').default} */ /** @type {import('../../../nodes/Attribute.js').default} */
node; node;
/** @type {import('./index.js').default} */ /** @type {import('./index.js').default} */
parent; parent;
/**
* @param {import('./index.js').default} parent
* @param {import('../../Block.js').default} block
* @param {import('../../../nodes/Attribute.js').default} node
*/
constructor(parent, block, node) { constructor(parent, block, node) {
this.node = node; this.node = node;
this.parent = parent; this.parent = parent;
@ -141,7 +148,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const dependencies = this.get_dependencies(); const dependencies = this.get_dependencies();
const value = this.get_value(block); const value = this.get_value(block);
/** @type {Node[]} */ /** @type {import('estree').Node[]} */
let updater; let updater;
const init = this.get_init(block, value); const init = this.get_init(block, value);
if (is_legacy_input_type) { if (is_legacy_input_type) {
@ -304,7 +311,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
/** @param {import('../../Block.js').default} block */ /** @param {import('../../Block.js').default} block */
get_class_name_text(block) { get_class_name_text(block) {
const scoped_css = this.node.chunks.some((chunk) => chunk.synthetic); const scoped_css = this.node.chunks.some(
(/** @type {import('../../../nodes/Text.js').default} */ chunk) => chunk.synthetic
);
const rendered = this.render_chunks(block); const rendered = this.render_chunks(block);
if (scoped_css && rendered.length === 2) { if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined} // we have a situation like class={possiblyUndefined}
@ -336,9 +345,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
} }
/** /**
* @type {{ [key in BooleanAttributes]: AttributeMetadata } & { * @type {{
* [key in string]: AttributeMetadata; * [key in import('../../../../../shared/boolean_attributes.js').BooleanAttributes]: { property_name?: string; applies_to?: string[] } } &
* }} * { [key in string]: { property_name?: string; applies_to?: string[] }; }
* }
*/ */
const attribute_lookup = { const attribute_lookup = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
@ -415,5 +425,3 @@ function is_indirectly_bound_value(attribute) {
))) )))
); );
} }
/** @typedef {{ property_name?: string; applies_to?: string[] }} AttributeMetadata */

@ -1,76 +1,72 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object'; import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object'; import replace_object from '../../../utils/replace_object';
import Block from '../../Block';
import Renderer, { BindingGroup } from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import { Node, Identifier } from 'estree';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import mark_each_block_bindings from '../shared/mark_each_block_bindings'; import mark_each_block_bindings from '../shared/mark_each_block_bindings';
import handle_select_value_binding from './handle_select_value_binding'; import handle_select_value_binding from './handle_select_value_binding';
import { regex_box_size } from '../../../../utils/patterns'; import { regex_box_size } from '../../../../utils/patterns';
/** */
export default class BindingWrapper { export default class BindingWrapper {
node: Binding;
parent: ElementWrapper | InlineComponentWrapper;
object: string; /** @type {import('../../../nodes/Binding.js').default} */
handler: { node = undefined;
uses_context: boolean;
mutation: Node | Node[]; /** @type {ElementWrapper | InlineComponentWrapper} */
contextual_dependencies: Set<string>; parent = undefined;
lhs?: Node;
}; /** @type {string} */
snippet: Node; object = undefined;
is_readonly: boolean; /**
needs_lock: boolean; * @type {{
binding_group: BindingGroup; * uses_context: boolean;
* mutation: Node | Node[];
* contextual_dependencies: Set<string>;
* lhs?: Node;
* }}
*/
handler = undefined;
/** @type {import('estree').Node} */
snippet = undefined;
constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) { /** @type {boolean} */
is_readonly = undefined;
/** @type {boolean} */
needs_lock = undefined;
/** @type {import('../../Renderer.js').BindingGroup} */
binding_group = undefined;
/**
* @param {import('../../Block.js').default} block
* @param {import('../../../nodes/Binding.js').default} node
* @param {ElementWrapper | InlineComponentWrapper} parent
*/
constructor(block, node, parent) {
this.node = node; this.node = node;
this.parent = parent; this.parent = parent;
const { dependencies } = this.node.expression; const { dependencies } = this.node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`? // TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
handle_select_value_binding(this, dependencies); handle_select_value_binding(this, dependencies);
if (node.is_contextual) { if (node.is_contextual) {
mark_each_block_bindings(this.parent, this.node); mark_each_block_bindings(this.parent, this.node);
} }
this.object = get_object(this.node.expression.node).name; this.object = get_object(this.node.expression.node).name;
if (this.node.name === 'group') { if (this.node.name === 'group') {
this.binding_group = get_binding_group(parent.renderer, this, block); this.binding_group = get_binding_group(parent.renderer, this, block);
} }
// view to model // view to model
this.handler = get_event_handler( this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression);
this,
parent.renderer,
block,
this.object,
this.node.raw_expression
);
this.snippet = this.node.expression.manipulate(block); this.snippet = this.node.expression.manipulate(block);
this.is_readonly = this.node.is_readonly; this.is_readonly = this.node.is_readonly;
this.needs_lock = this.node.name === 'currentTime'; // TODO others? this.needs_lock = this.node.name === 'currentTime'; // TODO others?
} }
get_dependencies() { get_dependencies() {
const dependencies = new Set(this.node.expression.dependencies); const dependencies = new Set(this.node.expression.dependencies);
this.node.expression.dependencies.forEach((prop) => {
this.node.expression.dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach((indirect_dependency) => { indirect_dependencies.forEach((indirect_dependency) => {
@ -78,182 +74,166 @@ export default class BindingWrapper {
}); });
} }
}); });
if (this.binding_group) { if (this.binding_group) {
this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep)); this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep));
} }
return dependencies; return dependencies;
} }
get_update_dependencies() { get_update_dependencies() {
const object = this.object; const object = this.object;
const dependencies = new Set<string>(); const dependencies = new Set();
if (this.node.expression.template_scope.names.has(object)) { if (this.node.expression.template_scope.names.has(object)) {
this.node.expression.template_scope.dependencies_for_name this.node.expression.template_scope.dependencies_for_name
.get(object) .get(object)
.forEach((name) => dependencies.add(name)); .forEach((name) => dependencies.add(name));
} else { }
else {
dependencies.add(object); dependencies.add(object);
} }
const result = new Set(dependencies); const result = new Set(dependencies);
dependencies.forEach((dependency) => { dependencies.forEach((dependency) => {
const indirect_dependencies = const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(dependency);
this.parent.renderer.component.indirect_dependencies.get(dependency);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach((indirect_dependency) => { indirect_dependencies.forEach((indirect_dependency) => {
result.add(indirect_dependency); result.add(indirect_dependency);
}); });
} }
}); });
return result; return result;
} }
is_readonly_media_attribute() { is_readonly_media_attribute() {
return this.node.is_readonly_media_attribute(); return this.node.is_readonly_media_attribute();
} }
render(block: Block, lock: Identifier) { /**
if (this.is_readonly) return; * @param {import('../../Block.js').default} block
* @param {import('estree').Identifier} lock
*/
render(block, lock) {
if (this.is_readonly)
return;
const { parent } = this; const { parent } = this;
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : []; /** @type {any[]} */
const mount_conditions: any[] = []; const update_conditions = this.needs_lock ? [x `!${lock}`] : [];
let update_or_condition: any = null;
const dependency_array = Array.from(this.get_dependencies()); /** @type {any[]} */
const mount_conditions = [];
/** @type {any} */
let update_or_condition = null;
const dependency_array = Array.from(this.get_dependencies());
if (dependency_array.length > 0) { if (dependency_array.length > 0) {
update_conditions.push(block.renderer.dirty(dependency_array)); update_conditions.push(block.renderer.dirty(dependency_array));
} }
if (parent.node.name === 'input') { if (parent.node.name === 'input') {
const type = parent.node.get_static_attribute_value('type'); const type = parent.node.get_static_attribute_value('type');
if (type === null ||
if (
type === null ||
type === '' || type === '' ||
type === 'text' || type === 'text' ||
type === 'email' || type === 'email' ||
type === 'password' || type === 'password' ||
type === 'search' || type === 'search' ||
type === 'url' type === 'url') {
) {
update_conditions.push(x `${parent.var}.${this.node.name} !== ${this.snippet}`); update_conditions.push(x `${parent.var}.${this.node.name} !== ${this.snippet}`);
} else if (type === 'number') { }
else if (type === 'number') {
update_conditions.push(x `@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`); update_conditions.push(x `@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`);
} }
} }
// model to view // model to view
let update_dom = get_dom_updater(parent, this, false); let update_dom = get_dom_updater(parent, this, false);
let mount_dom = get_dom_updater(parent, this, true); let mount_dom = get_dom_updater(parent, this, true);
// special cases // special cases
switch (this.node.name) { switch (this.node.name) {
case 'group': { case 'group': {
block.renderer.add_to_context('$$binding_groups'); block.renderer.add_to_context('$$binding_groups');
this.binding_group.add_element(block, this.parent.var); this.binding_group.add_element(block, this.parent.var);
if (( /** @type {import('../Element.js').default} */(this.parent)).has_dynamic_value) {
if ((this.parent as ElementWrapper).has_dynamic_value) { update_or_condition = ( /** @type {import('../Element.js').default} */(this.parent)).dynamic_value_condition;
update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition;
} }
break; break;
} }
case 'textContent': case 'textContent':
update_conditions.push(x `${this.snippet} !== ${parent.var}.textContent`); update_conditions.push(x `${this.snippet} !== ${parent.var}.textContent`);
mount_conditions.push(x `${this.snippet} !== void 0`); mount_conditions.push(x `${this.snippet} !== void 0`);
break; break;
case 'innerText': case 'innerText':
update_conditions.push(x `${this.snippet} !== ${parent.var}.innerText`); update_conditions.push(x `${this.snippet} !== ${parent.var}.innerText`);
mount_conditions.push(x `${this.snippet} !== void 0`); mount_conditions.push(x `${this.snippet} !== void 0`);
break; break;
case 'innerHTML': case 'innerHTML':
update_conditions.push(x `${this.snippet} !== ${parent.var}.innerHTML`); update_conditions.push(x `${this.snippet} !== ${parent.var}.innerHTML`);
mount_conditions.push(x `${this.snippet} !== void 0`); mount_conditions.push(x `${this.snippet} !== void 0`);
break; break;
case 'currentTime': case 'currentTime':
update_conditions.push(x `!@_isNaN(${this.snippet})`); update_conditions.push(x `!@_isNaN(${this.snippet})`);
mount_dom = null; mount_dom = null;
break; break;
case 'playbackRate': case 'playbackRate':
case 'volume': case 'volume':
update_conditions.push(x `!@_isNaN(${this.snippet})`); update_conditions.push(x `!@_isNaN(${this.snippet})`);
mount_conditions.push(x `!@_isNaN(${this.snippet})`); mount_conditions.push(x `!@_isNaN(${this.snippet})`);
break; break;
case 'paused': { case 'paused': {
// this is necessary to prevent audio restarting by itself // this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var.name}_is_paused`); const last = block.get_unique_name(`${parent.var.name}_is_paused`);
block.add_variable(last, x `true`); block.add_variable(last, x `true`);
update_conditions.push(x `${last} !== (${last} = ${this.snippet})`); update_conditions.push(x `${last} !== (${last} = ${this.snippet})`);
update_dom = b `${parent.var}[${last} ? "pause" : "play"]();`; update_dom = b `${parent.var}[${last} ? "pause" : "play"]();`;
mount_dom = null; mount_dom = null;
break; break;
} }
case 'value': case 'value':
if (parent.node.get_static_attribute_value('type') === 'file') { if (parent.node.get_static_attribute_value('type') === 'file') {
update_dom = null; update_dom = null;
mount_dom = null; mount_dom = null;
} }
} }
if (update_dom) { if (update_dom) {
if (update_conditions.length > 0) { if (update_conditions.length > 0) {
let condition = update_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`); let condition = update_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`);
if (update_or_condition) condition = x`${update_or_condition} || (${condition})`; if (update_or_condition)
condition = x `${update_or_condition} || (${condition})`;
block.chunks.update.push(b ` block.chunks.update.push(b `
if (${condition}) { if (${condition}) {
${update_dom} ${update_dom}
} }
`); `);
} else { }
else {
block.chunks.update.push(update_dom); block.chunks.update.push(update_dom);
} }
} }
if (mount_dom) { if (mount_dom) {
if (mount_conditions.length > 0) { if (mount_conditions.length > 0) {
const condition = mount_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`); const condition = mount_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`);
block.chunks.mount.push(b ` block.chunks.mount.push(b `
if (${condition}) { if (${condition}) {
${mount_dom} ${mount_dom}
} }
`); `);
} else { }
else {
block.chunks.mount.push(mount_dom); block.chunks.mount.push(mount_dom);
} }
} }
} }
} }
function get_dom_updater( /**
element: ElementWrapper | InlineComponentWrapper, * @param {ElementWrapper | InlineComponentWrapper} element
binding: BindingWrapper, * @param {BindingWrapper} binding
mounting: boolean * @param {boolean} mounting
) { */
function get_dom_updater(element, binding, mounting) {
const { node } = element; const { node } = element;
if (binding.is_readonly_media_attribute()) { if (binding.is_readonly_media_attribute()) {
return null; return null;
} }
if (binding.node.name === 'this') { if (binding.node.name === 'this') {
return null; return null;
} }
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true return node.get_static_attribute_value('multiple') === true
? b `@select_options(${element.var}, ${binding.snippet})` ? b `@select_options(${element.var}, ${binding.snippet})`
@ -261,34 +241,34 @@ function get_dom_updater(
? b `@select_option(${element.var}, ${binding.snippet}, true)` ? b `@select_option(${element.var}, ${binding.snippet}, true)`
: b `@select_option(${element.var}, ${binding.snippet})`; : b `@select_option(${element.var}, ${binding.snippet})`;
} }
if (binding.node.name === 'group') { if (binding.node.name === 'group') {
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox'
const condition =
type === 'checkbox'
? x `~(${binding.snippet} || []).indexOf(${element.var}.__value)` ? x `~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x `${element.var}.__value === ${binding.snippet}`; : x `${element.var}.__value === ${binding.snippet}`;
return b `${element.var}.checked = ${condition};`; return b `${element.var}.checked = ${condition};`;
} }
if (binding.node.name === 'value') { if (binding.node.name === 'value') {
return b `@set_input_value(${element.var}, ${binding.snippet});`; return b `@set_input_value(${element.var}, ${binding.snippet});`;
} }
return b `${element.var}.${binding.node.name} = ${binding.snippet};`; return b `${element.var}.${binding.node.name} = ${binding.snippet};`;
} }
function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: Block) { /**
* @param {import('../../Renderer.js').default} renderer
* @param {BindingWrapper} binding
* @param {import('../../Block.js').default} block
*/
function get_binding_group(renderer, binding, block) {
const value = binding.node; const value = binding.node;
const { parts } = flatten_reference(value.raw_expression); const { parts } = flatten_reference(value.raw_expression);
let keypath = parts.join('.'); let keypath = parts.join('.');
const contexts = []; const contexts = [];
const contextual_dependencies = new Set<string>(); const contextual_dependencies = new Set();
const { template_scope } = value.expression; const { template_scope } = value.expression;
const add_contextual_dependency = (dep: string) => {
/** @param {string} dep */
const add_contextual_dependency = (dep) => {
contextual_dependencies.add(dep); contextual_dependencies.add(dep);
const owner = template_scope.get_owner(dep); const owner = template_scope.get_owner(dep);
if (owner.type === 'EachBlock') { if (owner.type === 'EachBlock') {
@ -300,28 +280,27 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
for (const dep of value.expression.contextual_dependencies) { for (const dep of value.expression.contextual_dependencies) {
add_contextual_dependency(dep); add_contextual_dependency(dep);
} }
for (const dep of contextual_dependencies) { for (const dep of contextual_dependencies) {
const context = block.bindings.get(dep); const context = block.bindings.get(dep);
let key: string; let key;
let name: string; let name;
if (context) { if (context) {
key = context.object.name; key = context.object.name;
name = context.property.name; name = context.property.name;
} else { }
else {
key = dep; key = dep;
name = dep; name = dep;
} }
keypath = `${key}@${keypath}`; keypath = `${key}@${keypath}`;
contexts.push(name); contexts.push(name);
} }
// create a global binding_group across blocks // create a global binding_group across blocks
if (!renderer.binding_groups.has(keypath)) { if (!renderer.binding_groups.has(keypath)) {
const index = renderer.binding_groups.size; const index = renderer.binding_groups.size;
// the bind:group depends on the list in the {#each} block as well // the bind:group depends on the list in the {#each} block as well
// as reordering (removing and adding back to the DOM) may affect the value // as reordering (removing and adding back to the DOM) may affect the value
const list_dependencies = new Set<string>(); const list_dependencies = new Set();
let parent = value.parent; let parent = value.parent;
while (parent) { while (parent) {
if (parent.type === 'EachBlock') { if (parent.type === 'EachBlock') {
@ -331,21 +310,17 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
} }
parent = parent.parent; parent = parent.parent;
} }
/** /**
* When using bind:group with logic blocks, the inputs with bind:group may be scattered across different blocks. * When using bind:group with logic blocks, the inputs with bind:group may be scattered across different blocks.
* This therefore keeps track of all the <input> elements that have the same bind:group within the same block. * This therefore keeps track of all the <input> elements that have the same bind:group within the same block.
*/ */
const elements = new Map<Block, any>(); const elements = new Map();
contexts.forEach((context) => { contexts.forEach((context) => {
renderer.add_to_context(context, true); renderer.add_to_context(context, true);
}); });
renderer.binding_groups.set(keypath, { renderer.binding_groups.set(keypath, {
binding_group: () => { binding_group: () => {
let obj = x `$$binding_groups[${index}]`; let obj = x `$$binding_groups[${index}]`;
if (contexts.length > 0) { if (contexts.length > 0) {
contexts.forEach((secondary_index) => { contexts.forEach((secondary_index) => {
obj = x `${obj}[${secondary_index}]`; obj = x `${obj}[${secondary_index}]`;
@ -371,109 +346,90 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
type: 'ArrayExpression', type: 'ArrayExpression',
elements: contexts.map((name) => block.renderer.reference(name)) elements: contexts.map((name) => block.renderer.reference(name))
}; };
block.chunks.init.push( block.chunks.init.push(b `${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`);
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})` block.chunks.update.push(b `if (${block.renderer.dirty(Array.from(list_dependencies))}) ${local_name}.u(${indexes})`);
); }
block.chunks.update.push( else {
b`if (${block.renderer.dirty( block.chunks.init.push(b `${local_name} = @init_binding_group(${binding_group}[${index}])`);
Array.from(list_dependencies)
)}) ${local_name}.u(${indexes})`
);
} else {
block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])`
);
} }
block.chunks.hydrate.push(b `${local_name}.p(${elements.get(block)})`); block.chunks.hydrate.push(b `${local_name}.p(${elements.get(block)})`);
block.chunks.destroy.push(b `${local_name}.r()`); block.chunks.destroy.push(b `${local_name}.r()`);
} }
}); });
} }
// register the binding_group for the block // register the binding_group for the block
const binding_group = renderer.binding_groups.get(keypath); const binding_group = renderer.binding_groups.get(keypath);
block.binding_groups.add(binding_group); block.binding_groups.add(binding_group);
return binding_group; return binding_group;
} }
function get_event_handler( /**
binding: BindingWrapper, * @param {BindingWrapper} binding
renderer: Renderer, * @param {import('../../Renderer.js').default} renderer
block: Block, * @param {import('../../Block.js').default} block
name: string, * @param {string} name
lhs: Node * @param {import('estree').Node} lhs
): { * @returns {{ uses_context: boolean; mutation: import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").Node | import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").Node[]; contextual_dependencies: Set<string>; lhs?: import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").Node; }}
uses_context: boolean; */
mutation: Node | Node[]; function get_event_handler(binding, renderer, block, name, lhs) {
contextual_dependencies: Set<string>; const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
lhs?: Node;
} {
const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);
const context = block.bindings.get(name); const context = block.bindings.get(name);
let set_store: Node[] | undefined;
/** @type {Node[] | undefined} */
let set_store;
if (context) { if (context) {
const { object, property, store, snippet } = context; const { object, property, store, snippet } = context;
lhs = replace_object(lhs, snippet); lhs = replace_object(lhs, snippet);
contextual_dependencies.add(object.name); contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name); contextual_dependencies.add(property.name);
contextual_dependencies.delete(name); contextual_dependencies.delete(name);
if (store) { if (store) {
set_store = b `${store}.set(${`$${store}`});`; set_store = b `${store}.set(${`$${store}`});`;
} }
} else { }
else {
const object = get_object(lhs); const object = get_object(lhs);
if (object.name[0] === '$') { if (object.name[0] === '$') {
const store = object.name.slice(1); const store = object.name.slice(1);
set_store = b `${store}.set(${object.name});`; set_store = b `${store}.set(${object.name});`;
} }
} }
const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies); const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies);
const mutation = b ` const mutation = b `
${lhs} = ${value}; ${lhs} = ${value};
${set_store} ${set_store}
`; `;
return { return {
uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy uses_context: binding.node.is_contextual || binding.node.expression.uses_context,
mutation, mutation,
contextual_dependencies, contextual_dependencies,
lhs lhs
}; };
} }
function get_value_from_dom( /**
_renderer: Renderer, * @param {import('../../Renderer.js').default} _renderer
element: ElementWrapper | InlineComponentWrapper, * @param {ElementWrapper | InlineComponentWrapper} element
binding: BindingWrapper, * @param {BindingWrapper} binding
contextual_dependencies: Set<string> * @param {Set<string>} contextual_dependencies
) { */
function get_value_from_dom(_renderer, element, binding, contextual_dependencies) {
const { node } = element; const { node } = element;
const { name } = binding.node; const { name } = binding.node;
if (name === 'this') { if (name === 'this') {
return x `$$value`; return x `$$value`;
} }
// <div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize> // <div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize>
if (regex_box_size.test(name)) { if (regex_box_size.test(name)) {
return x `@ResizeObserverSingleton.entries.get(this)?.${name}`; return x `@ResizeObserverSingleton.entries.get(this)?.${name}`;
} }
// <select bind:value='selected> // <select bind:value='selected>
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true return node.get_static_attribute_value('multiple') === true
? x `@select_multiple_value(this)` ? x `@select_multiple_value(this)`
: x `@select_value(this)`; : x `@select_value(this)`;
} }
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
// <input type='checkbox' bind:group='foo'> // <input type='checkbox' bind:group='foo'>
if (name === 'group') { if (name === 'group') {
if (type === 'checkbox') { if (type === 'checkbox') {
@ -481,19 +437,19 @@ function get_value_from_dom(
add_to_set(contextual_dependencies, contexts); add_to_set(contextual_dependencies, contexts);
return x `@get_binding_group_value(${binding_group()}, this.__value, this.checked)`; return x `@get_binding_group_value(${binding_group()}, this.__value, this.checked)`;
} }
return x `this.__value`; return x `this.__value`;
} }
// <input type='range|number' bind:value> // <input type='range|number' bind:value>
if (type === 'range' || type === 'number') { if (type === 'range' || type === 'number') {
return x `@to_number(this.${name})`; return x `@to_number(this.${name})`;
} }
if (name === 'buffered' || name === 'seekable' || name === 'played') { if (name === 'buffered' || name === 'seekable' || name === 'played') {
return x `@time_ranges_to_array(this.${name})`; return x `@time_ranges_to_array(this.${name})`;
} }
// everything else // everything else
return x `this.${name}`; return x `this.${name}`;
} }

Loading…
Cancel
Save