kerfjs 4.1.1 → 4.2.0-beta.2

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/actions.d.ts +72 -0
  3. package/dist/actions.js +26 -0
  4. package/dist/actions.js.map +1 -0
  5. package/dist/array-signal.js +3 -104
  6. package/dist/array-signal.js.map +1 -1
  7. package/dist/async.d.ts +59 -0
  8. package/dist/async.js +55 -0
  9. package/dist/async.js.map +1 -0
  10. package/dist/attrSelector-Cmu2ZoGO.d.ts +79 -0
  11. package/dist/chunk-4MY2656S.js +1395 -0
  12. package/dist/chunk-4MY2656S.js.map +1 -0
  13. package/dist/{chunk-JXAR5J54.js → chunk-FSAQR6IU.js} +7 -4
  14. package/dist/chunk-FSAQR6IU.js.map +1 -0
  15. package/dist/chunk-KEZTD6H4.js +54 -0
  16. package/dist/chunk-KEZTD6H4.js.map +1 -0
  17. package/dist/chunk-MRYM3O3V.js +106 -0
  18. package/dist/chunk-MRYM3O3V.js.map +1 -0
  19. package/dist/chunk-U32TFTGZ.js +74 -0
  20. package/dist/chunk-U32TFTGZ.js.map +1 -0
  21. package/dist/delegate-CL9VTZFb.d.ts +93 -0
  22. package/dist/html.js +2 -2
  23. package/dist/imperative.d.ts +34 -0
  24. package/dist/imperative.js +20 -0
  25. package/dist/imperative.js.map +1 -0
  26. package/dist/index.d.ts +21 -219
  27. package/dist/index.js +13 -1511
  28. package/dist/index.js.map +1 -1
  29. package/dist/jsx-runtime.d.ts +13 -1
  30. package/dist/jsx-runtime.js +2 -2
  31. package/dist/list.d.ts +39 -0
  32. package/dist/list.js +146 -0
  33. package/dist/list.js.map +1 -0
  34. package/dist/mount-Bo2qOx25.d.ts +57 -0
  35. package/dist/overlay.d.ts +204 -0
  36. package/dist/overlay.js +406 -0
  37. package/dist/overlay.js.map +1 -0
  38. package/dist/remount.d.ts +52 -0
  39. package/dist/remount.js +45 -0
  40. package/dist/remount.js.map +1 -0
  41. package/dist/scope.d.ts +67 -0
  42. package/dist/scope.js +71 -0
  43. package/dist/scope.js.map +1 -0
  44. package/dist/timing.d.ts +65 -0
  45. package/dist/timing.js +80 -0
  46. package/dist/timing.js.map +1 -0
  47. package/package.json +34 -1
  48. package/dist/chunk-JXAR5J54.js.map +0 -1
@@ -1162,6 +1162,18 @@ declare class SafeHtml {
1162
1162
  declare function isSafeHtml(value: unknown): value is SafeHtml;
1163
1163
  /** Inject a pre-escaped HTML string. Use sparingly — caller is responsible for escaping. */
1164
1164
  declare function raw(html: string): SafeHtml;
1165
+ /**
1166
+ * `trustedRaw(html)` — identical to {@link raw} at runtime, but names your intent:
1167
+ * "this dynamic value is server-trusted, inject it verbatim." The
1168
+ * `kerfjs/no-raw-with-dynamic-arg` lint rule flags a `raw()` with a NON-literal
1169
+ * argument (unsanitized user input is the common XSS mistake) but leaves
1170
+ * `trustedRaw()` alone — so a CSRF token, a trusted `<script src>`, or a
1171
+ * server-issued id can be injected without scattering `eslint-disable` comments.
1172
+ *
1173
+ * It is NOT a sanitizer — it bypasses escaping exactly like `raw()`. Only pass
1174
+ * values you control (server output, config, hard-coded), never raw user input.
1175
+ */
1176
+ declare function trustedRaw(html: string): SafeHtml;
1165
1177
  /**
1166
1178
  * Internal: build a `SafeHtml` representing a list segment. Used by
1167
1179
  * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction.
@@ -1236,4 +1248,4 @@ declare namespace JSX {
1236
1248
  declare function _toSegment(child: unknown): Segment;
1237
1249
  declare function _renderAttrVerbatim(name: string, value: unknown): string;
1238
1250
 
1239
- export { type AttrLike, type AttrValue, type DataAriaAttrs, Fragment, JSX, type KerfBaseAttrs, type KerfCustomElement, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw };
1251
+ export { type AttrLike, type AttrValue, type DataAriaAttrs, Fragment, JSX, type KerfBaseAttrs, type KerfCustomElement, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw, trustedRaw };
@@ -1,6 +1,6 @@
1
- export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-JXAR5J54.js';
2
- import './chunk-GY4XV2UV.js';
1
+ export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw, trustedRaw } from './chunk-FSAQR6IU.js';
3
2
  import './chunk-3APBEVHF.js';
3
+ import './chunk-GY4XV2UV.js';
4
4
  import './chunk-VVDJLWMP.js';
5
5
  //# sourceMappingURL=jsx-runtime.js.map
6
6
  //# sourceMappingURL=jsx-runtime.js.map
package/dist/list.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { M as MountResult } from './mount-Bo2qOx25.js';
2
+ import './jsx-runtime.js';
3
+ import '@preact/signals-core';
4
+ import './bindings-CYwoJpQb.js';
5
+
6
+ /** A row's stable key. */
7
+ type ListKey = string | number;
8
+ /** Anything with a tracking `.value` array read — a `signal<readonly T[]>` or an `arraySignal<T>`. */
9
+ interface ListSource<T> {
10
+ readonly value: readonly T[];
11
+ }
12
+ /** Options for {@link bindList}. */
13
+ interface BindListOptions<T> {
14
+ /** Stable per-row key. Rows are matched, moved, and reused by this. */
15
+ key: (item: T) => ListKey;
16
+ /** Renders a row's content into its (individually mounted) row element. Read signals here for per-row reactivity. */
17
+ render: (item: T) => MountResult;
18
+ /** Row element tag. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). */
19
+ tag?: string;
20
+ /**
21
+ * Turn on viewport virtualization. `rowHeight` is the fixed pixel height of
22
+ * every row; `overscan` (default 3) is how many extra rows to render above and
23
+ * below the viewport. `parent` must be a scroll container (your CSS: a fixed
24
+ * height + `overflow: auto`).
25
+ */
26
+ virtualize?: {
27
+ rowHeight: number;
28
+ overscan?: number;
29
+ };
30
+ }
31
+ /**
32
+ * Bind a keyed, per-row-reactive list to `parent`, driven by `source` (a
33
+ * `signal<readonly T[]>` or an `arraySignal<T>`). Returns a disposer that tears
34
+ * down every row mount, the scroll listener (if virtualized), and the source
35
+ * subscription.
36
+ */
37
+ declare function bindList<T>(parent: HTMLElement, source: ListSource<T>, options: BindListOptions<T>): () => void;
38
+
39
+ export { type BindListOptions, type ListKey, type ListSource, bindList };
package/dist/list.js ADDED
@@ -0,0 +1,146 @@
1
+ import { ARRAY_SIGNAL_BRAND } from './chunk-MRYM3O3V.js';
2
+ import { mount } from './chunk-4MY2656S.js';
3
+ import './chunk-QIP723L4.js';
4
+ import './chunk-YHH7OUFA.js';
5
+ import './chunk-FSAQR6IU.js';
6
+ import { effect } from './chunk-3APBEVHF.js';
7
+ import './chunk-GY4XV2UV.js';
8
+ import './chunk-VVDJLWMP.js';
9
+
10
+ // src/list.ts
11
+ function bindList(parent, source, options) {
12
+ const { key, render, tag = "div", virtualize } = options;
13
+ const overscan = virtualize?.overscan ?? 3;
14
+ const rows = /* @__PURE__ */ new Map();
15
+ const order = [];
16
+ let items = [];
17
+ let disposed = false;
18
+ let rafPending = false;
19
+ let firstRender = true;
20
+ const patchSource = source;
21
+ const granularEligible = virtualize === void 0 && patchSource[ARRAY_SIGNAL_BRAND] === true;
22
+ const container = virtualize === void 0 ? parent : document.createElement("div");
23
+ if (virtualize !== void 0) parent.appendChild(container);
24
+ const makeRow = (item) => {
25
+ const el = document.createElement(tag);
26
+ if (virtualize !== void 0) el.style.height = `${virtualize.rowHeight}px`;
27
+ const dispose = mount(el, () => render(item));
28
+ return { el, item, dispose };
29
+ };
30
+ const syncRows = (visible) => {
31
+ const wanted = /* @__PURE__ */ new Set();
32
+ for (const item of visible) wanted.add(key(item));
33
+ for (const [k, row] of rows) {
34
+ if (!wanted.has(k)) {
35
+ row.dispose();
36
+ row.el.remove();
37
+ rows.delete(k);
38
+ }
39
+ }
40
+ order.length = 0;
41
+ for (const item of visible) {
42
+ const k = key(item);
43
+ let row = rows.get(k);
44
+ if (row !== void 0 && row.item !== item) {
45
+ row.dispose();
46
+ row.el.remove();
47
+ rows.delete(k);
48
+ row = void 0;
49
+ }
50
+ if (row === void 0) {
51
+ row = makeRow(item);
52
+ rows.set(k, row);
53
+ }
54
+ order.push(row);
55
+ }
56
+ let ref = null;
57
+ for (let i = order.length - 1; i >= 0; i--) {
58
+ const el = order[i].el;
59
+ if (el.parentNode !== container || el.nextSibling !== ref) {
60
+ container.insertBefore(el, ref);
61
+ }
62
+ ref = el;
63
+ }
64
+ };
65
+ const applyPatches = (patches) => {
66
+ for (const patch of patches) {
67
+ if (patch.type === "insert") {
68
+ const row = makeRow(patch.item);
69
+ rows.set(key(patch.item), row);
70
+ order.splice(patch.index, 0, row);
71
+ container.insertBefore(row.el, order[patch.index + 1]?.el ?? null);
72
+ } else if (patch.type === "remove") {
73
+ const [row] = order.splice(patch.index, 1);
74
+ row.dispose();
75
+ row.el.remove();
76
+ rows.delete(key(row.item));
77
+ } else if (patch.type === "move") {
78
+ const [row] = order.splice(patch.from, 1);
79
+ order.splice(patch.to, 0, row);
80
+ container.insertBefore(row.el, order[patch.to + 1]?.el ?? null);
81
+ } else if (patch.type === "update") {
82
+ const current = order[patch.index];
83
+ if (current.item !== patch.item) {
84
+ current.dispose();
85
+ current.el.remove();
86
+ rows.delete(key(current.item));
87
+ const row = makeRow(patch.item);
88
+ rows.set(key(patch.item), row);
89
+ order[patch.index] = row;
90
+ container.insertBefore(row.el, order[patch.index + 1]?.el ?? null);
91
+ }
92
+ }
93
+ }
94
+ };
95
+ const renderWindow = () => {
96
+ if (virtualize === void 0) {
97
+ if (granularEligible) {
98
+ const patches = patchSource._consumePatches();
99
+ if (!firstRender && patches.length > 0 && !patches.some((p) => p.type === "replace")) {
100
+ applyPatches(patches);
101
+ return;
102
+ }
103
+ }
104
+ syncRows(items);
105
+ firstRender = false;
106
+ return;
107
+ }
108
+ const { rowHeight } = virtualize;
109
+ const total = items.length;
110
+ const start = Math.max(0, Math.floor(parent.scrollTop / rowHeight) - overscan);
111
+ const end = Math.min(total, Math.ceil((parent.scrollTop + parent.clientHeight) / rowHeight) + overscan);
112
+ syncRows(items.slice(start, end));
113
+ container.style.paddingTop = `${start * rowHeight}px`;
114
+ container.style.paddingBottom = `${Math.max(0, total - end) * rowHeight}px`;
115
+ };
116
+ const stopEffect = effect(() => {
117
+ items = source.value;
118
+ renderWindow();
119
+ });
120
+ const onScroll = () => {
121
+ if (rafPending) return;
122
+ rafPending = true;
123
+ globalThis.requestAnimationFrame(() => {
124
+ rafPending = false;
125
+ if (!disposed) renderWindow();
126
+ });
127
+ };
128
+ if (virtualize !== void 0) parent.addEventListener("scroll", onScroll);
129
+ return () => {
130
+ disposed = true;
131
+ stopEffect();
132
+ for (const row of rows.values()) {
133
+ row.dispose();
134
+ if (virtualize === void 0) row.el.remove();
135
+ }
136
+ rows.clear();
137
+ if (virtualize !== void 0) {
138
+ parent.removeEventListener("scroll", onScroll);
139
+ container.remove();
140
+ }
141
+ };
142
+ }
143
+
144
+ export { bindList };
145
+ //# sourceMappingURL=list.js.map
146
+ //# sourceMappingURL=list.js.map
@@ -0,0 +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/** 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 /** Renders a row's content into its (individually mounted) row element. Read signals here for per-row reactivity. */\n render: (item: T) => MountResult;\n /** Row element tag. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). */\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 makeRow = (item: T): Row<T> => {\n const el = document.createElement(tag);\n // bindList knows the fixed row height, so it sizes rows itself — no\n // consumer CSS needed for the windowing math to line up.\n if (virtualize !== undefined) el.style.height = `${virtualize.rowHeight}px`;\n const dispose = mount(el, () => render(item));\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"]}
@@ -0,0 +1,57 @@
1
+ import { SafeHtml } from './jsx-runtime.js';
2
+
3
+ /**
4
+ * `mount(rootEl, render)` — kerf's render primitive.
5
+ *
6
+ * Wraps `effect()` so that whenever any signal read inside `render()`
7
+ * changes, we re-run `render()` and apply the minimum DOM mutations against
8
+ * the live tree. Element identity (and thus focus, selection, in-flight
9
+ * pointer interactions, and event listeners on preserved nodes) is preserved
10
+ * wherever the keyed/positional diff matches.
11
+ *
12
+ * Two phases per render:
13
+ *
14
+ * - Static surrounds (everything outside `each()` lists): kerf's native
15
+ * `morph()` reconciler walks a freshly-built template against the live
16
+ * tree. Conventions: id/data-key matching, `data-morph-skip`, focus
17
+ * preservation.
18
+ *
19
+ * - List interiors (children of every `each()` parent): native keyed
20
+ * reconciler operates directly on the live parent's children. No
21
+ * re-parse, no morph walk for cache-hit rows. Cost is O(changes), not
22
+ * O(rows).
23
+ *
24
+ * Compared to a `replaceChildren(...rows.map(toElement))` rebuild pattern,
25
+ * the user-visible win is that an `<input>` the user is typing into
26
+ * survives an unrelated re-render — its DOM node, focus state, and cursor
27
+ * position are not destroyed and recreated on each tick.
28
+ */
29
+
30
+ /** What `mount()`'s render function may return; non-SafeHtml values coerce (nullish/boolean → render nothing). */
31
+ type MountResult = SafeHtml | string | number | boolean | null | undefined;
32
+ /**
33
+ * Bind `render()` to the children of `rootEl`. Re-runs whenever any signal
34
+ * read inside `render()` changes. Returns a disposer that tears down the
35
+ * effect; call it when the host element is removed from the DOM.
36
+ *
37
+ * Conventions:
38
+ *
39
+ * - Diff keys: `id` and `data-key` are matched across the morph by key
40
+ * rather than positionally, so list reorders move existing nodes instead
41
+ * of churning unrelated siblings.
42
+ * - `data-morph-skip`: any element with this attribute is left untouched
43
+ * inside on subsequent renders. Used for library-owned subtrees (xterm-
44
+ * style widgets, charts, third-party editors) where the library's own
45
+ * lifecycle manages the children.
46
+ * - Focused text-entry inputs (`<input>` of typing kinds, `<textarea>`)
47
+ * keep their current value + selection range across morphs while focused.
48
+ * The user never sees their cursor jump mid-keystroke.
49
+ * - Focused `[contenteditable]` elements have their entire subtree
50
+ * skipped (same mechanism as `data-morph-skip`). The user's in-progress
51
+ * edit — typed content, caret position, multi-range selections, anything
52
+ * else they did to the DOM — survives verbatim. The next render after
53
+ * blur catches up.
54
+ */
55
+ declare function mount(rootEl: HTMLElement, render: () => MountResult): () => void;
56
+
57
+ export { type MountResult as M, mount as m };
@@ -0,0 +1,204 @@
1
+ import { SafeHtml } from './jsx-runtime.js';
2
+ import { M as MountResult } from './mount-Bo2qOx25.js';
3
+ import '@preact/signals-core';
4
+ import './bindings-CYwoJpQb.js';
5
+
6
+ /** A user-initiated dismissal trigger. */
7
+ type DismissTrigger = 'escape' | 'backdrop' | 'outside';
8
+ /** Content for an overlay: static `SafeHtml`, or a render function `mount()` drives reactively. */
9
+ type OverlayContent = SafeHtml | (() => MountResult);
10
+ /** Options for {@link overlay}. */
11
+ interface OverlayOptions {
12
+ /** Where to append the overlay wrapper. Default `document.body`. */
13
+ container?: Element;
14
+ /** Class on the wrapper element (you style it — kerf ships no CSS). Default `'kerf-overlay'`. */
15
+ className?: string;
16
+ /**
17
+ * Which user actions dismiss the overlay. Default `['escape', 'backdrop']`.
18
+ * `'backdrop'` = a click on the wrapper itself (not its content); `'outside'`
19
+ * = a click anywhere outside the wrapper (for anchored popovers). `false`
20
+ * disables user dismissal (close it programmatically).
21
+ */
22
+ dismiss?: DismissTrigger | DismissTrigger[] | false;
23
+ /**
24
+ * Where focus lands on open: a selector, `true` (first focusable element, or
25
+ * the wrapper if none), or `false` (leave focus alone). Default `true`.
26
+ */
27
+ initialFocus?: string | boolean;
28
+ /**
29
+ * Trap Tab / Shift+Tab within the overlay while open and mark it
30
+ * `role="dialog"` / `aria-modal="true"`. Default `true`. Set `false` for a
31
+ * non-modal popover.
32
+ */
33
+ trap?: boolean;
34
+ /** ARIA role for the wrapper when `trap` is on. Default `'dialog'`. */
35
+ role?: string;
36
+ /** Called on any user-initiated dismissal (before `close()` runs). */
37
+ onDismiss?: () => void;
38
+ /** For `'outside'` dismissal: clicks on these elements do NOT count as outside (e.g. the trigger button). */
39
+ outsideIgnore?: Element | readonly Element[];
40
+ }
41
+ /** Handle returned by {@link overlay}. Holds no framework state — it's a closure. */
42
+ interface OverlayHandle {
43
+ /** The wrapper element (mounted into, appended to `container`). */
44
+ el: HTMLElement;
45
+ /** Tear down: dispose the mount, remove listeners + the node, restore focus, resolve `result`. Idempotent. */
46
+ close(result?: unknown): void;
47
+ /** Resolves with the value passed to `close()` (or `undefined` on user dismissal). */
48
+ result: Promise<unknown>;
49
+ }
50
+ /**
51
+ * Open an overlay: append a wrapper to `container`, `mount()` `content` inside
52
+ * it, wire the requested dismissals + (optionally) a focus trap, and return a
53
+ * handle. See {@link OverlayOptions}.
54
+ */
55
+ declare function overlay(content: OverlayContent, options?: OverlayOptions): OverlayHandle;
56
+ /** Options for {@link confirm}. */
57
+ interface ConfirmOptions {
58
+ /** Where to append the overlay. Default `document.body`. */
59
+ container?: Element;
60
+ /** Wrapper class. Default `'kerf-overlay'`. */
61
+ className?: string;
62
+ /** Optional heading above the message. */
63
+ title?: string;
64
+ /** Confirm button label. Default `'OK'`. */
65
+ okText?: string;
66
+ /** Cancel button label. Default `'Cancel'`. */
67
+ cancelText?: string;
68
+ /** Add a `kerf-confirm--danger` class to the wrapper for destructive actions. */
69
+ danger?: boolean;
70
+ }
71
+ /**
72
+ * A promise-based `window.confirm` replacement (that global is a no-op in Tauri
73
+ * webviews). Renders a two-button dialog and resolves `true` for OK, `false`
74
+ * for Cancel or any dismissal (Escape / backdrop). Message + labels are
75
+ * auto-escaped (rendered through the JSX runtime).
76
+ */
77
+ declare function confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
78
+ /**
79
+ * Validate a single field's value. Return a non-empty error string to BLOCK
80
+ * submission (shown inline next to the field); return `undefined`/`null`/`''` to
81
+ * allow it.
82
+ */
83
+ type FieldValidator = (value: string) => string | null | undefined | void;
84
+ /** Options for {@link prompt}. */
85
+ interface PromptOptions {
86
+ /** Where to append the overlay. Default `document.body`. */
87
+ container?: Element;
88
+ /** Wrapper class. Default `'kerf-overlay'`. */
89
+ className?: string;
90
+ /** Optional heading above the message. */
91
+ title?: string;
92
+ /** Pre-filled input value. Default `''`. */
93
+ defaultValue?: string;
94
+ /** Input placeholder. */
95
+ placeholder?: string;
96
+ /** `type` attribute of the input (`'text'`, `'email'`, `'password'`, …). Default `'text'`. */
97
+ inputType?: string;
98
+ /** Confirm button label. Default `'OK'`. */
99
+ okText?: string;
100
+ /** Cancel button label. Default `'Cancel'`. */
101
+ cancelText?: string;
102
+ /** Block OK while this returns an error string; the message shows inline. */
103
+ validate?: FieldValidator;
104
+ }
105
+ /**
106
+ * A promise-based `window.prompt` replacement (that global is a no-op in Tauri
107
+ * webviews). Renders a one-field dialog and resolves the entered **string** on OK
108
+ * (an empty string is a valid result) or `null` on Cancel / dismissal. Enter in
109
+ * the input submits. `message`, the default value, and labels are auto-escaped
110
+ * (rendered through the JSX runtime). Optional `validate` blocks OK inline.
111
+ */
112
+ declare function prompt(message: string, options?: PromptOptions): Promise<string | null>;
113
+ /** A single field in a {@link form}. */
114
+ interface FormField {
115
+ /** Field name — the key in the resolved record (and the input's `name`). */
116
+ name: string;
117
+ /** Label shown above the input. Defaults to `name`. */
118
+ label?: string;
119
+ /** Pre-filled value. Default `''`. */
120
+ defaultValue?: string;
121
+ /** Input placeholder. */
122
+ placeholder?: string;
123
+ /** `type` attribute of the input. Default `'text'`. */
124
+ type?: string;
125
+ /** Block OK while this returns an error string; the message shows inline for this field. */
126
+ validate?: FieldValidator;
127
+ }
128
+ /** Options for {@link form}. */
129
+ interface FormOptions {
130
+ /** Where to append the overlay. Default `document.body`. */
131
+ container?: Element;
132
+ /** Wrapper class. Default `'kerf-overlay'`. */
133
+ className?: string;
134
+ /** Optional heading above the fields. */
135
+ title?: string;
136
+ /** Confirm button label. Default `'OK'`. */
137
+ okText?: string;
138
+ /** Cancel button label. Default `'Cancel'`. */
139
+ cancelText?: string;
140
+ }
141
+ /**
142
+ * A promise-based multi-field dialog — the two-or-three-input sibling of
143
+ * {@link prompt}. Renders one labeled input per {@link FormField} and resolves a
144
+ * `Record<name, value>` on OK (after every field's `validate` passes) or `null`
145
+ * on Cancel / dismissal. Enter in any field submits. All labels, defaults, and
146
+ * the title are auto-escaped through the JSX runtime.
147
+ */
148
+ declare function form(fields: readonly FormField[], options?: FormOptions): Promise<Record<string, string> | null>;
149
+ /** Vertical placement of a {@link popover} relative to its anchor. */
150
+ type PopoverPlacement = 'bottom' | 'top';
151
+ /** Options for {@link popover}. */
152
+ interface PopoverOptions {
153
+ /** Where to append the popover wrapper. Default `document.body`. */
154
+ container?: Element;
155
+ /** Class on the wrapper. Default `'kerf-popover'`. */
156
+ className?: string;
157
+ /** Preferred side of the anchor. Flips to the other side if it would overflow the viewport. Default `'bottom'`. */
158
+ placement?: PopoverPlacement;
159
+ /** Horizontal edge to line up with the anchor: `'start'` (left edges) or `'end'` (right edges). Default `'start'`. */
160
+ align?: 'start' | 'end';
161
+ /** Gap in px between the anchor and the popover. Default `4`. */
162
+ gap?: number;
163
+ /**
164
+ * Which user actions dismiss the popover. Default `['outside']` (a click
165
+ * outside the popover, the anchor exempt). Pass `false` to close only via `close()`.
166
+ */
167
+ dismiss?: DismissTrigger | DismissTrigger[] | false;
168
+ /** Focus behavior on open. Default `false` (non-modal — leave focus alone). */
169
+ initialFocus?: string | boolean;
170
+ /** Extra elements (besides the anchor) whose clicks do NOT count as outside. */
171
+ outsideIgnore?: Element | readonly Element[];
172
+ /** Called on any user-initiated dismissal. */
173
+ onDismiss?: () => void;
174
+ }
175
+ /**
176
+ * Anchored, non-modal overlay: positions `content` relative to `anchor` (below by
177
+ * default, flipping above if it would overflow, and clamped horizontally to the
178
+ * viewport) and repositions on scroll / resize while open. A thin wrapper over
179
+ * {@link overlay} with non-modal defaults — `trap: false`, `dismiss: ['outside']`,
180
+ * and the anchor added to `outsideIgnore` so the trigger click doesn't self-close.
181
+ * Returns the same {@link OverlayHandle}; `close()` also drops the reposition
182
+ * listeners. `position: fixed` is set inline (you style everything else).
183
+ */
184
+ declare function popover(anchor: Element, content: OverlayContent, options?: PopoverOptions): OverlayHandle;
185
+ /** Content for a {@link toast}: text, `SafeHtml`, or a render function. */
186
+ type ToastContent = string | SafeHtml | (() => MountResult);
187
+ /** Options for {@link toast}. */
188
+ interface ToastOptions {
189
+ /** Where toasts stack. Default: a lazily-created `<div class="kerf-toasts">` on `document.body`. */
190
+ container?: Element;
191
+ /** Class on the toast element. Default `'kerf-toast'`. */
192
+ className?: string;
193
+ /** Auto-dismiss after this many ms. `0` keeps it until dismissed by hand. Default `4000`. */
194
+ duration?: number;
195
+ /** ARIA role. Default `'status'`. */
196
+ role?: string;
197
+ }
198
+ /**
199
+ * Show a non-modal, auto-dismissing notification. Stacks in a shared body-level
200
+ * region (or your `container`). Returns a `() => void` that dismisses it early.
201
+ */
202
+ declare function toast(content: ToastContent, options?: ToastOptions): () => void;
203
+
204
+ export { type ConfirmOptions, type DismissTrigger, type FieldValidator, type FormField, type FormOptions, type OverlayContent, type OverlayHandle, type OverlayOptions, type PopoverOptions, type PopoverPlacement, type PromptOptions, type ToastContent, type ToastOptions, confirm, form, overlay, popover, prompt, toast };