kerfjs 4.2.0-beta.3 → 4.2.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -6
- package/dist/async.d.ts +10 -0
- package/dist/async.js +7 -1
- package/dist/async.js.map +1 -1
- package/dist/attach.d.ts +45 -0
- package/dist/{imperative.js → attach.js} +5 -5
- package/dist/attach.js.map +1 -0
- package/dist/{chunk-4MY2656S.js → chunk-LKWAKC2X.js} +3 -3
- package/dist/{chunk-4MY2656S.js.map → chunk-LKWAKC2X.js.map} +1 -1
- package/dist/{chunk-FSAQR6IU.js → chunk-SUPUPSBE.js} +3 -6
- package/dist/chunk-SUPUPSBE.js.map +1 -0
- package/dist/html.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/jsx-runtime.d.ts +24 -12
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.d.ts +86 -15
- package/dist/list.js +196 -38
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +63 -5
- package/dist/overlay.js +79 -10
- package/dist/overlay.js.map +1 -1
- package/dist/remount.d.ts +3 -3
- package/dist/remount.js +2 -2
- package/dist/remount.js.map +1 -1
- package/dist/scope.js +2 -2
- package/package.json +4 -4
- package/dist/chunk-FSAQR6IU.js.map +0 -1
- package/dist/imperative.d.ts +0 -34
- package/dist/imperative.js.map +0 -1
package/dist/list.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { ARRAY_SIGNAL_BRAND } from './chunk-MRYM3O3V.js';
|
|
2
|
-
import { mount } from './chunk-
|
|
2
|
+
import { mount } from './chunk-LKWAKC2X.js';
|
|
3
3
|
import './chunk-QIP723L4.js';
|
|
4
4
|
import './chunk-YHH7OUFA.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-SUPUPSBE.js';
|
|
6
6
|
import { effect } from './chunk-3APBEVHF.js';
|
|
7
7
|
import './chunk-GY4XV2UV.js';
|
|
8
8
|
import './chunk-VVDJLWMP.js';
|
|
9
9
|
|
|
10
10
|
// src/list.ts
|
|
11
11
|
function bindList(parent, source, options) {
|
|
12
|
-
const { key, render, tag = "div", virtualize } = options;
|
|
12
|
+
const { key, render, tag = "div", virtualize, before } = options;
|
|
13
13
|
const overscan = virtualize?.overscan ?? 3;
|
|
14
|
+
const endAnchor = () => {
|
|
15
|
+
if (virtualize !== void 0 || before === void 0) return null;
|
|
16
|
+
return (typeof before === "function" ? before() : before) ?? null;
|
|
17
|
+
};
|
|
14
18
|
const rows = /* @__PURE__ */ new Map();
|
|
15
19
|
const order = [];
|
|
16
20
|
let items = [];
|
|
@@ -27,20 +31,32 @@ function bindList(parent, source, options) {
|
|
|
27
31
|
if (rendered instanceof HTMLElement) return { el: rendered, dispose: NOOP };
|
|
28
32
|
if (rendered !== null && typeof rendered === "object" && "el" in rendered && rendered.el instanceof HTMLElement) {
|
|
29
33
|
const r = rendered;
|
|
30
|
-
return { el: r.el, dispose: r.dispose ?? NOOP };
|
|
34
|
+
return { el: r.el, dispose: r.dispose ?? NOOP, update: r.update };
|
|
31
35
|
}
|
|
32
36
|
return null;
|
|
33
37
|
};
|
|
34
38
|
const makeRow = (item) => {
|
|
35
39
|
const elementRow = asElementRow(render(item));
|
|
36
40
|
if (elementRow !== null) {
|
|
37
|
-
|
|
38
|
-
return { el: elementRow.el, item, dispose: elementRow.dispose };
|
|
41
|
+
return { el: elementRow.el, item, dispose: elementRow.dispose, elementMode: true, update: elementRow.update };
|
|
39
42
|
}
|
|
40
43
|
const el = document.createElement(tag);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const dispose2 = mount(el, () => render(item));
|
|
45
|
+
return { el, item, dispose: dispose2, elementMode: false };
|
|
46
|
+
};
|
|
47
|
+
const reconcileItem = (row, k, item) => {
|
|
48
|
+
if (row.item === item) return row;
|
|
49
|
+
if (row.elementMode) {
|
|
50
|
+
row.item = item;
|
|
51
|
+
row.update?.(item);
|
|
52
|
+
return row;
|
|
53
|
+
}
|
|
54
|
+
row.dispose();
|
|
55
|
+
row.el.remove();
|
|
56
|
+
rows.delete(k);
|
|
57
|
+
const fresh = makeRow(item);
|
|
58
|
+
rows.set(k, fresh);
|
|
59
|
+
return fresh;
|
|
44
60
|
};
|
|
45
61
|
const syncRows = (visible) => {
|
|
46
62
|
const wanted = /* @__PURE__ */ new Set();
|
|
@@ -55,20 +71,17 @@ function bindList(parent, source, options) {
|
|
|
55
71
|
order.length = 0;
|
|
56
72
|
for (const item of visible) {
|
|
57
73
|
const k = key(item);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
row
|
|
62
|
-
|
|
63
|
-
row = void 0;
|
|
64
|
-
}
|
|
65
|
-
if (row === void 0) {
|
|
74
|
+
const existing = rows.get(k);
|
|
75
|
+
let row;
|
|
76
|
+
if (existing !== void 0) {
|
|
77
|
+
row = reconcileItem(existing, k, item);
|
|
78
|
+
} else {
|
|
66
79
|
row = makeRow(item);
|
|
67
80
|
rows.set(k, row);
|
|
68
81
|
}
|
|
69
82
|
order.push(row);
|
|
70
83
|
}
|
|
71
|
-
let ref =
|
|
84
|
+
let ref = endAnchor();
|
|
72
85
|
for (let i = order.length - 1; i >= 0; i--) {
|
|
73
86
|
const el = order[i].el;
|
|
74
87
|
if (el.parentNode !== container || el.nextSibling !== ref) {
|
|
@@ -83,7 +96,7 @@ function bindList(parent, source, options) {
|
|
|
83
96
|
const row = makeRow(patch.item);
|
|
84
97
|
rows.set(key(patch.item), row);
|
|
85
98
|
order.splice(patch.index, 0, row);
|
|
86
|
-
container.insertBefore(row.el, order[patch.index + 1]?.el ??
|
|
99
|
+
container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());
|
|
87
100
|
} else if (patch.type === "remove") {
|
|
88
101
|
const [row] = order.splice(patch.index, 1);
|
|
89
102
|
row.dispose();
|
|
@@ -92,21 +105,88 @@ function bindList(parent, source, options) {
|
|
|
92
105
|
} else if (patch.type === "move") {
|
|
93
106
|
const [row] = order.splice(patch.from, 1);
|
|
94
107
|
order.splice(patch.to, 0, row);
|
|
95
|
-
container.insertBefore(row.el, order[patch.to + 1]?.el ??
|
|
108
|
+
container.insertBefore(row.el, order[patch.to + 1]?.el ?? endAnchor());
|
|
96
109
|
} else if (patch.type === "update") {
|
|
97
110
|
const current = order[patch.index];
|
|
98
111
|
if (current.item !== patch.item) {
|
|
99
|
-
current.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
if (current.elementMode) {
|
|
113
|
+
const oldKey = key(current.item);
|
|
114
|
+
const newKey = key(patch.item);
|
|
115
|
+
current.item = patch.item;
|
|
116
|
+
if (newKey !== oldKey) {
|
|
117
|
+
rows.delete(oldKey);
|
|
118
|
+
rows.set(newKey, current);
|
|
119
|
+
}
|
|
120
|
+
current.update?.(patch.item);
|
|
121
|
+
} else {
|
|
122
|
+
current.dispose();
|
|
123
|
+
current.el.remove();
|
|
124
|
+
rows.delete(key(current.item));
|
|
125
|
+
const row = makeRow(patch.item);
|
|
126
|
+
rows.set(key(patch.item), row);
|
|
127
|
+
order[patch.index] = row;
|
|
128
|
+
container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());
|
|
129
|
+
}
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
}
|
|
109
133
|
};
|
|
134
|
+
const rowHeight = virtualize?.rowHeight;
|
|
135
|
+
const fixedHeight = typeof rowHeight === "number" ? rowHeight : null;
|
|
136
|
+
const measuring = typeof rowHeight === "object" && rowHeight !== null;
|
|
137
|
+
const measured = /* @__PURE__ */ new Map();
|
|
138
|
+
const estimateAt = (index) => {
|
|
139
|
+
const est = rowHeight.estimate;
|
|
140
|
+
return typeof est === "function" ? est(items[index], index) : est;
|
|
141
|
+
};
|
|
142
|
+
const variableHeightAt = fixedHeight !== null ? null : measuring ? (index) => {
|
|
143
|
+
const k = key(items[index]);
|
|
144
|
+
return measured.has(k) ? measured.get(k) : estimateAt(index);
|
|
145
|
+
} : (index) => rowHeight(items[index], index);
|
|
146
|
+
let offsets = [0];
|
|
147
|
+
let heightsDirty = true;
|
|
148
|
+
const indexByKey = /* @__PURE__ */ new Map();
|
|
149
|
+
let pendingAnchorDelta = 0;
|
|
150
|
+
const rebuildOffsets = () => {
|
|
151
|
+
const fn = variableHeightAt;
|
|
152
|
+
const total = items.length;
|
|
153
|
+
offsets = new Array(total + 1);
|
|
154
|
+
offsets[0] = 0;
|
|
155
|
+
if (measuring) indexByKey.clear();
|
|
156
|
+
for (let i = 0; i < total; i++) {
|
|
157
|
+
offsets[i + 1] = offsets[i] + fn(i);
|
|
158
|
+
if (measuring) indexByKey.set(key(items[i]), i);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const findStart = (target, total) => {
|
|
162
|
+
let lo = 0;
|
|
163
|
+
let hi = total;
|
|
164
|
+
while (lo < hi) {
|
|
165
|
+
const mid = lo + hi + 1 >> 1;
|
|
166
|
+
if (offsets[mid] <= target) lo = mid;
|
|
167
|
+
else hi = mid - 1;
|
|
168
|
+
}
|
|
169
|
+
return lo;
|
|
170
|
+
};
|
|
171
|
+
const findEnd = (target, total) => {
|
|
172
|
+
let lo = 0;
|
|
173
|
+
let hi = total;
|
|
174
|
+
while (lo < hi) {
|
|
175
|
+
const mid = lo + hi >> 1;
|
|
176
|
+
if (offsets[mid] >= target) hi = mid;
|
|
177
|
+
else lo = mid + 1;
|
|
178
|
+
}
|
|
179
|
+
return lo;
|
|
180
|
+
};
|
|
181
|
+
const sizeVisibleRows = (start) => {
|
|
182
|
+
if (measuring) return;
|
|
183
|
+
for (let j = 0; j < order.length; j++) {
|
|
184
|
+
const abs = start + j;
|
|
185
|
+
const h = fixedHeight !== null ? fixedHeight : offsets[abs + 1] - offsets[abs];
|
|
186
|
+
order[j].el.style.height = `${h}px`;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const renderSubscribers = /* @__PURE__ */ new Set();
|
|
110
190
|
const renderWindow = () => {
|
|
111
191
|
if (virtualize === void 0) {
|
|
112
192
|
if (granularEligible) {
|
|
@@ -120,28 +200,65 @@ function bindList(parent, source, options) {
|
|
|
120
200
|
firstRender = false;
|
|
121
201
|
return;
|
|
122
202
|
}
|
|
123
|
-
const { rowHeight } = virtualize;
|
|
124
203
|
const total = items.length;
|
|
125
|
-
const
|
|
126
|
-
const
|
|
204
|
+
const scrollTop = parent.scrollTop;
|
|
205
|
+
const viewportBottom = scrollTop + parent.clientHeight;
|
|
206
|
+
let start;
|
|
207
|
+
let end;
|
|
208
|
+
let padTop;
|
|
209
|
+
let padBottom;
|
|
210
|
+
if (fixedHeight !== null) {
|
|
211
|
+
start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);
|
|
212
|
+
end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);
|
|
213
|
+
padTop = start * fixedHeight;
|
|
214
|
+
padBottom = Math.max(0, total - end) * fixedHeight;
|
|
215
|
+
} else {
|
|
216
|
+
if (heightsDirty) {
|
|
217
|
+
rebuildOffsets();
|
|
218
|
+
heightsDirty = false;
|
|
219
|
+
}
|
|
220
|
+
start = Math.max(0, findStart(scrollTop, total) - overscan);
|
|
221
|
+
end = Math.min(total, findEnd(viewportBottom, total) + overscan);
|
|
222
|
+
padTop = offsets[start];
|
|
223
|
+
padBottom = offsets[total] - offsets[end];
|
|
224
|
+
}
|
|
127
225
|
syncRows(items.slice(start, end));
|
|
128
|
-
|
|
129
|
-
container.style.
|
|
226
|
+
sizeVisibleRows(start);
|
|
227
|
+
container.style.paddingTop = `${padTop}px`;
|
|
228
|
+
container.style.paddingBottom = `${padBottom}px`;
|
|
229
|
+
for (const cb of renderSubscribers) cb();
|
|
130
230
|
};
|
|
131
231
|
const stopEffect = effect(() => {
|
|
132
232
|
items = source.value;
|
|
233
|
+
heightsDirty = true;
|
|
133
234
|
renderWindow();
|
|
134
235
|
});
|
|
135
|
-
const
|
|
236
|
+
const scheduleRender = () => {
|
|
136
237
|
if (rafPending) return;
|
|
137
238
|
rafPending = true;
|
|
138
239
|
globalThis.requestAnimationFrame(() => {
|
|
139
240
|
rafPending = false;
|
|
140
|
-
if (
|
|
241
|
+
if (disposed) return;
|
|
242
|
+
if (pendingAnchorDelta !== 0) {
|
|
243
|
+
parent.scrollTop += pendingAnchorDelta;
|
|
244
|
+
pendingAnchorDelta = 0;
|
|
245
|
+
}
|
|
246
|
+
renderWindow();
|
|
141
247
|
});
|
|
142
248
|
};
|
|
143
|
-
if (virtualize !== void 0) parent.addEventListener("scroll",
|
|
144
|
-
|
|
249
|
+
if (virtualize !== void 0) parent.addEventListener("scroll", scheduleRender);
|
|
250
|
+
const setHeight = (k, height) => {
|
|
251
|
+
if (!measuring) return;
|
|
252
|
+
const idx = indexByKey.get(k);
|
|
253
|
+
if (idx === void 0) return;
|
|
254
|
+
const oldHeight = measured.has(k) ? measured.get(k) : estimateAt(idx);
|
|
255
|
+
if (height === oldHeight) return;
|
|
256
|
+
measured.set(k, height);
|
|
257
|
+
if (offsets[idx + 1] <= parent.scrollTop) pendingAnchorDelta += height - oldHeight;
|
|
258
|
+
heightsDirty = true;
|
|
259
|
+
scheduleRender();
|
|
260
|
+
};
|
|
261
|
+
const dispose = (() => {
|
|
145
262
|
disposed = true;
|
|
146
263
|
stopEffect();
|
|
147
264
|
for (const row of rows.values()) {
|
|
@@ -149,13 +266,54 @@ function bindList(parent, source, options) {
|
|
|
149
266
|
if (virtualize === void 0) row.el.remove();
|
|
150
267
|
}
|
|
151
268
|
rows.clear();
|
|
269
|
+
renderSubscribers.clear();
|
|
152
270
|
if (virtualize !== void 0) {
|
|
153
|
-
parent.removeEventListener("scroll",
|
|
271
|
+
parent.removeEventListener("scroll", scheduleRender);
|
|
154
272
|
container.remove();
|
|
273
|
+
VIRTUAL_INTERNALS.delete(handle);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
const handle = dispose;
|
|
277
|
+
handle.setHeight = setHeight;
|
|
278
|
+
if (virtualize !== void 0) {
|
|
279
|
+
VIRTUAL_INTERNALS.set(handle, {
|
|
280
|
+
visibleRows: () => order.map((row) => ({ key: key(row.item), el: row.el })),
|
|
281
|
+
onRender: (cb) => {
|
|
282
|
+
renderSubscribers.add(cb);
|
|
283
|
+
return () => renderSubscribers.delete(cb);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return handle;
|
|
288
|
+
}
|
|
289
|
+
var VIRTUAL_INTERNALS = /* @__PURE__ */ new WeakMap();
|
|
290
|
+
function observeRowHeights(handle) {
|
|
291
|
+
const internals = VIRTUAL_INTERNALS.get(handle);
|
|
292
|
+
const RO = globalThis.ResizeObserver;
|
|
293
|
+
if (internals === void 0 || RO === void 0) return () => {
|
|
294
|
+
};
|
|
295
|
+
const keyByEl = /* @__PURE__ */ new WeakMap();
|
|
296
|
+
const observer = new RO((entries) => {
|
|
297
|
+
for (const entry of entries) {
|
|
298
|
+
const k = keyByEl.get(entry.target);
|
|
299
|
+
if (k !== void 0) handle.setHeight(k, entry.target.offsetHeight);
|
|
155
300
|
}
|
|
301
|
+
});
|
|
302
|
+
const resync = () => {
|
|
303
|
+
observer.disconnect();
|
|
304
|
+
for (const { key: k, el } of internals.visibleRows()) {
|
|
305
|
+
keyByEl.set(el, k);
|
|
306
|
+
observer.observe(el);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const unsubscribe = internals.onRender(resync);
|
|
310
|
+
resync();
|
|
311
|
+
return () => {
|
|
312
|
+
observer.disconnect();
|
|
313
|
+
unsubscribe();
|
|
156
314
|
};
|
|
157
315
|
}
|
|
158
316
|
|
|
159
|
-
export { bindList };
|
|
317
|
+
export { bindList, observeRowHeights };
|
|
160
318
|
//# sourceMappingURL=list.js.map
|
|
161
319
|
//# sourceMappingURL=list.js.map
|
package/dist/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/list.ts"],"names":[],"mappings":"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","file":"list.js","sourcesContent":["/**\n * `kerfjs/list` — `bindList`, a keyed list with a live per-row mount and\n * optional viewport virtualization.\n *\n * This is a DELIBERATE second list API, distinct from `each()`. It does two\n * things `each()` structurally cannot:\n * 1. **Per-row reactivity.** Every row is individually `mount()`ed, so a signal\n * the row's `render` reads updates just that row (fine-grained binding or a\n * one-row morph) without touching its siblings — no full-list pass.\n * 2. **Virtualization.** With `{ virtualize: { rowHeight } }` only the rows in\n * the scroll viewport are rendered; padding on the scroll container keeps\n * `scrollHeight` honest.\n *\n * `each()` stays the choice for item-owned-state lists rendered to HTML strings;\n * reach for `bindList` when you need surgical per-row updates or windowing.\n *\n * import { bindList } from 'kerfjs/list';\n *\n * const dispose = bindList(listEl, itemsSignal, {\n * key: (row) => row.id,\n * render: (row) => <span class={selected} data-id={row.id}>{row.label}</span>,\n * tag: 'li',\n * virtualize: { rowHeight: 32 },\n * });\n *\n * `render` reads signals for reactivity (external state like a `selectedId`, or\n * signals the item carries) — keep the item OBJECTS stable across renders and\n * drive structure (add/remove/move) through `itemsSignal`. A row whose item\n * object identity changes is rebuilt (same rule as `each()`'s memo). `bindList`\n * OWNS `parent`'s children. It reads `itemsSignal.value`, so a plain\n * `signal<T[]>` or an `arraySignal<T>` both work.\n */\nimport { ARRAY_SIGNAL_BRAND, type ArrayPatch } from './array-signal.js';\nimport { mount, type MountResult } from './mount.js';\nimport { effect } from './reactive.js';\n\n/** A row's stable key. */\nexport type ListKey = string | number;\n\n/** Anything with a tracking `.value` array read — a `signal<readonly T[]>` or an `arraySignal<T>`. */\nexport interface ListSource<T> {\n readonly value: readonly T[];\n}\n\n/**\n * A row built imperatively by `render`: return the row **element** itself (kerf\n * keys/moves/reuses it and owns nothing inside it), or `{ el, dispose? }` to also\n * hand back a teardown that runs when the row is removed or rebuilt.\n */\nexport type RowElement = HTMLElement | { el: HTMLElement; dispose?: () => void };\n\n/** Options for {@link bindList}. */\nexport interface BindListOptions<T> {\n /** Stable per-row key. Rows are matched, moved, and reused by this. */\n key: (item: T) => ListKey;\n /**\n * Build a row. Two modes, chosen per call by what you return:\n * - **Content mode** (a `MountResult` — JSX / `SafeHtml`): kerf creates the\n * row element (`tag`) and `mount()`s your content inside it, so signals your\n * content reads drive per-row reactivity.\n * - **Element mode** (an `HTMLElement`, or `{ el, dispose? }`): the element you\n * return IS the row, so you own its tag, class, `data-*`, and listeners\n * (kerf keys/moves/reuses it). Reactivity + cleanup are yours — return a\n * `dispose` to tear down listeners when the row is removed or rebuilt.\n */\n render: (item: T) => MountResult | RowElement;\n /** Row element tag for **content mode**. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). Ignored in element mode. */\n tag?: string;\n /**\n * Turn on viewport virtualization. `rowHeight` is the fixed pixel height of\n * every row; `overscan` (default 3) is how many extra rows to render above and\n * below the viewport. `parent` must be a scroll container (your CSS: a fixed\n * height + `overflow: auto`).\n */\n virtualize?: { rowHeight: number; overscan?: number };\n}\n\ninterface Row<T> {\n el: HTMLElement;\n item: T;\n dispose: () => void;\n}\n\n/**\n * Bind a keyed, per-row-reactive list to `parent`, driven by `source` (a\n * `signal<readonly T[]>` or an `arraySignal<T>`). Returns a disposer that tears\n * down every row mount, the scroll listener (if virtualized), and the source\n * subscription.\n */\nexport function bindList<T>(\n parent: HTMLElement,\n source: ListSource<T>,\n options: BindListOptions<T>,\n): () => void {\n const { key, render, tag = 'div', virtualize } = options;\n const overscan = virtualize?.overscan ?? 3;\n\n const rows = new Map<ListKey, Row<T>>();\n // The current DOM order of rows, kept in step by both the keyed-diff and the\n // granular patch paths so index-based patches can address rows directly.\n const order: Array<Row<T>> = [];\n let items: readonly T[] = [];\n let disposed = false;\n let rafPending = false;\n let firstRender = true;\n\n // Granular fast path (KF-478): when the source is an `arraySignal` and the\n // list is NOT virtualized, apply its insert/remove/move/update patches\n // directly in O(patches) instead of diffing the whole snapshot. Virtualized\n // lists keep the keyed diff — their visible set is just the window (cheap),\n // and absolute-index patches don't compose with a shifting window. A plain\n // `signal<T[]>` has no patches, so it always uses the keyed diff.\n const patchSource = source as {\n [ARRAY_SIGNAL_BRAND]?: boolean;\n _consumePatches?: () => ArrayPatch<T>[];\n };\n const granularEligible = virtualize === undefined && patchSource[ARRAY_SIGNAL_BRAND] === true;\n\n // Virtualized lists put the windowing padding + rows on an INNER sizer, so the\n // padding never inflates the scroll container's clientHeight (padding counts\n // toward clientHeight). `parent` stays the clean scroll viewport; `container`\n // holds the rows. Non-virtualized lists render straight into `parent`.\n const container: HTMLElement = virtualize === undefined ? parent : document.createElement('div');\n if (virtualize !== undefined) parent.appendChild(container);\n\n const NOOP = (): void => { /* element-mode rows with no caller teardown */ };\n\n // Detect element mode from a render result: a raw `HTMLElement`, or a\n // `{ el, dispose? }` object. Everything else (SafeHtml / string / nullish) is\n // content mode. SafeHtml is an object but has no `el`, so it never matches.\n const asElementRow = (\n rendered: MountResult | RowElement,\n ): { el: HTMLElement; dispose: () => void } | null => {\n if (rendered instanceof HTMLElement) return { el: rendered, dispose: NOOP };\n if (\n rendered !== null\n && typeof rendered === 'object'\n && 'el' in rendered\n && (rendered as { el: unknown }).el instanceof HTMLElement\n ) {\n const r = rendered as { el: HTMLElement; dispose?: () => void };\n return { el: r.el, dispose: r.dispose ?? NOOP };\n }\n return null;\n };\n\n const makeRow = (item: T): Row<T> => {\n // One call decides the mode per row (so a list may mix element + content rows).\n const elementRow = asElementRow(render(item));\n if (elementRow !== null) {\n // Element mode: the returned element IS the row; the caller owns its\n // content + cleanup. bindList still sizes it for the windowing math.\n if (virtualize !== undefined) elementRow.el.style.height = `${virtualize.rowHeight}px`;\n return { el: elementRow.el, item, dispose: elementRow.dispose };\n }\n // Content mode: kerf creates the row element and mounts `render` inside it,\n // so the content is per-row reactive. (In content mode `render` runs once\n // more here for the mode probe than the mount itself needs — keep it a pure\n // projection, which bindList already requires.)\n const el = document.createElement(tag);\n if (virtualize !== undefined) el.style.height = `${virtualize.rowHeight}px`;\n // Content mode: `render` returns a MountResult here (element results were\n // handled above), so narrowing it for `mount` is sound.\n const dispose = mount(el, () => render(item) as MountResult);\n return { el, item, dispose };\n };\n\n // Reconcile the live rows to exactly `visible`, in order, keyed.\n const syncRows = (visible: readonly T[]): void => {\n const wanted = new Set<ListKey>();\n for (const item of visible) wanted.add(key(item));\n\n // Remove rows that are gone from the window.\n for (const [k, row] of rows) {\n if (!wanted.has(k)) {\n row.dispose();\n row.el.remove();\n rows.delete(k);\n }\n }\n\n // Create missing rows (and rebuild a row whose item OBJECT changed identity).\n order.length = 0;\n for (const item of visible) {\n const k = key(item);\n let row = rows.get(k);\n if (row !== undefined && row.item !== item) {\n row.dispose();\n row.el.remove();\n rows.delete(k);\n row = undefined;\n }\n if (row === undefined) {\n row = makeRow(item);\n rows.set(k, row);\n }\n order.push(row);\n }\n\n // Reverse pass: move only rows that are out of position.\n let ref: Node | null = null;\n for (let i = order.length - 1; i >= 0; i--) {\n const el = order[i].el;\n if (el.parentNode !== container || el.nextSibling !== ref) {\n container.insertBefore(el, ref);\n }\n ref = el;\n }\n };\n\n // Apply arraySignal structural patches directly to `order` + the DOM, in\n // O(patches). Indices are always valid by construction: `order` reflects the\n // last-rendered state and the patches are exactly the delta from it (bindList\n // drains the queue every render, and `replace` is filtered out by the caller,\n // which snapshots instead). The `splice()`s mirror `arraySignal`'s own\n // `_items` mutations exactly.\n const applyPatches = (patches: readonly ArrayPatch<T>[]): void => {\n for (const patch of patches) {\n if (patch.type === 'insert') {\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order.splice(patch.index, 0, row);\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? null);\n } else if (patch.type === 'remove') {\n const [row] = order.splice(patch.index, 1);\n row.dispose();\n row.el.remove();\n rows.delete(key(row.item));\n } else if (patch.type === 'move') {\n const [row] = order.splice(patch.from, 1);\n order.splice(patch.to, 0, row);\n container.insertBefore(row.el, order[patch.to + 1]?.el ?? null);\n } else if (patch.type === 'update') {\n // Decision #3: an item whose OBJECT identity changed rebuilds the row\n // (matching the keyed-diff rule). A same-ref update needs nothing here —\n // the row's own mount reacts to whatever signals its render reads.\n const current = order[patch.index];\n if (current.item !== patch.item) {\n current.dispose();\n current.el.remove();\n rows.delete(key(current.item));\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order[patch.index] = row;\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? null);\n }\n }\n // 'replace' never reaches here — the caller snapshots on it.\n }\n };\n\n const renderWindow = (): void => {\n if (virtualize === undefined) {\n if (granularEligible) {\n // Always drain to keep the single patch queue clean (so patches never\n // double-apply). Take the granular path past the first render, when\n // there are patches, and none is a `replace` (which reshapes the whole\n // array — snapshot instead). Otherwise fall through to a keyed diff.\n const patches = patchSource._consumePatches!();\n if (\n !firstRender\n && patches.length > 0\n && !patches.some((p) => p.type === 'replace')\n ) {\n applyPatches(patches);\n return;\n }\n }\n syncRows(items);\n firstRender = false;\n return;\n }\n const { rowHeight } = virtualize;\n const total = items.length;\n const start = Math.max(0, Math.floor(parent.scrollTop / rowHeight) - overscan);\n const end = Math.min(total, Math.ceil((parent.scrollTop + parent.clientHeight) / rowHeight) + overscan);\n syncRows(items.slice(start, end));\n container.style.paddingTop = `${start * rowHeight}px`;\n container.style.paddingBottom = `${Math.max(0, total - end) * rowHeight}px`;\n };\n\n const stopEffect = effect(() => {\n items = source.value; // tracking read — re-runs on any structural change\n renderWindow();\n });\n\n const onScroll = (): void => {\n if (rafPending) return;\n rafPending = true;\n globalThis.requestAnimationFrame(() => {\n rafPending = false;\n if (!disposed) renderWindow();\n });\n };\n if (virtualize !== undefined) parent.addEventListener('scroll', onScroll);\n\n return () => {\n disposed = true;\n stopEffect();\n for (const row of rows.values()) {\n row.dispose();\n if (virtualize === undefined) row.el.remove();\n }\n rows.clear();\n if (virtualize !== undefined) {\n parent.removeEventListener('scroll', onScroll);\n container.remove(); // removes the inner sizer and its rows in one go\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/list.ts"],"names":["dispose"],"mappings":"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","file":"list.js","sourcesContent":["/**\n * `kerfjs/list` — `bindList`, a keyed list with a live per-row mount and\n * optional viewport virtualization.\n *\n * This is a DELIBERATE second list API, distinct from `each()`. It does two\n * things `each()` structurally cannot:\n * 1. **Per-row reactivity.** Every row is individually `mount()`ed, so a signal\n * the row's `render` reads updates just that row (fine-grained binding or a\n * one-row morph) without touching its siblings — no full-list pass.\n * 2. **Virtualization.** With `{ virtualize: { rowHeight } }` only the rows in\n * the scroll viewport are rendered; padding on the scroll container keeps\n * `scrollHeight` honest. `rowHeight` is a fixed `number` (O(1) windowing), a\n * `(item, index) => number` for **app-declared variable** heights (a prefix\n * sum + binary-search window), or `{ estimate }` for **measured** heights —\n * the app reports real heights via the returned handle's `setHeight` (or the\n * `observeRowHeights` helper) and kerf anchor-corrects `scrollTop`. See\n * `docs/17-list-virtualization.md`.\n *\n * `each()` stays the choice for item-owned-state lists rendered to HTML strings;\n * reach for `bindList` when you need surgical per-row updates or windowing.\n *\n * import { bindList } from 'kerfjs/list';\n *\n * const dispose = bindList(listEl, itemsSignal, {\n * key: (row) => row.id,\n * render: (row) => <span class={selected} data-id={row.id}>{row.label}</span>,\n * tag: 'li',\n * virtualize: { rowHeight: 32 },\n * });\n *\n * `render` reads signals for reactivity (external state like a `selectedId`, or\n * signals the item carries) — keep the item OBJECTS stable across renders and\n * drive structure (add/remove/move) through `itemsSignal`. A row whose item\n * object identity changes is rebuilt (same rule as `each()`'s memo). `bindList`\n * OWNS `parent`'s children by default (append/move to the end) — to share\n * `parent` with fixed trailing siblings (an \"add\" button, an indicator), pass\n * `before` so the rows end just before that node. It reads `itemsSignal.value`,\n * so a plain `signal<T[]>` or an `arraySignal<T>` both work.\n */\nimport { ARRAY_SIGNAL_BRAND, type ArrayPatch } from './array-signal.js';\nimport { mount, type MountResult } from './mount.js';\nimport { effect } from './reactive.js';\n\n/** A row's stable key. */\nexport type ListKey = string | number;\n\n/** Anything with a tracking `.value` array read — a `signal<readonly T[]>` or an `arraySignal<T>`. */\nexport interface ListSource<T> {\n readonly value: readonly T[];\n}\n\n/**\n * A row built imperatively by `render`: return the row **element** itself (kerf\n * keys/moves/reuses it and owns nothing inside it), or `{ el, update?, dispose? }`\n * to also hand back an `update(item)` — called on the SAME element when the row's\n * key persists but its item changes — and a `dispose` that runs only when the row\n * is removed.\n */\nexport type RowElement<T> =\n | HTMLElement\n | { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n\n/**\n * The virtualization height model:\n * - **`number`** — every row is this fixed pixel height (O(1) windowing).\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index.\n * - **`{ estimate }`** — **measured** heights: kerf uses `estimate` for a row\n * until the app reports its real height through {@link BindListHandle.setHeight}\n * (or the `observeRowHeights` helper). See `docs/17-list-virtualization.md`.\n */\nexport type RowHeight<T> =\n | number\n | ((item: T, index: number) => number)\n | { estimate: number | ((item: T, index: number) => number) };\n\n/**\n * The value {@link bindList} returns: a disposer you call to tear the list down,\n * augmented with `setHeight` for the **measured** virtualization mode.\n */\nexport type BindListHandle = (() => void) & {\n /**\n * Report a row's real pixel height (measured after layout) for\n * `virtualize: { rowHeight: { estimate } }` lists. Keyed by the list `key`, so\n * a report survives reorders. kerf recomputes the window and, if the row sits\n * ABOVE the viewport, anchor-corrects `scrollTop` so content doesn't jump.\n * A no-op for fixed / declared-height lists and for unknown keys.\n */\n setHeight: (key: ListKey, height: number) => void;\n};\n\n/** Options for {@link bindList}. */\nexport interface BindListOptions<T> {\n /** Stable per-row key. Rows are matched, moved, and reused by this. */\n key: (item: T) => ListKey;\n /**\n * Build a row. Two modes, chosen per call by what you return:\n * - **Content mode** (a `MountResult` — JSX / `SafeHtml`): kerf creates the\n * row element (`tag`) and `mount()`s your content inside it, so signals your\n * content reads drive per-row reactivity.\n * - **Element mode** (an `HTMLElement`, or `{ el, update?, dispose? }`): the\n * element you return IS the row, so you own its tag, class, `data-*`, and\n * listeners. kerf **keys/moves/reuses** it — the SAME element survives an\n * append/remove/reorder or a fresh item object at the same key. Refresh its\n * content by reading signals inside it, or by returning an `update(item)`\n * that kerf calls on the existing element when the item changes. `dispose`\n * runs only when the row is genuinely removed.\n */\n render: (item: T) => MountResult | RowElement<T>;\n /** Row element tag for **content mode**. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). Ignored in element mode. */\n tag?: string;\n /**\n * Keep the rows as a contiguous block that ENDS just before this node, instead\n * of at the very end of `parent`. Use it when `parent` also holds non-row\n * siblings that must stay put — a trailing \"add\" button, a sliding indicator:\n * `before: () => addButton`. The node (a function is re-read each reconcile, or\n * pass the node directly) must be a child of `parent`. Without it, bindList\n * assumes exclusive ownership and appends rows to the end. Ignored when\n * virtualized (the rows own bindList's inner sizer exclusively).\n */\n before?: Node | (() => Node | null);\n /**\n * Turn on viewport virtualization. `parent` must be a scroll container (your\n * CSS: a fixed height + `overflow: auto`). `overscan` (default 3) is how many\n * extra rows to render above and below the viewport.\n *\n * `rowHeight` (a {@link RowHeight}) is the height model:\n * - **`number`** — every row is this fixed pixel height. O(1) windowing, no\n * cumulative model built.\n * - **`(item, index) => number`** — app-declared **variable** heights, derived\n * purely from the item and its index. kerf builds a prefix sum of the\n * heights (rebuilt when the source array changes, not per scroll frame) and\n * binary-searches it to find the visible window. Return a non-negative\n * number of pixels.\n * - **`{ estimate }`** — **measured** heights for rows whose height is only\n * known after layout. kerf sizes an unmeasured row by `estimate` (a number\n * or an `(item, index) => number`), and the app reports each row's real\n * height via {@link BindListHandle.setHeight} (or the `observeRowHeights`\n * helper). kerf anchor-corrects `scrollTop` when an above-viewport row is\n * remeasured, so content doesn't jump.\n */\n virtualize?: { rowHeight: RowHeight<T>; overscan?: number };\n}\n\ninterface Row<T> {\n el: HTMLElement;\n item: T;\n dispose: () => void;\n /** True for element-mode rows (the caller owns the element — reuse it, don't rebuild on item change). */\n elementMode: boolean;\n /** Element mode only: refresh the existing element when the item changes at the same key. */\n update?: (item: T) => void;\n}\n\n/**\n * Bind a keyed, per-row-reactive list to `parent`, driven by `source` (a\n * `signal<readonly T[]>` or an `arraySignal<T>`). Returns a disposer that tears\n * down every row mount, the scroll listener (if virtualized), and the source\n * subscription.\n */\nexport function bindList<T>(\n parent: HTMLElement,\n source: ListSource<T>,\n options: BindListOptions<T>,\n): BindListHandle {\n const { key, render, tag = 'div', virtualize, before } = options;\n const overscan = virtualize?.overscan ?? 3;\n\n // The node the row block ends before — `before` (KF-496) when the list shares\n // `parent` with trailing siblings, else the end of the container. Never applies\n // when virtualized: the rows own bindList's inner sizer exclusively.\n const endAnchor = (): Node | null => {\n if (virtualize !== undefined || before === undefined) return null;\n return (typeof before === 'function' ? before() : before) ?? null;\n };\n\n const rows = new Map<ListKey, Row<T>>();\n // The current DOM order of rows, kept in step by both the keyed-diff and the\n // granular patch paths so index-based patches can address rows directly.\n const order: Array<Row<T>> = [];\n let items: readonly T[] = [];\n let disposed = false;\n let rafPending = false;\n let firstRender = true;\n\n // Granular fast path (KF-478): when the source is an `arraySignal` and the\n // list is NOT virtualized, apply its insert/remove/move/update patches\n // directly in O(patches) instead of diffing the whole snapshot. Virtualized\n // lists keep the keyed diff — their visible set is just the window (cheap),\n // and absolute-index patches don't compose with a shifting window. A plain\n // `signal<T[]>` has no patches, so it always uses the keyed diff.\n const patchSource = source as {\n [ARRAY_SIGNAL_BRAND]?: boolean;\n _consumePatches?: () => ArrayPatch<T>[];\n };\n const granularEligible = virtualize === undefined && patchSource[ARRAY_SIGNAL_BRAND] === true;\n\n // Virtualized lists put the windowing padding + rows on an INNER sizer, so the\n // padding never inflates the scroll container's clientHeight (padding counts\n // toward clientHeight). `parent` stays the clean scroll viewport; `container`\n // holds the rows. Non-virtualized lists render straight into `parent`.\n const container: HTMLElement = virtualize === undefined ? parent : document.createElement('div');\n if (virtualize !== undefined) parent.appendChild(container);\n\n const NOOP = (): void => { /* element-mode rows with no caller teardown */ };\n\n // Detect element mode from a render result: a raw `HTMLElement`, or a\n // `{ el, dispose? }` object. Everything else (SafeHtml / string / nullish) is\n // content mode. SafeHtml is an object but has no `el`, so it never matches.\n const asElementRow = (\n rendered: MountResult | RowElement<T>,\n ): { el: HTMLElement; dispose: () => void; update?: (item: T) => void } | null => {\n if (rendered instanceof HTMLElement) return { el: rendered, dispose: NOOP };\n if (\n rendered !== null\n && typeof rendered === 'object'\n && 'el' in rendered\n && (rendered as { el: unknown }).el instanceof HTMLElement\n ) {\n const r = rendered as { el: HTMLElement; update?: (item: T) => void; dispose?: () => void };\n return { el: r.el, dispose: r.dispose ?? NOOP, update: r.update };\n }\n return null;\n };\n\n const makeRow = (item: T): Row<T> => {\n // One call decides the mode per row (so a list may mix element + content rows).\n const elementRow = asElementRow(render(item));\n if (elementRow !== null) {\n // Element mode: the returned element IS the row; the caller owns its\n // content + cleanup. bindList sizes it for the windowing math per render\n // (see `sizeVisibleRows`), not here, since a variable height depends on the\n // row's current index in the full list.\n return { el: elementRow.el, item, dispose: elementRow.dispose, elementMode: true, update: elementRow.update };\n }\n // Content mode: kerf creates the row element and mounts `render` inside it,\n // so the content is per-row reactive. (In content mode `render` runs once\n // more here for the mode probe than the mount itself needs — keep it a pure\n // projection, which bindList already requires.)\n const el = document.createElement(tag);\n // Content mode: `render` returns a MountResult here (element results were\n // handled above), so narrowing it for `mount` is sound.\n const dispose = mount(el, () => render(item) as MountResult);\n return { el, item, dispose, elementMode: false };\n };\n\n // A row whose KEY persists but whose item object changed. Content-mode rows are\n // rebuilt (their mount re-renders the fresh item); element-mode rows are REUSED\n // — the caller owns the element, so we keep it (preserving focus / scroll /\n // listeners) and refresh via the optional `update(item)`. Returns the row to\n // use at that key (a fresh one for content, the same one for element).\n const reconcileItem = (row: Row<T>, k: ListKey, item: T): Row<T> => {\n if (row.item === item) return row;\n if (row.elementMode) {\n row.item = item;\n row.update?.(item);\n return row;\n }\n row.dispose();\n row.el.remove();\n rows.delete(k);\n const fresh = makeRow(item);\n rows.set(k, fresh);\n return fresh;\n };\n\n // Reconcile the live rows to exactly `visible`, in order, keyed.\n const syncRows = (visible: readonly T[]): void => {\n const wanted = new Set<ListKey>();\n for (const item of visible) wanted.add(key(item));\n\n // Remove rows that are gone from the window.\n for (const [k, row] of rows) {\n if (!wanted.has(k)) {\n row.dispose();\n row.el.remove();\n rows.delete(k);\n }\n }\n\n // Create missing rows; reuse existing ones by key (element rows keep their\n // element across item changes; content rows rebuild on identity change).\n order.length = 0;\n for (const item of visible) {\n const k = key(item);\n const existing = rows.get(k);\n let row: Row<T>;\n if (existing !== undefined) {\n row = reconcileItem(existing, k, item);\n } else {\n row = makeRow(item);\n rows.set(k, row);\n }\n order.push(row);\n }\n\n // Reverse pass: move only rows that are out of position.\n let ref: Node | null = endAnchor();\n for (let i = order.length - 1; i >= 0; i--) {\n const el = order[i].el;\n if (el.parentNode !== container || el.nextSibling !== ref) {\n container.insertBefore(el, ref);\n }\n ref = el;\n }\n };\n\n // Apply arraySignal structural patches directly to `order` + the DOM, in\n // O(patches). Indices are always valid by construction: `order` reflects the\n // last-rendered state and the patches are exactly the delta from it (bindList\n // drains the queue every render, and `replace` is filtered out by the caller,\n // which snapshots instead). The `splice()`s mirror `arraySignal`'s own\n // `_items` mutations exactly.\n const applyPatches = (patches: readonly ArrayPatch<T>[]): void => {\n for (const patch of patches) {\n if (patch.type === 'insert') {\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order.splice(patch.index, 0, row);\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n } else if (patch.type === 'remove') {\n const [row] = order.splice(patch.index, 1);\n row.dispose();\n row.el.remove();\n rows.delete(key(row.item));\n } else if (patch.type === 'move') {\n const [row] = order.splice(patch.from, 1);\n order.splice(patch.to, 0, row);\n container.insertBefore(row.el, order[patch.to + 1]?.el ?? endAnchor());\n } else if (patch.type === 'update') {\n // An item whose OBJECT identity changed: content rows rebuild (their mount\n // re-renders the fresh item); element rows are REUSED — keep the caller's\n // element and refresh via update(), re-keying if the key changed. A\n // same-ref update needs nothing (the row's mount reacts to its signals).\n const current = order[patch.index];\n if (current.item !== patch.item) {\n if (current.elementMode) {\n const oldKey = key(current.item);\n const newKey = key(patch.item);\n current.item = patch.item;\n if (newKey !== oldKey) {\n rows.delete(oldKey);\n rows.set(newKey, current);\n }\n current.update?.(patch.item);\n } else {\n current.dispose();\n current.el.remove();\n rows.delete(key(current.item));\n const row = makeRow(patch.item);\n rows.set(key(patch.item), row);\n order[patch.index] = row;\n container.insertBefore(row.el, order[patch.index + 1]?.el ?? endAnchor());\n }\n }\n }\n // 'replace' never reaches here — the caller snapshots on it.\n }\n };\n\n // Virtualization height model, three modes:\n // - `fixedHeight` (a `number`): the O(1) fast path — no cumulative model.\n // - `variableHeightAt` (a function): app-declared per-row heights.\n // - measuring (`{ estimate }`): `variableHeightAt` returns the measured height\n // when the app has reported one (via `setHeight`), else the estimate.\n // In the two variable cases, `offsets[i]` is the total height of rows 0..i-1\n // (a prefix sum, length total+1), so `offsets[i+1] - offsets[i]` is row i's\n // height and `offsets[total]` is the full scroll height. It is rebuilt only\n // when `items` changes or a height is reported (heightsDirty), never per scroll\n // frame — a scroll reuses the prefix sum and pays only the O(log n) searches.\n const rowHeight = virtualize?.rowHeight;\n const fixedHeight = typeof rowHeight === 'number' ? rowHeight : null;\n const measuring = typeof rowHeight === 'object' && rowHeight !== null;\n const measured = new Map<ListKey, number>(); // key → real reported height\n const estimateAt = (index: number): number => {\n const est = (rowHeight as { estimate: number | ((item: T, index: number) => number) }).estimate;\n return typeof est === 'function' ? est(items[index], index) : est;\n };\n const variableHeightAt: ((index: number) => number) | null =\n fixedHeight !== null\n ? null\n : measuring\n ? (index): number => {\n const k = key(items[index]);\n return measured.has(k) ? (measured.get(k) as number) : estimateAt(index);\n }\n : (index): number => (rowHeight as (item: T, index: number) => number)(items[index], index);\n\n let offsets: number[] = [0];\n let heightsDirty = true;\n // Measuring only: key → current absolute index, so `setHeight(key, …)` locates\n // the row in O(1). Rebuilt with the prefix sum when `items` changes.\n const indexByKey = new Map<ListKey, number>();\n // Accumulated scroll-anchor correction: the summed height delta of remeasured\n // rows that sit entirely ABOVE the viewport top, applied to `scrollTop` before\n // the next window render so on-screen content does not jump.\n let pendingAnchorDelta = 0;\n\n const rebuildOffsets = (): void => {\n const fn = variableHeightAt as (index: number) => number;\n const total = items.length;\n offsets = new Array<number>(total + 1);\n offsets[0] = 0;\n if (measuring) indexByKey.clear();\n for (let i = 0; i < total; i++) {\n offsets[i + 1] = offsets[i] + fn(i);\n if (measuring) indexByKey.set(key(items[i]), i);\n }\n };\n\n // Greatest index i in [0, total] with `offsets[i] <= target` — the first row\n // whose top is at or above `target` (the viewport top).\n const findStart = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (offsets[mid] <= target) lo = mid;\n else hi = mid - 1;\n }\n return lo;\n };\n\n // Smallest index i in [0, total] with `offsets[i] >= target` — one past the\n // last row that starts before `target` (the viewport bottom). `total` if none.\n const findEnd = (target: number, total: number): number => {\n let lo = 0;\n let hi = total;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (offsets[mid] >= target) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n };\n\n // Size each visible row for the windowing math. `order` holds the visible rows\n // in order, so `order[j]` is the item at absolute index `start + j`.\n // MEASURED mode is the exception: the row must take its NATURAL height so the\n // app (or `observeRowHeights`) can read the real `offsetHeight` — forcing a\n // height here would make the measurement echo the estimate. Its offsets come\n // from `setHeight` reports instead.\n const sizeVisibleRows = (start: number): void => {\n if (measuring) return;\n for (let j = 0; j < order.length; j++) {\n const abs = start + j;\n const h = fixedHeight !== null ? fixedHeight : offsets[abs + 1] - offsets[abs];\n order[j].el.style.height = `${h}px`;\n }\n };\n\n // Called after each virtualized window render (used by `observeRowHeights` to\n // re-observe the current visible rows).\n const renderSubscribers = new Set<() => void>();\n\n const renderWindow = (): void => {\n if (virtualize === undefined) {\n if (granularEligible) {\n // Always drain to keep the single patch queue clean (so patches never\n // double-apply). Take the granular path past the first render, when\n // there are patches, and none is a `replace` (which reshapes the whole\n // array — snapshot instead). Otherwise fall through to a keyed diff.\n const patches = patchSource._consumePatches!();\n if (\n !firstRender\n && patches.length > 0\n && !patches.some((p) => p.type === 'replace')\n ) {\n applyPatches(patches);\n return;\n }\n }\n syncRows(items);\n firstRender = false;\n return;\n }\n const total = items.length;\n const scrollTop = parent.scrollTop;\n const viewportBottom = scrollTop + parent.clientHeight;\n let start: number;\n let end: number;\n let padTop: number;\n let padBottom: number;\n if (fixedHeight !== null) {\n start = Math.max(0, Math.floor(scrollTop / fixedHeight) - overscan);\n end = Math.min(total, Math.ceil(viewportBottom / fixedHeight) + overscan);\n padTop = start * fixedHeight;\n padBottom = Math.max(0, total - end) * fixedHeight;\n } else {\n if (heightsDirty) {\n rebuildOffsets();\n heightsDirty = false;\n }\n start = Math.max(0, findStart(scrollTop, total) - overscan);\n end = Math.min(total, findEnd(viewportBottom, total) + overscan);\n padTop = offsets[start];\n padBottom = offsets[total] - offsets[end];\n }\n syncRows(items.slice(start, end));\n sizeVisibleRows(start);\n container.style.paddingTop = `${padTop}px`;\n container.style.paddingBottom = `${padBottom}px`;\n for (const cb of renderSubscribers) cb();\n };\n\n const stopEffect = effect(() => {\n items = source.value; // tracking read — re-runs on any structural change\n heightsDirty = true; // items changed → the prefix sum (if any) is stale\n renderWindow();\n });\n\n // One rAF-coalesced render, shared by scroll and by measurement reports. A\n // pending anchor correction is applied to `scrollTop` first (which itself may\n // fire a scroll, but with the delta already cleared the follow-up is a no-op).\n const scheduleRender = (): void => {\n if (rafPending) return;\n rafPending = true;\n globalThis.requestAnimationFrame(() => {\n rafPending = false;\n if (disposed) return;\n if (pendingAnchorDelta !== 0) {\n parent.scrollTop += pendingAnchorDelta;\n pendingAnchorDelta = 0;\n }\n renderWindow();\n });\n };\n if (virtualize !== undefined) parent.addEventListener('scroll', scheduleRender);\n\n // Measured mode: report a row's real height. No-op for fixed / declared lists\n // and for keys not currently in the list.\n const setHeight = (k: ListKey, height: number): void => {\n if (!measuring) return;\n const idx = indexByKey.get(k);\n if (idx === undefined) return;\n const oldHeight = measured.has(k) ? (measured.get(k) as number) : estimateAt(idx);\n if (height === oldHeight) return;\n measured.set(k, height);\n // A row whose bottom is at/above the viewport top shifts everything below it\n // (the on-screen content) by the height delta — correct `scrollTop` to match.\n // Uses the CURRENT (pre-rebuild) offsets, which reflect the on-screen layout.\n if (offsets[idx + 1] <= parent.scrollTop) pendingAnchorDelta += height - oldHeight;\n heightsDirty = true;\n scheduleRender();\n };\n\n const dispose = ((): void => {\n disposed = true;\n stopEffect();\n for (const row of rows.values()) {\n row.dispose();\n if (virtualize === undefined) row.el.remove();\n }\n rows.clear();\n renderSubscribers.clear();\n if (virtualize !== undefined) {\n parent.removeEventListener('scroll', scheduleRender);\n container.remove(); // removes the inner sizer and its rows in one go\n VIRTUAL_INTERNALS.delete(handle);\n }\n }) as BindListHandle;\n const handle = dispose;\n handle.setHeight = setHeight;\n\n // Register the coordination surface the `observeRowHeights` helper needs, kept\n // off the public type (a GC-tied WeakMap, so it doesn't count against Design\n // rule 5). Only virtualized lists have a window to observe.\n if (virtualize !== undefined) {\n VIRTUAL_INTERNALS.set(handle, {\n visibleRows: () => order.map((row) => ({ key: key(row.item), el: row.el })),\n onRender: (cb) => {\n renderSubscribers.add(cb);\n return () => renderSubscribers.delete(cb);\n },\n });\n }\n\n return handle;\n}\n\n/** Internal coordination surface between {@link bindList} and {@link observeRowHeights}. */\ninterface VirtualInternals {\n /** The current visible rows, in order, with their keys. */\n visibleRows: () => Array<{ key: ListKey; el: HTMLElement }>;\n /** Subscribe to each window render; returns an unsubscribe. */\n onRender: (cb: () => void) => () => void;\n}\n\n// GC-tied (WeakMap) coordination store — a pure cache, not counted against\n// Design rule 5 (same class as `bindings.ts:insertedTextNodes`).\nconst VIRTUAL_INTERNALS = new WeakMap<object, VirtualInternals>();\n\n/**\n * Drive a **measured** virtualized `bindList` (`virtualize: { rowHeight: {\n * estimate } }`) from real layout: install ONE `ResizeObserver` over the visible\n * rows and forward each row's `offsetHeight` to `handle.setHeight`, re-observing\n * as the window shifts. Returns a disposer.\n *\n * This is the batteries-included measurement path; it is deliberately separate\n * from `bindList` (which never depends on `ResizeObserver`) — you can measure\n * however you like and call `handle.setHeight` yourself instead. A no-op for a\n * non-virtualized handle or where `ResizeObserver` is unavailable (SSR).\n *\n * const list = bindList(scrollEl, source, { key, render, virtualize: { rowHeight: { estimate: 64 } } });\n * const stopMeasuring = observeRowHeights(list);\n */\nexport function observeRowHeights(handle: BindListHandle): () => void {\n const internals = VIRTUAL_INTERNALS.get(handle);\n const RO = globalThis.ResizeObserver;\n if (internals === undefined || RO === undefined) return () => { /* nothing to observe */ };\n\n const keyByEl = new WeakMap<Element, ListKey>();\n const observer = new RO((entries) => {\n for (const entry of entries) {\n const k = keyByEl.get(entry.target);\n if (k !== undefined) handle.setHeight(k, (entry.target as HTMLElement).offsetHeight);\n }\n });\n\n const resync = (): void => {\n observer.disconnect();\n for (const { key: k, el } of internals.visibleRows()) {\n keyByEl.set(el, k);\n observer.observe(el);\n }\n };\n\n const unsubscribe = internals.onRender(resync);\n resync(); // observe the initial window\n\n return () => {\n observer.disconnect();\n unsubscribe();\n };\n}\n"]}
|
package/dist/overlay.d.ts
CHANGED
|
@@ -212,6 +212,44 @@ interface FormOptions {
|
|
|
212
212
|
* the title are auto-escaped through the JSX runtime.
|
|
213
213
|
*/
|
|
214
214
|
declare function form(fields: readonly FormField[], options?: FormOptions): Promise<Record<string, string> | null>;
|
|
215
|
+
/** One choosable action in a {@link choice} dialog. */
|
|
216
|
+
interface ChoiceAction<R> {
|
|
217
|
+
/** The value this action resolves. */
|
|
218
|
+
value: R;
|
|
219
|
+
/** Button label (auto-escaped). */
|
|
220
|
+
label: string;
|
|
221
|
+
/** Extra class on this action's button. */
|
|
222
|
+
className?: string;
|
|
223
|
+
}
|
|
224
|
+
/** Wiring slots for a {@link ChoiceOptions.render} — spread `actions[i]` onto your i-th button. */
|
|
225
|
+
interface ChoiceRenderSlots {
|
|
226
|
+
message: string;
|
|
227
|
+
/** One attribute bag per action (in order) — spread onto that action's control. */
|
|
228
|
+
actions: Array<Record<string, string>>;
|
|
229
|
+
}
|
|
230
|
+
/** Options for {@link choice}. */
|
|
231
|
+
interface ChoiceOptions<R> {
|
|
232
|
+
/** Where to append the overlay. Default `document.body`. */
|
|
233
|
+
container?: Element;
|
|
234
|
+
/** Wrapper class. Default `'kerf-overlay'`. */
|
|
235
|
+
className?: string;
|
|
236
|
+
/** Optional heading above the message. */
|
|
237
|
+
title?: string;
|
|
238
|
+
/** The value resolved when **Enter** is pressed anywhere in the dialog (the default action). */
|
|
239
|
+
defaultValue?: R;
|
|
240
|
+
/** Bring your own markup: return the full body, spreading each `slots.actions[i]` onto your buttons. */
|
|
241
|
+
render?: (slots: ChoiceRenderSlots) => OverlayContent;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The **N-way** sibling of {@link confirm}: renders one button per {@link ChoiceAction}
|
|
245
|
+
* and resolves that action's `value` on click, or `null` on Cancel / dismissal.
|
|
246
|
+
* Pass `defaultValue` to make **Enter** (anywhere in the dialog) resolve a default
|
|
247
|
+
* action — the "global Enter-to-confirm" model — without you having to hold the
|
|
248
|
+
* overlay handle. `message` + labels are auto-escaped; pass `render` for your own
|
|
249
|
+
* markup. kerf owns dismiss / focus-trap / focus-restore. For fully bespoke
|
|
250
|
+
* keyboard/close control, drive {@link overlay} directly.
|
|
251
|
+
*/
|
|
252
|
+
declare function choice<R>(message: string, actions: ReadonlyArray<ChoiceAction<R>>, options?: ChoiceOptions<R>): Promise<R | null>;
|
|
215
253
|
/** Vertical placement relative to an anchor (used by {@link popover}, {@link positionAnchored}, {@link tooltip}). */
|
|
216
254
|
type PopoverPlacement = 'bottom' | 'top';
|
|
217
255
|
/** Placement options for {@link positionAnchored} / {@link autoReposition}. */
|
|
@@ -316,21 +354,41 @@ interface ToastOptions {
|
|
|
316
354
|
* the region's current toast(s) first (collapse-to-latest for a rapid sequence).
|
|
317
355
|
*/
|
|
318
356
|
mode?: 'stack' | 'replace';
|
|
357
|
+
/**
|
|
358
|
+
* How `mode: 'replace'` drops the prior toast(s): `'fade'` (default) runs their
|
|
359
|
+
* full exit transition (nice for a STACKING region), or `'instant'` removes them
|
|
360
|
+
* synchronously with no exit — what a single, exactly-centered toast slot wants,
|
|
361
|
+
* so the outgoing and incoming messages never cross-fade in the same spot.
|
|
362
|
+
*/
|
|
363
|
+
collapse?: 'fade' | 'instant';
|
|
319
364
|
/** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */
|
|
320
365
|
variant?: ToastVariant;
|
|
321
366
|
/** Class added on the next animation frame after mount, so a CSS **entrance** transition can run. */
|
|
322
367
|
enterClass?: string;
|
|
323
|
-
/**
|
|
368
|
+
/**
|
|
369
|
+
* Class added when dismissing, so CSS owns the **exit**. On dismiss the
|
|
370
|
+
* `enterClass` (if any) is also REMOVED, so `exitClass` doesn't have to
|
|
371
|
+
* out-specify it — and a symmetric single-class fade (entrance = add
|
|
372
|
+
* `enterClass`, exit = remove it) works by setting only `enterClass` +
|
|
373
|
+
* `exitDuration`. The node is removed `exitDuration` ms later.
|
|
374
|
+
*/
|
|
324
375
|
exitClass?: string;
|
|
325
|
-
/** ms to wait
|
|
376
|
+
/** ms to wait before removing the node on dismiss — applies when `exitClass` is set OR when it's > 0 (to let a removed `enterClass` transition out). Default `0`. */
|
|
326
377
|
exitDuration?: number;
|
|
327
378
|
}
|
|
328
379
|
/** Handle returned by {@link toast}. */
|
|
329
380
|
interface ToastHandle {
|
|
330
381
|
/** The toast element — inspect it, or run your own entrance/exit transitions. */
|
|
331
382
|
el: HTMLElement;
|
|
332
|
-
/**
|
|
333
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Dismiss it early. Default runs the `exitClass` transition (removed after
|
|
385
|
+
* `exitDuration`); pass `{ instant: true }` to remove it **synchronously** with
|
|
386
|
+
* no exit — for an action button that immediately shows a replacement toast in a
|
|
387
|
+
* single centered slot (no cross-fade). Idempotent.
|
|
388
|
+
*/
|
|
389
|
+
dismiss(options?: {
|
|
390
|
+
instant?: boolean;
|
|
391
|
+
}): void;
|
|
334
392
|
}
|
|
335
393
|
/**
|
|
336
394
|
* Show a non-modal, auto-dismissing notification. Stacks in a shared body-level
|
|
@@ -341,4 +399,4 @@ interface ToastHandle {
|
|
|
341
399
|
*/
|
|
342
400
|
declare function toast(content: ToastContent, options?: ToastOptions): ToastHandle;
|
|
343
401
|
|
|
344
|
-
export { type AnchorPositionOptions, type ConfirmOptions, type ConfirmRenderSlots, type DismissTrigger, type FieldValidator, type FormField, type FormOptions, type FormRenderField, type FormRenderSlots, type OverlayContent, type OverlayHandle, type OverlayOptions, type PopoverOptions, type PopoverPlacement, type PromptOptions, type PromptRenderSlots, type ToastContent, type ToastHandle, type ToastOptions, type ToastVariant, type TooltipContent, type TooltipOptions, autoReposition, confirm, form, overlay, popover, positionAnchored, prompt, toast, tooltip };
|
|
402
|
+
export { type AnchorPositionOptions, type ChoiceAction, type ChoiceOptions, type ChoiceRenderSlots, type ConfirmOptions, type ConfirmRenderSlots, type DismissTrigger, type FieldValidator, type FormField, type FormOptions, type FormRenderField, type FormRenderSlots, type OverlayContent, type OverlayHandle, type OverlayOptions, type PopoverOptions, type PopoverPlacement, type PromptOptions, type PromptRenderSlots, type ToastContent, type ToastHandle, type ToastOptions, type ToastVariant, type TooltipContent, type TooltipOptions, autoReposition, choice, confirm, form, overlay, popover, positionAnchored, prompt, toast, tooltip };
|