|
|
|
|
@ -1,10 +1,16 @@
|
|
|
|
|
import { namespace_svg } from '../../../../constants.js';
|
|
|
|
|
import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../hydration.js';
|
|
|
|
|
import { empty } from '../operations.js';
|
|
|
|
|
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
|
|
|
|
|
import {
|
|
|
|
|
destroy_effect,
|
|
|
|
|
pause_effect,
|
|
|
|
|
render_effect,
|
|
|
|
|
resume_effect
|
|
|
|
|
} from '../../reactivity/effects.js';
|
|
|
|
|
import { insert, remove } from '../reconciler.js';
|
|
|
|
|
import { current_block, execute_effect } from '../../runtime.js';
|
|
|
|
|
import { is_array } from '../../utils.js';
|
|
|
|
|
import { run_transitions, set_run_transitions } from '../../render.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {import('#client').Block} block
|
|
|
|
|
@ -45,90 +51,101 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
hydrate_block_anchor(anchor_node);
|
|
|
|
|
let has_mounted = false;
|
|
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
|
/** @type {string | null} */
|
|
|
|
|
let tag;
|
|
|
|
|
|
|
|
|
|
/** @type {string | null} */
|
|
|
|
|
let current_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;
|
|
|
|
|
}
|
|
|
|
|
/** @type {import('#client').Effect | null} */
|
|
|
|
|
let effect;
|
|
|
|
|
|
|
|
|
|
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 wrapper = render_effect(() => {
|
|
|
|
|
const next_tag = tag_fn() || null;
|
|
|
|
|
if (next_tag === tag) return;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
// 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 || next_tag === 'svg'
|
|
|
|
|
? namespace_svg
|
|
|
|
|
: is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject'
|
|
|
|
|
? null
|
|
|
|
|
: anchor_node.parentElement?.namespaceURI ?? null;
|
|
|
|
|
|
|
|
|
|
if (effect) {
|
|
|
|
|
if (next_tag === null) {
|
|
|
|
|
// start outro
|
|
|
|
|
pause_effect(effect, () => {
|
|
|
|
|
effect = null;
|
|
|
|
|
current_tag = null;
|
|
|
|
|
element?.remove(); // TODO this should be unnecessary
|
|
|
|
|
});
|
|
|
|
|
} else if (next_tag === current_tag) {
|
|
|
|
|
// same tag as is currently rendered — abort outro
|
|
|
|
|
resume_effect(effect);
|
|
|
|
|
} else {
|
|
|
|
|
// tag is changing — destroy immediately, render contents without intro transitions
|
|
|
|
|
destroy_effect(effect);
|
|
|
|
|
set_run_transitions(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
block,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element_effect.ondestroy = () => {
|
|
|
|
|
if (next_tag && next_tag !== current_tag) {
|
|
|
|
|
effect = render_effect(
|
|
|
|
|
() => {
|
|
|
|
|
const prev_element = element;
|
|
|
|
|
element = hydrating
|
|
|
|
|
? /** @type {Element} */ (current_hydration_fragment[0])
|
|
|
|
|
: ns
|
|
|
|
|
? document.createElementNS(ns, next_tag)
|
|
|
|
|
: document.createElement(next_tag);
|
|
|
|
|
|
|
|
|
|
if (render_fn) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
insert(element, null, anchor_node);
|
|
|
|
|
if (prev_element) {
|
|
|
|
|
swap_block_dom(block.p, prev_element, element);
|
|
|
|
|
prev_element.remove();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
block,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tag = next_tag;
|
|
|
|
|
if (tag) current_tag = tag;
|
|
|
|
|
set_run_transitions(true);
|
|
|
|
|
}, block);
|
|
|
|
|
|
|
|
|
|
wrapper.ondestroy = () => {
|
|
|
|
|
if (element !== null) {
|
|
|
|
|
remove(element);
|
|
|
|
|
block.d = null;
|
|
|
|
|
element = null;
|
|
|
|
|
}
|
|
|
|
|
destroy_effect(render_effect_signal);
|
|
|
|
|
|
|
|
|
|
if (effect) {
|
|
|
|
|
destroy_effect(effect);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
block.e = element_effect;
|
|
|
|
|
block.e = wrapper;
|
|
|
|
|
}
|
|
|
|
|
|