diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 58ecd392fc..d09cb51abe 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -9,11 +9,11 @@ import { build_template_literal, build_update } from './utils.js'; * (e.g. `{a} b {c}`) into a single update function. Along the way it creates * corresponding template node references these updates are applied to. * @param {SvelteNode[]} nodes - * @param {(is_text: boolean) => Expression} expression + * @param {(is_text: boolean, is_last?: boolean) => Expression} expression * @param {boolean} is_element * @param {ComponentContext} context */ -export function process_children(nodes, expression, is_element, { visit, state }) { +export function process_children(nodes, expression, is_element, { visit, state, path }) { const within_bound_contenteditable = state.metadata.bound_contenteditable; /** @typedef {Array} Sequence */ @@ -30,7 +30,14 @@ export function process_children(nodes, expression, is_element, { visit, state } if (node.type === 'Text') { let prev = expression; - expression = () => b.call('$.sibling', prev(true)); + expression = (_, is_last) => + is_last + ? b.conditional( + b.id('$.hydrating'), + b.call('$.sibling', prev(true)), + b.call('$.sibling_dangle', prev(true)) + ) + : b.call('$.sibling', prev(true)); state.template.push(node.raw); return; } @@ -108,14 +115,30 @@ export function process_children(nodes, expression, is_element, { visit, state } node.metadata.is_controlled = true; visit(node, state); } else { + const is_last = + node.type === 'RegularElement' && + path.at(-1)?.type === 'Fragment' && + !node.fragment.metadata.dynamic && + nodes.length - 1 === i; + const id = get_node_id( - expression(false), + expression(false, is_last), state, node.type === 'RegularElement' ? node.name : 'node' ); - expression = (is_text) => - is_text ? b.call('$.sibling', id, b.true) : b.call('$.sibling', id); + expression = (is_text, is_last) => { + if (is_text) { + return is_text ? b.call('$.sibling', id, b.true) : b.call('$.sibling', id); + } + return is_last + ? b.conditional( + b.id('$.hydrating'), + b.call('$.sibling', id), + b.call('$.sibling_dangle', id) + ) + : b.call('$.sibling', id); + }; visit(node, { ...state, diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index c6130c4332..61f9a52107 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -131,6 +131,16 @@ export function sibling(node, is_text = false) { return /** @type {TemplateNode} */ (next_sibling); } +/** + * @template {Node} N + * @param {N} node + * @returns {Node | null} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function sibling_dangle(node) { + return node.nextSibling; +} + /** * @template {Node} N * @param {N} node diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 5739e9646c..91b12532b7 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -66,7 +66,7 @@ export { bind_focused } from './dom/elements/bindings/universal.js'; export { bind_window_scroll, bind_window_size } from './dom/elements/bindings/window.js'; -export { hydrate_template, next, reset } from './dom/hydration.js'; +export { hydrate_template, next, reset, hydrating } from './dom/hydration.js'; export { once, preventDefault, @@ -156,6 +156,7 @@ export { child, first_child, sibling, + sibling_dangle, $window as window, $document as document } from './dom/operations.js';