@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
JavaScript
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