UNPKG

ripple

Version:

Ripple is an elegant TypeScript UI framework

184 lines (161 loc) • 5.49 kB
/** @import { AppendIntoAnchor } from '#client' */ import { hydrate_first_child, hydrate_next_sibling, hydrate_node, hydrating, set_hydrate_node, } from './hydration.js'; import { get_descriptor } from '@tsrx/core/runtime/language-helpers'; import { HYDRATION } from 'ripple/internal/client/hydration-enabled'; export { hydrate_first_child, hydrate_next_sibling }; /** * The `firstChild` / `nextSibling` getters, called directly so a traversal * site never becomes a megamorphic property read across node types. * @type {(() => Node | null)} */ export var first_child_getter; /** @type {(() => Node | null)} */ export var next_sibling_getter; /** @type {(() => Node | null)} */ var last_child_getter; /** @type {Document} */ export var document; /** @type {boolean} */ export var is_firefox; export function init_operations() { var node_prototype = Node.prototype; var element_prototype = Element.prototype; // Firefox prefers `importNode` for template content (see `template.js`). // Its Firefox-only `window` property is the test: the first read of // `navigator` creates its wrapper, about 0.1 ms on a fresh page, paid by // the first mount. is_firefox = 'mozInnerScreenX' in window; document = window.document; first_child_getter = /** @type {(() => Node | null)} */ ( get_descriptor(node_prototype, 'firstChild')?.get ); next_sibling_getter = /** @type {(() => Node | null)} */ ( get_descriptor(node_prototype, 'nextSibling')?.get ); last_child_getter = /** @type {(() => Node | null)} */ ( get_descriptor(node_prototype, 'lastChild')?.get ); // Declared on the prototype so a delegated handler lookup on an element // stops at Element instead of walking to Object. `Event.prototype.__root` // is declared with the first delegated listener (`events.js`): touching // `Event.prototype` costs about 0.05 ms on a fresh page. element_prototype.__click = undefined; } /** * @template {Node} N * @param {N} node * @returns {Node | null} */ export function get_first_child(node) { return first_child_getter.call(node); } /** * @template {Node} N * @param {N} node * @returns {Node | null} */ export function get_last_child(node) { return last_child_getter.call(node); } /** * @template {Node} N * @param {N} node * @param {boolean} [is_text] * @returns {Node | null} */ export function first_child(node, is_text) { if (!(HYDRATION && hydrating)) { return node.firstChild; } return hydrate_first_child(is_text); } /** * Anchor sentinel for all-component children: components render directly into * `parent` instead of each inserting before a placeholder comment. `append()` * detects the sentinel (no `nodeType`) and appendChild()s into `parent`. During * hydration we descend the cursor into `parent` (mirroring first_child) so the * first appended component adopts the server's first child of `parent`. * @param {Node} parent * @param {boolean} [tail] the sentinel stands for the tail of `parent`: what * it anchors is the last thing the framework appends there, so it stays the * right insertion point for its whole life (see the `if` runtime) * @returns {AppendIntoAnchor} */ export function append_into(parent, tail) { if (HYDRATION && hydrating) { var child = get_first_child(/** @type {Node} */ (hydrate_node)); if (child === null) { child = /** @type {Node} */ (hydrate_node).appendChild(create_text()); } set_hydrate_node(child); } return { parent, into: true, tail: tail === true }; } /** * Resolves the anchor for a block that keeps inserting relative to it for its * whole life (root-controlled control flow). An append-into sentinel is only * valid for a one-shot append: a later branch swap or list move must land at * the block's own position, not after whatever siblings were appended since, * so the sentinel is materialized into a text anchor at the current end of * the parent. During hydration the cursor sits on the block's SSR boundary * marker, which is exactly the anchor a non-sentinel root-controlled block * receives, so it serves as the anchor there. * @param {Node | AppendIntoAnchor} node * @returns {Node} */ export function resolve_anchor(node) { if (/** @type {AppendIntoAnchor} */ (node).into !== true) { return /** @type {Node} */ (node); } if (HYDRATION && hydrating) { return /** @type {Node} */ (hydrate_node); } return /** @type {AppendIntoAnchor} */ (node).parent.appendChild(create_text()); } /** * @template {Node} N * @param {N} node * @param {boolean} [is_text] * @returns {Node | null} */ export function first_child_frag(node, is_text) { // During hydration, for fragment templates, hydrate_node is already // pointing to the first element of the fragment. Don't descend into it. if (HYDRATION && hydrating) { return hydrate_node; } var child = /** @type {Text} */ (first_child(node, is_text)); if (child.nodeType === Node.COMMENT_NODE && child.data === '') { return next_sibling(child); } return child; } /** * @template {Node} N * @param {N} node * @returns {Node | null} */ export function get_next_sibling(node) { return next_sibling_getter.call(node); } /** * @template {Node} N * @param {N} node * @param {boolean} [is_text] * @returns {Node | null} */ export function next_sibling(node, is_text) { if (!(HYDRATION && hydrating)) { return node.nextSibling; } return hydrate_next_sibling(is_text); } export function create_text(value = '') { return document.createTextNode(value); }