fix: deal with spreads on select AND option

pull/16017/head
paoloricciuti 4 months ago
parent 4fc352642a
commit 68c30bca4c

@ -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;
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)))
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.type === 'Attribute') {
} else if (value) {
select_with_value = true;
const left = b.id('$$payload.select_value');
if (value.type === 'Attribute') {
state.template.push(
b.stmt(b.assignment('=', left, build_attribute_value(value.value, context)))
);

@ -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<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} 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

@ -1,12 +1,12 @@
import { ok, test } from '../../test';
export default test({
html: `
ssrHtml: `
<p>selected: b</p>
<select>
<option>a</option>
<option>b</option>
<option selected>b</option>
<option>c</option>
</select>
@ -17,7 +17,21 @@ export default test({
return { selected: 'b' };
},
test({ assert, target }) {
test({ assert, target, variant }) {
assert.htmlEqual(
target.innerHTML,
`
<p>selected: b</p>
<select>
<option>a</option>
<option${variant === 'hydrate' ? ' selected' : ''}>b</option>
<option>c</option>
</select>
<p>selected: b</p>
`
);
const select = target.querySelector('select');
ok(select);
const options = [...target.querySelectorAll('option')];

@ -2,11 +2,11 @@ import { flushSync } from 'svelte';
import { ok, test } from '../../test';
export default test({
html: `
ssrHtml: `
<p>selected: one</p>
<select>
<option>one</option>
<option selected>one</option>
<option>two</option>
<option>three</option>
</select>
@ -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,
`
<p>selected: one</p>
<select>
<option${variant === 'hydrate' ? ' selected' : ''}>one</option$>
<option>two</option>
<option>three</option>
</select>
<p>selected: one</p>
`
);
const select = target.querySelector('select');
ok(select);
@ -40,7 +54,7 @@ export default test({
<p>selected: two</p>
<select>
<option>one</option>
<option${variant === 'hydrate' ? ' selected' : ''}>one</option$>
<option>two</option>
<option>three</option>
</select>

@ -1,9 +1,19 @@
import { test } from '../../test';
export default test({
html: `
ssrHtml: `
<select>
<option value="value" class="option">Label</option>
<option selected value="value" class="option">Label</option>
</select>
`,
test({ assert, target, variant }) {
assert.htmlEqual(
target.innerHTML,
`
<select>
<option ${variant === 'hydrate' ? 'selected ' : ''}value="value" class="option">Label</option>
</select>
`
);
}
});

@ -3,5 +3,5 @@ import * as $ from 'svelte/internal/server';
export default function Skip_static_subtree($$payload, $$props) {
let { title, content } = $$props;
$$payload.out += `<header><nav><a href="/">Home</a> <a href="/away">Away</a></nav></header> <main><h1>${$.escape(title)}</h1> <div class="static"><p>we don't need to traverse these nodes</p></div> <p>or</p> <p>these</p> <p>ones</p> ${$.html(content)} <p>these</p> <p>trailing</p> <p>nodes</p> <p>can</p> <p>be</p> <p>completely</p> <p>ignored</p></main> <cant-skip><custom-elements with="attributes"></custom-elements></cant-skip> <div><input autofocus/></div> <div><source muted/></div> <select><option value="a"${$.maybe_selected($$payload, 'a')}>a</option></select> <img src="..." alt="" loading="lazy"/> <div><img src="..." alt="" loading="lazy"/></div>`;
$$payload.out += `<header><nav><a href="/">Home</a> <a href="/away">Away</a></nav></header> <main><h1>${$.escape(title)}</h1> <div class="static"><p>we don't need to traverse these nodes</p></div> <p>or</p> <p>these</p> <p>ones</p> ${$.html(content)} <p>these</p> <p>trailing</p> <p>nodes</p> <p>can</p> <p>be</p> <p>completely</p> <p>ignored</p></main> <cant-skip><custom-elements with="attributes"></custom-elements></cant-skip> <div><input autofocus/></div> <div><source muted/></div> <select><option value="a"${$.maybe_selected($$payload, 'a')}${$.maybe_selected($$payload, 'a')}>a</option></select> <img src="..." alt="" loading="lazy"/> <div><img src="..." alt="" loading="lazy"/></div>`;
}
Loading…
Cancel
Save