diff --git a/.changeset/free-signs-ask.md b/.changeset/free-signs-ask.md new file mode 100644 index 0000000000..7e6c925526 --- /dev/null +++ b/.changeset/free-signs-ask.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: reliably resolve append_style to its correct root diff --git a/packages/svelte/src/internal/client/dom/css.js b/packages/svelte/src/internal/client/dom/css.js index 74bf2d49f9..23f4eff4f7 100644 --- a/packages/svelte/src/internal/client/dom/css.js +++ b/packages/svelte/src/internal/client/dom/css.js @@ -2,14 +2,19 @@ import { DEV } from 'esm-env'; import { register_style } from '../dev/css.js'; import { effect } from '../reactivity/effects.js'; import { create_element } from './operations.js'; +import { active_effect } from '../runtime.js'; /** * @param {Node} anchor * @param {{ hash: string, code: string }} css */ export function append_styles(anchor, css) { - // Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results + // Use an effect to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results effect(() => { + // Bit of a hack: branches.js/each.js use offscreen fragments with temporary text nodes that will + // never be connected to the real dom. Therfore walk up to the branch that has created the component + // whose styles we want to append, and check its node instead. It will be connected by the time we get here. + anchor = active_effect?.parent?.nodes?.start ?? anchor; var root = anchor.getRootNode(); var target = /** @type {ShadowRoot} */ (root).host diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte new file mode 100644 index 0000000000..86064ab3b1 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte @@ -0,0 +1,7 @@ +

child

+ + diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js new file mode 100644 index 0000000000..739921ebf5 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js @@ -0,0 +1,24 @@ +import { assert_ok, test } from '../../assert'; + +const tick = () => Promise.resolve(); + +export default test({ + async test({ assert, target }) { + target.innerHTML = ''; + + // wait for the initial mount, the `onMount` reveal and the deferred re-render + await tick(); + await tick(); + await tick(); + await tick(); + + /** @type {any} */ + const el = target.querySelector('my-app'); + const p = el.shadowRoot.querySelector('p'); + assert_ok(p); + + // The child's scoped styles must be injected into the shadow root, not `document.head` + assert_ok(el.shadowRoot.querySelector('style')); + assert.equal(getComputedStyle(p).color, 'rgb(255, 0, 0)'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte new file mode 100644 index 0000000000..2bd0456fc1 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte @@ -0,0 +1,18 @@ + + + + +{#each items as item (item)}{/each}