diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js new file mode 100644 index 0000000000..0c30af2d92 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -0,0 +1,118 @@ +import { namespace_svg } from '../../../../constants.js'; +import { noop } from '../../../common.js'; +import { BRANCH_EFFECT } from '../../constants.js'; +import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../../hydration.js'; +import { empty } from '../../operations.js'; +import { + destroy_effect, + pause_effect, + render_effect, + resume_effect +} from '../../reactivity/computations.js'; + +/** + * @param {Comment} anchor_node + * @param {() => string} get_tag + * @param {boolean | null} is_svg `null` == not statically known + * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn + * @returns {void} + */ +export function element(anchor_node, get_tag, is_svg, render_fn) { + hydrate_block_anchor(anchor_node); + + /** @type {string} */ + let tag; + + /** @type {import('../../types.js').BlockEffect | null} */ + let block; + + /** @type {Element | null} */ + let element; + + /** @type {import('../../types.js').EffectSignal} */ + let branch; + + branch = render_effect(() => { + if (tag === (tag = get_tag())) return; + + // We try our best infering the namespace in case it's not possible to determine statically, + // but on the first render on the client (without hydration) the parent will be undefined, + // since the anchor is not attached to its parent / the dom yet. + const ns = + is_svg || tag === 'svg' + ? namespace_svg + : is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject' + ? null + : anchor_node.parentElement?.namespaceURI ?? null; + + const next_element = tag + ? hydrating + ? /** @type {Element} */ (current_hydration_fragment[0]) + : ns + ? document.createElementNS(ns, tag) + : document.createElement(tag) + : null; + + // TODO handle hydration mismatch + + if (element) { + if (next_element) { + // while (element.firstChild) { + // next_element.appendChild(element.firstChild); + // } + + element.replaceWith(next_element); + + next_element.appendChild(anchor_node); + + if (render_fn) { + if (block) { + pause_effect(block, noop); // TODO we don't actually want to pause it, we want to just destroy it immediately + destroy_effect(block); + } + + block = render_effect(() => render_fn(next_element, anchor_node), {}, true); // TODO disable transitions + } + } else if (block) { + const old_element = element; + pause_effect(block, () => { + block = null; + if (old_element === element) { + old_element.remove(); + } + }); + } else { + element.remove(); + } + } else if (next_element) { + anchor_node.parentNode?.insertBefore(next_element, anchor_node); + + if (render_fn) { + /** @type {Node} */ + let anchor; + + 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 + anchor = /** @type {Comment} */ (next_element.firstChild); + } else { + anchor = empty(); + next_element.appendChild(anchor); + } + + if (block) { + resume_effect(block); + } else { + block = render_effect(() => render_fn(next_element, anchor), {}, true); + } + } + } + + element = next_element; + if (branch) branch.dom = element; + }); + + branch.f |= BRANCH_EFFECT; + + branch.dom = element; +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 24ef448c60..69553eae04 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -1522,116 +1522,6 @@ export function head(fn) { } } -/** - * @param {import('./types.js').Block} block - * @param {Element} from - * @param {Element} to - * @returns {void} - */ -function swap_block_dom(block, from, to) { - const dom = block.d; - if (is_array(dom)) { - for (let i = 0; i < dom.length; i++) { - if (dom[i] === from) { - dom[i] = to; - break; - } - } - } else if (dom === from) { - block.d = to; - } -} - -/** - * @param {Comment} anchor_node - * @param {() => string} tag_fn - * @param {boolean | null} is_svg `null` == not statically known - * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn - * @returns {void} - */ -export function element(anchor_node, tag_fn, is_svg, render_fn) { - const block = create_dynamic_element_block(); - hydrate_block_anchor(anchor_node); - let has_mounted = false; - - /** @type {string} */ - let tag; - - /** @type {null | Element} */ - let element = null; - const element_effect = render_effect( - () => { - tag = tag_fn(); - if (has_mounted) { - execute_effect(render_effect_signal); - } - has_mounted = true; - }, - block, - false - ); - // Managed effect - const render_effect_signal = render_effect( - () => { - // We try our best infering the namespace in case it's not possible to determine statically, - // but on the first render on the client (without hydration) the parent will be undefined, - // since the anchor is not attached to its parent / the dom yet. - const ns = - is_svg || tag === 'svg' - ? namespace_svg - : is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject' - ? null - : anchor_node.parentElement?.namespaceURI ?? null; - const next_element = tag - ? hydrating - ? /** @type {Element} */ (current_hydration_fragment[0]) - : ns - ? document.createElementNS(ns, tag) - : document.createElement(tag) - : null; - const prev_element = element; - if (prev_element !== null) { - block.d = null; - } - element = next_element; - if (element !== null && render_fn !== undefined) { - let anchor; - 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 - anchor = /** @type {Comment} */ (element.firstChild); - } else { - anchor = empty(); - element.appendChild(anchor); - } - render_fn(element, anchor); - } - const has_prev_element = prev_element !== null; - if (has_prev_element) { - remove(prev_element); - } - if (element !== null) { - insert(element, null, anchor_node); - if (has_prev_element) { - const parent_block = block.p; - swap_block_dom(parent_block, prev_element, element); - } - } - }, - block, - true - ); - push_destroy_fn(element_effect, () => { - if (element !== null) { - remove(element); - block.d = null; - element = null; - } - destroy_signal(render_effect_signal); - }); - block.e = element_effect; -} - /** * @template {Record} P * @template {(node: Node, props: P) => void} C diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 741391ebc6..4daa7920fd 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -24,6 +24,7 @@ export * from './client/dev/ownership.js'; export { await_block as await } from './client/dom/blocks/await.js'; export { if_block as if } from './client/dom/blocks/if.js'; export { key_block as key } from './client/dom/blocks/key.js'; +export { element } from './client/dom/blocks/svelte-element.js'; export * from './client/dom/blocks/each.js'; export * from './client/reactivity/computations.js'; export * from './client/reactivity/sources.js';