UNPKG

svelte

Version:

Cybernetically enhanced web apps

207 lines (174 loc) 5.4 kB
/** @import { TemplateNode } from '#client' */ import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { DEV } from 'esm-env'; import { init_array_prototype_warnings } from '../dev/equality.js'; import { get_descriptor, is_extensible } from '../../shared/utils.js'; // export these for reference in the compiled code, making global name deduplication unnecessary /** @type {Window} */ export var $window; /** @type {Document} */ export var $document; /** @type {boolean} */ export var is_firefox; /** @type {() => Node | null} */ var first_child_getter; /** @type {() => Node | null} */ var next_sibling_getter; /** * Initialize these lazily to avoid issues when using the runtime in a server context * where these globals are not available while avoiding a separate server entry point */ export function init_operations() { if ($window !== undefined) { return; } $window = window; $document = document; is_firefox = /Firefox/.test(navigator.userAgent); var element_prototype = Element.prototype; var node_prototype = Node.prototype; var text_prototype = Text.prototype; // @ts-ignore first_child_getter = get_descriptor(node_prototype, 'firstChild').get; // @ts-ignore next_sibling_getter = get_descriptor(node_prototype, 'nextSibling').get; if (is_extensible(element_prototype)) { // the following assignments improve perf of lookups on DOM nodes // @ts-expect-error element_prototype.__click = undefined; // @ts-expect-error element_prototype.__className = undefined; // @ts-expect-error element_prototype.__attributes = null; // @ts-expect-error element_prototype.__style = undefined; // @ts-expect-error element_prototype.__e = undefined; } if (is_extensible(text_prototype)) { // @ts-expect-error text_prototype.__t = undefined; } if (DEV) { // @ts-expect-error element_prototype.__svelte_meta = null; init_array_prototype_warnings(); } } /** * @param {string} value * @returns {Text} */ export function create_text(value = '') { return document.createTextNode(value); } /** * @template {Node} N * @param {N} node * @returns {Node | null} */ /*@__NO_SIDE_EFFECTS__*/ export function get_first_child(node) { return first_child_getter.call(node); } /** * @template {Node} N * @param {N} node * @returns {Node | null} */ /*@__NO_SIDE_EFFECTS__*/ export function get_next_sibling(node) { return next_sibling_getter.call(node); } /** * Don't mark this as side-effect-free, hydration needs to walk all nodes * @template {Node} N * @param {N} node * @param {boolean} is_text * @returns {Node | null} */ export function child(node, is_text) { if (!hydrating) { return get_first_child(node); } var child = /** @type {TemplateNode} */ (get_first_child(hydrate_node)); // Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty if (child === null) { child = hydrate_node.appendChild(create_text()); } else if (is_text && child.nodeType !== 3) { var text = create_text(); child?.before(text); set_hydrate_node(text); return text; } set_hydrate_node(child); return child; } /** * Don't mark this as side-effect-free, hydration needs to walk all nodes * @param {DocumentFragment | TemplateNode[]} fragment * @param {boolean} is_text * @returns {Node | null} */ export function first_child(fragment, is_text) { if (!hydrating) { // when not hydrating, `fragment` is a `DocumentFragment` (the result of calling `open_frag`) var first = /** @type {DocumentFragment} */ (get_first_child(/** @type {Node} */ (fragment))); // TODO prevent user comments with the empty string when preserveComments is true if (first instanceof Comment && first.data === '') return get_next_sibling(first); return first; } // if an {expression} is empty during SSR, there might be no // text node to hydrate — we must therefore create one if (is_text && hydrate_node?.nodeType !== 3) { var text = create_text(); hydrate_node?.before(text); set_hydrate_node(text); return text; } return hydrate_node; } /** * Don't mark this as side-effect-free, hydration needs to walk all nodes * @param {TemplateNode} node * @param {number} count * @param {boolean} is_text * @returns {Node | null} */ export function sibling(node, count = 1, is_text = false) { let next_sibling = hydrating ? hydrate_node : node; var last_sibling; while (count--) { last_sibling = next_sibling; next_sibling = /** @type {TemplateNode} */ (get_next_sibling(next_sibling)); } if (!hydrating) { return next_sibling; } var type = next_sibling?.nodeType; // if a sibling {expression} is empty during SSR, there might be no // text node to hydrate — we must therefore create one if (is_text && type !== 3) { var text = create_text(); // If the next sibling is `null` and we're handling text then it's because // the SSR content was empty for the text, so we need to generate a new text // node and insert it after the last sibling if (next_sibling === null) { last_sibling?.after(text); } else { next_sibling.before(text); } set_hydrate_node(text); return text; } set_hydrate_node(next_sibling); return /** @type {TemplateNode} */ (next_sibling); } /** * @template {Node} N * @param {N} node * @returns {void} */ export function clear_text_content(node) { node.textContent = ''; }