From 259d286f14b186d68f9dfb64c20e00159ca96a8c Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 28 Aug 2025 15:26:48 -0600 Subject: [PATCH] fix select/option stuff --- .../2-analyze/visitors/RegularElement.js | 2 +- .../client/visitors/RegularElement.js | 36 +++++++++++++---- .../server/visitors/RegularElement.js | 39 ++++++++++++------- .../svelte/src/compiler/types/template.d.ts | 4 ++ packages/svelte/src/internal/server/index.js | 30 ++++++++++++-- .../_config.js | 18 ++++----- .../_config.js | 6 +-- .../samples/binding-select-late-2/_config.js | 6 +-- .../samples/binding-select-late-3/_config.js | 6 +-- .../samples/binding-select-late/_config.js | 6 +-- .../binding-select-unmatched-2/_config.js | 30 +++++++------- .../binding-select-unmatched-3/_config.js | 4 +- 12 files changed, 123 insertions(+), 64 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js index fab5d46e1b..4f41621db3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js @@ -70,7 +70,7 @@ export function RegularElement(node, context) { ) ) { const child = node.fragment.nodes[0]; - node.attributes.push(create_attribute('value', child.start, child.end, [child])); + node.metadata.synthetic_value_node = child; } const binding = context.state.scope.get(node.name); 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 4296aa959e..e08878cce5 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 @@ -11,7 +11,7 @@ import { import { is_ignored } from '../../../../state.js'; import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; -import { is_custom_element_node } from '../../../nodes.js'; +import { create_attribute, is_custom_element_node } from '../../../nodes.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; import { build_getter } from '../utils.js'; import { @@ -392,10 +392,25 @@ export function RegularElement(node, context) { } if (!has_spread && needs_special_value_handling) { - for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { - if (attribute.name === 'value') { - build_element_special_value_attribute(node.name, node_id, attribute, context); - break; + if (node.metadata.synthetic_value_node) { + const synthetic_node = node.metadata.synthetic_value_node; + const synthetic_attribute = create_attribute( + 'value', + synthetic_node.start, + synthetic_node.end, + [synthetic_node] + ); + // TODO idk if necessary + synthetic_attribute.metadata.synthetic_option_value = true; + // this node is an `option` that didn't have a `value` attribute, but had + // a single-expression child, so we synthesize a value for it + build_element_special_value_attribute(node.name, node_id, synthetic_attribute, context); + } else { + for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { + if (attribute.name === 'value') { + build_element_special_value_attribute(node.name, node_id, attribute, context); + break; + } } } } @@ -646,7 +661,10 @@ function build_element_special_value_attribute(element, node_id, attribute, cont const evaluated = context.state.scope.evaluate(value); const assignment = b.assignment('=', b.member(node_id, '__value'), value); - const inner_assignment = b.assignment( + const is_synthetic_option = + element === 'option' && attribute.metadata.synthetic_option_value === true; + + const set_value_assignment = b.assignment( '=', b.member(node_id, 'value'), evaluated.is_defined ? assignment : b.logical('??', assignment, b.literal('')) @@ -655,14 +673,16 @@ function build_element_special_value_attribute(element, node_id, attribute, cont const update = b.stmt( is_select_with_value ? b.sequence([ - inner_assignment, + set_value_assignment, // 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.call('$.select_option', node_id, value) ]) - : inner_assignment + : is_synthetic_option + ? assignment + : set_value_assignment ); if (has_state) { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js index cdc1a14f84..ed13fb8a53 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js @@ -143,22 +143,33 @@ export function RegularElement(node, context) { attribute.name === 'value') ) ) { - const inner_state = { ...state, template: [], init: [] }; - process_children(trimmed, { ...context, state: inner_state }); - - state.template.push( - b.stmt( - b.call( - '$.valueless_option', - b.id('$$payload'), - b.arrow( - [b.id('$$payload')], - b.block([...inner_state.init, ...build_template(inner_state.template)]), - context.state.analysis.has_blocking_await + if (node.metadata.synthetic_value_node) { + state.template.push( + b.stmt( + b.call( + '$.simple_valueless_option', + b.id('$$payload'), + node.metadata.synthetic_value_node.expression ) ) - ) - ); + ); + } else { + const inner_state = { ...state, template: [], init: [] }; + process_children(trimmed, { ...context, state: inner_state }); + state.template.push( + b.stmt( + b.call( + '$.valueless_option', + b.id('$$payload'), + b.arrow( + [b.id('$$payload')], + b.block([...inner_state.init, ...build_template(inner_state.template)]), + context.state.analysis.has_blocking_await + ) + ) + ) + ); + } } else if (body !== null) { // if this is a `