UNPKG

@jay-js/system

Version:

A powerful and flexible TypeScript library for UI, state management, lazy loading, routing and managing draggable elements in modern web applications.

274 lines 9.54 kB
import { subscriberManager } from "../state/core/subscriber.js"; import { effect } from "../state/utils/helpers.js"; const EACH_NODE_MARKER = Symbol("jayjs-each-node"); function scheduleMicrotask(fn) { if (typeof queueMicrotask === "function") { queueMicrotask(fn); return; } Promise.resolve().then(fn); } function untrack(fn) { const current = subscriberManager.getSubscriber(); subscriberManager.clearSubscriber(); try { return fn(); } finally { subscriberManager.setSubscriber(current); } } function isNode(value) { return typeof value === "object" && value !== null && value instanceof Node; } function normalizeChildrenToNodes(value) { if (value === null || value === undefined || typeof value === "boolean") { return [document.createTextNode("")]; } if (Array.isArray(value)) { const out = []; for (const item of value) { out.push(...normalizeChildrenToNodes(item)); } return out.length ? out : [document.createTextNode("")]; } if (value instanceof Promise) { const slot = document.createElement("jayjs-lazy-slot"); value .then((resolved) => { const nodes = normalizeChildrenToNodes(resolved); slot.replaceWith(...nodes); }) .catch((error) => { console.error("JayJS: Error resolving each() Promise child:", error); slot.replaceWith(document.createTextNode("")); }); return [slot]; } if (typeof value === "string" || typeof value === "number") { return [document.createTextNode(String(value))]; } if (value instanceof DocumentFragment) { const nodes = Array.from(value.childNodes); return nodes.length ? nodes : [document.createTextNode("")]; } if (isNode(value)) { return [value]; } return [document.createTextNode(String(value))]; } function resolveKey(item, index, key) { if (typeof key === "function") { return key(item, index); } return item[key]; } export function each(getter, key, render) { const start = document.createComment("jayjs-each-start"); const end = document.createComment("jayjs-each-end"); let mountRetryScheduled = false; const keyToIndex = new Map(); let identityKeys = null; let identitySeq = 0; function fallbackKeyFor(item, index) { if (typeof item === "object" && item !== null) { if (!identityKeys) { identityKeys = new WeakMap(); } const existing = identityKeys.get(item); if (existing !== undefined) { return existing; } const created = Symbol(`jayjs-each:${identitySeq++}`); identityKeys.set(item, created); return created; } return `__idx:${index}`; } function resolvedKeyFor(item, index) { if (item === null || item === undefined) { return `__null:${index}`; } const resolved = resolveKey(item, index, key); if (resolved === null || resolved === undefined) { return fallbackKeyFor(item, index); } return resolved; } const cache = new Map(); function nextManagedSibling(after) { let n = after.nextSibling; while (n && n !== end) { if (n[EACH_NODE_MARKER] === start) { return n; } n = n.nextSibling; } return end; } function insertAfter(cursor, nodes) { const parent = cursor.parentNode; if (!parent) return; const reference = nextManagedSibling(cursor); const fragment = document.createDocumentFragment(); for (const node of nodes) { fragment.appendChild(node); } parent.insertBefore(fragment, reference); } function moveAfter(cursor, entry) { const first = entry.nodes[0]; if (!first) return; const reference = nextManagedSibling(cursor); if (reference === first) { return; } insertAfter(cursor, entry.nodes); } function markNodes(nodes) { for (const node of nodes) { node[EACH_NODE_MARKER] = start; } } function createStableListItemProxy(resolved) { return new Proxy(Object.create(null), { get(_target, prop) { var _a; const idx = keyToIndex.get(resolved); if (idx === undefined) return undefined; const list = getter(); if (!list) return undefined; return (_a = list[idx]) === null || _a === void 0 ? void 0 : _a[prop]; }, set(_target, prop, value) { const idx = keyToIndex.get(resolved); if (idx === undefined) return true; const list = getter(); if (!list) return true; list[idx][prop] = value; return true; }, has(_target, prop) { var _a; const idx = keyToIndex.get(resolved); if (idx === undefined) return false; const list = getter(); return Boolean(list && prop in ((_a = list[idx]) !== null && _a !== void 0 ? _a : {})); }, ownKeys() { var _a; const idx = keyToIndex.get(resolved); if (idx === undefined) return []; const list = getter(); return Reflect.ownKeys((_a = list === null || list === void 0 ? void 0 : list[idx]) !== null && _a !== void 0 ? _a : {}); }, getOwnPropertyDescriptor(_target, prop) { const idx = keyToIndex.get(resolved); if (idx === undefined) return undefined; const list = getter(); const obj = list === null || list === void 0 ? void 0 : list[idx]; const desc = obj ? Object.getOwnPropertyDescriptor(obj, prop) : undefined; if (desc) return desc; return { configurable: true, enumerable: true, writable: true, value: obj === null || obj === void 0 ? void 0 : obj[prop], }; }, }); } function createEntry(index, resolved) { const stableItem = createStableListItemProxy(resolved); const rendered = render(stableItem, index); const nodes = normalizeChildrenToNodes(rendered); markNodes(nodes); return { key: resolved, item: stableItem, nodes, }; } function removeEntry(entry) { for (const node of entry.nodes) { const anyNode = node; if (typeof anyNode.remove === "function") { anyNode.remove(); continue; } if (node.parentNode) { node.parentNode.removeChild(node); } } } function update() { var _a, _b; // First run can happen before the fragment is mounted; // in that case, we can't insert nodes yet. if (!start.parentNode) { if (!mountRetryScheduled) { mountRetryScheduled = true; scheduleMicrotask(() => { mountRetryScheduled = false; effect(update); }); } return; } const listProxy = (_a = getter()) !== null && _a !== void 0 ? _a : []; const length = (_b = listProxy.length) !== null && _b !== void 0 ? _b : 0; void listProxy.length; // subscribe only to array length changes keyToIndex.clear(); untrack(() => { for (let i = 0; i < length; i++) { const item = listProxy[i]; const resolved = resolvedKeyFor(item, i); keyToIndex.set(resolved, i); } }); const seen = new Set(); const keep = new Set(); let cursor = start; for (let i = 0; i < length; i++) { const rawItem = untrack(() => listProxy[i]); const resolved = untrack(() => resolvedKeyFor(rawItem, i)); if (seen.has(resolved)) { console.warn(`JayJS: each() received a duplicate key: ${String(resolved)}`); } seen.add(resolved); keep.add(resolved); let entry = cache.get(resolved); if (!entry) { entry = createEntry(i, resolved); cache.set(resolved, entry); insertAfter(cursor, entry.nodes); } else { moveAfter(cursor, entry); } cursor = entry.nodes[entry.nodes.length - 1]; } for (const [cachedKey, entry] of cache) { if (!keep.has(cachedKey)) { removeEntry(entry); cache.delete(cachedKey); } } } effect(update); const fragment = document.createDocumentFragment(); fragment.appendChild(start); fragment.appendChild(end); return fragment; } //# sourceMappingURL=each.js.map