From ef173e3a1ae8e08ad3eeef806be7b5d63c7af89e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 19 May 2025 20:20:25 -0400 Subject: [PATCH 01/16] fix: stable attachments --- .../client/visitors/shared/element.js | 7 +-- .../client/dom/elements/attachments.js | 2 +- .../client/dom/elements/attributes.js | 44 +++++++++++++++++++ packages/svelte/src/internal/client/index.js | 1 + 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index a093a0bf4a96..f7d72cbb3b85 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -104,9 +104,10 @@ export function build_set_attributes( ); if (is_dynamic) { - context.state.init.push(b.let(attributes_id)); - const update = b.stmt(b.assignment('=', attributes_id, call)); - context.state.update.push(update); + context.state.init.push( + b.let(attributes_id), + b.stmt(b.call('$.set_attribute_effect', element_id, b.thunk(b.object(values)))) + ); } else { context.state.init.push(b.stmt(call)); } diff --git a/packages/svelte/src/internal/client/dom/elements/attachments.js b/packages/svelte/src/internal/client/dom/elements/attachments.js index 6e3089a384c1..f3add0cf2e4a 100644 --- a/packages/svelte/src/internal/client/dom/elements/attachments.js +++ b/packages/svelte/src/internal/client/dom/elements/attachments.js @@ -5,7 +5,7 @@ import { effect } from '../../reactivity/effects.js'; * @param {() => (node: Element) => void} get_fn */ export function attach(node, get_fn) { - effect(() => { + return effect(() => { const fn = get_fn(); // we use `&&` rather than `?.` so that things like diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 3d1acbd31ce1..4c689e6969f7 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -18,6 +18,7 @@ import { clsx } from '../../../shared/attributes.js'; import { set_class } from './class.js'; import { set_style } from './style.js'; import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; +import { block, branch, destroy_effect } from '../../reactivity/effects.js'; export const CLASS = Symbol('class'); export const STYLE = Symbol('style'); @@ -456,6 +457,49 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal return current; } +/** + * @param {Element & ElementCSSInlineStyle} element + * @param {() => Record} fn + * @param {string} [css_hash] + * @param {boolean} [skip_warning] + */ +export function set_attribute_effect(element, fn, css_hash, skip_warning = false) { + /** @type {Record} */ + var prev = {}; + + /** @type {Record} */ + var effects = {}; + + block(() => { + var next = fn(); + + for (const key in prev) { + if (!next[key]) { + element.removeAttribute(key); + } + } + + for (let symbol of Object.getOwnPropertySymbols(prev)) { + if (!next[symbol]) { + destroy_effect(effects[symbol]); + delete effects[symbol]; + } + } + + for (const key in next) { + set_attribute(element, key, next[key], skip_warning); + } + + for (let symbol of Object.getOwnPropertySymbols(next)) { + if (symbol.description === ATTACHMENT_KEY && next[symbol] !== prev[symbol]) { + effects[symbol] = branch(() => attach(element, () => next[symbol])); + } + } + + prev = next; + }); +} + /** * * @param {Element} element diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index b5f746b276bf..383e19522bf5 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -28,6 +28,7 @@ export { remove_input_defaults, set_attribute, set_attributes, + set_attribute_effect, set_custom_element_data, set_xlink_attribute, set_value, From 673151adcebaf99a9953e7720e35c4e4e410f7b8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 11:40:00 -0400 Subject: [PATCH 02/16] WIP --- .../client/visitors/shared/element.js | 62 ++++++++----------- .../client/dom/elements/attributes.js | 45 +++++++------- 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index f7d72cbb3b85..7ed941efb703 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -27,17 +27,21 @@ export function build_set_attributes( element_id, attributes_id ) { - let is_dynamic = false; - /** @type {ObjectExpression['properties']} */ const values = []; + /** @type {Expression[]} */ + const expressions = []; + + /** @param {Expression} value */ + function memoize(value) { + return b.id(`$${expressions.push(value) - 1}`); + } + for (const attribute of attributes) { if (attribute.type === 'Attribute') { - const { value, has_state } = build_attribute_value( - attribute.value, - context, - (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) + const { value } = build_attribute_value(attribute.value, context, (value, metadata) => + metadata.has_call ? memoize(value) : value ); if ( @@ -51,16 +55,11 @@ export function build_set_attributes( } else { values.push(b.init(attribute.name, value)); } - - is_dynamic ||= has_state; } else { - // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive - is_dynamic = true; - let value = /** @type {Expression} */ (context.visit(attribute)); if (attribute.metadata.expression.has_call) { - value = get_expression_id(context.state, value); + value = memoize(value); } values.push(b.spread(value)); @@ -75,9 +74,6 @@ export function build_set_attributes( build_class_directives_object(class_directives, context) ) ); - - is_dynamic ||= - class_directives.find((directive) => directive.metadata.expression.has_state) !== null; } if (style_directives.length) { @@ -88,29 +84,25 @@ export function build_set_attributes( build_style_directives_object(style_directives, context) ) ); - - is_dynamic ||= style_directives.some((directive) => directive.metadata.expression.has_state); } - const call = b.call( - '$.set_attributes', - element_id, - is_dynamic ? attributes_id : b.null, - b.object(values), - element.metadata.scoped && - context.state.analysis.css.hash !== '' && - b.literal(context.state.analysis.css.hash), - is_ignored(element, 'hydration_attribute_changed') && b.true + context.state.init.push( + b.stmt( + b.call( + '$.set_attribute_effect', + element_id, + b.arrow( + expressions.map((_, i) => b.id(`$${i}`)), + b.object(values) + ), + expressions.length > 0 && b.array(expressions.map((expression) => b.thunk(expression))), + element.metadata.scoped && + context.state.analysis.css.hash !== '' && + b.literal(context.state.analysis.css.hash), + is_ignored(element, 'hydration_attribute_changed') && b.true + ) + ) ); - - if (is_dynamic) { - context.state.init.push( - b.let(attributes_id), - b.stmt(b.call('$.set_attribute_effect', element_id, b.thunk(b.object(values)))) - ); - } else { - context.state.init.push(b.stmt(call)); - } } /** diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 4c689e6969f7..8561de31da16 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -10,6 +10,7 @@ import { is_capture_event, is_delegated, normalize_attribute } from '../../../.. import { active_effect, active_reaction, + get, set_active_effect, set_active_reaction } from '../../runtime.js'; @@ -19,6 +20,7 @@ import { set_class } from './class.js'; import { set_style } from './style.js'; import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; import { block, branch, destroy_effect } from '../../reactivity/effects.js'; +import { derived } from '../../reactivity/deriveds.js'; export const CLASS = Symbol('class'); export const STYLE = Symbol('style'); @@ -448,22 +450,32 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal set_hydrating(true); } - for (let symbol of Object.getOwnPropertySymbols(next)) { - if (symbol.description === ATTACHMENT_KEY) { - attach(element, () => next[symbol]); - } - } + // for (let symbol of Object.getOwnPropertySymbols(next)) { + // if (symbol.description === ATTACHMENT_KEY) { + // attach(element, () => next[symbol]); + // } + // } return current; } /** * @param {Element & ElementCSSInlineStyle} element - * @param {() => Record} fn + * @param {(...expressions: any) => Record} fn + * @param {Array<() => any>} thunks * @param {string} [css_hash] * @param {boolean} [skip_warning] */ -export function set_attribute_effect(element, fn, css_hash, skip_warning = false) { +export function set_attribute_effect( + element, + fn, + thunks = [], + css_hash, + skip_warning = false, + d = derived +) { + const deriveds = thunks.map(d); + /** @type {Record} */ var prev = {}; @@ -471,24 +483,9 @@ export function set_attribute_effect(element, fn, css_hash, skip_warning = false var effects = {}; block(() => { - var next = fn(); - - for (const key in prev) { - if (!next[key]) { - element.removeAttribute(key); - } - } + var next = fn(...deriveds.map(get)); - for (let symbol of Object.getOwnPropertySymbols(prev)) { - if (!next[symbol]) { - destroy_effect(effects[symbol]); - delete effects[symbol]; - } - } - - for (const key in next) { - set_attribute(element, key, next[key], skip_warning); - } + set_attributes(element, prev, next, css_hash, skip_warning); for (let symbol of Object.getOwnPropertySymbols(next)) { if (symbol.description === ATTACHMENT_KEY && next[symbol] !== prev[symbol]) { From 6bb5c279445518b2fa48103c7452ad5d622e623c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 11:51:26 -0400 Subject: [PATCH 03/16] WIP --- .../3-transform/client/visitors/RegularElement.js | 15 +++++++++------ .../3-transform/client/visitors/shared/element.js | 12 ++++++------ .../3-transform/client/visitors/shared/utils.js | 9 +++++---- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index c1806b5128df..d46b7e6080d2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -290,7 +290,8 @@ export function RegularElement(node, context) { const { value, has_state } = build_attribute_value( attribute.value, context, - (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value) + (value, metadata) => + metadata.has_call ? get_expression_id(context.state.expressions, value) : value ); const update = build_element_attribute_update(node, node_id, name, value, attributes); @@ -482,10 +483,11 @@ function setup_select_synchronization(value_binding, context) { /** * @param {AST.ClassDirective[]} class_directives + * @param {Expression[]} expressions * @param {ComponentContext} context * @return {ObjectExpression | Identifier} */ -export function build_class_directives_object(class_directives, context) { +export function build_class_directives_object(class_directives, expressions, context) { let properties = []; let has_call_or_state = false; @@ -497,15 +499,16 @@ export function build_class_directives_object(class_directives, context) { const directives = b.object(properties); - return has_call_or_state ? get_expression_id(context.state, directives) : directives; + return has_call_or_state ? get_expression_id(expressions, directives) : directives; } /** * @param {AST.StyleDirective[]} style_directives + * @param {Expression[]} expressions * @param {ComponentContext} context * @return {ObjectExpression | ArrayExpression}} */ -export function build_style_directives_object(style_directives, context) { +export function build_style_directives_object(style_directives, expressions, context) { let normal_properties = []; let important_properties = []; @@ -514,7 +517,7 @@ export function build_style_directives_object(style_directives, context) { directive.value === true ? build_getter({ name: directive.name, type: 'Identifier' }, context.state) : build_attribute_value(directive.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(context.state, value) : value + metadata.has_call ? get_expression_id(expressions, value) : value ).value; const property = b.init(directive.name, expression); @@ -653,7 +656,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont ? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately is_select_with_value ? memoize_expression(state, value) - : get_expression_id(state, value) + : get_expression_id(state.expressions, value) // TODO i think this will break in spread, needs to be `expressions` : value ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 7ed941efb703..595feca5627f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -71,7 +71,7 @@ export function build_set_attributes( b.prop( 'init', b.array([b.id('$.CLASS')]), - build_class_directives_object(class_directives, context) + build_class_directives_object(class_directives, expressions, context) ) ); } @@ -81,7 +81,7 @@ export function build_set_attributes( b.prop( 'init', b.array([b.id('$.STYLE')]), - build_style_directives_object(style_directives, context) + build_style_directives_object(style_directives, expressions, context) ) ); } @@ -160,7 +160,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c value = b.call('$.clsx', value); } - return metadata.has_call ? get_expression_id(context.state, value) : value; + return metadata.has_call ? get_expression_id(context.state.expressions, value) : value; }); /** @type {Identifier | undefined} */ @@ -173,7 +173,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c let next; if (class_directives.length) { - next = build_class_directives_object(class_directives, context); + next = build_class_directives_object(class_directives, context.state.expressions, context); has_state ||= class_directives.some((d) => d.metadata.expression.has_state); if (has_state) { @@ -228,7 +228,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c */ export function build_set_style(node_id, attribute, style_directives, context) { let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call ? get_expression_id(context.state, value) : value + metadata.has_call ? get_expression_id(context.state.expressions, value) : value ); /** @type {Identifier | undefined} */ @@ -241,7 +241,7 @@ export function build_set_style(node_id, attribute, style_directives, context) { let next; if (style_directives.length) { - next = build_style_directives_object(style_directives, context); + next = build_style_directives_object(style_directives, context.state.expressions, context); has_state ||= style_directives.some((d) => d.metadata.expression.has_state); if (has_state) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 80b472ac378e..8d2f62735f3e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -22,11 +22,11 @@ export function memoize_expression(state, value) { /** * - * @param {ComponentClientTransformState} state + * @param {Expression[]} expressions * @param {Expression} value */ -export function get_expression_id(state, value) { - return b.id(`$${state.expressions.push(value) - 1}`); +export function get_expression_id(expressions, value) { + return b.id(`$${expressions.push(value) - 1}`); } /** @@ -40,7 +40,8 @@ export function build_template_chunk( values, visit, state, - memoize = (value, metadata) => (metadata.has_call ? get_expression_id(state, value) : value) + memoize = (value, metadata) => + metadata.has_call ? get_expression_id(state.expressions, value) : value ) { /** @type {Expression[]} */ const expressions = []; From df7fb8d580a171311d729eff8bd1e531023cb808 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 11:55:07 -0400 Subject: [PATCH 04/16] WIP --- .../3-transform/client/visitors/RegularElement.js | 10 +--------- .../3-transform/client/visitors/SvelteElement.js | 5 +---- .../3-transform/client/visitors/shared/element.js | 4 +--- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index d46b7e6080d2..1393342eeefe 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -203,15 +203,7 @@ export function RegularElement(node, context) { if (has_spread) { const attributes_id = b.id(context.state.scope.generate('attributes')); - build_set_attributes( - attributes, - class_directives, - style_directives, - context, - node, - node_id, - attributes_id - ); + build_set_attributes(attributes, class_directives, style_directives, context, node, node_id); // If value binding exists, that one takes care of calling $.init_select if (node.name === 'select' && !bindings.has('value')) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js index 7e97035e0017..951021740a73 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteElement.js @@ -80,8 +80,6 @@ export function SvelteElement(node, context) { ) { build_set_class(node, element_id, attributes[0], class_directives, inner_context, false); } else if (attributes.length) { - const attributes_id = b.id(context.state.scope.generate('attributes')); - // Always use spread because we don't know whether the element is a custom element or not, // therefore we need to do the "how to set an attribute" logic at runtime. build_set_attributes( @@ -90,8 +88,7 @@ export function SvelteElement(node, context) { style_directives, inner_context, node, - element_id, - attributes_id + element_id ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 595feca5627f..867d18f5e38f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -16,7 +16,6 @@ import { build_template_chunk, get_expression_id } from './utils.js'; * @param {ComponentContext} context * @param {AST.RegularElement | AST.SvelteElement} element * @param {Identifier} element_id - * @param {Identifier} attributes_id */ export function build_set_attributes( attributes, @@ -24,8 +23,7 @@ export function build_set_attributes( style_directives, context, element, - element_id, - attributes_id + element_id ) { /** @type {ObjectExpression['properties']} */ const values = []; From 1a1db41293e6298dc1169d2171f526f870eef999 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 11:57:00 -0400 Subject: [PATCH 05/16] WIP --- .../phases/3-transform/client/visitors/shared/element.js | 2 +- packages/svelte/src/internal/client/dom/elements/attributes.js | 2 +- packages/svelte/src/internal/client/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 867d18f5e38f..78cff78209fb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -87,7 +87,7 @@ export function build_set_attributes( context.state.init.push( b.stmt( b.call( - '$.set_attribute_effect', + '$.attribute_effect', element_id, b.arrow( expressions.map((_, i) => b.id(`$${i}`)), diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 8561de31da16..35afa5e7c4dd 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -466,7 +466,7 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal * @param {string} [css_hash] * @param {boolean} [skip_warning] */ -export function set_attribute_effect( +export function attribute_effect( element, fn, thunks = [], diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index f3249ca749ab..fdc84b115226 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -28,7 +28,7 @@ export { remove_input_defaults, set_attribute, set_attributes, - set_attribute_effect, + attribute_effect, set_custom_element_data, set_xlink_attribute, set_value, From fe4b86d36b5b90eb25a821792a71474b8446b1f8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 12:06:18 -0400 Subject: [PATCH 06/16] WIP --- .../src/internal/client/dom/elements/attributes.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 35afa5e7c4dd..80d390441d62 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -1,3 +1,4 @@ +/** @import { Effect } from '#client' */ import { DEV } from 'esm-env'; import { hydrating, set_hydrating } from '../hydration.js'; import { get_descriptors, get_prototype_of } from '../../../shared/utils.js'; @@ -479,7 +480,7 @@ export function attribute_effect( /** @type {Record} */ var prev = {}; - /** @type {Record} */ + /** @type {Record} */ var effects = {}; block(() => { @@ -487,9 +488,16 @@ export function attribute_effect( set_attributes(element, prev, next, css_hash, skip_warning); + for (let symbol of Object.getOwnPropertySymbols(effects)) { + if (!next[symbol]) destroy_effect(effects[symbol]); + } + for (let symbol of Object.getOwnPropertySymbols(next)) { - if (symbol.description === ATTACHMENT_KEY && next[symbol] !== prev[symbol]) { - effects[symbol] = branch(() => attach(element, () => next[symbol])); + var n = next[symbol]; + + if (symbol.description === ATTACHMENT_KEY && n !== prev[symbol]) { + if (effects[symbol]) destroy_effect(effects[symbol]); + effects[symbol] = branch(() => attach(element, () => n)); } } From 5876ef710277af139802865d6d51ea01e73c8a90 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 25 May 2025 12:33:21 -0400 Subject: [PATCH 07/16] WIP --- .../client/visitors/RegularElement.js | 38 +++++++++---------- .../client/dom/elements/attributes.js | 16 +++++++- .../client/dom/elements/bindings/select.js | 16 +++----- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 1393342eeefe..07760592d4f4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -205,25 +205,25 @@ export function RegularElement(node, context) { build_set_attributes(attributes, class_directives, style_directives, context, node, node_id); - // If value binding exists, that one takes care of calling $.init_select - if (node.name === 'select' && !bindings.has('value')) { - context.state.init.push( - b.stmt(b.call('$.init_select', node_id, b.thunk(b.member(attributes_id, 'value')))) - ); - - context.state.update.push( - b.if( - b.binary('in', b.literal('value'), attributes_id), - b.block([ - // This ensures a one-way street to the DOM in case it's . We need it in addition to $.init_select - // because the select value is not reflected as an attribute, so the - // mutation observer wouldn't notice. - b.stmt(b.call('$.select_option', node_id, b.member(attributes_id, 'value'))) - ]) - ) - ); - } + // // If value binding exists, that one takes care of calling $.init_select + // if (node.name === 'select' && !bindings.has('value')) { + // context.state.init.push( + // b.stmt(b.call('$.init_select', node_id, b.thunk(b.member(attributes_id, 'value')))) + // ); + + // context.state.update.push( + // b.if( + // b.binary('in', b.literal('value'), attributes_id), + // b.block([ + // // This ensures a one-way street to the DOM in case it's . We need it in addition to $.init_select + // // because the select value is not reflected as an attribute, so the + // // mutation observer wouldn't notice. + // b.stmt(b.call('$.select_option', node_id, b.member(attributes_id, 'value'))) + // ]) + // ) + // ); + // } } else { /** If true, needs `__value` for inputs */ const needs_special_value_handling = diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 80d390441d62..103c2cfcdb74 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -20,8 +20,9 @@ import { clsx } from '../../../shared/attributes.js'; import { set_class } from './class.js'; import { set_style } from './style.js'; import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; -import { block, branch, destroy_effect } from '../../reactivity/effects.js'; +import { block, branch, destroy_effect, effect } from '../../reactivity/effects.js'; import { derived } from '../../reactivity/deriveds.js'; +import { init_select, select_option } from './bindings/select.js'; export const CLASS = Symbol('class'); export const STYLE = Symbol('style'); @@ -483,11 +484,18 @@ export function attribute_effect( /** @type {Record} */ var effects = {}; + var is_select = element.nodeName === 'SELECT'; + var inited = false; + block(() => { var next = fn(...deriveds.map(get)); set_attributes(element, prev, next, css_hash, skip_warning); + if (inited && is_select) { + select_option(/** @type {HTMLSelectElement} */ (element), next.value, false); + } + for (let symbol of Object.getOwnPropertySymbols(effects)) { if (!next[symbol]) destroy_effect(effects[symbol]); } @@ -503,6 +511,12 @@ export function attribute_effect( prev = next; }); + + if (is_select) { + init_select(/** @type {HTMLSelectElement} */ (element), () => prev.value); + } + + inited = true; } /** diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js index 20f95af5ec0c..c9b7cea4f021 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -25,7 +25,11 @@ export function select_option(select, value, mounting) { } // Otherwise, update the selection - return select_options(select, value); + for (var option of select.options) { + option.selected = value.includes(get_option_value(option)); + } + + return; } for (var option of select.options) { @@ -136,16 +140,6 @@ export function bind_select_value(select, get, set = get) { init_select(select); } -/** - * @param {HTMLSelectElement} select - * @param {unknown[]} value - */ -function select_options(select, value) { - for (var option of select.options) { - option.selected = value.includes(get_option_value(option)); - } -} - /** @param {HTMLOptionElement} option */ function get_option_value(option) { // __value only exists if the