pull/10948/head
Rich Harris 2 years ago
parent 9ba60e5330
commit c08bd16103

@ -6,6 +6,7 @@ import {
branch,
destroy_effect,
pause_effect,
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
@ -45,110 +46,105 @@ function swap_block_dom(effect, from, to) {
export function element(anchor, get_tag, is_svg, render_fn) {
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
/** @type {string | null} */
let tag;
/** @type {string | null} */
let current_tag;
/** @type {null | Element} */
let element = null;
/** @type {import('#client').Effect | null} */
let effect;
/**
* 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
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item;
const wrapper = block(() => {
const next_tag = get_tag() || null;
if (next_tag === tag) return;
// See explanation of `each_item_block` above
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
// 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.parentElement?.tagName === 'foreignObject'
? null
: anchor.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_should_intro(false);
render_effect(() => {
/** @type {string | null} */
let tag;
/** @type {string | null} */
let current_tag;
/** @type {null | Element} */
let element = null;
/** @type {import('#client').Effect | null} */
let effect;
/**
* 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
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item;
block(() => {
const next_tag = get_tag() || null;
if (next_tag === tag) return;
// See explanation of `each_item_block` above
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
// 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.parentElement?.tagName === 'foreignObject'
? null
: anchor.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_should_intro(false);
}
}
}
if (next_tag && next_tag !== current_tag) {
effect = branch(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (hydrate_nodes[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
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
var child_anchor = hydrating
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: element.appendChild(empty());
if (child_anchor) {
// `child_anchor` can be undefined if this is a void element with children,
// i.e. `<svelte:element this={"hr"}>...</svelte:element>`. This is
// user error, but we warn on it elsewhere (in dev) so here we just
// silently ignore it
render_fn(element, child_anchor);
if (next_tag && next_tag !== current_tag) {
effect = branch(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (hydrate_nodes[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
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
var child_anchor = hydrating
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: element.appendChild(empty());
if (child_anchor) {
// `child_anchor` can be undefined if this is a void element with children,
// i.e. `<svelte:element this={"hr"}>...</svelte:element>`. This is
// user error, but we warn on it elsewhere (in dev) so here we just
// silently ignore it
render_fn(element, child_anchor);
}
}
}
anchor.before(element);
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
}
});
}
anchor.before(element);
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
}
});
}
set_current_each_item(previous_each_item);
});
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
wrapper.ondestroy = () => {
if (element !== null) {
remove(element);
element = null;
}
set_current_each_item(previous_each_item);
});
if (effect) {
destroy_effect(effect);
}
};
return () => {
element?.remove();
};
});
}

@ -12,6 +12,9 @@ export function head(render_fn) {
let previous_hydrate_nodes = null;
let was_hydrating = hydrating;
/** @type {Comment | Text} */
var anchor;
if (hydrating) {
previous_hydrate_nodes = hydrate_nodes;
@ -21,10 +24,10 @@ export function head(render_fn) {
}
anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(anchor));
} else {
anchor = document.head.appendChild(empty());
}
var anchor = document.head.appendChild(empty());
try {
block(() => render_fn(anchor));
} finally {

Loading…
Cancel
Save