From 29a97b6cf7119723f30824b3a06ad17ff7957832 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 May 2023 13:10:03 +0200 Subject: [PATCH] Convert src/compiler/compile/render_dom/wrappers/Element/Binding.ts to JavaScript --- .../render_dom/wrappers/Element/Attribute.js | 24 +- .../render_dom/wrappers/Element/Binding.ts | 888 +++++++++--------- 2 files changed, 438 insertions(+), 474 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.js b/src/compiler/compile/render_dom/wrappers/Element/Attribute.js index 615f238c4c..cfd1e47982 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.js +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.js @@ -5,6 +5,7 @@ import handle_select_value_binding from './handle_select_value_binding.js'; import { namespaces } from '../../../../utils/namespaces.js'; import { boolean_attributes } from '../../../../../shared/boolean_attributes.js'; import { regex_double_quotes } from '../../../../utils/patterns.js'; + const non_textlike_input_types = new Set([ 'button', 'checkbox', @@ -19,13 +20,19 @@ const non_textlike_input_types = new Set([ 'reset', 'submit' ]); -/** */ + export class BaseAttributeWrapper { /** @type {import('../../../nodes/Attribute.js').default} */ node; /** @type {import('./index.js').default} */ parent; + + /** + * @param {import('./index.js').default} parent + * @param {import('../../Block.js').default} block + * @param {import('../../../nodes/Attribute.js').default} node + */ constructor(parent, block, node) { this.node = node; this.parent = parent; @@ -141,7 +148,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { const dependencies = this.get_dependencies(); const value = this.get_value(block); - /** @type {Node[]} */ + /** @type {import('estree').Node[]} */ let updater; const init = this.get_init(block, value); if (is_legacy_input_type) { @@ -304,7 +311,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper { /** @param {import('../../Block.js').default} 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); if (scoped_css && rendered.length === 2) { // we have a situation like class={possiblyUndefined} @@ -336,9 +345,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper { } } /** - * @type {{ [key in BooleanAttributes]: AttributeMetadata } & { - * [key in string]: AttributeMetadata; - * }} + * @type {{ + * [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 = { 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 */ diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 7551a1b054..8d1c142946 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -1,499 +1,455 @@ 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 replace_object from '../../../utils/replace_object'; -import Block from '../../Block'; -import Renderer, { BindingGroup } from '../../Renderer'; import flatten_reference from '../../../utils/flatten_reference'; -import { Node, Identifier } from 'estree'; import add_to_set from '../../../utils/add_to_set'; import mark_each_block_bindings from '../shared/mark_each_block_bindings'; import handle_select_value_binding from './handle_select_value_binding'; import { regex_box_size } from '../../../../utils/patterns'; - +/** */ export default class BindingWrapper { - node: Binding; - parent: ElementWrapper | InlineComponentWrapper; - - object: string; - handler: { - uses_context: boolean; - mutation: Node | Node[]; - contextual_dependencies: Set; - lhs?: Node; - }; - snippet: Node; - is_readonly: boolean; - needs_lock: boolean; - binding_group: BindingGroup; - - constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) { - this.node = node; - this.parent = parent; - - const { dependencies } = this.node.expression; - - block.add_dependencies(dependencies); - - // TODO does this also apply to e.g. ``? - - handle_select_value_binding(this, dependencies); - - if (node.is_contextual) { - mark_each_block_bindings(this.parent, this.node); - } - - this.object = get_object(this.node.expression.node).name; - - if (this.node.name === 'group') { - this.binding_group = get_binding_group(parent.renderer, this, block); - } - - // view to model - this.handler = get_event_handler( - this, - parent.renderer, - block, - this.object, - this.node.raw_expression - ); - - this.snippet = this.node.expression.manipulate(block); - - this.is_readonly = this.node.is_readonly; - - this.needs_lock = this.node.name === 'currentTime'; // TODO others? - } - - get_dependencies() { - const dependencies = new Set(this.node.expression.dependencies); - - this.node.expression.dependencies.forEach((prop: string) => { - const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); - if (indirect_dependencies) { - indirect_dependencies.forEach((indirect_dependency) => { - dependencies.add(indirect_dependency); - }); - } - }); - - if (this.binding_group) { - this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep)); - } - - return dependencies; - } - - get_update_dependencies() { - const object = this.object; - const dependencies = new Set(); - if (this.node.expression.template_scope.names.has(object)) { - this.node.expression.template_scope.dependencies_for_name - .get(object) - .forEach((name) => dependencies.add(name)); - } else { - dependencies.add(object); - } - - const result = new Set(dependencies); - dependencies.forEach((dependency) => { - const indirect_dependencies = - this.parent.renderer.component.indirect_dependencies.get(dependency); - if (indirect_dependencies) { - indirect_dependencies.forEach((indirect_dependency) => { - result.add(indirect_dependency); - }); - } - }); - - return result; - } - - is_readonly_media_attribute() { - return this.node.is_readonly_media_attribute(); - } - - render(block: Block, lock: Identifier) { - if (this.is_readonly) return; - - const { parent } = this; - - const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : []; - const mount_conditions: any[] = []; - let update_or_condition: any = null; - - const dependency_array = Array.from(this.get_dependencies()); - - if (dependency_array.length > 0) { - update_conditions.push(block.renderer.dirty(dependency_array)); - } - - if (parent.node.name === 'input') { - const type = parent.node.get_static_attribute_value('type'); - - if ( - type === null || - type === '' || - type === 'text' || - type === 'email' || - type === 'password' || - type === 'search' || - type === 'url' - ) { - update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`); - } else if (type === 'number') { - update_conditions.push(x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`); - } - } - - // model to view - let update_dom = get_dom_updater(parent, this, false); - let mount_dom = get_dom_updater(parent, this, true); - - // special cases - switch (this.node.name) { - case 'group': { - block.renderer.add_to_context('$$binding_groups'); - this.binding_group.add_element(block, this.parent.var); - - if ((this.parent as ElementWrapper).has_dynamic_value) { - update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition; - } - break; - } - - case 'textContent': - update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`); - mount_conditions.push(x`${this.snippet} !== void 0`); - break; - - case 'innerText': - update_conditions.push(x`${this.snippet} !== ${parent.var}.innerText`); - mount_conditions.push(x`${this.snippet} !== void 0`); - break; - - case 'innerHTML': - update_conditions.push(x`${this.snippet} !== ${parent.var}.innerHTML`); - mount_conditions.push(x`${this.snippet} !== void 0`); - break; - - case 'currentTime': - update_conditions.push(x`!@_isNaN(${this.snippet})`); - mount_dom = null; - break; - - case 'playbackRate': - case 'volume': - update_conditions.push(x`!@_isNaN(${this.snippet})`); - mount_conditions.push(x`!@_isNaN(${this.snippet})`); - break; - case 'paused': { - // this is necessary to prevent audio restarting by itself - const last = block.get_unique_name(`${parent.var.name}_is_paused`); - block.add_variable(last, x`true`); - - update_conditions.push(x`${last} !== (${last} = ${this.snippet})`); - update_dom = b`${parent.var}[${last} ? "pause" : "play"]();`; - mount_dom = null; - break; - } - - case 'value': - if (parent.node.get_static_attribute_value('type') === 'file') { - update_dom = null; - mount_dom = null; - } - } - - if (update_dom) { - if (update_conditions.length > 0) { - let condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`); - if (update_or_condition) condition = x`${update_or_condition} || (${condition})`; - - block.chunks.update.push(b` + /** @type {import('../../../nodes/Binding.js').default} */ + node = undefined; + + /** @type {ElementWrapper | InlineComponentWrapper} */ + parent = undefined; + + /** @type {string} */ + object = undefined; + /** + * @type {{ + * uses_context: boolean; + * mutation: Node | Node[]; + * contextual_dependencies: Set; + * lhs?: Node; + * }} + */ + handler = undefined; + + /** @type {import('estree').Node} */ + snippet = undefined; + + /** @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.parent = parent; + const { dependencies } = this.node.expression; + block.add_dependencies(dependencies); + // TODO does this also apply to e.g. ``? + handle_select_value_binding(this, dependencies); + if (node.is_contextual) { + mark_each_block_bindings(this.parent, this.node); + } + this.object = get_object(this.node.expression.node).name; + if (this.node.name === 'group') { + this.binding_group = get_binding_group(parent.renderer, this, block); + } + // view to model + this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression); + this.snippet = this.node.expression.manipulate(block); + this.is_readonly = this.node.is_readonly; + this.needs_lock = this.node.name === 'currentTime'; // TODO others? + } + get_dependencies() { + const dependencies = new Set(this.node.expression.dependencies); + this.node.expression.dependencies.forEach((prop) => { + const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); + if (indirect_dependencies) { + indirect_dependencies.forEach((indirect_dependency) => { + dependencies.add(indirect_dependency); + }); + } + }); + if (this.binding_group) { + this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep)); + } + return dependencies; + } + get_update_dependencies() { + const object = this.object; + const dependencies = new Set(); + if (this.node.expression.template_scope.names.has(object)) { + this.node.expression.template_scope.dependencies_for_name + .get(object) + .forEach((name) => dependencies.add(name)); + } + else { + dependencies.add(object); + } + const result = new Set(dependencies); + dependencies.forEach((dependency) => { + const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(dependency); + if (indirect_dependencies) { + indirect_dependencies.forEach((indirect_dependency) => { + result.add(indirect_dependency); + }); + } + }); + return result; + } + is_readonly_media_attribute() { + return this.node.is_readonly_media_attribute(); + } + + /** + * @param {import('../../Block.js').default} block + * @param {import('estree').Identifier} lock + */ + render(block, lock) { + if (this.is_readonly) + return; + const { parent } = this; + + /** @type {any[]} */ + const update_conditions = this.needs_lock ? [x `!${lock}`] : []; + + /** @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) { + update_conditions.push(block.renderer.dirty(dependency_array)); + } + if (parent.node.name === 'input') { + const type = parent.node.get_static_attribute_value('type'); + if (type === null || + type === '' || + type === 'text' || + type === 'email' || + type === 'password' || + type === 'search' || + type === 'url') { + update_conditions.push(x `${parent.var}.${this.node.name} !== ${this.snippet}`); + } + else if (type === 'number') { + update_conditions.push(x `@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`); + } + } + // model to view + let update_dom = get_dom_updater(parent, this, false); + let mount_dom = get_dom_updater(parent, this, true); + // special cases + switch (this.node.name) { + case 'group': { + block.renderer.add_to_context('$$binding_groups'); + this.binding_group.add_element(block, this.parent.var); + if (( /** @type {import('../Element.js').default} */(this.parent)).has_dynamic_value) { + update_or_condition = ( /** @type {import('../Element.js').default} */(this.parent)).dynamic_value_condition; + } + break; + } + case 'textContent': + update_conditions.push(x `${this.snippet} !== ${parent.var}.textContent`); + mount_conditions.push(x `${this.snippet} !== void 0`); + break; + case 'innerText': + update_conditions.push(x `${this.snippet} !== ${parent.var}.innerText`); + mount_conditions.push(x `${this.snippet} !== void 0`); + break; + case 'innerHTML': + update_conditions.push(x `${this.snippet} !== ${parent.var}.innerHTML`); + mount_conditions.push(x `${this.snippet} !== void 0`); + break; + case 'currentTime': + update_conditions.push(x `!@_isNaN(${this.snippet})`); + mount_dom = null; + break; + case 'playbackRate': + case 'volume': + update_conditions.push(x `!@_isNaN(${this.snippet})`); + mount_conditions.push(x `!@_isNaN(${this.snippet})`); + break; + case 'paused': { + // this is necessary to prevent audio restarting by itself + const last = block.get_unique_name(`${parent.var.name}_is_paused`); + block.add_variable(last, x `true`); + update_conditions.push(x `${last} !== (${last} = ${this.snippet})`); + update_dom = b `${parent.var}[${last} ? "pause" : "play"]();`; + mount_dom = null; + break; + } + case 'value': + if (parent.node.get_static_attribute_value('type') === 'file') { + update_dom = null; + mount_dom = null; + } + } + if (update_dom) { + if (update_conditions.length > 0) { + let condition = update_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`); + if (update_or_condition) + condition = x `${update_or_condition} || (${condition})`; + block.chunks.update.push(b ` if (${condition}) { ${update_dom} } `); - } else { - block.chunks.update.push(update_dom); - } - } - - if (mount_dom) { - if (mount_conditions.length > 0) { - const condition = mount_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`); - - block.chunks.mount.push(b` + } + else { + block.chunks.update.push(update_dom); + } + } + if (mount_dom) { + if (mount_conditions.length > 0) { + const condition = mount_conditions.reduce((lhs, rhs) => x `${lhs} && ${rhs}`); + block.chunks.mount.push(b ` if (${condition}) { ${mount_dom} } `); - } else { - block.chunks.mount.push(mount_dom); - } - } - } + } + else { + block.chunks.mount.push(mount_dom); + } + } + } } -function get_dom_updater( - element: ElementWrapper | InlineComponentWrapper, - binding: BindingWrapper, - mounting: boolean -) { - const { node } = element; - - if (binding.is_readonly_media_attribute()) { - return null; - } - - if (binding.node.name === 'this') { - return null; - } - - if (node.name === 'select') { - return node.get_static_attribute_value('multiple') === true - ? b`@select_options(${element.var}, ${binding.snippet})` - : mounting - ? b`@select_option(${element.var}, ${binding.snippet}, true)` - : b`@select_option(${element.var}, ${binding.snippet})`; - } - - if (binding.node.name === 'group') { - const type = node.get_static_attribute_value('type'); - - const condition = - type === 'checkbox' - ? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)` - : x`${element.var}.__value === ${binding.snippet}`; - - return b`${element.var}.checked = ${condition};`; - } - - if (binding.node.name === 'value') { - return b`@set_input_value(${element.var}, ${binding.snippet});`; - } - - return b`${element.var}.${binding.node.name} = ${binding.snippet};`; +/** + * @param {ElementWrapper | InlineComponentWrapper} element + * @param {BindingWrapper} binding + * @param {boolean} mounting + */ +function get_dom_updater(element, binding, mounting) { + const { node } = element; + if (binding.is_readonly_media_attribute()) { + return null; + } + if (binding.node.name === 'this') { + return null; + } + if (node.name === 'select') { + return node.get_static_attribute_value('multiple') === true + ? b `@select_options(${element.var}, ${binding.snippet})` + : mounting + ? b `@select_option(${element.var}, ${binding.snippet}, true)` + : b `@select_option(${element.var}, ${binding.snippet})`; + } + if (binding.node.name === 'group') { + const type = node.get_static_attribute_value('type'); + const condition = type === 'checkbox' + ? x `~(${binding.snippet} || []).indexOf(${element.var}.__value)` + : x `${element.var}.__value === ${binding.snippet}`; + return b `${element.var}.checked = ${condition};`; + } + if (binding.node.name === 'value') { + return b `@set_input_value(${element.var}, ${binding.snippet});`; + } + return b `${element.var}.${binding.node.name} = ${binding.snippet};`; } -function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: Block) { - const value = binding.node; - const { parts } = flatten_reference(value.raw_expression); - let keypath = parts.join('.'); - - const contexts = []; - const contextual_dependencies = new Set(); - const { template_scope } = value.expression; - const add_contextual_dependency = (dep: string) => { - contextual_dependencies.add(dep); - const owner = template_scope.get_owner(dep); - if (owner.type === 'EachBlock') { - for (const dep of owner.expression.contextual_dependencies) { - add_contextual_dependency(dep); - } - } - }; - for (const dep of value.expression.contextual_dependencies) { - add_contextual_dependency(dep); - } - - for (const dep of contextual_dependencies) { - const context = block.bindings.get(dep); - let key: string; - let name: string; - if (context) { - key = context.object.name; - name = context.property.name; - } else { - key = dep; - name = dep; - } - keypath = `${key}@${keypath}`; - contexts.push(name); - } - - // create a global binding_group across blocks - if (!renderer.binding_groups.has(keypath)) { - const index = renderer.binding_groups.size; - // 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 - const list_dependencies = new Set(); - let parent = value.parent; - while (parent) { - if (parent.type === 'EachBlock') { - for (const dep of parent.expression.dynamic_dependencies()) { - list_dependencies.add(dep); - } - } - parent = parent.parent; - } - - /** - * 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 elements that have the same bind:group within the same block. - */ - const elements = new Map(); - - contexts.forEach((context) => { - renderer.add_to_context(context, true); - }); - - renderer.binding_groups.set(keypath, { - binding_group: () => { - let obj = x`$$binding_groups[${index}]`; - - if (contexts.length > 0) { - contexts.forEach((secondary_index) => { - obj = x`${obj}[${secondary_index}]`; - }); - } - return obj; - }, - contexts, - list_dependencies, - keypath, - add_element(block, element) { - if (!elements.has(block)) { - elements.set(block, []); - } - elements.get(block).push(element); - }, - render(block) { - const local_name = block.get_unique_name('binding_group'); - const binding_group = block.renderer.reference('$$binding_groups'); - block.add_variable(local_name); - if (contexts.length > 0) { - const indexes = { - type: 'ArrayExpression', - elements: contexts.map((name) => block.renderer.reference(name)) - }; - block.chunks.init.push( - 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})` - ); - } 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.destroy.push(b`${local_name}.r()`); - } - }); - } - - // register the binding_group for the block - const binding_group = renderer.binding_groups.get(keypath); - block.binding_groups.add(binding_group); - - return binding_group; +/** + * @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 { parts } = flatten_reference(value.raw_expression); + let keypath = parts.join('.'); + const contexts = []; + const contextual_dependencies = new Set(); + const { template_scope } = value.expression; + + /** @param {string} dep */ + const add_contextual_dependency = (dep) => { + contextual_dependencies.add(dep); + const owner = template_scope.get_owner(dep); + if (owner.type === 'EachBlock') { + for (const dep of owner.expression.contextual_dependencies) { + add_contextual_dependency(dep); + } + } + }; + for (const dep of value.expression.contextual_dependencies) { + add_contextual_dependency(dep); + } + for (const dep of contextual_dependencies) { + const context = block.bindings.get(dep); + let key; + let name; + if (context) { + key = context.object.name; + name = context.property.name; + } + else { + key = dep; + name = dep; + } + keypath = `${key}@${keypath}`; + contexts.push(name); + } + // create a global binding_group across blocks + if (!renderer.binding_groups.has(keypath)) { + const index = renderer.binding_groups.size; + // 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 + const list_dependencies = new Set(); + let parent = value.parent; + while (parent) { + if (parent.type === 'EachBlock') { + for (const dep of parent.expression.dynamic_dependencies()) { + list_dependencies.add(dep); + } + } + parent = parent.parent; + } + /** + * 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 elements that have the same bind:group within the same block. + */ + const elements = new Map(); + contexts.forEach((context) => { + renderer.add_to_context(context, true); + }); + renderer.binding_groups.set(keypath, { + binding_group: () => { + let obj = x `$$binding_groups[${index}]`; + if (contexts.length > 0) { + contexts.forEach((secondary_index) => { + obj = x `${obj}[${secondary_index}]`; + }); + } + return obj; + }, + contexts, + list_dependencies, + keypath, + add_element(block, element) { + if (!elements.has(block)) { + elements.set(block, []); + } + elements.get(block).push(element); + }, + render(block) { + const local_name = block.get_unique_name('binding_group'); + const binding_group = block.renderer.reference('$$binding_groups'); + block.add_variable(local_name); + if (contexts.length > 0) { + const indexes = { + type: 'ArrayExpression', + elements: contexts.map((name) => block.renderer.reference(name)) + }; + block.chunks.init.push(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})`); + } + 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.destroy.push(b `${local_name}.r()`); + } + }); + } + // register the binding_group for the block + const binding_group = renderer.binding_groups.get(keypath); + block.binding_groups.add(binding_group); + return binding_group; } -function get_event_handler( - binding: BindingWrapper, - renderer: Renderer, - block: Block, - name: string, - lhs: Node -): { - uses_context: boolean; - mutation: Node | Node[]; - contextual_dependencies: Set; - lhs?: Node; -} { - const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies); - - const context = block.bindings.get(name); - let set_store: Node[] | undefined; - - if (context) { - const { object, property, store, snippet } = context; - lhs = replace_object(lhs, snippet); - contextual_dependencies.add(object.name); - contextual_dependencies.add(property.name); - contextual_dependencies.delete(name); - - if (store) { - set_store = b`${store}.set(${`$${store}`});`; - } - } else { - const object = get_object(lhs); - if (object.name[0] === '$') { - const store = object.name.slice(1); - set_store = b`${store}.set(${object.name});`; - } - } - - const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies); - - const mutation = b` +/** + * @param {BindingWrapper} binding + * @param {import('../../Renderer.js').default} renderer + * @param {import('../../Block.js').default} block + * @param {string} name + * @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; lhs?: import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").Node; }} + */ +function get_event_handler(binding, renderer, block, name, lhs) { + const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies); + const context = block.bindings.get(name); + + /** @type {Node[] | undefined} */ + let set_store; + if (context) { + const { object, property, store, snippet } = context; + lhs = replace_object(lhs, snippet); + contextual_dependencies.add(object.name); + contextual_dependencies.add(property.name); + contextual_dependencies.delete(name); + if (store) { + set_store = b `${store}.set(${`$${store}`});`; + } + } + else { + const object = get_object(lhs); + if (object.name[0] === '$') { + const store = object.name.slice(1); + set_store = b `${store}.set(${object.name});`; + } + } + const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies); + const mutation = b ` ${lhs} = ${value}; ${set_store} `; - - return { - uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy - mutation, - contextual_dependencies, - lhs - }; + return { + uses_context: binding.node.is_contextual || binding.node.expression.uses_context, + mutation, + contextual_dependencies, + lhs + }; } -function get_value_from_dom( - _renderer: Renderer, - element: ElementWrapper | InlineComponentWrapper, - binding: BindingWrapper, - contextual_dependencies: Set -) { - const { node } = element; - const { name } = binding.node; - - if (name === 'this') { - return x`$$value`; - } - - //
- if (regex_box_size.test(name)) { - return x`@ResizeObserverSingleton.entries.get(this)?.${name}`; - } - - // + if (name === 'group') { + if (type === 'checkbox') { + const { binding_group, contexts } = binding.binding_group; + add_to_set(contextual_dependencies, contexts); + return x `@get_binding_group_value(${binding_group()}, this.__value, this.checked)`; + } + return x `this.__value`; + } + // + if (type === 'range' || type === 'number') { + return x `@to_number(this.${name})`; + } + if (name === 'buffered' || name === 'seekable' || name === 'played') { + return x `@time_ranges_to_array(this.${name})`; + } + // everything else + return x `this.${name}`; +} - return x`this.__value`; - } - // - if (type === 'range' || type === 'number') { - return x`@to_number(this.${name})`; - } - if (name === 'buffered' || name === 'seekable' || name === 'played') { - return x`@time_ranges_to_array(this.${name})`; - } - // everything else - return x`this.${name}`; -}