fix: trigger selectedcontent reactivity (#17486)

* fix: trigger `selectedcontent` reactivity

* fix: comments

* fix: use `is_supported`

* fix: use `cloneNode` to preserve attributes

* fix: update `selectedcontent` variable after cloning

* Apply suggestions from code review

* Apply suggestions from code review

* remove unused code

* do everything in a single for-of loop

* early return

* shorten

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/17488/head
Paolo Ricciuti 2 days ago committed by GitHub
parent 9cf60666ea
commit 4ea7631bf5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: trigger `selectedcontent` reactivity

@ -80,7 +80,7 @@ export function RegularElement(node, context) {
// Special case: <select>, <option> or <optgroup> with rich content needs special hydration handling
// We mark the subtree as dynamic so parent elements properly include the child init code
if (is_customizable_select_element(node)) {
if (is_customizable_select_element(node) || node.name === 'selectedcontent') {
// Mark the element's own fragment as dynamic so it's not treated as static
node.fragment.metadata.dynamic = true;
// Also mark ancestor fragments so parents properly include the child init code

@ -457,6 +457,18 @@ export function RegularElement(node, context) {
context.state.after_update.push(...element_state.after_update);
}
if (node.name === 'selectedcontent') {
context.state.init.push(
b.stmt(
b.call(
'$.selectedcontent',
context.state.node,
b.arrow([b.id('$$element')], b.assignment('=', context.state.node, b.id('$$element')))
)
)
);
}
if (lookup.has('dir')) {
// This fixes an issue with Chromium where updates to text content within an element
// does not update the direction when set to auto. If we just re-assign the dir, this fixes it.

@ -1,5 +1,6 @@
import { hydrating, reset, set_hydrate_node, set_hydrating } from '../hydration.js';
import { create_comment } from '../operations.js';
import { attach } from './attachments.js';
/** @type {boolean | null} */
let supported = null;
@ -20,6 +21,52 @@ function is_supported() {
return supported;
}
/**
*
* @param {HTMLElement} element
* @param {(new_element: HTMLElement) => void} update_element
*/
export function selectedcontent(element, update_element) {
// if it's not supported no need for special logic
if (!is_supported()) return;
// we use the attach function directly just to make sure is executed when is mounted to the dom
attach(element, () => () => {
const select = element.closest('select');
if (!select) return;
const observer = new MutationObserver((entries) => {
var selected = false;
for (const entry of entries) {
if (entry.target === element) {
// the `<selectedcontent>` already changed, no need to replace it
return;
}
// if the changes doesn't include the selected `<option>` we don't need to do anything
selected ||= !!entry.target.parentElement?.closest('option')?.selected;
}
if (selected) {
// replace the `<selectedcontent>` with a clone
element.replaceWith((element = /** @type {HTMLElement} */ (element.cloneNode(true))));
update_element(element);
}
});
observer.observe(select, {
childList: true,
characterData: true,
subtree: true
});
return () => {
observer.disconnect();
};
});
}
/**
* Handles rich HTML content inside `<option>`, `<optgroup>`, or `<select>` elements with browser-specific branching.
* Modern browsers preserve HTML inside options, while older browsers strip it to text only.

@ -42,7 +42,7 @@ export {
export { set_class } from './dom/elements/class.js';
export { apply, event, delegate, replay_events } from './dom/elements/events.js';
export { autofocus, remove_textarea_child } from './dom/elements/misc.js';
export { customizable_select } from './dom/elements/customizable-select.js';
export { customizable_select, selectedcontent } from './dom/elements/customizable-select.js';
export { set_style } from './dom/elements/style.js';
export { animation, transition } from './dom/elements/transitions.js';
export { bind_active_element } from './dom/elements/bindings/document.js';

Loading…
Cancel
Save