fix: runtime valueless option selection

pull/16017/head
paoloricciuti 4 months ago
parent e69e5525b0
commit 7df45050d7

@ -137,19 +137,35 @@ export function RegularElement(node, context) {
state.template.push(b.const(id, body)); state.template.push(b.const(id, body));
} }
// if this is a `<textarea>` value or a contenteditable binding, we only add // we need the body if:
// the body if the attribute/binding is falsy
// - it's a <textarea> or a contenteditable element...we will add it ad the inner template in case the value of value is falsy
// - it's a valueless <option> element...we will check the body value at runtime to determine the implicit value selection
const inner_state = { ...state, template: [], init: [] }; const inner_state = { ...state, template: [], init: [] };
process_children(trimmed, { ...context, state: inner_state }); process_children(trimmed, { ...context, state: inner_state });
// Use the body expression as the body if it's truthy, otherwise use the inner template if (node.name === 'option') {
state.template.push( // in case of a valueless `<option>` element, $$body is a function that accepts the children...internally it
b.if( // will run the children to get the value of the body at runtime since it's needed to for the implicit value
id, // selection
b.block(build_template([id])), state.template.push(
b.block([...inner_state.init, ...build_template(inner_state.template)]) b.stmt(
) b.call(
); id,
b.thunk(b.block([...inner_state.init, ...build_template(inner_state.template)]))
)
)
);
} else {
// Use the body expression as the body if it's truthy, otherwise use the inner template
state.template.push(
b.if(
id,
b.block(build_template([id])),
b.block([...inner_state.init, ...build_template(inner_state.template)])
)
);
}
} }
if (select_with_value) { if (select_with_value) {

@ -305,14 +305,7 @@ export function build_element_attributes(node, context) {
} }
if (!option_has_value && node.name === 'option') { if (!option_has_value && node.name === 'option') {
context.state.template.push( content = b.call('$.valueless_option', b.id('$$payload'));
b.call(
'$.maybe_selected',
b.id('$$payload'),
// this should be fine for the moment since option can only contain text and expressions
build_attribute_value(/** @type {*} */ (node.fragment.nodes), context)
)
);
} }
if (events_to_capture.size !== 0) { if (events_to_capture.size !== 0) {

@ -544,3 +544,24 @@ export function derived(fn) {
export function maybe_selected(payload, value) { export function maybe_selected(payload, value) {
return value === payload.select_value ? ' selected' : ''; return value === payload.select_value ? ' selected' : '';
} }
/**
* @param {Payload} payload
* @returns {(child: () => void) => void}
*/
export function valueless_option(payload) {
return (child) => {
// store the initial payload before executing the child
let initial_payload = payload.out;
// execute the child to get the runtime body of the option
child();
// remove the initial payload and eventual hydration comments
let body = payload.out.substring(initial_payload.length).replace(/<!---->/g, '');
// check the value of the body with the select_value
if (body === payload.select_value) {
// substring the initial payload to remove the last character (the closing `>`)
// append selected and the body (the closing tag will be added later)
payload.out = initial_payload.substring(0, initial_payload.length - 1) + ' selected>' + body;
}
};
}

@ -1 +1 @@
<select><option value="">--Please choose an option--</option><option value="dog" selected>Dog</option><option value="cat">Cat</option></select> <select><option>--Please choose an option--</option><option selected>dog</option><option>cat</option></select>

@ -1,7 +1,13 @@
<select value="dog"> <select value="dog">
<option>{@render option("--Please choose an option--")}</option> <option>
<option>{@render option("dog")}</option> {@render option("--Please choose an option--")}
<option>{@render option("cat")}</option> </option>
<option>
{@render option("dog")}
</option>
<option>
{@render option("cat")}
</option>
</select> </select>
{#snippet option(val)}{val}{/snippet} {#snippet option(val)}{val}{/snippet}

@ -3,5 +3,13 @@ import * as $ from 'svelte/internal/server';
export default function Skip_static_subtree($$payload, $$props) { export default function Skip_static_subtree($$payload, $$props) {
let { title, content } = $$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')}${$.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')}>`;
const $$body = $.valueless_option($$payload);
$$body(() => {
$$payload.out += `a`;
});
$$payload.out += `</option></select> <img src="..." alt="" loading="lazy"/> <div><img src="..." alt="" loading="lazy"/></div>`;
} }
Loading…
Cancel
Save