import { signal, effect, batch, owned, onCleanup } from './signal.js';
import { anchors } from './dom.js';
function read(value) {
return typeof value === 'function' ? value() : value;
}
export function each(listGetter, keyOf, render) {
const fragment = anchors();
eachInto(fragment.firstChild, fragment.lastChild, listGetter, keyOf, render);
return fragment;
}
function settledPositions(from) {
const previous = new Array(from.length);
const tails = [];
for (let i = 0; i < from.length; i += 1) {
if (from[i] === -1) continue;
let low = 0;
let high = tails.length;
while (low < high) {
const middle = (low + high) >> 1;
if (from[tails[middle]] < from[i]) low = middle + 1;
else high = middle;
}
previous[i] = low === 0 ? -1 : tails[low - 1];
tails[low] = i;
}
const settled = new Set();
let i = tails.length === 0 ? -1 : tails[tails.length - 1];
while (i !== -1) {
settled.add(i);
i = previous[i];
}
return settled;
}
export function eachInto(start, end, listGetter, keyOf, render) {
let mounted = new Map();
onCleanup(() => mounted.forEach((entry) => entry.dispose()));
effect(() => {
const items = [...(read(listGetter) ?? [])];
const parent = end.parentNode;
batch(() => {
const keys = [];
const live = new Set();
for (const item of items) {
const key = keyOf(item, keys.length);
if (live.has(key)) {
throw new Error(`Duplicate key ${JSON.stringify(key)} in a list. Keys must be unique.`);
}
live.add(key);
keys.push(key);
}
mounted.forEach((entry, key) => {
if (live.has(key)) return;
for (const node of entry.nodes) node.remove();
entry.dispose();
mounted.delete(key);
});
const was = new Map();
mounted.forEach((_entry, key) => was.set(key, was.size));
const next = new Map();
const rows = new Array(items.length);
const from = new Array(items.length);
for (let i = 0; i < items.length; i += 1) {
const item = items[i];
const key = keys[i];
let entry = mounted.get(key);
if (entry === undefined) {
const [get, set] = signal(item);
const [rendered, dispose] = owned(() => render(get));
const nodes =
rendered.nodeType === 11 ? [...rendered.childNodes] : [rendered];
entry = { nodes, set, dispose };
from[i] = -1;
} else {
entry.set(item);
from[i] = was.get(key);
}
rows[i] = entry;
next.set(key, entry);
}
const settled = settledPositions(from);
let cursor = end;
for (let i = items.length - 1; i >= 0; i -= 1) {
const entry = rows[i];
if (!settled.has(i)) {
for (const node of entry.nodes) parent.insertBefore(node, cursor);
}
cursor = entry.nodes[0];
}
mounted = next;
assertPlaced(start, end, keys, mounted);
});
});
}
function assertPlaced(start, end, keys, mounted) {
const placed = [];
for (let node = start.nextSibling; node && node !== end; node = node.nextSibling) {
placed.push(node);
}
const expected = [];
for (const key of keys) {
for (const node of mounted.get(key).nodes) expected.push(node);
}
for (let i = 0; i < Math.max(placed.length, expected.length); i += 1) {
if (placed[i] !== expected[i]) {
throw new Error(
`A list of ${keys.length} rows placed ${placed.length} nodes where ` +
`${expected.length} were reconciled, first differing at ${i}. ` +
`Reconciliation moved a row to the wrong place.`
);
}
}
}
export function byPosition(item, index) {
return index;
}