reimplement <svelte:element>

blockless
Rich Harris 2 years ago
parent 18885879d8
commit 7b9fb6053e

@ -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;
}

@ -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<string, any>} P
* @template {(node: Node, props: P) => void} C

@ -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';

Loading…
Cancel
Save