fix: store `segments` and use them to remove nodes when interleaving is happening

paoloricciuti 1 month ago
parent fe31c69943
commit 030c665a6f

@ -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();

@ -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']>} */ (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

@ -65,12 +65,21 @@ export interface Derived<V = unknown> extends Value<V>, 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

Loading…
Cancel
Save