diff --git a/packages/svelte/src/internal/client/custom-renderer/state.js b/packages/svelte/src/internal/client/custom-renderer/state.js index 007da9ee8d..7e24ab0550 100644 --- a/packages/svelte/src/internal/client/custom-renderer/state.js +++ b/packages/svelte/src/internal/client/custom-renderer/state.js @@ -7,6 +7,14 @@ import { hydrating, set_hydrating } from '../dom/hydration.js'; */ export let current_renderer = null; +/** + * The renderer that was active before `current_renderer` was pushed. This + * allows custom renderers to be interleaved (e.g. a custom renderer rendering + * into another one) by keeping a reference to the renderer one level up. + * @type {Renderer | null} + */ +export let parent_renderer = null; + /** * @param {Renderer | null} value */ @@ -14,11 +22,21 @@ export function set_renderer(value) { current_renderer = value; } +/** + * @param {Renderer | null} value + */ +export function set_parent_renderer(value) { + parent_renderer = value; +} + /** * * @param {Renderer | null} value + * @param {Renderer | null} [parent] the renderer to restore as the + * `parent_renderer`. Defaults to the current renderer so that, in the common case, the + * renderer that was active before this push becomes the parent. */ -export function push_renderer(value) { +export function push_renderer(value, parent = current_renderer) { var previous_hydrating = hydrating; var should_disable_hydration = hydrating && value != null; // this is to allow hydration code to treeshake @@ -26,10 +44,13 @@ export function push_renderer(value) { set_hydrating(false); } var previous_renderer = current_renderer; + var previous_parent_renderer = parent_renderer; + parent_renderer = parent; current_renderer = value; return () => { current_renderer = previous_renderer; + parent_renderer = previous_parent_renderer; if (should_disable_hydration) { set_hydrating(previous_hydrating); } diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 3cca70076a..dffc8ddad2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -230,7 +230,7 @@ export class Boundary { this.#pending_effect = branch(() => pending(this.#anchor)); queue_micro_task(() => { - var pop_renderer = push_renderer(this.#effect.r); + var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); var fragment = (this.#offscreen_fragment = create_fragment()); var anchor = create_text(); @@ -324,7 +324,7 @@ export class Boundary { set_active_reaction(this.#effect); set_component_context(this.#effect.ctx); - var pop_renderer = push_renderer(this.#effect.r); + var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); try { Batch.ensure(); @@ -368,7 +368,7 @@ export class Boundary { } if (this.#offscreen_fragment) { - var pop_renderer = push_renderer(this.#effect.r); + var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); insert_before(this.#anchor, this.#offscreen_fragment); this.#offscreen_fragment = null; pop_renderer?.(); diff --git a/packages/svelte/src/internal/client/dom/blocks/branches.js b/packages/svelte/src/internal/client/dom/blocks/branches.js index 2c045df256..3f3f54fe8b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/branches.js +++ b/packages/svelte/src/internal/client/dom/blocks/branches.js @@ -20,7 +20,7 @@ import { get_last_child } from '../operations.js'; import { DEV } from 'esm-env'; -import { push_renderer, current_renderer } from '../../custom-renderer/state.js'; +import { push_renderer, current_renderer, parent_renderer } from '../../custom-renderer/state.js'; /** * @typedef {{ effect: Effect, fragment: DocumentFragment }} Branch @@ -79,6 +79,12 @@ export class BranchManager { */ #renderer = null; + /** + * The parent renderer that was active when this BranchManager was created. + * @type {Renderer | null} + */ + #parent_renderer = null; + /** * @param {TemplateNode} anchor * @param {boolean} transition @@ -87,6 +93,7 @@ export class BranchManager { this.anchor = anchor; this.#transition = transition; this.#renderer = current_renderer; + this.#parent_renderer = parent_renderer; } /** @@ -96,7 +103,7 @@ export class BranchManager { // if this batch was made obsolete, bail if (!this.#batches.has(batch)) return; - var pop_renderer = push_renderer(this.#renderer); + var pop_renderer = push_renderer(this.#renderer, this.#parent_renderer); var key = /** @type {Key} */ (this.#batches.get(batch)); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 5e8bccd37d..763bc06d22 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -50,7 +50,7 @@ import { current_batch } from '../../reactivity/batch.js'; import * as e from '../../errors.js'; import { tag } from '../../dev/tracing.js'; -import { push_renderer, current_renderer } from '../../custom-renderer/state.js'; +import { push_renderer, current_renderer, parent_renderer } from '../../custom-renderer/state.js'; // When making substantive changes to this file, validate them with the each block stress test: // https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b @@ -220,6 +220,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f // Needed so that the commit callback can push the correct renderer when doing // DOM operations outside of an effect context (e.g. as a batch commit callback). var renderer = current_renderer; + var parent = parent_renderer; if (is_controlled) { var parent_node = /** @type {Element} */ (node); @@ -267,7 +268,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f return; } - var pop_renderer = push_renderer(renderer); + var pop_renderer = push_renderer(renderer, parent); state.pending.delete(batch); diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 91afbee853..03afc6c644 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -9,7 +9,12 @@ import { set_dev_stack } from '../context.js'; import { Boundary } from '../dom/blocks/boundary.js'; -import { current_renderer, set_renderer } from '../custom-renderer/state.js'; +import { + current_renderer, + parent_renderer, + set_parent_renderer, + set_renderer +} from '../custom-renderer/state.js'; import { invoke_error_boundary } from '../error-handling.js'; import { active_effect, @@ -134,6 +139,7 @@ export function capture() { var previous_component_context = component_context; var previous_batch = /** @type {Batch} */ (current_batch); var previous_renderer = current_renderer; + var previous_parent_renderer = parent_renderer; if (DEV) { var previous_dev_stack = dev_stack; @@ -145,6 +151,7 @@ export function capture() { set_component_context(previous_component_context); set_renderer(previous_renderer); + set_parent_renderer(previous_parent_renderer); if (activate_batch && (previous_effect.f & DESTROYED) === 0) { // TODO we only need optional chaining here because `{#await ...}` blocks @@ -277,6 +284,7 @@ export function unset_context(deactivate_batch = true) { set_active_reaction(null); set_component_context(null); set_renderer(null); + set_parent_renderer(null); if (deactivate_batch) current_batch?.deactivate(); if (DEV) { diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index ae7408abd8..b6cb56a866 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -45,7 +45,7 @@ import { Batch, collected_effects, current_batch } from './batch.js'; import { flatten } from './async.js'; import { without_reactive_context } from '../dom/elements/bindings/shared.js'; import { set_signal_status } from './status.js'; -import { push_renderer, current_renderer } from '../custom-renderer/state.js'; +import { push_renderer, current_renderer, parent_renderer } from '../custom-renderer/state.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -114,7 +114,8 @@ function create_effect(type, fn) { teardown: null, wv: 0, ac: null, - r: current_renderer + r: current_renderer, + pr: parent_renderer }; if (DEV) { @@ -513,7 +514,7 @@ export function destroy_block_effect_children(signal) { export function destroy_effect(effect, remove_dom = true) { var removed = false; - var pop_renderer = push_renderer(effect.r); + var pop_renderer = push_renderer(effect.r, effect.pr); if ( (remove_dom || (effect.f & HEAD_EFFECT) !== 0) && @@ -564,6 +565,7 @@ export function destroy_effect(effect, remove_dom = true) { effect.ac = effect.b = effect.r = + effect.pr = null; pop_renderer?.(); @@ -738,7 +740,7 @@ export function aborted(effect = /** @type {Effect} */ (active_effect)) { export function move_effect(effect, fragment) { if (!effect.nodes) return; - var pop_renderer = push_renderer(effect.r); + var pop_renderer = push_renderer(effect.r, effect.pr); /** @type {TemplateNode | null} */ var node = effect.nodes.start; diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 5f513ef1ad..ecae5f1d36 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -97,6 +97,8 @@ export interface Effect extends Reaction { b: Boundary | null; /** The renderer this effect was created with */ r: Renderer | null; + /** The parent renderer this effect was created with */ + pr: Renderer | null; /** Dev only */ component_function?: any; /** Dev only. Only set for certain block effects. Contains a reference to the stack that represents the render tree */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 45171ca4a7..b5de8032e9 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -447,7 +447,7 @@ export function update_effect(effect) { active_effect = effect; is_updating_effect = true; - var pop_renderer = push_renderer(effect.r); + var pop_renderer = push_renderer(effect.r, effect.pr); if (DEV) { var previous_component_fn = dev_current_component_function;