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 4c248cb19c..cbdf3fa9bb 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 @@ -6,7 +6,7 @@ import { is_void } from '../../../../../utils.js'; import { dev, locator } from '../../../../state.js'; import * as b from '#compiler/builders'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; -import { build_element_attributes } from './shared/element.js'; +import { build_element_attributes, build_spread_object } from './shared/element.js'; import { process_children, build_template, build_attribute_value } from './shared/utils.js'; /** @@ -76,18 +76,42 @@ export function RegularElement(node, context) { if (node.name === 'select') { const value = node.attributes.find( (attribute) => - ((attribute.type === 'Attribute' || attribute.type === 'BindDirective') && - attribute.name === 'value') || - attribute.type === 'SpreadAttribute' + (attribute.type === 'Attribute' || attribute.type === 'BindDirective') && + attribute.name === 'value' ); - if (value) { + if (node.attributes.some((attribute) => attribute.type === 'SpreadAttribute')) { + select_with_value = true; + state.template.push( + b.stmt( + b.assignment( + '=', + b.id('$$payload.select_value'), + b.member( + b.call( + '$.spread_attributes', + build_spread_object( + node, + node.attributes.filter( + (attribute) => + attribute.type === 'Attribute' || + attribute.type === 'BindDirective' || + attribute.type === 'SpreadAttribute' + ), + context + ), + b.null + ), + 'value', + false, + true + ) + ) + ) + ); + } else if (value) { select_with_value = true; const left = b.id('$$payload.select_value'); - if (value.type === 'SpreadAttribute') { - state.template.push( - b.stmt(b.assignment('=', left, b.member(value.expression, 'value', false, true))) - ); - } else if (value.type === 'Attribute') { + if (value.type === 'Attribute') { state.template.push( b.stmt(b.assignment('=', left, build_attribute_value(value.value, context))) ); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index af493e82b1..54250eda8c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -204,6 +204,34 @@ export function build_element_attributes(node, context) { if (has_spread) { build_element_spread_attributes(node, attributes, style_directives, class_directives, context); + if (node.name === 'option') { + option_has_value = true; + context.state.template.push( + b.call( + '$.maybe_selected', + b.id('$$payload'), + b.member( + b.call( + '$.spread_attributes', + build_spread_object( + node, + node.attributes.filter( + (attribute) => + attribute.type === 'Attribute' || + attribute.type === 'BindDirective' || + attribute.type === 'SpreadAttribute' + ), + context + ), + b.null + ), + 'value', + false, + true + ) + ) + ); + } } else { const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null; @@ -302,7 +330,7 @@ export function build_element_attributes(node, context) { /** * @param {AST.RegularElement | AST.SvelteElement} element - * @param {AST.Attribute} attribute + * @param {AST.Attribute | AST.BindDirective} attribute */ function get_attribute_name(element, attribute) { let name = attribute.name; @@ -314,6 +342,36 @@ function get_attribute_name(element, attribute) { return name; } +/** + * @param {AST.RegularElement | AST.SvelteElement} element + * @param {Array} attributes + * @param {ComponentContext} context + */ +export function build_spread_object(element, attributes, context) { + return b.object( + attributes.map((attribute) => { + if (attribute.type === 'Attribute') { + const name = get_attribute_name(element, attribute); + const value = build_attribute_value( + attribute.value, + context, + WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name) + ); + return b.prop('init', b.key(name), value); + } else if (attribute.type === 'BindDirective') { + const name = get_attribute_name(element, attribute); + const value = + attribute.expression.type === 'SequenceExpression' + ? b.call(attribute.expression.expressions[0]) + : /** @type {Expression} */ (context.visit(attribute.expression)); + return b.prop('init', b.key(name), value); + } + + return b.spread(/** @type {Expression} */ (context.visit(attribute))); + }) + ); +} + /** * * @param {AST.RegularElement | AST.SvelteElement} element @@ -364,21 +422,7 @@ function build_element_spread_attributes( flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE; } - const object = b.object( - attributes.map((attribute) => { - if (attribute.type === 'Attribute') { - const name = get_attribute_name(element, attribute); - const value = build_attribute_value( - attribute.value, - context, - WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name) - ); - return b.prop('init', b.key(name), value); - } - - return b.spread(/** @type {Expression} */ (context.visit(attribute))); - }) - ); + const object = build_spread_object(element, attributes, context); const css_hash = element.metadata.scoped && context.state.analysis.css.hash diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-select-initial-value/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-select-initial-value/_config.js index 176a1e0dfc..c19272fce0 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-select-initial-value/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/binding-select-initial-value/_config.js @@ -1,12 +1,12 @@ import { ok, test } from '../../test'; export default test({ - html: ` + ssrHtml: `

selected: b

@@ -17,7 +17,21 @@ export default test({ return { selected: 'b' }; }, - test({ assert, target }) { + test({ assert, target, variant }) { + assert.htmlEqual( + target.innerHTML, + ` +

selected: b

+ + + +

selected: b

+ ` + ); const select = target.querySelector('select'); ok(select); const options = [...target.querySelectorAll('option')]; diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-select/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-select/_config.js index 5577ec2393..2507f5fc83 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-select/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/binding-select/_config.js @@ -2,11 +2,11 @@ import { flushSync } from 'svelte'; import { ok, test } from '../../test'; export default test({ - html: ` + ssrHtml: `

selected: one

@@ -18,7 +18,21 @@ export default test({ return { selected: 'one' }; }, - test({ assert, component, target, window }) { + test({ assert, component, target, window, variant }) { + assert.htmlEqual( + target.innerHTML, + ` +

selected: one

+ + + +

selected: one

+ ` + ); const select = target.querySelector('select'); ok(select); @@ -40,7 +54,7 @@ export default test({

selected: two

diff --git a/packages/svelte/tests/runtime-legacy/samples/select-options-spread-attributes/_config.js b/packages/svelte/tests/runtime-legacy/samples/select-options-spread-attributes/_config.js index df0e94b7da..dfbf890fca 100644 --- a/packages/svelte/tests/runtime-legacy/samples/select-options-spread-attributes/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/select-options-spread-attributes/_config.js @@ -1,9 +1,19 @@ import { test } from '../../test'; export default test({ - html: ` + ssrHtml: ` - ` + `, + test({ assert, target, variant }) { + assert.htmlEqual( + target.innerHTML, + ` + + ` + ); + } }); diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js index 0532ec5aa9..2dc6abd731 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js @@ -3,5 +3,5 @@ import * as $ from 'svelte/internal/server'; export default function Skip_static_subtree($$payload, $$props) { let { title, content } = $$props; - $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}

these

trailing

nodes

can

be

completely

ignored

`; + $$payload.out += `

${$.escape(title)}

we don't need to traverse these nodes

or

these

ones

${$.html(content)}

these

trailing

nodes

can

be

completely

ignored

`; } \ No newline at end of file