diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 9e68e7867f..fca13e208e 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -15,7 +15,7 @@ import { TEMPLATE_USE_MATHML, TEMPLATE_USE_SVG } from '../../../constants.js'; -import { current_renderer } from '../custom-renderer/state.js'; +import { current_renderer, parent_renderer } from '../custom-renderer/state.js'; import { active_effect } from '../runtime.js'; import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { @@ -50,10 +50,36 @@ const SCRIPT_TAG = IS_XHTML ? 'script' : 'SCRIPT'; export function assign_nodes(start, end) { var effect = /** @type {Effect} */ (active_effect); if (effect.nodes === null) { - effect.nodes = { start, end, a: null, t: null }; + effect.nodes = { + start, + end, + segments: should_segment_nodes(effect) + ? [{ start, end, r: current_renderer, pr: parent_renderer }] + : null, + a: null, + t: null + }; + } else if (should_segment_nodes(effect)) { + var nodes = effect.nodes; + (nodes.segments ??= [{ start: nodes.start, end: nodes.end, r: effect.r, pr: effect.pr }]).push({ + start, + end, + r: current_renderer, + pr: parent_renderer + }); } } +/** + * @param {Effect} effect + */ +function should_segment_nodes(effect) { + return ( + (current_renderer !== effect.r || parent_renderer !== effect.pr) && + (current_renderer?.foreign != null || parent_renderer?.foreign != null) + ); +} + /** * @param {string} content * @param {number} flags @@ -386,6 +412,12 @@ export function append(anchor, dom) { // of the parent component. Check for defined for that reason to avoid rewinding the parent's end marker. if ((effect.f & REACTION_RAN) === 0 || effect.nodes.end === null) { effect.nodes.end = hydrate_node; + + // this is to cover interleaved custom renders where an hydrated dom component interleaves with a custom renderer + // since it will use segments instead of start/end, we need to make sure to update the segment's end as well + if (effect.nodes.segments !== null) { + effect.nodes.segments[0].end = hydrate_node; + } } hydrate_next(); diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index b6cb56a866..ad2fb4ce27 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -521,7 +521,7 @@ export function destroy_effect(effect, remove_dom = true) { effect.nodes !== null && effect.nodes.end !== null ) { - remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end)); + remove_effect_nodes(effect); removed = true; } @@ -586,6 +586,26 @@ export function remove_effect_dom(node, end) { } } +/** + * @param {Effect} effect + */ +function remove_effect_nodes(effect) { + var nodes = /** @type {NonNullable} */ (effect.nodes); + var segments = nodes.segments; + + if (segments === null) { + remove_effect_dom(nodes.start, /** @type {TemplateNode} */ (nodes.end)); + return; + } + + for (var i = segments.length - 1; i >= 0; i--) { + var segment = segments[i]; + var pop_renderer = push_renderer(segment.r, segment.pr); + remove_effect_dom(segment.start, /** @type {TemplateNode} */ (segment.end)); + pop_renderer?.(); + } +} + /** * Detach an effect from the effect tree, freeing up memory and * reducing the amount of work that happens on subsequent traversals diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index ecae5f1d36..8b5be77af2 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -65,12 +65,21 @@ export interface Derived extends Value, Reaction { export interface EffectNodes { start: TemplateNode; end: TemplateNode | null; + /** Renderer-local ranges, used when a component interleaves custom and foreign-rendered nodes */ + segments: null | EffectNodeSegment[]; /** $.animation */ a: AnimationManager | null; /** $.transition */ t: TransitionManager[] | null; } +export interface EffectNodeSegment { + start: TemplateNode; + end: TemplateNode | null; + r: Renderer | null; + pr: Renderer | null; +} + export interface Effect extends Reaction { /** * Branch effects store their start/end nodes so that they can be