diff --git a/.changeset/stale-nails-listen.md b/.changeset/stale-nails-listen.md new file mode 100644 index 000000000000..9f9b473294f9 --- /dev/null +++ b/.changeset/stale-nails-listen.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +chore: remove anchor node from each block items diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 0b6209c5058a..b8ac9aba112b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -85,7 +85,6 @@ function pause_effects(items, controlled_anchor, items_map) { var item = items[i]; if (!is_controlled) { items_map.delete(item.k); - item.o.remove(); link(item.prev, item.next); } destroy_effect(item.e, !is_controlled); @@ -182,11 +181,10 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback break; } - var child_open = /** @type {Comment} */ (child_anchor); child_anchor = hydrate_anchor(child_anchor); var value = array[i]; var key = get_key(value, i); - item = create_item(child_open, child_anchor, prev, null, value, key, i, render_fn, flags); + item = create_item(child_anchor, prev, null, value, key, i, render_fn, flags); state.items.set(key, item); child_anchor = /** @type {Comment} */ (child_anchor.nextSibling); @@ -293,22 +291,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { item = items.get(key); if (item === undefined) { - var child_open = empty(); - var child_anchor = current ? current.o : anchor; - - child_anchor.before(child_open); - - prev = create_item( - child_open, - child_anchor, - prev, - prev.next, - value, - key, - i, - render_fn, - flags - ); + var child_anchor = current ? get_first_node(current.e) : anchor; + + prev = create_item(child_anchor, prev, prev.next, value, key, i, render_fn, flags); items.set(key, prev); @@ -451,7 +436,6 @@ function update_item(item, value, index, type) { /** * @template V - * @param {Comment | Text} open * @param {Node} anchor * @param {import('#client').EachItem | import('#client').EachState} prev * @param {import('#client').EachItem | null} next @@ -462,7 +446,7 @@ function update_item(item, value, index, type) { * @param {number} flags * @returns {import('#client').EachItem} */ -function create_item(open, anchor, prev, next, value, key, index, render_fn, flags) { +function create_item(anchor, prev, next, value, key, index, render_fn, flags) { var previous_each_item = current_each_item; try { @@ -480,7 +464,6 @@ function create_item(open, anchor, prev, next, value, key, index, render_fn, fla a: null, // @ts-expect-error e: null, - o: open, prev, next }; @@ -497,16 +480,52 @@ function create_item(open, anchor, prev, next, value, key, index, render_fn, fla } } +/** + * @param {import('#client').TemplateNode} dom + * @param {import("#client").Effect} effect + * @returns {import('#client').TemplateNode} + */ +function get_adjusted_first_node(dom, effect) { + if ((dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || dom.nodeType === 8) { + var adjusted = effect.first; + var next; + while (adjusted !== null) { + next = adjusted.first; + if (adjusted.dom !== null) { + break; + } else if (next === null) { + return /** @type {import('#client').TemplateNode} */ (dom.previousSibling); + } + adjusted = next; + } + return get_first_node(/** @type {import("#client").Effect} */ (adjusted)); + } + return dom; +} + +/** + * + * @param {import('#client').Effect} effect + * @returns {import('#client').TemplateNode} + */ +function get_first_node(effect) { + var dom = effect.dom; + if (is_array(dom)) { + return get_adjusted_first_node(dom[0], effect); + } + return get_adjusted_first_node(/** @type {import('#client').TemplateNode} **/ (dom), effect); +} + /** * @param {import('#client').EachItem} item * @param {import('#client').EachItem | null} next * @param {Text | Element | Comment} anchor */ function move(item, next, anchor) { - var end = item.next ? item.next.o : anchor; - var dest = next ? next.o : anchor; + var end = item.next ? get_first_node(item.next.e) : anchor; + var dest = next ? get_first_node(next.e) : anchor; - var node = /** @type {import('#client').TemplateNode} */ (item.o); + var node = get_first_node(item.e); while (node !== end) { var next_node = /** @type {import('#client').TemplateNode} */ (node.nextSibling); diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 651c5fd7cade..6e0740b937b3 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -47,7 +47,6 @@ function swap_block_dom(effect, from, to) { * @returns {void} */ export function element(node, get_tag, is_svg, render_fn, get_namespace, location) { - const parent_effect = /** @type {import('#client').Effect} */ (current_effect); const filename = DEV && location && current_component_context?.function.filename; /** @type {string | null} */ @@ -64,6 +63,18 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio /** @type {import('#client').Effect | null} */ let effect; + const parent_effect = /** @type {import('#client').Effect} */ (current_effect); + + // Remove the the hydrated effect dom entry for our dynamic element + if (hydrating && is_array(parent_effect.dom)) { + var remove_index = parent_effect.dom.indexOf( + /** @type {import('#client').TemplateNode} */ (element) + ); + if (remove_index !== -1) { + parent_effect.dom.splice(remove_index, 1); + } + } + /** * The keyed `{#each ...}` item block, if any, that this element is inside. * We track this so we can set it when changing the element, allowing any @@ -72,6 +83,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio let each_item_block = current_each_item; block(() => { + const element_effect = /** @type {import('#client').Effect} */ (current_effect); const next_tag = get_tag() || null; const ns = get_namespace ? get_namespace() @@ -124,6 +136,13 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio }; } + if (prev_element && !hydrating) { + swap_block_dom(element_effect, prev_element, element); + prev_element.remove(); + } else { + push_template_node(element, element_effect); + } + if (render_fn) { // If hydrating, use the existing ssr comment as the anchor so that the // inner open and close methods can pick up the existing nodes correctly @@ -144,15 +163,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio anchor.before(element); - if (!hydrating) { - if (prev_element) { - swap_block_dom(parent_effect, prev_element, element); - prev_element.remove(); - } else { - push_template_node(element, parent_effect); - } - } - // See below return noop; }); diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 986fbfe7cf1e..292c233fa691 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -85,8 +85,6 @@ export type EachItem = { i: number | Source; /** key */ k: unknown; - /** anchor for items inserted before this */ - o: Comment | Text; prev: EachItem | EachState; next: EachItem | null; };