+10
-0
@@ -81,2 +81,12 @@ /** The lifecycle status of a {@link Resource}. */ | ||
| reset(): void; | ||
| /** | ||
| * Read-only: the cached value for a `cacheKey` key, or `undefined` if that key | ||
| * isn't cached (or no `cacheKey` was given). Lets a consumer ask "is this slice | ||
| * cached?" without running it (which would mutate state). | ||
| */ | ||
| cached(key: string): T | undefined; | ||
| /** Read-only: the keys currently in the per-input cache (`.length` is the cache size). */ | ||
| cachedKeys(): string[]; | ||
| /** Evict the per-input cache — one `key`, or the whole cache when called with no argument. Does NOT change `value`. */ | ||
| clearCache(key?: string): void; | ||
| } | ||
@@ -83,0 +93,0 @@ /** Create an async-state {@link Resource}. No per-instance framework state — it's a closure over a signal. */ |
+7
-1
@@ -90,3 +90,9 @@ import { signal } from './chunk-3APBEVHF.js'; | ||
| run, | ||
| reset | ||
| reset, | ||
| cached: (k) => cache.get(k), | ||
| cachedKeys: () => Array.from(cache.keys()), | ||
| clearCache: (k) => { | ||
| if (k === void 0) cache.clear(); | ||
| else cache.delete(k); | ||
| } | ||
| }; | ||
@@ -93,0 +99,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/async.ts"],"names":[],"mappings":"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","file":"async.js","sourcesContent":["/**\n * `kerfjs/async` — model async state, with the stale-response guard built in.\n *\n * Every real kerf app reproduces the same shape — `{ status, data, error }` —\n * for loading/error UI, each paired with a hand-rolled generation counter so a\n * slow response can't overwrite a newer one. This subpath blesses exactly that,\n * and no more: you still write the fetch (Node `fetch` for SSR, browser `fetch`\n * client-side), and `.run()` owns the status transitions plus the stale guard.\n *\n * import { resource } from 'kerfjs/async';\n *\n * const users = resource<User[]>();\n * users.run(() => fetch('/api/users').then((r) => r.json()));\n * // render off users.value.status: 'idle' | 'running' | 'completed' | 'failed'\n *\n * Only the LATEST run may resolve the state, so out-of-order responses are\n * dropped automatically. Optional progress: declare the `report` parameter on\n * your fetcher and call it (e.g. from an upload's progress events).\n *\n * Pass an input — `run(input, fetcher)` — to carry which request a run is for\n * through to `value.input` (set for `running`/`completed`/`failed`), so a\n * failure handler can recover the id/params of the run that failed:\n *\n * const diff = resource<Diff, { fileId: string }>();\n * diff.run({ fileId }, (report) => fetchDiff(fileId, report));\n * // on failure: diff.value.status === 'failed' && diff.value.input.fileId\n *\n * For a real SWR-with-cache section, pass `cacheKey` to keep the last value PER\n * input key (switching back to a loaded key paints its cached slice instantly\n * while it revalidates), and read `value.revision` — a counter that bumps only\n * when `data` actually CHANGES (by `equals`, default `Object.is`) — to skip a\n * redundant paint when a poll tick returns identical data:\n *\n * const win = resource<Slice, string>({ cacheKey: (w) => w, equals: sameSlice });\n * win.run(w, () => fetchSlice(w)); // instant cached paint for a revisited w\n */\nimport { signal } from './reactive.js';\n\n/** The lifecycle status of a {@link Resource}. */\nexport type ResourceStatus = 'idle' | 'running' | 'completed' | 'failed';\n\n/** Optional progress for a long-running fetch (uploads, chunked work). */\nexport interface ResourceProgress {\n completed: number;\n total: number;\n}\n\n/** The reactive state a {@link Resource} exposes. */\nexport interface ResourceState<T, I = void> {\n status: ResourceStatus;\n /** The last successful value. Kept across a re-run (stale-while-revalidate) and on failure. */\n data: T | undefined;\n /** The rejection from the most recent failed run. */\n error: unknown;\n /** Latest reported progress while running, or `undefined`. */\n progress: ResourceProgress | undefined;\n /**\n * The input of the LATEST run — the value passed to {@link Resource.run} as\n * `run(input, fetcher)`. Set for `running`, `completed`, AND `failed` (same\n * stale-guard rule as the rest of the state), so an effect can branch on\n * `status === 'failed'` and still know which request failed. `undefined` in\n * `idle`, and for the no-input `run(fetcher)` form.\n */\n input: I | undefined;\n /**\n * A monotonic counter that increments only when `data` actually CHANGES (by\n * the resource's `equals`, default `Object.is`). Compare it against the value\n * you last painted to skip a redundant re-render — e.g. a 30s poll returning\n * identical data leaves `revision` untouched, so you can bail before wiping\n * scroll / sort / hover state. Starts at `0`.\n */\n revision: number;\n}\n\n/** Construction options for {@link resource}. */\nexport interface ResourceOptions<T, I = void> {\n /**\n * Derive a cache key from a run's `input`. When set, the resource keeps the\n * last successful value PER key: starting a run for a key that was loaded\n * before paints its cached slice immediately (still `running`) while the fetch\n * revalidates in the background; a never-loaded key starts with no `data`.\n * Without `cacheKey`, a run keeps the previous run's `data` (single-slot\n * stale-while-revalidate), as before.\n */\n cacheKey?: (input: I) => string;\n /**\n * Equality used to decide whether `data` changed (drives `value.revision`).\n * Default `Object.is`. Pass a structural comparison to dedup a poll that\n * returns a fresh-but-equal object.\n */\n equals?: (a: T, b: T) => boolean;\n}\n\n/**\n * The fetcher passed to {@link Resource.run}. You own the transport. It receives\n * a `report(completed, total)` callback for optional progress — ignore it if you\n * don't need progress (a plain `() => Promise<T>` is assignable here).\n */\nexport type ResourceFetcher<T> = (report: (completed: number, total: number) => void) => Promise<T>;\n\n/**\n * An async-state container. Its `value` is a tracking read; drive UI off\n * `value.status`. `I` is the run-input type — parametrize it (`resource<T, I>()`)\n * to carry a typed `run(input, fetcher)` input through to `value.input`.\n */\nexport interface Resource<T, I = void> {\n /** Tracking read of the current {@link ResourceState}. */\n readonly value: ResourceState<T, I>;\n /**\n * Run `fetcher`, driving `idle`/`running` → `completed`/`failed` and guarding\n * against stale responses (only the latest run resolves the state). Never\n * rejects — a failure lands in `value.error`; resolves with the data (or\n * `undefined` on failure) for callers who want to await it.\n */\n run(fetcher: ResourceFetcher<T>): Promise<T | undefined>;\n /**\n * Run `fetcher` for a given `input`, exposing it as `value.input` for the\n * `running`/`completed`/`failed` states of THIS run — so a failure handler can\n * recover which request failed. Same stale guard: only the latest run resolves.\n */\n run(input: I, fetcher: ResourceFetcher<T>): Promise<T | undefined>;\n /** Reset to `idle` (clearing data/error/progress/input, and the per-key cache) and invalidate any in-flight run. */\n reset(): void;\n}\n\n/** Create an async-state {@link Resource}. No per-instance framework state — it's a closure over a signal. */\nexport function resource<T, I = void>(options: ResourceOptions<T, I> = {}): Resource<T, I> {\n const { cacheKey, equals } = options;\n const eq: (a: T, b: T) => boolean = equals ?? Object.is;\n const cache = new Map<string, T>(); // per-key SWR cache (GC-tied to the resource)\n\n // Revision tracking: `revision` bumps only when `data` changes (by `eq`).\n let revision = 0;\n let lastData: T | undefined;\n const changed = (next: T | undefined): boolean =>\n // undefined transitions are handled by reference; two defined values by `eq`.\n lastData === undefined || next === undefined ? lastData !== next : !eq(lastData, next);\n const commit = (next: T | undefined): number => {\n if (changed(next)) {\n revision++;\n lastData = next;\n }\n return revision;\n };\n\n const state = signal<ResourceState<T, I>>({\n status: 'idle',\n data: undefined,\n error: undefined,\n progress: undefined,\n input: undefined,\n revision: 0,\n });\n // Per-resource run counter (closure-local, not module state) — the stale guard.\n let generation = 0;\n\n function run(\n inputOrFetcher: I | ResourceFetcher<T>,\n maybeFetcher?: ResourceFetcher<T>,\n ): Promise<T | undefined> {\n // Two-arg form is (input, fetcher); one-arg form is (fetcher) with no input.\n // A fetcher is always a function, so `maybeFetcher === undefined` uniquely\n // identifies the one-arg call — even when the input value is itself undefined.\n const fetcher = (maybeFetcher ?? inputOrFetcher) as ResourceFetcher<T>;\n const input = (maybeFetcher === undefined ? undefined : inputOrFetcher) as I | undefined;\n const key = cacheKey !== undefined && maybeFetcher !== undefined ? cacheKey(input as I) : undefined;\n\n // What `data` shows while running: the cached slice for this key (per-key\n // SWR), or the previous run's data (single-slot SWR) when no cacheKey.\n const runningData = cacheKey !== undefined\n ? (key !== undefined ? cache.get(key) : undefined)\n : state.value.data;\n\n const gen = ++generation;\n state.value = {\n status: 'running',\n data: runningData,\n error: undefined,\n progress: undefined,\n input,\n revision: commit(runningData),\n };\n\n const report = (completed: number, total: number): void => {\n if (gen === generation) {\n state.value = { ...state.value, progress: { completed, total } };\n }\n };\n\n return fetcher(report).then(\n (data) => {\n if (gen === generation) {\n if (key !== undefined) cache.set(key, data);\n state.value = {\n status: 'completed',\n data,\n error: undefined,\n progress: undefined,\n input,\n revision: commit(data),\n };\n }\n return data;\n },\n (error: unknown) => {\n if (gen === generation) {\n // Keep `data` (and thus `revision`) on failure — stale-while-error.\n state.value = { ...state.value, status: 'failed', error, progress: undefined, input };\n }\n return undefined;\n },\n );\n }\n\n function reset(): void {\n generation++; // invalidate any in-flight run\n cache.clear();\n state.value = {\n status: 'idle',\n data: undefined,\n error: undefined,\n progress: undefined,\n input: undefined,\n revision: commit(undefined),\n };\n }\n\n return {\n get value() {\n return state.value;\n },\n run,\n reset,\n };\n}\n"]} | ||
| {"version":3,"sources":["../src/async.ts"],"names":[],"mappings":"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","file":"async.js","sourcesContent":["/**\n * `kerfjs/async` — model async state, with the stale-response guard built in.\n *\n * Every real kerf app reproduces the same shape — `{ status, data, error }` —\n * for loading/error UI, each paired with a hand-rolled generation counter so a\n * slow response can't overwrite a newer one. This subpath blesses exactly that,\n * and no more: you still write the fetch (Node `fetch` for SSR, browser `fetch`\n * client-side), and `.run()` owns the status transitions plus the stale guard.\n *\n * import { resource } from 'kerfjs/async';\n *\n * const users = resource<User[]>();\n * users.run(() => fetch('/api/users').then((r) => r.json()));\n * // render off users.value.status: 'idle' | 'running' | 'completed' | 'failed'\n *\n * Only the LATEST run may resolve the state, so out-of-order responses are\n * dropped automatically. Optional progress: declare the `report` parameter on\n * your fetcher and call it (e.g. from an upload's progress events).\n *\n * Pass an input — `run(input, fetcher)` — to carry which request a run is for\n * through to `value.input` (set for `running`/`completed`/`failed`), so a\n * failure handler can recover the id/params of the run that failed:\n *\n * const diff = resource<Diff, { fileId: string }>();\n * diff.run({ fileId }, (report) => fetchDiff(fileId, report));\n * // on failure: diff.value.status === 'failed' && diff.value.input.fileId\n *\n * For a real SWR-with-cache section, pass `cacheKey` to keep the last value PER\n * input key (switching back to a loaded key paints its cached slice instantly\n * while it revalidates), and read `value.revision` — a counter that bumps only\n * when `data` actually CHANGES (by `equals`, default `Object.is`) — to skip a\n * redundant paint when a poll tick returns identical data:\n *\n * const win = resource<Slice, string>({ cacheKey: (w) => w, equals: sameSlice });\n * win.run(w, () => fetchSlice(w)); // instant cached paint for a revisited w\n */\nimport { signal } from './reactive.js';\n\n/** The lifecycle status of a {@link Resource}. */\nexport type ResourceStatus = 'idle' | 'running' | 'completed' | 'failed';\n\n/** Optional progress for a long-running fetch (uploads, chunked work). */\nexport interface ResourceProgress {\n completed: number;\n total: number;\n}\n\n/** The reactive state a {@link Resource} exposes. */\nexport interface ResourceState<T, I = void> {\n status: ResourceStatus;\n /** The last successful value. Kept across a re-run (stale-while-revalidate) and on failure. */\n data: T | undefined;\n /** The rejection from the most recent failed run. */\n error: unknown;\n /** Latest reported progress while running, or `undefined`. */\n progress: ResourceProgress | undefined;\n /**\n * The input of the LATEST run — the value passed to {@link Resource.run} as\n * `run(input, fetcher)`. Set for `running`, `completed`, AND `failed` (same\n * stale-guard rule as the rest of the state), so an effect can branch on\n * `status === 'failed'` and still know which request failed. `undefined` in\n * `idle`, and for the no-input `run(fetcher)` form.\n */\n input: I | undefined;\n /**\n * A monotonic counter that increments only when `data` actually CHANGES (by\n * the resource's `equals`, default `Object.is`). Compare it against the value\n * you last painted to skip a redundant re-render — e.g. a 30s poll returning\n * identical data leaves `revision` untouched, so you can bail before wiping\n * scroll / sort / hover state. Starts at `0`.\n */\n revision: number;\n}\n\n/** Construction options for {@link resource}. */\nexport interface ResourceOptions<T, I = void> {\n /**\n * Derive a cache key from a run's `input`. When set, the resource keeps the\n * last successful value PER key: starting a run for a key that was loaded\n * before paints its cached slice immediately (still `running`) while the fetch\n * revalidates in the background; a never-loaded key starts with no `data`.\n * Without `cacheKey`, a run keeps the previous run's `data` (single-slot\n * stale-while-revalidate), as before.\n */\n cacheKey?: (input: I) => string;\n /**\n * Equality used to decide whether `data` changed (drives `value.revision`).\n * Default `Object.is`. Pass a structural comparison to dedup a poll that\n * returns a fresh-but-equal object.\n */\n equals?: (a: T, b: T) => boolean;\n}\n\n/**\n * The fetcher passed to {@link Resource.run}. You own the transport. It receives\n * a `report(completed, total)` callback for optional progress — ignore it if you\n * don't need progress (a plain `() => Promise<T>` is assignable here).\n */\nexport type ResourceFetcher<T> = (report: (completed: number, total: number) => void) => Promise<T>;\n\n/**\n * An async-state container. Its `value` is a tracking read; drive UI off\n * `value.status`. `I` is the run-input type — parametrize it (`resource<T, I>()`)\n * to carry a typed `run(input, fetcher)` input through to `value.input`.\n */\nexport interface Resource<T, I = void> {\n /** Tracking read of the current {@link ResourceState}. */\n readonly value: ResourceState<T, I>;\n /**\n * Run `fetcher`, driving `idle`/`running` → `completed`/`failed` and guarding\n * against stale responses (only the latest run resolves the state). Never\n * rejects — a failure lands in `value.error`; resolves with the data (or\n * `undefined` on failure) for callers who want to await it.\n */\n run(fetcher: ResourceFetcher<T>): Promise<T | undefined>;\n /**\n * Run `fetcher` for a given `input`, exposing it as `value.input` for the\n * `running`/`completed`/`failed` states of THIS run — so a failure handler can\n * recover which request failed. Same stale guard: only the latest run resolves.\n */\n run(input: I, fetcher: ResourceFetcher<T>): Promise<T | undefined>;\n /** Reset to `idle` (clearing data/error/progress/input, and the per-key cache) and invalidate any in-flight run. */\n reset(): void;\n /**\n * Read-only: the cached value for a `cacheKey` key, or `undefined` if that key\n * isn't cached (or no `cacheKey` was given). Lets a consumer ask \"is this slice\n * cached?\" without running it (which would mutate state).\n */\n cached(key: string): T | undefined;\n /** Read-only: the keys currently in the per-input cache (`.length` is the cache size). */\n cachedKeys(): string[];\n /** Evict the per-input cache — one `key`, or the whole cache when called with no argument. Does NOT change `value`. */\n clearCache(key?: string): void;\n}\n\n/** Create an async-state {@link Resource}. No per-instance framework state — it's a closure over a signal. */\nexport function resource<T, I = void>(options: ResourceOptions<T, I> = {}): Resource<T, I> {\n const { cacheKey, equals } = options;\n const eq: (a: T, b: T) => boolean = equals ?? Object.is;\n const cache = new Map<string, T>(); // per-key SWR cache (GC-tied to the resource)\n\n // Revision tracking: `revision` bumps only when `data` changes (by `eq`).\n let revision = 0;\n let lastData: T | undefined;\n const changed = (next: T | undefined): boolean =>\n // undefined transitions are handled by reference; two defined values by `eq`.\n lastData === undefined || next === undefined ? lastData !== next : !eq(lastData, next);\n const commit = (next: T | undefined): number => {\n if (changed(next)) {\n revision++;\n lastData = next;\n }\n return revision;\n };\n\n const state = signal<ResourceState<T, I>>({\n status: 'idle',\n data: undefined,\n error: undefined,\n progress: undefined,\n input: undefined,\n revision: 0,\n });\n // Per-resource run counter (closure-local, not module state) — the stale guard.\n let generation = 0;\n\n function run(\n inputOrFetcher: I | ResourceFetcher<T>,\n maybeFetcher?: ResourceFetcher<T>,\n ): Promise<T | undefined> {\n // Two-arg form is (input, fetcher); one-arg form is (fetcher) with no input.\n // A fetcher is always a function, so `maybeFetcher === undefined` uniquely\n // identifies the one-arg call — even when the input value is itself undefined.\n const fetcher = (maybeFetcher ?? inputOrFetcher) as ResourceFetcher<T>;\n const input = (maybeFetcher === undefined ? undefined : inputOrFetcher) as I | undefined;\n const key = cacheKey !== undefined && maybeFetcher !== undefined ? cacheKey(input as I) : undefined;\n\n // What `data` shows while running: the cached slice for this key (per-key\n // SWR), or the previous run's data (single-slot SWR) when no cacheKey.\n const runningData = cacheKey !== undefined\n ? (key !== undefined ? cache.get(key) : undefined)\n : state.value.data;\n\n const gen = ++generation;\n state.value = {\n status: 'running',\n data: runningData,\n error: undefined,\n progress: undefined,\n input,\n revision: commit(runningData),\n };\n\n const report = (completed: number, total: number): void => {\n if (gen === generation) {\n state.value = { ...state.value, progress: { completed, total } };\n }\n };\n\n return fetcher(report).then(\n (data) => {\n if (gen === generation) {\n if (key !== undefined) cache.set(key, data);\n state.value = {\n status: 'completed',\n data,\n error: undefined,\n progress: undefined,\n input,\n revision: commit(data),\n };\n }\n return data;\n },\n (error: unknown) => {\n if (gen === generation) {\n // Keep `data` (and thus `revision`) on failure — stale-while-error.\n state.value = { ...state.value, status: 'failed', error, progress: undefined, input };\n }\n return undefined;\n },\n );\n }\n\n function reset(): void {\n generation++; // invalidate any in-flight run\n cache.clear();\n state.value = {\n status: 'idle',\n data: undefined,\n error: undefined,\n progress: undefined,\n input: undefined,\n revision: commit(undefined),\n };\n }\n\n return {\n get value() {\n return state.value;\n },\n run,\n reset,\n cached: (k) => cache.get(k),\n cachedKeys: () => Array.from(cache.keys()),\n clearCache: (k) => {\n if (k === undefined) cache.clear();\n else cache.delete(k);\n },\n };\n}\n"]} |
+14
-8
@@ -14,7 +14,10 @@ import { M as MountResult } from './mount-Bo2qOx25.js'; | ||
| * A row built imperatively by `render`: return the row **element** itself (kerf | ||
| * keys/moves/reuses it and owns nothing inside it), or `{ el, dispose? }` to also | ||
| * hand back a teardown that runs when the row is removed or rebuilt. | ||
| * keys/moves/reuses it and owns nothing inside it), or `{ el, update?, dispose? }` | ||
| * to also hand back an `update(item)` — called on the SAME element when the row's | ||
| * key persists but its item changes — and a `dispose` that runs only when the row | ||
| * is removed. | ||
| */ | ||
| type RowElement = HTMLElement | { | ||
| type RowElement<T> = HTMLElement | { | ||
| el: HTMLElement; | ||
| update?: (item: T) => void; | ||
| dispose?: () => void; | ||
@@ -31,8 +34,11 @@ }; | ||
| * content reads drive per-row reactivity. | ||
| * - **Element mode** (an `HTMLElement`, or `{ el, dispose? }`): the element you | ||
| * return IS the row, so you own its tag, class, `data-*`, and listeners | ||
| * (kerf keys/moves/reuses it). Reactivity + cleanup are yours — return a | ||
| * `dispose` to tear down listeners when the row is removed or rebuilt. | ||
| * - **Element mode** (an `HTMLElement`, or `{ el, update?, dispose? }`): the | ||
| * element you return IS the row, so you own its tag, class, `data-*`, and | ||
| * listeners. kerf **keys/moves/reuses** it — the SAME element survives an | ||
| * append/remove/reorder or a fresh item object at the same key. Refresh its | ||
| * content by reading signals inside it, or by returning an `update(item)` | ||
| * that kerf calls on the existing element when the item changes. `dispose` | ||
| * runs only when the row is genuinely removed. | ||
| */ | ||
| render: (item: T) => MountResult | RowElement; | ||
| render: (item: T) => MountResult | RowElement<T>; | ||
| /** Row element tag for **content mode**. Default `'div'` (use `'li'` inside a `<ul>`, `'tr'` inside a `<tbody>`, …). Ignored in element mode. */ | ||
@@ -39,0 +45,0 @@ tag?: string; |
+40
-18
@@ -30,3 +30,3 @@ import { ARRAY_SIGNAL_BRAND } from './chunk-MRYM3O3V.js'; | ||
| const r = rendered; | ||
| return { el: r.el, dispose: r.dispose ?? NOOP }; | ||
| return { el: r.el, dispose: r.dispose ?? NOOP, update: r.update }; | ||
| } | ||
@@ -39,3 +39,3 @@ return null; | ||
| if (virtualize !== void 0) elementRow.el.style.height = `${virtualize.rowHeight}px`; | ||
| return { el: elementRow.el, item, dispose: elementRow.dispose }; | ||
| return { el: elementRow.el, item, dispose: elementRow.dispose, elementMode: true, update: elementRow.update }; | ||
| } | ||
@@ -45,4 +45,18 @@ const el = document.createElement(tag); | ||
| const dispose = mount(el, () => render(item)); | ||
| return { el, item, dispose }; | ||
| return { el, item, dispose, elementMode: false }; | ||
| }; | ||
| const reconcileItem = (row, k, item) => { | ||
| if (row.item === item) return row; | ||
| if (row.elementMode) { | ||
| row.item = item; | ||
| row.update?.(item); | ||
| return row; | ||
| } | ||
| row.dispose(); | ||
| row.el.remove(); | ||
| rows.delete(k); | ||
| const fresh = makeRow(item); | ||
| rows.set(k, fresh); | ||
| return fresh; | ||
| }; | ||
| const syncRows = (visible) => { | ||
@@ -61,10 +75,7 @@ const wanted = /* @__PURE__ */ new Set(); | ||
| const k = key(item); | ||
| let row = rows.get(k); | ||
| if (row !== void 0 && row.item !== item) { | ||
| row.dispose(); | ||
| row.el.remove(); | ||
| rows.delete(k); | ||
| row = void 0; | ||
| } | ||
| if (row === void 0) { | ||
| const existing = rows.get(k); | ||
| let row; | ||
| if (existing !== void 0) { | ||
| row = reconcileItem(existing, k, item); | ||
| } else { | ||
| row = makeRow(item); | ||
@@ -103,9 +114,20 @@ rows.set(k, row); | ||
| if (current.item !== patch.item) { | ||
| current.dispose(); | ||
| current.el.remove(); | ||
| rows.delete(key(current.item)); | ||
| const row = makeRow(patch.item); | ||
| rows.set(key(patch.item), row); | ||
| order[patch.index] = row; | ||
| container.insertBefore(row.el, order[patch.index + 1]?.el ?? null); | ||
| if (current.elementMode) { | ||
| const oldKey = key(current.item); | ||
| const newKey = key(patch.item); | ||
| current.item = patch.item; | ||
| if (newKey !== oldKey) { | ||
| rows.delete(oldKey); | ||
| rows.set(newKey, current); | ||
| } | ||
| current.update?.(patch.item); | ||
| } else { | ||
| current.dispose(); | ||
| current.el.remove(); | ||
| rows.delete(key(current.item)); | ||
| const row = makeRow(patch.item); | ||
| rows.set(key(patch.item), row); | ||
| order[patch.index] = row; | ||
| container.insertBefore(row.el, order[patch.index + 1]?.el ?? null); | ||
| } | ||
| } | ||
@@ -112,0 +134,0 @@ } |
+1
-1
@@ -1,1 +0,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"]} | ||
| {"version":3,"sources":["../src/list.ts"],"names":[],"mappings":";;;;;;;;;;AAoGO,SAAS,QAAA,CACd,MAAA,EACA,MAAA,EACA,OAAA,EACY;AACZ,EAAA,MAAM,EAAE,GAAA,EAAK,MAAA,EAAQ,GAAA,GAAM,KAAA,EAAO,YAAW,GAAI,OAAA;AACjD,EAAA,MAAM,QAAA,GAAW,YAAY,QAAA,IAAY,CAAA;AAEzC,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAqB;AAGtC,EAAA,MAAM,QAAuB,EAAC;AAC9B,EAAA,IAAI,QAAsB,EAAC;AAC3B,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,UAAA,GAAa,KAAA;AACjB,EAAA,IAAI,WAAA,GAAc,IAAA;AAQlB,EAAA,MAAM,WAAA,GAAc,MAAA;AAIpB,EAAA,MAAM,gBAAA,GAAmB,UAAA,KAAe,MAAA,IAAa,WAAA,CAAY,kBAAkB,CAAA,KAAM,IAAA;AAMzF,EAAA,MAAM,YAAyB,UAAA,KAAe,MAAA,GAAY,MAAA,GAAS,QAAA,CAAS,cAAc,KAAK,CAAA;AAC/F,EAAA,IAAI,UAAA,KAAe,MAAA,EAAW,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA;AAE1D,EAAA,MAAM,OAAO,MAAY;AAAA,EAAkD,CAAA;AAK3E,EAAA,MAAM,YAAA,GAAe,CACnB,QAAA,KACgF;AAChF,IAAA,IAAI,oBAAoB,WAAA,EAAa,OAAO,EAAE,EAAA,EAAI,QAAA,EAAU,SAAS,IAAA,EAAK;AAC1E,IAAA,IACE,QAAA,KAAa,QACV,OAAO,QAAA,KAAa,YACpB,IAAA,IAAQ,QAAA,IACP,QAAA,CAA6B,EAAA,YAAc,WAAA,EAC/C;AACA,MAAA,MAAM,CAAA,GAAI,QAAA;AACV,MAAA,OAAO,EAAE,EAAA,EAAI,CAAA,CAAE,EAAA,EAAI,OAAA,EAAS,EAAE,OAAA,IAAW,IAAA,EAAM,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO;AAAA,IAClE;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAAoB;AAEnC,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,MAAA,CAAO,IAAI,CAAC,CAAA;AAC5C,IAAA,IAAI,eAAe,IAAA,EAAM;AAGvB,MAAA,IAAI,UAAA,KAAe,QAAW,UAAA,CAAW,EAAA,CAAG,MAAM,MAAA,GAAS,CAAA,EAAG,WAAW,SAAS,CAAA,EAAA,CAAA;AAClF,MAAA,OAAO,EAAE,EAAA,EAAI,UAAA,CAAW,EAAA,EAAI,IAAA,EAAM,OAAA,EAAS,UAAA,CAAW,OAAA,EAAS,WAAA,EAAa,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAO;AAAA,IAC9G;AAKA,IAAA,MAAM,EAAA,GAAK,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACrC,IAAA,IAAI,eAAe,MAAA,EAAW,EAAA,CAAG,MAAM,MAAA,GAAS,CAAA,EAAG,WAAW,SAAS,CAAA,EAAA,CAAA;AAGvE,IAAA,MAAM,UAAU,KAAA,CAAM,EAAA,EAAI,MAAM,MAAA,CAAO,IAAI,CAAgB,CAAA;AAC3D,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,OAAA,EAAS,aAAa,KAAA,EAAM;AAAA,EACjD,CAAA;AAOA,EAAA,MAAM,aAAA,GAAgB,CAAC,GAAA,EAAa,CAAA,EAAY,IAAA,KAAoB;AAClE,IAAA,IAAI,GAAA,CAAI,IAAA,KAAS,IAAA,EAAM,OAAO,GAAA;AAC9B,IAAA,IAAI,IAAI,WAAA,EAAa;AACnB,MAAA,GAAA,CAAI,IAAA,GAAO,IAAA;AACX,MAAA,GAAA,CAAI,SAAS,IAAI,CAAA;AACjB,MAAA,OAAO,GAAA;AAAA,IACT;AACA,IAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,IAAA,GAAA,CAAI,GAAG,MAAA,EAAO;AACd,IAAA,IAAA,CAAK,OAAO,CAAC,CAAA;AACb,IAAA,MAAM,KAAA,GAAQ,QAAQ,IAAI,CAAA;AAC1B,IAAA,IAAA,CAAK,GAAA,CAAI,GAAG,KAAK,CAAA;AACjB,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAGA,EAAA,MAAM,QAAA,GAAW,CAAC,OAAA,KAAgC;AAChD,IAAA,MAAM,MAAA,uBAAa,GAAA,EAAa;AAChC,IAAA,KAAA,MAAW,QAAQ,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,GAAA,CAAI,IAAI,CAAC,CAAA;AAGhD,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,GAAG,CAAA,IAAK,IAAA,EAAM;AAC3B,MAAA,IAAI,CAAC,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG;AAClB,QAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,QAAA,GAAA,CAAI,GAAG,MAAA,EAAO;AACd,QAAA,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,MACf;AAAA,IACF;AAIA,IAAA,KAAA,CAAM,MAAA,GAAS,CAAA;AACf,IAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,MAAA,MAAM,CAAA,GAAI,IAAI,IAAI,CAAA;AAClB,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AAC3B,MAAA,IAAI,GAAA;AACJ,MAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,QAAA,GAAA,GAAM,aAAA,CAAc,QAAA,EAAU,CAAA,EAAG,IAAI,CAAA;AAAA,MACvC,CAAA,MAAO;AACL,QAAA,GAAA,GAAM,QAAQ,IAAI,CAAA;AAClB,QAAA,IAAA,CAAK,GAAA,CAAI,GAAG,GAAG,CAAA;AAAA,MACjB;AACA,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,IAChB;AAGA,IAAA,IAAI,GAAA,GAAmB,IAAA;AACvB,IAAA,KAAA,IAAS,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC1C,MAAA,MAAM,EAAA,GAAK,KAAA,CAAM,CAAC,CAAA,CAAE,EAAA;AACpB,MAAA,IAAI,EAAA,CAAG,UAAA,KAAe,SAAA,IAAa,EAAA,CAAG,gBAAgB,GAAA,EAAK;AACzD,QAAA,SAAA,CAAU,YAAA,CAAa,IAAI,GAAG,CAAA;AAAA,MAChC;AACA,MAAA,GAAA,GAAM,EAAA;AAAA,IACR;AAAA,EACF,CAAA;AAQA,EAAA,MAAM,YAAA,GAAe,CAAC,OAAA,KAA4C;AAChE,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,MAAA,IAAI,KAAA,CAAM,SAAS,QAAA,EAAU;AAC3B,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA;AAC9B,QAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,IAAI,GAAG,GAAG,CAAA;AAC7B,QAAA,KAAA,CAAM,MAAA,CAAO,KAAA,CAAM,KAAA,EAAO,CAAA,EAAG,GAAG,CAAA;AAChC,QAAA,SAAA,CAAU,YAAA,CAAa,IAAI,EAAA,EAAI,KAAA,CAAM,MAAM,KAAA,GAAQ,CAAC,CAAA,EAAG,EAAA,IAAM,IAAI,CAAA;AAAA,MACnE,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,QAAA,EAAU;AAClC,QAAA,MAAM,CAAC,GAAG,CAAA,GAAI,MAAM,MAAA,CAAO,KAAA,CAAM,OAAO,CAAC,CAAA;AACzC,QAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,QAAA,GAAA,CAAI,GAAG,MAAA,EAAO;AACd,QAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,MAC3B,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,MAAA,EAAQ;AAChC,QAAA,MAAM,CAAC,GAAG,CAAA,GAAI,MAAM,MAAA,CAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AACxC,QAAA,KAAA,CAAM,MAAA,CAAO,KAAA,CAAM,EAAA,EAAI,CAAA,EAAG,GAAG,CAAA;AAC7B,QAAA,SAAA,CAAU,YAAA,CAAa,IAAI,EAAA,EAAI,KAAA,CAAM,MAAM,EAAA,GAAK,CAAC,CAAA,EAAG,EAAA,IAAM,IAAI,CAAA;AAAA,MAChE,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,QAAA,EAAU;AAKlC,QAAA,MAAM,OAAA,GAAU,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA;AACjC,QAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,KAAA,CAAM,IAAA,EAAM;AAC/B,UAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,YAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAC/B,YAAA,MAAM,MAAA,GAAS,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA;AAC7B,YAAA,OAAA,CAAQ,OAAO,KAAA,CAAM,IAAA;AACrB,YAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,cAAA,IAAA,CAAK,OAAO,MAAM,CAAA;AAClB,cAAA,IAAA,CAAK,GAAA,CAAI,QAAQ,OAAO,CAAA;AAAA,YAC1B;AACA,YAAA,OAAA,CAAQ,MAAA,GAAS,MAAM,IAAI,CAAA;AAAA,UAC7B,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,YAAA,OAAA,CAAQ,GAAG,MAAA,EAAO;AAClB,YAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAC,CAAA;AAC7B,YAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA;AAC9B,YAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,IAAI,GAAG,GAAG,CAAA;AAC7B,YAAA,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA,GAAI,GAAA;AACrB,YAAA,SAAA,CAAU,YAAA,CAAa,IAAI,EAAA,EAAI,KAAA,CAAM,MAAM,KAAA,GAAQ,CAAC,CAAA,EAAG,EAAA,IAAM,IAAI,CAAA;AAAA,UACnE;AAAA,QACF;AAAA,MACF;AAAA,IAEF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,eAAe,MAAY;AAC/B,IAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,MAAA,IAAI,gBAAA,EAAkB;AAKpB,QAAA,MAAM,OAAA,GAAU,YAAY,eAAA,EAAiB;AAC7C,QAAA,IACE,CAAC,WAAA,IACE,OAAA,CAAQ,MAAA,GAAS,CAAA,IACjB,CAAC,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,SAAS,CAAA,EAC5C;AACA,UAAA,YAAA,CAAa,OAAO,CAAA;AACpB,UAAA;AAAA,QACF;AAAA,MACF;AACA,MAAA,QAAA,CAAS,KAAK,CAAA;AACd,MAAA,WAAA,GAAc,KAAA;AACd,MAAA;AAAA,IACF;AACA,IAAA,MAAM,EAAE,WAAU,GAAI,UAAA;AACtB,IAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AACpB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,MAAA,CAAO,SAAA,GAAY,SAAS,CAAA,GAAI,QAAQ,CAAA;AAC7E,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,IAAA,CAAK,IAAA,CAAA,CAAM,MAAA,CAAO,SAAA,GAAY,MAAA,CAAO,YAAA,IAAgB,SAAS,CAAA,GAAI,QAAQ,CAAA;AACtG,IAAA,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,GAAG,CAAC,CAAA;AAChC,IAAA,SAAA,CAAU,KAAA,CAAM,UAAA,GAAa,CAAA,EAAG,KAAA,GAAQ,SAAS,CAAA,EAAA,CAAA;AACjD,IAAA,SAAA,CAAU,KAAA,CAAM,gBAAgB,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAG,KAAA,GAAQ,GAAG,CAAA,GAAI,SAAS,CAAA,EAAA,CAAA;AAAA,EACzE,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,OAAO,MAAM;AAC9B,IAAA,KAAA,GAAQ,MAAA,CAAO,KAAA;AACf,IAAA,YAAA,EAAa;AAAA,EACf,CAAC,CAAA;AAED,EAAA,MAAM,WAAW,MAAY;AAC3B,IAAA,IAAI,UAAA,EAAY;AAChB,IAAA,UAAA,GAAa,IAAA;AACb,IAAA,UAAA,CAAW,sBAAsB,MAAM;AACrC,MAAA,UAAA,GAAa,KAAA;AACb,MAAA,IAAI,CAAC,UAAU,YAAA,EAAa;AAAA,IAC9B,CAAC,CAAA;AAAA,EACH,CAAA;AACA,EAAA,IAAI,UAAA,KAAe,MAAA,EAAW,MAAA,CAAO,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAExE,EAAA,OAAO,MAAM;AACX,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,UAAA,EAAW;AACX,IAAA,KAAA,MAAW,GAAA,IAAO,IAAA,CAAK,MAAA,EAAO,EAAG;AAC/B,MAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,MAAA,IAAI,UAAA,KAAe,MAAA,EAAW,GAAA,CAAI,EAAA,CAAG,MAAA,EAAO;AAAA,IAC9C;AACA,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAC7C,MAAA,SAAA,CAAU,MAAA,EAAO;AAAA,IACnB;AAAA,EACF,CAAA;AACF","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, 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/** 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 * 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 /** 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): () => 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<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 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, 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 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, 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 = 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 // 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 ?? null);\n }\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"]} |
+54
-3
@@ -215,2 +215,40 @@ import { SafeHtml } from './jsx-runtime.js'; | ||
| declare function form(fields: readonly FormField[], options?: FormOptions): Promise<Record<string, string> | null>; | ||
| /** One choosable action in a {@link choice} dialog. */ | ||
| interface ChoiceAction<R> { | ||
| /** The value this action resolves. */ | ||
| value: R; | ||
| /** Button label (auto-escaped). */ | ||
| label: string; | ||
| /** Extra class on this action's button. */ | ||
| className?: string; | ||
| } | ||
| /** Wiring slots for a {@link ChoiceOptions.render} — spread `actions[i]` onto your i-th button. */ | ||
| interface ChoiceRenderSlots { | ||
| message: string; | ||
| /** One attribute bag per action (in order) — spread onto that action's control. */ | ||
| actions: Array<Record<string, string>>; | ||
| } | ||
| /** Options for {@link choice}. */ | ||
| interface ChoiceOptions<R> { | ||
| /** Where to append the overlay. Default `document.body`. */ | ||
| container?: Element; | ||
| /** Wrapper class. Default `'kerf-overlay'`. */ | ||
| className?: string; | ||
| /** Optional heading above the message. */ | ||
| title?: string; | ||
| /** The value resolved when **Enter** is pressed anywhere in the dialog (the default action). */ | ||
| defaultValue?: R; | ||
| /** Bring your own markup: return the full body, spreading each `slots.actions[i]` onto your buttons. */ | ||
| render?: (slots: ChoiceRenderSlots) => OverlayContent; | ||
| } | ||
| /** | ||
| * The **N-way** sibling of {@link confirm}: renders one button per {@link ChoiceAction} | ||
| * and resolves that action's `value` on click, or `null` on Cancel / dismissal. | ||
| * Pass `defaultValue` to make **Enter** (anywhere in the dialog) resolve a default | ||
| * action — the "global Enter-to-confirm" model — without you having to hold the | ||
| * overlay handle. `message` + labels are auto-escaped; pass `render` for your own | ||
| * markup. kerf owns dismiss / focus-trap / focus-restore. For fully bespoke | ||
| * keyboard/close control, drive {@link overlay} directly. | ||
| */ | ||
| declare function choice<R>(message: string, actions: ReadonlyArray<ChoiceAction<R>>, options?: ChoiceOptions<R>): Promise<R | null>; | ||
| /** Vertical placement relative to an anchor (used by {@link popover}, {@link positionAnchored}, {@link tooltip}). */ | ||
@@ -320,2 +358,9 @@ type PopoverPlacement = 'bottom' | 'top'; | ||
| mode?: 'stack' | 'replace'; | ||
| /** | ||
| * How `mode: 'replace'` drops the prior toast(s): `'fade'` (default) runs their | ||
| * full exit transition (nice for a STACKING region), or `'instant'` removes them | ||
| * synchronously with no exit — what a single, exactly-centered toast slot wants, | ||
| * so the outgoing and incoming messages never cross-fade in the same spot. | ||
| */ | ||
| collapse?: 'fade' | 'instant'; | ||
| /** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */ | ||
@@ -325,5 +370,11 @@ variant?: ToastVariant; | ||
| enterClass?: string; | ||
| /** Class added when dismissing, so CSS owns the **exit** — the node is removed `exitDuration` ms later. */ | ||
| /** | ||
| * Class added when dismissing, so CSS owns the **exit**. On dismiss the | ||
| * `enterClass` (if any) is also REMOVED, so `exitClass` doesn't have to | ||
| * out-specify it — and a symmetric single-class fade (entrance = add | ||
| * `enterClass`, exit = remove it) works by setting only `enterClass` + | ||
| * `exitDuration`. The node is removed `exitDuration` ms later. | ||
| */ | ||
| exitClass?: string; | ||
| /** ms to wait after `exitClass` is added before removing the node. Default `0`. */ | ||
| /** 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`. */ | ||
| exitDuration?: number; | ||
@@ -347,2 +398,2 @@ } | ||
| 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 }; | ||
| 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 }; |
+69
-5
@@ -345,2 +345,51 @@ import { mount } from './chunk-4MY2656S.js'; | ||
| } | ||
| function choice(message, actions, options = {}) { | ||
| const { container, className = "kerf-overlay", title, defaultValue, render } = options; | ||
| const hasDefault = "defaultValue" in options; | ||
| const actionAttrs = actions.map((_, i) => ({ "data-choice": String(i) })); | ||
| const body = render !== void 0 ? render({ message, actions: actionAttrs }) : jsx("div", { | ||
| class: "kerf-choice", | ||
| children: [ | ||
| title !== void 0 ? jsx("h2", { class: "kerf-choice__title", children: title }) : "", | ||
| jsx("p", { class: "kerf-choice__message", children: message }), | ||
| jsx("div", { | ||
| class: "kerf-choice__actions", | ||
| children: actions.map( | ||
| (action, i) => jsx("button", { | ||
| type: "button", | ||
| class: action.className !== void 0 ? `kerf-choice__action ${action.className}` : "kerf-choice__action", | ||
| ...actionAttrs[i], | ||
| children: action.label | ||
| }) | ||
| ) | ||
| }) | ||
| ] | ||
| }); | ||
| let resolveChoice; | ||
| const result = new Promise((resolve) => { | ||
| resolveChoice = resolve; | ||
| }); | ||
| const handle = overlay(body, { | ||
| container, | ||
| className, | ||
| dismiss: ["escape", "backdrop"], | ||
| initialFocus: "[data-choice]", | ||
| trap: true | ||
| }); | ||
| delegate(handle.el, "click", "[data-choice]", (_event, el) => { | ||
| resolveChoice(actions[Number(el.getAttribute("data-choice"))].value); | ||
| handle.close(); | ||
| }); | ||
| if (hasDefault) { | ||
| handle.el.addEventListener("keydown", (event) => { | ||
| if (event.key === "Enter") { | ||
| event.preventDefault(); | ||
| resolveChoice(defaultValue); | ||
| handle.close(); | ||
| } | ||
| }); | ||
| } | ||
| void handle.result.then(() => resolveChoice(null)); | ||
| return result; | ||
| } | ||
| function positionAnchored(el, anchor, options = {}) { | ||
@@ -469,2 +518,3 @@ const { placement = "bottom", align = "start", gap = 4 } = options; | ||
| mode = "stack", | ||
| collapse = "fade", | ||
| variant, | ||
@@ -477,3 +527,8 @@ enterClass, | ||
| const active = region[TOAST_SET] ??= /* @__PURE__ */ new Set(); | ||
| if (mode === "replace") for (const d of [...active]) d(); | ||
| if (mode === "replace") { | ||
| for (const d of [...active]) { | ||
| if (collapse === "instant") d.removeNow(); | ||
| else d(); | ||
| } | ||
| } | ||
| const el = document.createElement("div"); | ||
@@ -496,8 +551,13 @@ el.className = className; | ||
| }; | ||
| function dismiss() { | ||
| const finish = (instant) => { | ||
| if (state.dismissed) return; | ||
| state.dismissed = true; | ||
| if (state.timer !== void 0) clearTimeout(state.timer); | ||
| if (exitClass !== void 0) { | ||
| el.classList.add(exitClass); | ||
| if (instant) { | ||
| remove(); | ||
| return; | ||
| } | ||
| if (enterClass !== void 0) el.classList.remove(enterClass); | ||
| if (exitClass !== void 0) el.classList.add(exitClass); | ||
| if (exitClass !== void 0 || exitDuration > 0) { | ||
| setTimeout(remove, exitDuration); | ||
@@ -507,3 +567,7 @@ } else { | ||
| } | ||
| }; | ||
| function dismiss() { | ||
| finish(false); | ||
| } | ||
| dismiss.removeNow = () => finish(true); | ||
| active.add(dismiss); | ||
@@ -514,4 +578,4 @@ if (duration > 0) state.timer = setTimeout(dismiss, duration); | ||
| export { autoReposition, confirm, form, overlay, popover, positionAnchored, prompt, toast, tooltip }; | ||
| export { autoReposition, choice, confirm, form, overlay, popover, positionAnchored, prompt, toast, tooltip }; | ||
| //# sourceMappingURL=overlay.js.map | ||
| //# sourceMappingURL=overlay.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/overlay.ts"],"names":[],"mappings":"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","file":"overlay.js","sourcesContent":["/**\n * `kerfjs/overlay` — the modal / overlay + dismiss manager.\n *\n * Every real kerf app hand-rolls this: `toElement → body.appendChild → mount →\n * wire dismissal → remove`, plus the fiddly parts (Escape, backdrop / outside\n * click, focus trap, restoring focus on close). `window.confirm` is a no-op in\n * Tauri WKWebViews, so a hand-built overlay is mandatory there. This subpath\n * blesses the pattern as three functions over `mount()` — `overlay()`, and the\n * `confirm()` / `toast()` conveniences built on it. No per-instance framework\n * state: each call owns its DOM + listeners in a closure and returns a handle.\n *\n * import { overlay, confirm, toast } from 'kerfjs/overlay';\n *\n * const ok = await confirm('Delete this file?', { danger: true });\n * toast('Saved');\n * const dialog = overlay(<Settings />, { dismiss: ['escape', 'backdrop'] });\n * // …later: dialog.close(); or await dialog.result;\n *\n * Structural only — kerf ships no CSS. The wrapper gets your `className`; style\n * the backdrop / centering / animation yourself.\n */\nimport { delegate } from './delegate.js';\nimport { jsx, type SafeHtml } from './jsx-runtime.js';\nimport { mount, type MountResult } from './mount.js';\n\n/** A user-initiated dismissal trigger. */\nexport type DismissTrigger = 'escape' | 'backdrop' | 'outside';\n\n/** Content for an overlay: static `SafeHtml`, or a render function `mount()` drives reactively. */\nexport type OverlayContent = SafeHtml | (() => MountResult);\n\n/** Options for {@link overlay}. */\nexport interface OverlayOptions {\n /** Where to append the overlay wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper element (you style it — kerf ships no CSS). Default `'kerf-overlay'`. */\n className?: string;\n /**\n * Which user actions dismiss the overlay. Default `['escape', 'backdrop']`.\n * `'backdrop'` = a click on the wrapper itself (not its content); `'outside'`\n * = a click anywhere outside the wrapper (for anchored popovers). `false`\n * disables user dismissal (close it programmatically).\n */\n dismiss?: DismissTrigger | DismissTrigger[] | false;\n /**\n * Where focus lands on open: a selector, `true` (first focusable element, or\n * the wrapper if none), or `false` (leave focus alone). Default `true`.\n */\n initialFocus?: string | boolean;\n /**\n * Trap Tab / Shift+Tab within the overlay while open and mark it\n * `role=\"dialog\"` / `aria-modal=\"true\"`. Default `true`. Set `false` for a\n * non-modal popover.\n */\n trap?: boolean;\n /** ARIA role for the wrapper when `trap` is on. Default `'dialog'`. */\n role?: string;\n /** Called on any user-initiated dismissal (before `close()` runs). */\n onDismiss?: () => void;\n /** For `'outside'` dismissal: clicks on these elements do NOT count as outside (e.g. the trigger button). */\n outsideIgnore?: Element | readonly Element[];\n}\n\n/** Handle returned by {@link overlay}. Holds no framework state — it's a closure. */\nexport interface OverlayHandle {\n /** The wrapper element (mounted into, appended to `container`). */\n el: HTMLElement;\n /** Tear down: dispose the mount, remove listeners + the node, restore focus, resolve `result`. Idempotent. */\n close(result?: unknown): void;\n /** Resolves with the value passed to `close()` (or `undefined` on user dismissal). */\n result: Promise<unknown>;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),'\n + 'select:not([disabled]),textarea:not([disabled]),iframe,'\n + '[tabindex]:not([tabindex=\"-1\"]),[contenteditable=\"true\"]';\n\nfunction focusable(root: Element): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => !el.hasAttribute('hidden'),\n );\n}\n\n/**\n * Open an overlay: append a wrapper to `container`, `mount()` `content` inside\n * it, wire the requested dismissals + (optionally) a focus trap, and return a\n * handle. See {@link OverlayOptions}.\n */\nexport function overlay(content: OverlayContent, options: OverlayOptions = {}): OverlayHandle {\n const {\n container = document.body,\n className = 'kerf-overlay',\n dismiss = ['escape', 'backdrop'],\n initialFocus = true,\n trap = true,\n role = 'dialog',\n onDismiss,\n outsideIgnore,\n } = options;\n\n const triggers: readonly DismissTrigger[] =\n dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];\n const restoreTo = document.activeElement;\n\n const wrapper = document.createElement('div');\n wrapper.className = className;\n if (trap) {\n wrapper.setAttribute('role', role);\n wrapper.setAttribute('aria-modal', 'true');\n }\n container.appendChild(wrapper);\n\n const disposeMount = mount(wrapper, typeof content === 'function' ? content : () => content);\n\n const removers: Array<() => void> = [];\n const resultBox: { resolve?: (value: unknown) => void } = {};\n const result = new Promise<unknown>((resolve) => {\n resultBox.resolve = resolve;\n });\n const state = { closed: false };\n\n function close(value?: unknown): void {\n if (state.closed) return;\n state.closed = true;\n for (const remove of removers) remove();\n disposeMount();\n wrapper.remove();\n if (restoreTo instanceof HTMLElement && restoreTo.isConnected) restoreTo.focus();\n resultBox.resolve?.(value);\n }\n\n function userDismiss(): void {\n onDismiss?.();\n close();\n }\n\n const wantEscape = triggers.includes('escape');\n if (wantEscape || trap) {\n const onKeydown = (event: KeyboardEvent): void => {\n if (wantEscape && event.key === 'Escape') {\n event.stopPropagation();\n userDismiss();\n return;\n }\n if (trap && event.key === 'Tab') {\n const items = focusable(wrapper);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const active = document.activeElement;\n const outside = !wrapper.contains(active);\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener('keydown', onKeydown, true);\n removers.push(() => document.removeEventListener('keydown', onKeydown, true));\n }\n\n if (triggers.includes('backdrop')) {\n const onClick = (event: Event): void => {\n if (event.target === wrapper) userDismiss();\n };\n wrapper.addEventListener('click', onClick);\n removers.push(() => wrapper.removeEventListener('click', onClick));\n }\n\n if (triggers.includes('outside')) {\n const ignore = outsideIgnore === undefined\n ? []\n : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];\n // Capture phase: the click that opened this overlay already passed\n // document's capture phase, so this never fires for that opening click.\n const onDocClick = (event: Event): void => {\n const target = event.target as Node | null;\n if (target === null) return;\n if (wrapper.contains(target)) return;\n if (ignore.some((el) => el === target || el.contains(target))) return;\n userDismiss();\n };\n document.addEventListener('click', onDocClick, true);\n removers.push(() => document.removeEventListener('click', onDocClick, true));\n }\n\n if (initialFocus !== false) {\n if (typeof initialFocus === 'string') {\n wrapper.querySelector<HTMLElement>(initialFocus)?.focus();\n } else {\n const first = focusable(wrapper)[0];\n if (first !== undefined) {\n first.focus();\n } else {\n wrapper.tabIndex = -1;\n wrapper.focus();\n }\n }\n }\n\n return { el: wrapper, close, result };\n}\n\n/**\n * Wiring slots passed to a {@link ConfirmOptions.render} — spread `ok` / `cancel`\n * onto your own clickable elements so `confirm()` still resolves them (they are\n * `data-confirm` attribute bags). `message` is the raw message (escape it by\n * interpolating through JSX).\n */\nexport interface ConfirmRenderSlots {\n message: string;\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/** Options for {@link confirm}. */\nexport interface ConfirmOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the message. */\n title?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /** Add a `kerf-confirm--danger` class to the wrapper for destructive actions. */\n danger?: boolean;\n /**\n * Bring your own markup (design-system dialogs): return the full dialog body,\n * spreading the provided `ok`/`cancel` wiring onto your buttons. Overrides the\n * default two-button markup; `confirm()` keeps owning dismiss / focus-trap /\n * focus-restore and still resolves `true`/`false` for OK/Cancel/dismissal.\n */\n render?: (slots: ConfirmRenderSlots) => OverlayContent;\n}\n\n/**\n * A promise-based `window.confirm` replacement (that global is a no-op in Tauri\n * webviews). Renders a two-button dialog and resolves `true` for OK, `false`\n * for Cancel or any dismissal (Escape / backdrop). Message + labels are\n * auto-escaped (rendered through the JSX runtime). Pass `render` for your own markup.\n */\nexport function confirm(message: string, options: ConfirmOptions = {}): Promise<boolean> {\n const {\n container,\n className = 'kerf-overlay',\n title,\n okText = 'OK',\n cancelText = 'Cancel',\n danger = false,\n render,\n } = options;\n\n const body: OverlayContent = render !== undefined\n ? render({ message, ok: { 'data-confirm': 'ok' }, cancel: { 'data-confirm': 'cancel' } })\n : jsx('div', {\n class: 'kerf-confirm',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-confirm__title', children: title }) : '',\n jsx('p', { class: 'kerf-confirm__message', children: message }),\n jsx('div', {\n class: 'kerf-confirm__actions',\n children: [\n jsx('button', { type: 'button', 'data-confirm': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-confirm': 'ok',\n class: 'kerf-confirm__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className: danger ? `${className} kerf-confirm--danger` : className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-confirm=\"ok\"]',\n trap: true,\n });\n\n delegate(handle.el, 'click', '[data-confirm]', (_event, el) => {\n handle.close(el.getAttribute('data-confirm') === 'ok');\n });\n\n return handle.result.then((value) => value === true);\n}\n\n/**\n * Validate a single field's value. Return a non-empty error string to BLOCK\n * submission (shown inline next to the field); return `undefined`/`null`/`''` to\n * allow it.\n */\nexport type FieldValidator = (value: string) => string | null | undefined | void;\n\n/** Options for {@link prompt}. */\nexport interface PromptOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the message. */\n title?: string;\n /** Pre-filled input value. Default `''`. */\n defaultValue?: string;\n /** Input placeholder. */\n placeholder?: string;\n /** `type` attribute of the input (`'text'`, `'email'`, `'password'`, …). Default `'text'`. */\n inputType?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /** Block OK while this returns an error string; the message shows inline. */\n validate?: FieldValidator;\n /**\n * Bring your own markup: return the full dialog body, spreading the provided\n * `input` (the text field), `ok`/`cancel` (buttons), and optional `error` (the\n * inline-error slot) wiring. `prompt()` still reads the input, runs `validate`,\n * submits on Enter, and owns dismiss / focus. If you omit the `error` slot,\n * `validate` simply re-focuses the input without an inline message.\n */\n render?: (slots: PromptRenderSlots) => OverlayContent;\n}\n\n/** Wiring slots for a {@link PromptOptions.render} — spread each onto your own markup. */\nexport interface PromptRenderSlots {\n message: string;\n /** Spread onto your `<input>` — carries the marker, `type`, `value`, and `placeholder`. */\n input: Record<string, string>;\n /** Spread onto your inline-error element (optional). */\n error: Record<string, string>;\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/**\n * A promise-based `window.prompt` replacement (that global is a no-op in Tauri\n * webviews). Renders a one-field dialog and resolves the entered **string** on OK\n * (an empty string is a valid result) or `null` on Cancel / dismissal. Enter in\n * the input submits. `message`, the default value, and labels are auto-escaped\n * (rendered through the JSX runtime). Optional `validate` blocks OK inline. Pass\n * `render` for your own markup.\n */\nexport function prompt(message: string, options: PromptOptions = {}): Promise<string | null> {\n const {\n container,\n className = 'kerf-overlay',\n title,\n defaultValue = '',\n placeholder,\n inputType = 'text',\n okText = 'OK',\n cancelText = 'Cancel',\n validate,\n render,\n } = options;\n\n const inputAttrs: Record<string, string> = {\n 'data-prompt-input': '',\n type: inputType,\n value: defaultValue,\n ...(placeholder !== undefined ? { placeholder } : {}),\n };\n\n const body: OverlayContent = render !== undefined\n ? render({\n message,\n input: inputAttrs,\n error: { 'data-prompt-error': '' },\n ok: { 'data-prompt': 'ok' },\n cancel: { 'data-prompt': 'cancel' },\n })\n : jsx('div', {\n class: 'kerf-prompt',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-prompt__title', children: title }) : '',\n jsx('label', { class: 'kerf-prompt__message', children: message }),\n jsx('input', { class: 'kerf-prompt__input', ...inputAttrs }),\n jsx('p', { class: 'kerf-prompt__error', 'data-prompt-error': '', children: '' }),\n jsx('div', {\n class: 'kerf-prompt__actions',\n children: [\n jsx('button', { type: 'button', 'data-prompt': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-prompt': 'ok',\n class: 'kerf-prompt__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-prompt-input]',\n trap: true,\n });\n\n // The input is required; the error slot is optional (BYO markup may omit it).\n const input = handle.el.querySelector<HTMLInputElement>('[data-prompt-input]')!;\n const errorEl = handle.el.querySelector<HTMLElement>('[data-prompt-error]');\n if (errorEl !== null) errorEl.hidden = true;\n\n function attemptOk(): void {\n const value = input.value;\n const error = validate?.(value);\n if (typeof error === 'string' && error.length > 0) {\n if (errorEl !== null) {\n errorEl.textContent = error;\n errorEl.hidden = false;\n }\n input.focus();\n return;\n }\n handle.close(value);\n }\n\n delegate(handle.el, 'click', '[data-prompt]', (_event, el) => {\n if (el.getAttribute('data-prompt') === 'ok') attemptOk();\n else handle.close(null);\n });\n\n // Enter in the field submits, like the native prompt.\n handle.el.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Enter' && event.target === input) {\n event.preventDefault();\n attemptOk();\n }\n });\n\n return handle.result.then((value) => (typeof value === 'string' ? value : null));\n}\n\n/** A single field in a {@link form}. */\nexport interface FormField {\n /** Field name — the key in the resolved record (and the input's `name`). */\n name: string;\n /** Label shown above the input. Defaults to `name`. */\n label?: string;\n /** Pre-filled value. Default `''`. */\n defaultValue?: string;\n /** Input placeholder. */\n placeholder?: string;\n /** `type` attribute of the input. Default `'text'`. */\n type?: string;\n /** Block OK while this returns an error string; the message shows inline for this field. */\n validate?: FieldValidator;\n}\n\n/** One field's wiring in a {@link FormRenderSlots} — spread `input`/`error` onto your markup. */\nexport interface FormRenderField {\n name: string;\n label: string;\n /** Spread onto your `<input>` — carries the marker, `name`, `type`, `value`, `placeholder`. */\n input: Record<string, string>;\n /** Spread onto your inline-error element (optional). */\n error: Record<string, string>;\n}\n\n/** Wiring slots for a {@link FormOptions.render}. */\nexport interface FormRenderSlots {\n fields: FormRenderField[];\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/** Options for {@link form}. */\nexport interface FormOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the fields. */\n title?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /**\n * Bring your own markup: return the full form body, laying out `slots.fields`\n * (each with `input`/`error` wiring to spread) and the `ok`/`cancel` buttons.\n * `form()` still reads each input, runs per-field `validate`, focuses the first\n * invalid field, submits on Enter, and owns dismiss / focus. Omit a field's\n * `error` slot to skip its inline message.\n */\n render?: (slots: FormRenderSlots) => OverlayContent;\n}\n\n/**\n * A promise-based multi-field dialog — the two-or-three-input sibling of\n * {@link prompt}. Renders one labeled input per {@link FormField} and resolves a\n * `Record<name, value>` on OK (after every field's `validate` passes) or `null`\n * on Cancel / dismissal. Enter in any field submits. All labels, defaults, and\n * the title are auto-escaped through the JSX runtime.\n */\nexport function form(\n fields: readonly FormField[],\n options: FormOptions = {},\n): Promise<Record<string, string> | null> {\n const { container, className = 'kerf-overlay', title, okText = 'OK', cancelText = 'Cancel', render } = options;\n\n const fieldAttrs = (field: FormField): Record<string, string> => ({\n 'data-field': field.name,\n name: field.name,\n type: field.type ?? 'text',\n value: field.defaultValue ?? '',\n ...(field.placeholder !== undefined ? { placeholder: field.placeholder } : {}),\n });\n\n const body: OverlayContent = render !== undefined\n ? render({\n fields: fields.map((field) => ({\n name: field.name,\n label: field.label ?? field.name,\n input: fieldAttrs(field),\n error: { 'data-field-error': field.name },\n })),\n ok: { 'data-form': 'ok' },\n cancel: { 'data-form': 'cancel' },\n })\n : jsx('div', {\n class: 'kerf-form',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-form__title', children: title }) : '',\n ...fields.map((field) =>\n jsx('div', {\n class: 'kerf-form__field',\n children: [\n jsx('label', { class: 'kerf-form__label', children: field.label ?? field.name }),\n jsx('input', { class: 'kerf-form__input', ...fieldAttrs(field) }),\n jsx('p', { class: 'kerf-form__error', 'data-field-error': field.name, children: '' }),\n ],\n }),\n ),\n jsx('div', {\n class: 'kerf-form__actions',\n children: [\n jsx('button', { type: 'button', 'data-form': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-form': 'ok',\n class: 'kerf-form__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-field]',\n trap: true,\n });\n\n // Inputs are required; error nodes are optional (BYO markup may omit them).\n const byAttr = <E extends HTMLElement>(attr: string, name: string): E =>\n Array.from(handle.el.querySelectorAll<E>(`[${attr}]`)).find(\n (el) => el.getAttribute(attr) === name,\n )!;\n const errorFor = (name: string): HTMLElement | null =>\n Array.from(handle.el.querySelectorAll<HTMLElement>('[data-field-error]')).find(\n (el) => el.getAttribute('data-field-error') === name,\n ) ?? null;\n\n // Start with every field's error hidden.\n for (const field of fields) {\n const errorEl = errorFor(field.name);\n if (errorEl !== null) errorEl.hidden = true;\n }\n\n function attemptOk(): void {\n const record: Record<string, string> = {};\n let firstInvalid: HTMLInputElement | null = null;\n for (const field of fields) {\n const el = byAttr<HTMLInputElement>('data-field', field.name);\n const value = el.value;\n record[field.name] = value;\n const error = field.validate?.(value);\n const errorEl = errorFor(field.name);\n if (typeof error === 'string' && error.length > 0) {\n if (errorEl !== null) {\n errorEl.textContent = error;\n errorEl.hidden = false;\n }\n if (firstInvalid === null) firstInvalid = el;\n } else if (errorEl !== null) {\n errorEl.hidden = true;\n }\n }\n if (firstInvalid !== null) {\n firstInvalid.focus();\n return;\n }\n handle.close(record);\n }\n\n delegate(handle.el, 'click', '[data-form]', (_event, el) => {\n if (el.getAttribute('data-form') === 'ok') attemptOk();\n else handle.close(null);\n });\n\n handle.el.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Enter' && (event.target as Element | null)?.matches('[data-field]')) {\n event.preventDefault();\n attemptOk();\n }\n });\n\n return handle.result.then((value) =>\n value !== null && typeof value === 'object' ? (value as Record<string, string>) : null,\n );\n}\n\n/** Vertical placement relative to an anchor (used by {@link popover}, {@link positionAnchored}, {@link tooltip}). */\nexport type PopoverPlacement = 'bottom' | 'top';\n\n/** Placement options for {@link positionAnchored} / {@link autoReposition}. */\nexport interface AnchorPositionOptions {\n /** Preferred side of the anchor; flips to the other side if it would overflow the viewport. Default `'bottom'`. */\n placement?: PopoverPlacement;\n /** Horizontal edge to line up with the anchor: `'start'` (left edges) or `'end'` (right edges). Default `'start'`. */\n align?: 'start' | 'end';\n /** Gap in px between the anchor and the element. Default `4`. */\n gap?: number;\n}\n\n/**\n * One-shot: position `el` relative to `anchor` — below by default, flipping above\n * if it would overflow the viewport, aligned to a horizontal edge and clamped into\n * view. Sets `el.style` `position: fixed`, `margin: 0`, `left`, and `top` (fixed so\n * `left`/`top` are viewport coordinates, matching `getBoundingClientRect`). This is\n * `popover()`'s placement core, usable on any element (an inline hint, a tooltip) —\n * no overlay lifecycle. Pair with {@link autoReposition} to keep it glued while open.\n */\nexport function positionAnchored(el: HTMLElement, anchor: Element, options: AnchorPositionOptions = {}): void {\n const { placement = 'bottom', align = 'start', gap = 4 } = options;\n const a = anchor.getBoundingClientRect();\n const p = el.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // Vertical: preferred side, flipped only if it overflows and the other side fits.\n const belowTop = a.bottom + gap;\n const aboveTop = a.top - gap - p.height;\n let below = placement !== 'top';\n if (below && belowTop + p.height > vh && aboveTop >= 0) below = false;\n else if (!below && aboveTop < 0 && belowTop + p.height <= vh) below = true;\n\n // Horizontal: align to an anchor edge, then clamp into the viewport.\n let left = align === 'end' ? a.right - p.width : a.left;\n left = Math.max(0, Math.min(left, vw - p.width));\n\n el.style.position = 'fixed';\n el.style.margin = '0';\n el.style.left = `${left}px`;\n el.style.top = `${below ? belowTop : aboveTop}px`;\n}\n\n/**\n * Keep `el` positioned against `anchor` (via {@link positionAnchored}) as the page\n * scrolls or resizes. Positions once immediately, then re-runs on `scroll`\n * (capture phase — catches scrolls in any inner container, not just `window`) and\n * `resize`. Returns a disposer that removes the listeners.\n */\nexport function autoReposition(el: HTMLElement, anchor: Element, options: AnchorPositionOptions = {}): () => void {\n const reposition = (): void => positionAnchored(el, anchor, options);\n reposition();\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n return () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n}\n\n/** Options for {@link popover}. */\nexport interface PopoverOptions {\n /** Where to append the popover wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper. Default `'kerf-popover'`. */\n className?: string;\n /** Preferred side of the anchor. Flips to the other side if it would overflow the viewport. Default `'bottom'`. */\n placement?: PopoverPlacement;\n /** Horizontal edge to line up with the anchor: `'start'` (left edges) or `'end'` (right edges). Default `'start'`. */\n align?: 'start' | 'end';\n /** Gap in px between the anchor and the popover. Default `4`. */\n gap?: number;\n /**\n * Which user actions dismiss the popover. Default `['outside']` (a click\n * outside the popover, the anchor exempt). Pass `false` to close only via `close()`.\n */\n dismiss?: DismissTrigger | DismissTrigger[] | false;\n /** Focus behavior on open. Default `false` (non-modal — leave focus alone). */\n initialFocus?: string | boolean;\n /** Extra elements (besides the anchor) whose clicks do NOT count as outside. */\n outsideIgnore?: Element | readonly Element[];\n /** Called on any user-initiated dismissal. */\n onDismiss?: () => void;\n}\n\n/**\n * Anchored, non-modal overlay: positions `content` relative to `anchor` (below by\n * default, flipping above if it would overflow, and clamped horizontally to the\n * viewport) and repositions on scroll / resize while open. A thin wrapper over\n * {@link overlay} with non-modal defaults — `trap: false`, `dismiss: ['outside']`,\n * and the anchor added to `outsideIgnore` so the trigger click doesn't self-close.\n * Returns the same {@link OverlayHandle}; `close()` also drops the reposition\n * listeners. `position: fixed` is set inline (you style everything else).\n */\nexport function popover(\n anchor: Element,\n content: OverlayContent,\n options: PopoverOptions = {},\n): OverlayHandle {\n const {\n container,\n className = 'kerf-popover',\n placement = 'bottom',\n align = 'start',\n gap = 4,\n dismiss = ['outside'],\n initialFocus = false,\n outsideIgnore,\n onDismiss,\n } = options;\n\n const extraIgnore = outsideIgnore === undefined\n ? []\n : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];\n\n const handle = overlay(content, {\n container,\n className,\n dismiss,\n trap: false,\n initialFocus,\n onDismiss,\n outsideIgnore: [anchor, ...extraIgnore],\n });\n\n // Position + keep it glued while open; drop the listeners on close.\n const stopReposition = autoReposition(handle.el, anchor, { placement, align, gap });\n void handle.result.then(stopReposition);\n\n return handle;\n}\n\n/** Content for a {@link tooltip}: text (auto-escaped), `SafeHtml`, or a render function. */\nexport type TooltipContent = string | SafeHtml | (() => MountResult);\n\n/** Options for {@link tooltip}. */\nexport interface TooltipOptions extends AnchorPositionOptions {\n /** Where to append the tooltip wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper. Default `'kerf-tooltip'`. */\n className?: string;\n /** Delay in ms before showing after hover/focus enters. Default `400`. */\n delay?: number;\n /** Delay in ms before hiding after hover/focus leaves. Default `100`. */\n hideDelay?: number;\n /** ARIA role on the wrapper. Default `'tooltip'`. */\n role?: string;\n}\n\n/**\n * A hover/focus-triggered, non-modal, auto-hiding tooltip anchored to `anchor`.\n * Shows after `delay` on `pointerenter`/`focus`, hides after `hideDelay` on\n * `pointerleave`/`blur`, and positions itself with {@link autoReposition} (above\n * the anchor by default). Unlike {@link popover} there is no click-dismiss model —\n * it follows the pointer/focus. Returns a disposer that removes the anchor\n * listeners and hides any shown tooltip. Structural only (kerf ships no CSS).\n */\nexport function tooltip(anchor: Element, content: TooltipContent, options: TooltipOptions = {}): () => void {\n const {\n container,\n className = 'kerf-tooltip',\n delay = 400,\n hideDelay = 100,\n role = 'tooltip',\n placement = 'top',\n align = 'start',\n gap = 4,\n } = options;\n\n const body: OverlayContent = typeof content === 'function'\n ? content\n : typeof content === 'string'\n ? jsx('span', { class: `${className}__text`, children: content })\n : content;\n\n const timers: { show?: ReturnType<typeof setTimeout>; hide?: ReturnType<typeof setTimeout> } = {};\n let current: { handle: OverlayHandle; stop: () => void } | undefined;\n\n function show(): void {\n const handle = overlay(body, { container, className, dismiss: false, trap: false, initialFocus: false });\n handle.el.setAttribute('role', role);\n const stop = autoReposition(handle.el, anchor, { placement, align, gap });\n current = { handle, stop };\n }\n\n function hide(): void {\n if (current === undefined) return;\n current.stop();\n current.handle.close();\n current = undefined;\n }\n\n const onEnter = (): void => {\n if (timers.hide !== undefined) clearTimeout(timers.hide);\n if (current !== undefined) return;\n if (timers.show !== undefined) clearTimeout(timers.show); // debounce: one pending show at a time\n timers.show = setTimeout(show, delay);\n };\n const onLeave = (): void => {\n if (timers.show !== undefined) clearTimeout(timers.show);\n if (current === undefined) return;\n timers.hide = setTimeout(hide, hideDelay);\n };\n\n anchor.addEventListener('pointerenter', onEnter);\n anchor.addEventListener('pointerleave', onLeave);\n anchor.addEventListener('focus', onEnter);\n anchor.addEventListener('blur', onLeave);\n\n return () => {\n anchor.removeEventListener('pointerenter', onEnter);\n anchor.removeEventListener('pointerleave', onLeave);\n anchor.removeEventListener('focus', onEnter);\n anchor.removeEventListener('blur', onLeave);\n if (timers.show !== undefined) clearTimeout(timers.show);\n if (timers.hide !== undefined) clearTimeout(timers.hide);\n hide();\n };\n}\n\n/** Content for a {@link toast}: text, `SafeHtml`, or a render function. */\nexport type ToastContent = string | SafeHtml | (() => MountResult);\n\n/** Accent variant for a {@link toast} — mapped to a `${className}--${variant}` class. */\nexport type ToastVariant = 'info' | 'success' | 'warning';\n\n/** Options for {@link toast}. */\nexport interface ToastOptions {\n /** Where toasts stack. Default: a lazily-created `<div class=\"kerf-toasts\">` on `document.body`. */\n container?: Element;\n /** Class on the toast element. Default `'kerf-toast'`. */\n className?: string;\n /** Auto-dismiss after this many ms. `0` keeps it until dismissed by hand. Default `4000`. */\n duration?: number;\n /** ARIA role. Default `'status'`. */\n role?: string;\n /**\n * `'stack'` (default) shows toasts stacked in the region; `'replace'` dismisses\n * the region's current toast(s) first (collapse-to-latest for a rapid sequence).\n */\n mode?: 'stack' | 'replace';\n /** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */\n variant?: ToastVariant;\n /** Class added on the next animation frame after mount, so a CSS **entrance** transition can run. */\n enterClass?: string;\n /** Class added when dismissing, so CSS owns the **exit** — the node is removed `exitDuration` ms later. */\n exitClass?: string;\n /** ms to wait after `exitClass` is added before removing the node. Default `0`. */\n exitDuration?: number;\n}\n\n/** Handle returned by {@link toast}. */\nexport interface ToastHandle {\n /** The toast element — inspect it, or run your own entrance/exit transitions. */\n el: HTMLElement;\n /** Dismiss it early (running the `exitClass` transition if set). Idempotent. */\n dismiss(): void;\n}\n\n/** The region's active toasts live on the region element (in the DOM), not in module state. */\nconst TOAST_SET = Symbol('kerf.toasts');\ntype ToastCarrier = Element & { [TOAST_SET]?: Set<() => void> };\n\n/** The singleton toast region lives in the DOM (queried, not held in a module variable). */\nfunction toastRegion(container?: Element): Element {\n if (container !== undefined) return container;\n const existing = document.querySelector('.kerf-toasts');\n if (existing !== null) return existing;\n const region = document.createElement('div');\n region.className = 'kerf-toasts';\n region.setAttribute('aria-live', 'polite');\n document.body.appendChild(region);\n return region;\n}\n\n/**\n * Show a non-modal, auto-dismissing notification. Stacks in a shared body-level\n * region (or your `container`). Returns a {@link ToastHandle} (`{ el, dismiss }`)\n * so you can run entrance/exit transitions, wire an action button, or inspect the\n * node. `mode: 'replace'` collapses a rapid sequence to the latest; `variant`\n * adds an accent class; `enterClass`/`exitClass` let CSS own the animation.\n */\nexport function toast(content: ToastContent, options: ToastOptions = {}): ToastHandle {\n const {\n container,\n className = 'kerf-toast',\n duration = 4000,\n role = 'status',\n mode = 'stack',\n variant,\n enterClass,\n exitClass,\n exitDuration = 0,\n } = options;\n\n const region = toastRegion(container) as ToastCarrier;\n const active = (region[TOAST_SET] ??= new Set<() => void>());\n if (mode === 'replace') for (const d of [...active]) d(); // collapse-to-latest\n\n const el = document.createElement('div');\n el.className = className;\n if (variant !== undefined) el.classList.add(`${className}--${variant}`);\n el.setAttribute('role', role);\n region.appendChild(el);\n\n const disposeMount = mount(el, typeof content === 'function' ? content : () => content);\n const state: {\n dismissed: boolean;\n timer: ReturnType<typeof setTimeout> | undefined;\n } = { dismissed: false, timer: undefined };\n\n if (enterClass !== undefined) {\n globalThis.requestAnimationFrame(() => {\n if (!state.dismissed) el.classList.add(enterClass);\n });\n }\n\n const remove = (): void => {\n disposeMount();\n el.remove();\n active.delete(dismiss);\n };\n\n function dismiss(): void {\n if (state.dismissed) return;\n state.dismissed = true;\n if (state.timer !== undefined) clearTimeout(state.timer);\n if (exitClass !== undefined) {\n el.classList.add(exitClass);\n setTimeout(remove, exitDuration);\n } else {\n remove();\n }\n }\n\n active.add(dismiss);\n if (duration > 0) state.timer = setTimeout(dismiss, duration);\n return { el, dismiss };\n}\n"]} | ||
| {"version":3,"sources":["../src/overlay.ts"],"names":[],"mappings":"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","file":"overlay.js","sourcesContent":["/**\n * `kerfjs/overlay` — the modal / overlay + dismiss manager.\n *\n * Every real kerf app hand-rolls this: `toElement → body.appendChild → mount →\n * wire dismissal → remove`, plus the fiddly parts (Escape, backdrop / outside\n * click, focus trap, restoring focus on close). `window.confirm` is a no-op in\n * Tauri WKWebViews, so a hand-built overlay is mandatory there. This subpath\n * blesses the pattern as three functions over `mount()` — `overlay()`, and the\n * `confirm()` / `toast()` conveniences built on it. No per-instance framework\n * state: each call owns its DOM + listeners in a closure and returns a handle.\n *\n * import { overlay, confirm, toast } from 'kerfjs/overlay';\n *\n * const ok = await confirm('Delete this file?', { danger: true });\n * toast('Saved');\n * const dialog = overlay(<Settings />, { dismiss: ['escape', 'backdrop'] });\n * // …later: dialog.close(); or await dialog.result;\n *\n * Structural only — kerf ships no CSS. The wrapper gets your `className`; style\n * the backdrop / centering / animation yourself.\n */\nimport { delegate } from './delegate.js';\nimport { jsx, type SafeHtml } from './jsx-runtime.js';\nimport { mount, type MountResult } from './mount.js';\n\n/** A user-initiated dismissal trigger. */\nexport type DismissTrigger = 'escape' | 'backdrop' | 'outside';\n\n/** Content for an overlay: static `SafeHtml`, or a render function `mount()` drives reactively. */\nexport type OverlayContent = SafeHtml | (() => MountResult);\n\n/** Options for {@link overlay}. */\nexport interface OverlayOptions {\n /** Where to append the overlay wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper element (you style it — kerf ships no CSS). Default `'kerf-overlay'`. */\n className?: string;\n /**\n * Which user actions dismiss the overlay. Default `['escape', 'backdrop']`.\n * `'backdrop'` = a click on the wrapper itself (not its content); `'outside'`\n * = a click anywhere outside the wrapper (for anchored popovers). `false`\n * disables user dismissal (close it programmatically).\n */\n dismiss?: DismissTrigger | DismissTrigger[] | false;\n /**\n * Where focus lands on open: a selector, `true` (first focusable element, or\n * the wrapper if none), or `false` (leave focus alone). Default `true`.\n */\n initialFocus?: string | boolean;\n /**\n * Trap Tab / Shift+Tab within the overlay while open and mark it\n * `role=\"dialog\"` / `aria-modal=\"true\"`. Default `true`. Set `false` for a\n * non-modal popover.\n */\n trap?: boolean;\n /** ARIA role for the wrapper when `trap` is on. Default `'dialog'`. */\n role?: string;\n /** Called on any user-initiated dismissal (before `close()` runs). */\n onDismiss?: () => void;\n /** For `'outside'` dismissal: clicks on these elements do NOT count as outside (e.g. the trigger button). */\n outsideIgnore?: Element | readonly Element[];\n}\n\n/** Handle returned by {@link overlay}. Holds no framework state — it's a closure. */\nexport interface OverlayHandle {\n /** The wrapper element (mounted into, appended to `container`). */\n el: HTMLElement;\n /** Tear down: dispose the mount, remove listeners + the node, restore focus, resolve `result`. Idempotent. */\n close(result?: unknown): void;\n /** Resolves with the value passed to `close()` (or `undefined` on user dismissal). */\n result: Promise<unknown>;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),'\n + 'select:not([disabled]),textarea:not([disabled]),iframe,'\n + '[tabindex]:not([tabindex=\"-1\"]),[contenteditable=\"true\"]';\n\nfunction focusable(root: Element): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => !el.hasAttribute('hidden'),\n );\n}\n\n/**\n * Open an overlay: append a wrapper to `container`, `mount()` `content` inside\n * it, wire the requested dismissals + (optionally) a focus trap, and return a\n * handle. See {@link OverlayOptions}.\n */\nexport function overlay(content: OverlayContent, options: OverlayOptions = {}): OverlayHandle {\n const {\n container = document.body,\n className = 'kerf-overlay',\n dismiss = ['escape', 'backdrop'],\n initialFocus = true,\n trap = true,\n role = 'dialog',\n onDismiss,\n outsideIgnore,\n } = options;\n\n const triggers: readonly DismissTrigger[] =\n dismiss === false ? [] : Array.isArray(dismiss) ? dismiss : [dismiss];\n const restoreTo = document.activeElement;\n\n const wrapper = document.createElement('div');\n wrapper.className = className;\n if (trap) {\n wrapper.setAttribute('role', role);\n wrapper.setAttribute('aria-modal', 'true');\n }\n container.appendChild(wrapper);\n\n const disposeMount = mount(wrapper, typeof content === 'function' ? content : () => content);\n\n const removers: Array<() => void> = [];\n const resultBox: { resolve?: (value: unknown) => void } = {};\n const result = new Promise<unknown>((resolve) => {\n resultBox.resolve = resolve;\n });\n const state = { closed: false };\n\n function close(value?: unknown): void {\n if (state.closed) return;\n state.closed = true;\n for (const remove of removers) remove();\n disposeMount();\n wrapper.remove();\n if (restoreTo instanceof HTMLElement && restoreTo.isConnected) restoreTo.focus();\n resultBox.resolve?.(value);\n }\n\n function userDismiss(): void {\n onDismiss?.();\n close();\n }\n\n const wantEscape = triggers.includes('escape');\n if (wantEscape || trap) {\n const onKeydown = (event: KeyboardEvent): void => {\n if (wantEscape && event.key === 'Escape') {\n event.stopPropagation();\n userDismiss();\n return;\n }\n if (trap && event.key === 'Tab') {\n const items = focusable(wrapper);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const active = document.activeElement;\n const outside = !wrapper.contains(active);\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener('keydown', onKeydown, true);\n removers.push(() => document.removeEventListener('keydown', onKeydown, true));\n }\n\n if (triggers.includes('backdrop')) {\n const onClick = (event: Event): void => {\n if (event.target === wrapper) userDismiss();\n };\n wrapper.addEventListener('click', onClick);\n removers.push(() => wrapper.removeEventListener('click', onClick));\n }\n\n if (triggers.includes('outside')) {\n const ignore = outsideIgnore === undefined\n ? []\n : Array.isArray(outsideIgnore) ? outsideIgnore : [outsideIgnore];\n // Capture phase: the click that opened this overlay already passed\n // document's capture phase, so this never fires for that opening click.\n const onDocClick = (event: Event): void => {\n const target = event.target as Node | null;\n if (target === null) return;\n if (wrapper.contains(target)) return;\n if (ignore.some((el) => el === target || el.contains(target))) return;\n userDismiss();\n };\n document.addEventListener('click', onDocClick, true);\n removers.push(() => document.removeEventListener('click', onDocClick, true));\n }\n\n if (initialFocus !== false) {\n if (typeof initialFocus === 'string') {\n wrapper.querySelector<HTMLElement>(initialFocus)?.focus();\n } else {\n const first = focusable(wrapper)[0];\n if (first !== undefined) {\n first.focus();\n } else {\n wrapper.tabIndex = -1;\n wrapper.focus();\n }\n }\n }\n\n return { el: wrapper, close, result };\n}\n\n/**\n * Wiring slots passed to a {@link ConfirmOptions.render} — spread `ok` / `cancel`\n * onto your own clickable elements so `confirm()` still resolves them (they are\n * `data-confirm` attribute bags). `message` is the raw message (escape it by\n * interpolating through JSX).\n */\nexport interface ConfirmRenderSlots {\n message: string;\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/** Options for {@link confirm}. */\nexport interface ConfirmOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the message. */\n title?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /** Add a `kerf-confirm--danger` class to the wrapper for destructive actions. */\n danger?: boolean;\n /**\n * Bring your own markup (design-system dialogs): return the full dialog body,\n * spreading the provided `ok`/`cancel` wiring onto your buttons. Overrides the\n * default two-button markup; `confirm()` keeps owning dismiss / focus-trap /\n * focus-restore and still resolves `true`/`false` for OK/Cancel/dismissal.\n */\n render?: (slots: ConfirmRenderSlots) => OverlayContent;\n}\n\n/**\n * A promise-based `window.confirm` replacement (that global is a no-op in Tauri\n * webviews). Renders a two-button dialog and resolves `true` for OK, `false`\n * for Cancel or any dismissal (Escape / backdrop). Message + labels are\n * auto-escaped (rendered through the JSX runtime). Pass `render` for your own markup.\n */\nexport function confirm(message: string, options: ConfirmOptions = {}): Promise<boolean> {\n const {\n container,\n className = 'kerf-overlay',\n title,\n okText = 'OK',\n cancelText = 'Cancel',\n danger = false,\n render,\n } = options;\n\n const body: OverlayContent = render !== undefined\n ? render({ message, ok: { 'data-confirm': 'ok' }, cancel: { 'data-confirm': 'cancel' } })\n : jsx('div', {\n class: 'kerf-confirm',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-confirm__title', children: title }) : '',\n jsx('p', { class: 'kerf-confirm__message', children: message }),\n jsx('div', {\n class: 'kerf-confirm__actions',\n children: [\n jsx('button', { type: 'button', 'data-confirm': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-confirm': 'ok',\n class: 'kerf-confirm__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className: danger ? `${className} kerf-confirm--danger` : className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-confirm=\"ok\"]',\n trap: true,\n });\n\n delegate(handle.el, 'click', '[data-confirm]', (_event, el) => {\n handle.close(el.getAttribute('data-confirm') === 'ok');\n });\n\n return handle.result.then((value) => value === true);\n}\n\n/**\n * Validate a single field's value. Return a non-empty error string to BLOCK\n * submission (shown inline next to the field); return `undefined`/`null`/`''` to\n * allow it.\n */\nexport type FieldValidator = (value: string) => string | null | undefined | void;\n\n/** Options for {@link prompt}. */\nexport interface PromptOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the message. */\n title?: string;\n /** Pre-filled input value. Default `''`. */\n defaultValue?: string;\n /** Input placeholder. */\n placeholder?: string;\n /** `type` attribute of the input (`'text'`, `'email'`, `'password'`, …). Default `'text'`. */\n inputType?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /** Block OK while this returns an error string; the message shows inline. */\n validate?: FieldValidator;\n /**\n * Bring your own markup: return the full dialog body, spreading the provided\n * `input` (the text field), `ok`/`cancel` (buttons), and optional `error` (the\n * inline-error slot) wiring. `prompt()` still reads the input, runs `validate`,\n * submits on Enter, and owns dismiss / focus. If you omit the `error` slot,\n * `validate` simply re-focuses the input without an inline message.\n */\n render?: (slots: PromptRenderSlots) => OverlayContent;\n}\n\n/** Wiring slots for a {@link PromptOptions.render} — spread each onto your own markup. */\nexport interface PromptRenderSlots {\n message: string;\n /** Spread onto your `<input>` — carries the marker, `type`, `value`, and `placeholder`. */\n input: Record<string, string>;\n /** Spread onto your inline-error element (optional). */\n error: Record<string, string>;\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/**\n * A promise-based `window.prompt` replacement (that global is a no-op in Tauri\n * webviews). Renders a one-field dialog and resolves the entered **string** on OK\n * (an empty string is a valid result) or `null` on Cancel / dismissal. Enter in\n * the input submits. `message`, the default value, and labels are auto-escaped\n * (rendered through the JSX runtime). Optional `validate` blocks OK inline. Pass\n * `render` for your own markup.\n */\nexport function prompt(message: string, options: PromptOptions = {}): Promise<string | null> {\n const {\n container,\n className = 'kerf-overlay',\n title,\n defaultValue = '',\n placeholder,\n inputType = 'text',\n okText = 'OK',\n cancelText = 'Cancel',\n validate,\n render,\n } = options;\n\n const inputAttrs: Record<string, string> = {\n 'data-prompt-input': '',\n type: inputType,\n value: defaultValue,\n ...(placeholder !== undefined ? { placeholder } : {}),\n };\n\n const body: OverlayContent = render !== undefined\n ? render({\n message,\n input: inputAttrs,\n error: { 'data-prompt-error': '' },\n ok: { 'data-prompt': 'ok' },\n cancel: { 'data-prompt': 'cancel' },\n })\n : jsx('div', {\n class: 'kerf-prompt',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-prompt__title', children: title }) : '',\n jsx('label', { class: 'kerf-prompt__message', children: message }),\n jsx('input', { class: 'kerf-prompt__input', ...inputAttrs }),\n jsx('p', { class: 'kerf-prompt__error', 'data-prompt-error': '', children: '' }),\n jsx('div', {\n class: 'kerf-prompt__actions',\n children: [\n jsx('button', { type: 'button', 'data-prompt': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-prompt': 'ok',\n class: 'kerf-prompt__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-prompt-input]',\n trap: true,\n });\n\n // The input is required; the error slot is optional (BYO markup may omit it).\n const input = handle.el.querySelector<HTMLInputElement>('[data-prompt-input]')!;\n const errorEl = handle.el.querySelector<HTMLElement>('[data-prompt-error]');\n if (errorEl !== null) errorEl.hidden = true;\n\n function attemptOk(): void {\n const value = input.value;\n const error = validate?.(value);\n if (typeof error === 'string' && error.length > 0) {\n if (errorEl !== null) {\n errorEl.textContent = error;\n errorEl.hidden = false;\n }\n input.focus();\n return;\n }\n handle.close(value);\n }\n\n delegate(handle.el, 'click', '[data-prompt]', (_event, el) => {\n if (el.getAttribute('data-prompt') === 'ok') attemptOk();\n else handle.close(null);\n });\n\n // Enter in the field submits, like the native prompt.\n handle.el.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Enter' && event.target === input) {\n event.preventDefault();\n attemptOk();\n }\n });\n\n return handle.result.then((value) => (typeof value === 'string' ? value : null));\n}\n\n/** A single field in a {@link form}. */\nexport interface FormField {\n /** Field name — the key in the resolved record (and the input's `name`). */\n name: string;\n /** Label shown above the input. Defaults to `name`. */\n label?: string;\n /** Pre-filled value. Default `''`. */\n defaultValue?: string;\n /** Input placeholder. */\n placeholder?: string;\n /** `type` attribute of the input. Default `'text'`. */\n type?: string;\n /** Block OK while this returns an error string; the message shows inline for this field. */\n validate?: FieldValidator;\n}\n\n/** One field's wiring in a {@link FormRenderSlots} — spread `input`/`error` onto your markup. */\nexport interface FormRenderField {\n name: string;\n label: string;\n /** Spread onto your `<input>` — carries the marker, `name`, `type`, `value`, `placeholder`. */\n input: Record<string, string>;\n /** Spread onto your inline-error element (optional). */\n error: Record<string, string>;\n}\n\n/** Wiring slots for a {@link FormOptions.render}. */\nexport interface FormRenderSlots {\n fields: FormRenderField[];\n /** Spread onto the confirm control. */\n ok: Record<string, string>;\n /** Spread onto the cancel control. */\n cancel: Record<string, string>;\n}\n\n/** Options for {@link form}. */\nexport interface FormOptions {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the fields. */\n title?: string;\n /** Confirm button label. Default `'OK'`. */\n okText?: string;\n /** Cancel button label. Default `'Cancel'`. */\n cancelText?: string;\n /**\n * Bring your own markup: return the full form body, laying out `slots.fields`\n * (each with `input`/`error` wiring to spread) and the `ok`/`cancel` buttons.\n * `form()` still reads each input, runs per-field `validate`, focuses the first\n * invalid field, submits on Enter, and owns dismiss / focus. Omit a field's\n * `error` slot to skip its inline message.\n */\n render?: (slots: FormRenderSlots) => OverlayContent;\n}\n\n/**\n * A promise-based multi-field dialog — the two-or-three-input sibling of\n * {@link prompt}. Renders one labeled input per {@link FormField} and resolves a\n * `Record<name, value>` on OK (after every field's `validate` passes) or `null`\n * on Cancel / dismissal. Enter in any field submits. All labels, defaults, and\n * the title are auto-escaped through the JSX runtime.\n */\nexport function form(\n fields: readonly FormField[],\n options: FormOptions = {},\n): Promise<Record<string, string> | null> {\n const { container, className = 'kerf-overlay', title, okText = 'OK', cancelText = 'Cancel', render } = options;\n\n const fieldAttrs = (field: FormField): Record<string, string> => ({\n 'data-field': field.name,\n name: field.name,\n type: field.type ?? 'text',\n value: field.defaultValue ?? '',\n ...(field.placeholder !== undefined ? { placeholder: field.placeholder } : {}),\n });\n\n const body: OverlayContent = render !== undefined\n ? render({\n fields: fields.map((field) => ({\n name: field.name,\n label: field.label ?? field.name,\n input: fieldAttrs(field),\n error: { 'data-field-error': field.name },\n })),\n ok: { 'data-form': 'ok' },\n cancel: { 'data-form': 'cancel' },\n })\n : jsx('div', {\n class: 'kerf-form',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-form__title', children: title }) : '',\n ...fields.map((field) =>\n jsx('div', {\n class: 'kerf-form__field',\n children: [\n jsx('label', { class: 'kerf-form__label', children: field.label ?? field.name }),\n jsx('input', { class: 'kerf-form__input', ...fieldAttrs(field) }),\n jsx('p', { class: 'kerf-form__error', 'data-field-error': field.name, children: '' }),\n ],\n }),\n ),\n jsx('div', {\n class: 'kerf-form__actions',\n children: [\n jsx('button', { type: 'button', 'data-form': 'cancel', children: cancelText }),\n jsx('button', {\n type: 'button',\n 'data-form': 'ok',\n class: 'kerf-form__ok',\n children: okText,\n }),\n ],\n }),\n ],\n });\n\n const handle = overlay(body, {\n container,\n className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-field]',\n trap: true,\n });\n\n // Inputs are required; error nodes are optional (BYO markup may omit them).\n const byAttr = <E extends HTMLElement>(attr: string, name: string): E =>\n Array.from(handle.el.querySelectorAll<E>(`[${attr}]`)).find(\n (el) => el.getAttribute(attr) === name,\n )!;\n const errorFor = (name: string): HTMLElement | null =>\n Array.from(handle.el.querySelectorAll<HTMLElement>('[data-field-error]')).find(\n (el) => el.getAttribute('data-field-error') === name,\n ) ?? null;\n\n // Start with every field's error hidden.\n for (const field of fields) {\n const errorEl = errorFor(field.name);\n if (errorEl !== null) errorEl.hidden = true;\n }\n\n function attemptOk(): void {\n const record: Record<string, string> = {};\n let firstInvalid: HTMLInputElement | null = null;\n for (const field of fields) {\n const el = byAttr<HTMLInputElement>('data-field', field.name);\n const value = el.value;\n record[field.name] = value;\n const error = field.validate?.(value);\n const errorEl = errorFor(field.name);\n if (typeof error === 'string' && error.length > 0) {\n if (errorEl !== null) {\n errorEl.textContent = error;\n errorEl.hidden = false;\n }\n if (firstInvalid === null) firstInvalid = el;\n } else if (errorEl !== null) {\n errorEl.hidden = true;\n }\n }\n if (firstInvalid !== null) {\n firstInvalid.focus();\n return;\n }\n handle.close(record);\n }\n\n delegate(handle.el, 'click', '[data-form]', (_event, el) => {\n if (el.getAttribute('data-form') === 'ok') attemptOk();\n else handle.close(null);\n });\n\n handle.el.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Enter' && (event.target as Element | null)?.matches('[data-field]')) {\n event.preventDefault();\n attemptOk();\n }\n });\n\n return handle.result.then((value) =>\n value !== null && typeof value === 'object' ? (value as Record<string, string>) : null,\n );\n}\n\n/** One choosable action in a {@link choice} dialog. */\nexport interface ChoiceAction<R> {\n /** The value this action resolves. */\n value: R;\n /** Button label (auto-escaped). */\n label: string;\n /** Extra class on this action's button. */\n className?: string;\n}\n\n/** Wiring slots for a {@link ChoiceOptions.render} — spread `actions[i]` onto your i-th button. */\nexport interface ChoiceRenderSlots {\n message: string;\n /** One attribute bag per action (in order) — spread onto that action's control. */\n actions: Array<Record<string, string>>;\n}\n\n/** Options for {@link choice}. */\nexport interface ChoiceOptions<R> {\n /** Where to append the overlay. Default `document.body`. */\n container?: Element;\n /** Wrapper class. Default `'kerf-overlay'`. */\n className?: string;\n /** Optional heading above the message. */\n title?: string;\n /** The value resolved when **Enter** is pressed anywhere in the dialog (the default action). */\n defaultValue?: R;\n /** Bring your own markup: return the full body, spreading each `slots.actions[i]` onto your buttons. */\n render?: (slots: ChoiceRenderSlots) => OverlayContent;\n}\n\n/**\n * The **N-way** sibling of {@link confirm}: renders one button per {@link ChoiceAction}\n * and resolves that action's `value` on click, or `null` on Cancel / dismissal.\n * Pass `defaultValue` to make **Enter** (anywhere in the dialog) resolve a default\n * action — the \"global Enter-to-confirm\" model — without you having to hold the\n * overlay handle. `message` + labels are auto-escaped; pass `render` for your own\n * markup. kerf owns dismiss / focus-trap / focus-restore. For fully bespoke\n * keyboard/close control, drive {@link overlay} directly.\n */\nexport function choice<R>(\n message: string,\n actions: ReadonlyArray<ChoiceAction<R>>,\n options: ChoiceOptions<R> = {},\n): Promise<R | null> {\n const { container, className = 'kerf-overlay', title, defaultValue, render } = options;\n const hasDefault = 'defaultValue' in options;\n\n const actionAttrs = actions.map((_, i) => ({ 'data-choice': String(i) }));\n\n const body: OverlayContent = render !== undefined\n ? render({ message, actions: actionAttrs })\n : jsx('div', {\n class: 'kerf-choice',\n children: [\n title !== undefined ? jsx('h2', { class: 'kerf-choice__title', children: title }) : '',\n jsx('p', { class: 'kerf-choice__message', children: message }),\n jsx('div', {\n class: 'kerf-choice__actions',\n children: actions.map((action, i) =>\n jsx('button', {\n type: 'button',\n class: action.className !== undefined ? `kerf-choice__action ${action.className}` : 'kerf-choice__action',\n ...actionAttrs[i],\n children: action.label,\n }),\n ),\n }),\n ],\n });\n\n // Own the promise (not overlay's result value) so an action `value` of\n // `undefined`/`null` stays distinct from a dismissal.\n let resolveChoice!: (r: R | null) => void;\n const result = new Promise<R | null>((resolve) => {\n resolveChoice = resolve;\n });\n\n const handle = overlay(body, {\n container,\n className,\n dismiss: ['escape', 'backdrop'],\n initialFocus: '[data-choice]',\n trap: true,\n });\n\n delegate(handle.el, 'click', '[data-choice]', (_event, el) => {\n resolveChoice(actions[Number(el.getAttribute('data-choice'))].value);\n handle.close();\n });\n\n if (hasDefault) {\n handle.el.addEventListener('keydown', (event: KeyboardEvent) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n resolveChoice(defaultValue as R);\n handle.close();\n }\n });\n }\n\n // Any close without a pick (Escape / backdrop / programmatic) → null. First\n // resolve wins, so a prior click/Enter value stands.\n void handle.result.then(() => resolveChoice(null));\n\n return result;\n}\n\n/** Vertical placement relative to an anchor (used by {@link popover}, {@link positionAnchored}, {@link tooltip}). */\nexport type PopoverPlacement = 'bottom' | 'top';\n\n/** Placement options for {@link positionAnchored} / {@link autoReposition}. */\nexport interface AnchorPositionOptions {\n /** Preferred side of the anchor; flips to the other side if it would overflow the viewport. Default `'bottom'`. */\n placement?: PopoverPlacement;\n /** Horizontal edge to line up with the anchor: `'start'` (left edges) or `'end'` (right edges). Default `'start'`. */\n align?: 'start' | 'end';\n /** Gap in px between the anchor and the element. Default `4`. */\n gap?: number;\n}\n\n/**\n * One-shot: position `el` relative to `anchor` — below by default, flipping above\n * if it would overflow the viewport, aligned to a horizontal edge and clamped into\n * view. Sets `el.style` `position: fixed`, `margin: 0`, `left`, and `top` (fixed so\n * `left`/`top` are viewport coordinates, matching `getBoundingClientRect`). This is\n * `popover()`'s placement core, usable on any element (an inline hint, a tooltip) —\n * no overlay lifecycle. Pair with {@link autoReposition} to keep it glued while open.\n */\nexport function positionAnchored(el: HTMLElement, anchor: Element, options: AnchorPositionOptions = {}): void {\n const { placement = 'bottom', align = 'start', gap = 4 } = options;\n const a = anchor.getBoundingClientRect();\n const p = el.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // Vertical: preferred side, flipped only if it overflows and the other side fits.\n const belowTop = a.bottom + gap;\n const aboveTop = a.top - gap - p.height;\n let below = placement !== 'top';\n if (below && belowTop + p.height > vh && aboveTop >= 0) below = false;\n else if (!below && aboveTop < 0 && belowTop + p.height <= vh) below = true;\n\n // Horizontal: align to an anchor edge, then clamp into the viewport.\n let left = align === 'end' ? a.right - p.width : a.left;\n left = Math.max(0, Math.min(left, vw - p.width));\n\n el.style.position = 'fixed';\n el.style.margin = '0';\n el.style.left = `${left}px`;\n el.style.top = `${below ? belowTop : aboveTop}px`;\n}\n\n/**\n * Keep `el` positioned against `anchor` (via {@link positionAnchored}) as the page\n * scrolls or resizes. Positions once immediately, then re-runs on `scroll`\n * (capture phase — catches scrolls in any inner container, not just `window`) and\n * `resize`. Returns a disposer that removes the listeners.\n */\nexport function autoReposition(el: HTMLElement, anchor: Element, options: AnchorPositionOptions = {}): () => void {\n const reposition = (): void => positionAnchored(el, anchor, options);\n reposition();\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n return () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n}\n\n/** Options for {@link popover}. */\nexport interface PopoverOptions {\n /** Where to append the popover wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper. Default `'kerf-popover'`. */\n className?: string;\n /** Preferred side of the anchor. Flips to the other side if it would overflow the viewport. Default `'bottom'`. */\n placement?: PopoverPlacement;\n /** Horizontal edge to line up with the anchor: `'start'` (left edges) or `'end'` (right edges). Default `'start'`. */\n align?: 'start' | 'end';\n /** Gap in px between the anchor and the popover. Default `4`. */\n gap?: number;\n /**\n * Which user actions dismiss the popover. Default `['outside']` (a click\n * outside the popover, the anchor exempt). Pass `false` to close only via `close()`.\n */\n dismiss?: DismissTrigger | DismissTrigger[] | false;\n /** Focus behavior on open. Default `false` (non-modal — leave focus alone). */\n initialFocus?: string | boolean;\n /** Extra elements (besides the anchor) whose clicks do NOT count as outside. */\n outsideIgnore?: Element | readonly Element[];\n /** Called on any user-initiated dismissal. */\n onDismiss?: () => void;\n}\n\n/**\n * Anchored, non-modal overlay: positions `content` relative to `anchor` (below by\n * default, flipping above if it would overflow, and clamped horizontally to the\n * viewport) and repositions on scroll / resize while open. A thin wrapper over\n * {@link overlay} with non-modal defaults — `trap: false`, `dismiss: ['outside']`,\n * and the anchor added to `outsideIgnore` so the trigger click doesn't self-close.\n * Returns the same {@link OverlayHandle}; `close()` also drops the reposition\n * listeners. `position: fixed` is set inline (you style everything else).\n */\nexport function popover(\n anchor: Element,\n content: OverlayContent,\n options: PopoverOptions = {},\n): OverlayHandle {\n const {\n container,\n className = 'kerf-popover',\n placement = 'bottom',\n align = 'start',\n gap = 4,\n dismiss = ['outside'],\n initialFocus = false,\n outsideIgnore,\n onDismiss,\n } = options;\n\n const extraIgnore = outsideIgnore === undefined\n ? []\n : Array.isArray(outsideIgnore) ? [...outsideIgnore] : [outsideIgnore];\n\n const handle = overlay(content, {\n container,\n className,\n dismiss,\n trap: false,\n initialFocus,\n onDismiss,\n outsideIgnore: [anchor, ...extraIgnore],\n });\n\n // Position + keep it glued while open; drop the listeners on close.\n const stopReposition = autoReposition(handle.el, anchor, { placement, align, gap });\n void handle.result.then(stopReposition);\n\n return handle;\n}\n\n/** Content for a {@link tooltip}: text (auto-escaped), `SafeHtml`, or a render function. */\nexport type TooltipContent = string | SafeHtml | (() => MountResult);\n\n/** Options for {@link tooltip}. */\nexport interface TooltipOptions extends AnchorPositionOptions {\n /** Where to append the tooltip wrapper. Default `document.body`. */\n container?: Element;\n /** Class on the wrapper. Default `'kerf-tooltip'`. */\n className?: string;\n /** Delay in ms before showing after hover/focus enters. Default `400`. */\n delay?: number;\n /** Delay in ms before hiding after hover/focus leaves. Default `100`. */\n hideDelay?: number;\n /** ARIA role on the wrapper. Default `'tooltip'`. */\n role?: string;\n}\n\n/**\n * A hover/focus-triggered, non-modal, auto-hiding tooltip anchored to `anchor`.\n * Shows after `delay` on `pointerenter`/`focus`, hides after `hideDelay` on\n * `pointerleave`/`blur`, and positions itself with {@link autoReposition} (above\n * the anchor by default). Unlike {@link popover} there is no click-dismiss model —\n * it follows the pointer/focus. Returns a disposer that removes the anchor\n * listeners and hides any shown tooltip. Structural only (kerf ships no CSS).\n */\nexport function tooltip(anchor: Element, content: TooltipContent, options: TooltipOptions = {}): () => void {\n const {\n container,\n className = 'kerf-tooltip',\n delay = 400,\n hideDelay = 100,\n role = 'tooltip',\n placement = 'top',\n align = 'start',\n gap = 4,\n } = options;\n\n const body: OverlayContent = typeof content === 'function'\n ? content\n : typeof content === 'string'\n ? jsx('span', { class: `${className}__text`, children: content })\n : content;\n\n const timers: { show?: ReturnType<typeof setTimeout>; hide?: ReturnType<typeof setTimeout> } = {};\n let current: { handle: OverlayHandle; stop: () => void } | undefined;\n\n function show(): void {\n const handle = overlay(body, { container, className, dismiss: false, trap: false, initialFocus: false });\n handle.el.setAttribute('role', role);\n const stop = autoReposition(handle.el, anchor, { placement, align, gap });\n current = { handle, stop };\n }\n\n function hide(): void {\n if (current === undefined) return;\n current.stop();\n current.handle.close();\n current = undefined;\n }\n\n const onEnter = (): void => {\n if (timers.hide !== undefined) clearTimeout(timers.hide);\n if (current !== undefined) return;\n if (timers.show !== undefined) clearTimeout(timers.show); // debounce: one pending show at a time\n timers.show = setTimeout(show, delay);\n };\n const onLeave = (): void => {\n if (timers.show !== undefined) clearTimeout(timers.show);\n if (current === undefined) return;\n timers.hide = setTimeout(hide, hideDelay);\n };\n\n anchor.addEventListener('pointerenter', onEnter);\n anchor.addEventListener('pointerleave', onLeave);\n anchor.addEventListener('focus', onEnter);\n anchor.addEventListener('blur', onLeave);\n\n return () => {\n anchor.removeEventListener('pointerenter', onEnter);\n anchor.removeEventListener('pointerleave', onLeave);\n anchor.removeEventListener('focus', onEnter);\n anchor.removeEventListener('blur', onLeave);\n if (timers.show !== undefined) clearTimeout(timers.show);\n if (timers.hide !== undefined) clearTimeout(timers.hide);\n hide();\n };\n}\n\n/** Content for a {@link toast}: text, `SafeHtml`, or a render function. */\nexport type ToastContent = string | SafeHtml | (() => MountResult);\n\n/** Accent variant for a {@link toast} — mapped to a `${className}--${variant}` class. */\nexport type ToastVariant = 'info' | 'success' | 'warning';\n\n/** Options for {@link toast}. */\nexport interface ToastOptions {\n /** Where toasts stack. Default: a lazily-created `<div class=\"kerf-toasts\">` on `document.body`. */\n container?: Element;\n /** Class on the toast element. Default `'kerf-toast'`. */\n className?: string;\n /** Auto-dismiss after this many ms. `0` keeps it until dismissed by hand. Default `4000`. */\n duration?: number;\n /** ARIA role. Default `'status'`. */\n role?: string;\n /**\n * `'stack'` (default) shows toasts stacked in the region; `'replace'` dismisses\n * the region's current toast(s) first (collapse-to-latest for a rapid sequence).\n */\n mode?: 'stack' | 'replace';\n /**\n * How `mode: 'replace'` drops the prior toast(s): `'fade'` (default) runs their\n * full exit transition (nice for a STACKING region), or `'instant'` removes them\n * synchronously with no exit — what a single, exactly-centered toast slot wants,\n * so the outgoing and incoming messages never cross-fade in the same spot.\n */\n collapse?: 'fade' | 'instant';\n /** Accent variant — adds a `${className}--${variant}` class (kerf ships no CSS; you style it). */\n variant?: ToastVariant;\n /** Class added on the next animation frame after mount, so a CSS **entrance** transition can run. */\n enterClass?: string;\n /**\n * Class added when dismissing, so CSS owns the **exit**. On dismiss the\n * `enterClass` (if any) is also REMOVED, so `exitClass` doesn't have to\n * out-specify it — and a symmetric single-class fade (entrance = add\n * `enterClass`, exit = remove it) works by setting only `enterClass` +\n * `exitDuration`. The node is removed `exitDuration` ms later.\n */\n exitClass?: string;\n /** 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`. */\n exitDuration?: number;\n}\n\n/** Handle returned by {@link toast}. */\nexport interface ToastHandle {\n /** The toast element — inspect it, or run your own entrance/exit transitions. */\n el: HTMLElement;\n /** Dismiss it early (running the `exitClass` transition if set). Idempotent. */\n dismiss(): void;\n}\n\n/** The region's active toasts live on the region element (in the DOM), not in module state. */\nconst TOAST_SET = Symbol('kerf.toasts');\n/** A dismiss function with an `.removeNow()` for instant (no-exit) teardown. */\ntype Dismisser = (() => void) & { removeNow: () => void };\ntype ToastCarrier = Element & { [TOAST_SET]?: Set<Dismisser> };\n\n/** The singleton toast region lives in the DOM (queried, not held in a module variable). */\nfunction toastRegion(container?: Element): Element {\n if (container !== undefined) return container;\n const existing = document.querySelector('.kerf-toasts');\n if (existing !== null) return existing;\n const region = document.createElement('div');\n region.className = 'kerf-toasts';\n region.setAttribute('aria-live', 'polite');\n document.body.appendChild(region);\n return region;\n}\n\n/**\n * Show a non-modal, auto-dismissing notification. Stacks in a shared body-level\n * region (or your `container`). Returns a {@link ToastHandle} (`{ el, dismiss }`)\n * so you can run entrance/exit transitions, wire an action button, or inspect the\n * node. `mode: 'replace'` collapses a rapid sequence to the latest; `variant`\n * adds an accent class; `enterClass`/`exitClass` let CSS own the animation.\n */\nexport function toast(content: ToastContent, options: ToastOptions = {}): ToastHandle {\n const {\n container,\n className = 'kerf-toast',\n duration = 4000,\n role = 'status',\n mode = 'stack',\n collapse = 'fade',\n variant,\n enterClass,\n exitClass,\n exitDuration = 0,\n } = options;\n\n const region = toastRegion(container) as ToastCarrier;\n const active = (region[TOAST_SET] ??= new Set<Dismisser>());\n // collapse-to-latest: 'instant' removes priors synchronously (no cross-fade in a\n // centered single slot); 'fade' runs their exit transition (nice for a stack).\n if (mode === 'replace') {\n for (const d of [...active]) {\n if (collapse === 'instant') d.removeNow();\n else d();\n }\n }\n\n const el = document.createElement('div');\n el.className = className;\n if (variant !== undefined) el.classList.add(`${className}--${variant}`);\n el.setAttribute('role', role);\n region.appendChild(el);\n\n const disposeMount = mount(el, typeof content === 'function' ? content : () => content);\n const state: {\n dismissed: boolean;\n timer: ReturnType<typeof setTimeout> | undefined;\n } = { dismissed: false, timer: undefined };\n\n if (enterClass !== undefined) {\n globalThis.requestAnimationFrame(() => {\n if (!state.dismissed) el.classList.add(enterClass);\n });\n }\n\n const remove = (): void => {\n disposeMount();\n el.remove();\n active.delete(dismiss as Dismisser);\n };\n\n const finish = (instant: boolean): void => {\n if (state.dismissed) return;\n state.dismissed = true;\n if (state.timer !== undefined) clearTimeout(state.timer);\n if (instant) {\n remove();\n return;\n }\n // Reverse the entrance (drop enterClass so exitClass needn't out-specify it),\n // add exitClass, and delay removal for the transition — including the\n // single-class idiom (enterClass removed + exitDuration, no exitClass).\n if (enterClass !== undefined) el.classList.remove(enterClass);\n if (exitClass !== undefined) el.classList.add(exitClass);\n if (exitClass !== undefined || exitDuration > 0) {\n setTimeout(remove, exitDuration);\n } else {\n remove();\n }\n };\n\n function dismiss(): void {\n finish(false);\n }\n (dismiss as Dismisser).removeNow = () => finish(true);\n\n active.add(dismiss as Dismisser);\n if (duration > 0) state.timer = setTimeout(dismiss, duration);\n return { el, dismiss };\n}\n"]} |
+1
-1
| { | ||
| "name": "kerfjs", | ||
| "version": "4.2.0-beta.3", | ||
| "version": "4.2.0-beta.4", | ||
| "description": "Tiny reactive UI framework — fine-grained signals + DOM morphing + JSX. Apply the smallest possible cut to update your DOM.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is too big to display
1062564
1.89%7430
2.2%