kerfjs 5.0.0-beta.9 → 5.0.0-beta.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +2586 -107
  2. package/README.md +96 -37
  3. package/ai/cursorrules +110 -61
  4. package/ai/manifest.json +15 -7
  5. package/ai/skill.md +117 -72
  6. package/dist/actions.d.ts +1 -1
  7. package/dist/actions.js +10 -6
  8. package/dist/actions.js.map +1 -1
  9. package/dist/array-signal.js +1 -1
  10. package/dist/async.js +22 -19
  11. package/dist/async.js.map +1 -1
  12. package/dist/attach.js +1 -66
  13. package/dist/attach.js.map +1 -1
  14. package/dist/{chunk-HW7KSM2Y.js → chunk-3C6F6NM4.js} +1 -1
  15. package/dist/{chunk-HW7KSM2Y.js.map → chunk-3C6F6NM4.js.map} +1 -1
  16. package/dist/{chunk-GY4XV2UV.js → chunk-4TX6YJDM.js} +10 -3
  17. package/dist/chunk-4TX6YJDM.js.map +1 -0
  18. package/dist/{chunk-QIP723L4.js → chunk-5WRGJZV6.js} +3 -2
  19. package/dist/chunk-5WRGJZV6.js.map +1 -0
  20. package/dist/{chunk-KZJXHFIB.js → chunk-BI4HGICV.js} +3 -3
  21. package/dist/chunk-BI4HGICV.js.map +1 -0
  22. package/dist/chunk-FM6N2B4N.js +70 -0
  23. package/dist/chunk-FM6N2B4N.js.map +1 -0
  24. package/dist/{chunk-LVH3GC6B.js → chunk-H7WTRM6F.js} +3 -3
  25. package/dist/chunk-H7WTRM6F.js.map +1 -0
  26. package/dist/{chunk-U6FK33SG.js → chunk-IHMDBBUJ.js} +2 -2
  27. package/dist/chunk-IHMDBBUJ.js.map +1 -0
  28. package/dist/chunk-JN7EJM3B.js +86 -0
  29. package/dist/chunk-JN7EJM3B.js.map +1 -0
  30. package/dist/{chunk-UZJ6I4T6.js → chunk-MK42GLPV.js} +1 -1
  31. package/dist/chunk-MK42GLPV.js.map +1 -0
  32. package/dist/{chunk-SVATPF5R.js → chunk-T33U7SRR.js} +261 -165
  33. package/dist/chunk-T33U7SRR.js.map +1 -0
  34. package/dist/{chunk-ZDCJZCNO.js → chunk-UGNPDLNI.js} +78 -45
  35. package/dist/chunk-UGNPDLNI.js.map +1 -0
  36. package/dist/{chunk-V46JKE44.js → chunk-WKIPLNVO.js} +1 -1
  37. package/dist/chunk-WKIPLNVO.js.map +1 -0
  38. package/dist/{delegate-CL9VTZFb.d.ts → delegate--CrfgYtC.d.ts} +0 -34
  39. package/dist/dev.d.ts +31 -13
  40. package/dist/dev.js +101 -35
  41. package/dist/dev.js.map +1 -1
  42. package/dist/html.js +5 -4
  43. package/dist/html.js.map +1 -1
  44. package/dist/index.d.ts +14 -9
  45. package/dist/index.js +29 -20
  46. package/dist/index.js.map +1 -1
  47. package/dist/jsx-runtime.d.ts +13 -5
  48. package/dist/jsx-runtime.js +1 -1
  49. package/dist/list.d.ts +7 -3
  50. package/dist/list.js +153 -46
  51. package/dist/list.js.map +1 -1
  52. package/dist/overlay.d.ts +128 -129
  53. package/dist/overlay.js +675 -366
  54. package/dist/overlay.js.map +1 -1
  55. package/dist/remount.js +2 -2
  56. package/dist/router.d.ts +13 -4
  57. package/dist/router.js +37 -10
  58. package/dist/router.js.map +1 -1
  59. package/dist/scope.d.ts +35 -8
  60. package/dist/scope.js +61 -30
  61. package/dist/scope.js.map +1 -1
  62. package/dist/testing.js +1 -1
  63. package/dist/timing.d.ts +3 -2
  64. package/dist/timing.js +1 -1
  65. package/dist/timing.js.map +1 -1
  66. package/llms.txt +13 -6
  67. package/package.json +33 -8
  68. package/setup/cli.mjs +90 -0
  69. package/setup/index.d.mts +37 -0
  70. package/setup/index.mjs +1347 -0
  71. package/setup/jsonc.mjs +201 -0
  72. package/setup/state.schema.json +56 -0
  73. package/dist/chunk-GY4XV2UV.js.map +0 -1
  74. package/dist/chunk-KPXIOG2C.js +0 -54
  75. package/dist/chunk-KPXIOG2C.js.map +0 -1
  76. package/dist/chunk-KZJXHFIB.js.map +0 -1
  77. package/dist/chunk-LVH3GC6B.js.map +0 -1
  78. package/dist/chunk-QIP723L4.js.map +0 -1
  79. package/dist/chunk-SVATPF5R.js.map +0 -1
  80. package/dist/chunk-U6FK33SG.js.map +0 -1
  81. package/dist/chunk-UZJ6I4T6.js.map +0 -1
  82. package/dist/chunk-V46JKE44.js.map +0 -1
  83. package/dist/chunk-ZDCJZCNO.js.map +0 -1
package/dist/async.js.map CHANGED
@@ -1 +1 @@
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 * 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 const fail = (error: unknown): undefined => {\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 let pending: Promise<T>;\n try {\n pending = fetcher(report);\n } catch (error: unknown) {\n return Promise.resolve(fail(error));\n }\n\n return pending.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 fail,\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"]}
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> = (\n report: (completed: number, total: number) => void,\n) => 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>(\n options: ResourceOptions<T, I> = {},\n): 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\n ? lastData !== next\n : !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\n I | undefined;\n const key =\n cacheKey !== undefined && maybeFetcher !== undefined\n ? cacheKey(input as I)\n : 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 =\n cacheKey !== undefined\n ? key !== undefined\n ? cache.get(key)\n : 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 const fail = (error: unknown): undefined => {\n if (gen === generation) {\n // Keep `data` (and thus `revision`) on failure — stale-while-error.\n state.value = {\n ...state.value,\n status: 'failed',\n error,\n progress: undefined,\n input,\n };\n }\n return undefined;\n };\n\n let pending: Promise<T>;\n try {\n pending = fetcher(report);\n } catch (error: unknown) {\n return Promise.resolve(fail(error));\n }\n\n return pending.then((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 }, fail);\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"]}
package/dist/attach.js CHANGED
@@ -1,68 +1,3 @@
1
- // src/attach.ts
2
- function attach(node, setup) {
3
- const teardown = setup(node);
4
- let done = false;
5
- let hasConnected = node.isConnected;
6
- let observedRoot;
7
- let connectionFrame = 0;
8
- const finish = () => {
9
- if (done) return;
10
- done = true;
11
- globalThis.cancelAnimationFrame(connectionFrame);
12
- observer.disconnect();
13
- if (typeof teardown === "function") teardown();
14
- };
15
- const addedWithin = (candidate) => {
16
- let current = node;
17
- while (current !== null) {
18
- if (current === candidate) return true;
19
- const root = current.getRootNode();
20
- current = current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);
21
- }
22
- return false;
23
- };
24
- const observer = new MutationObserver((records) => {
25
- if (done) return;
26
- if (!hasConnected) {
27
- if (!node.isConnected) {
28
- const connectedAndRemovedInBatch = records.some((record) => [...record.addedNodes].some(addedWithin));
29
- if (!connectedAndRemovedInBatch) return;
30
- hasConnected = true;
31
- finish();
32
- return;
33
- }
34
- hasConnected = true;
35
- globalThis.cancelAnimationFrame(connectionFrame);
36
- } else if (!node.isConnected) {
37
- finish();
38
- return;
39
- }
40
- const root = node.getRootNode();
41
- if (root !== observedRoot) observe(root);
42
- });
43
- const observe = (root) => {
44
- observer.disconnect();
45
- observedRoot = root;
46
- const options = { childList: true, subtree: true };
47
- observer.observe(node.ownerDocument, options);
48
- if (root !== node.ownerDocument) observer.observe(root, options);
49
- };
50
- const watchForConnection = () => {
51
- connectionFrame = globalThis.requestAnimationFrame(() => {
52
- if (done || hasConnected) return;
53
- if (node.isConnected) {
54
- hasConnected = true;
55
- observe(node.getRootNode());
56
- return;
57
- }
58
- watchForConnection();
59
- });
60
- };
61
- observe(hasConnected ? node.getRootNode() : node.ownerDocument);
62
- if (!hasConnected) watchForConnection();
63
- return finish;
64
- }
65
-
66
- export { attach };
1
+ export { attach } from './chunk-FM6N2B4N.js';
67
2
 
68
3
  //# sourceMappingURL=attach.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/attach.ts"],"names":[],"mappings":"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","file":"attach.js","sourcesContent":["/**\n * `kerfjs/attach` — bind a non-kerf widget's lifecycle to a single DOM node.\n *\n * `data-morph-skip` lets a library own a subtree so kerf won't touch it — but\n * nothing manages that widget's LIFECYCLE. You set it up imperatively after\n * render and must remember to tear it down when the node is replaced/removed\n * (dropping document-level listeners the widget added, etc.). `attach` closes\n * that seam: run a setup against one **existing** DOM node and auto-run its\n * teardown when that node leaves the document.\n *\n * import { attach } from 'kerfjs/attach';\n *\n * attach(canvasEl, (el) => {\n * const chart = D3.mount(el);\n * return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)\n * });\n *\n * `setup(node)` runs immediately — the node already exists, so there is nothing\n * to wait for (this is NOT React's `useEffect`: no dependency array, no re-run,\n * no render-phase or hook-order scoping; it is closer to a Web Component's\n * `connectedCallback`/`disconnectedCallback` pair, Svelte's\n * `onMount(() => () => cleanup)`, or Solid's `onCleanup`). A node may start\n * disconnected: setup still runs now, and teardown waits until it has first\n * connected and subsequently leaves the document. The returned teardown runs\n * once — whichever comes first — on that removal (detected by a\n * `MutationObserver`, so a morph swap, a `remountOn` replacement, or any removal\n * triggers it) or when the returned disposer is called. Re-creation is NOT\n * handled here: a fresh node is a fresh `attach()` call — pair it with\n * `kerfjs/remount`, which replaces the node and re-runs your render (and thus\n * this call) on the new one.\n *\n * Related: `kerfjs/scope`'s `observeRemovals` also auto-disposes on removal via a\n * `MutationObserver`, but scoped to a whole subtree's registered disposers rather\n * than one node's setup/teardown pair — reach for that when you're collecting\n * many disposers under an element, and for `attach` when you're binding one\n * widget's lifecycle to one node.\n */\n\n/** The setup callback for {@link attach}: run against `node`, optionally return a teardown. */\nexport type AttachSetup = (node: Element) => (() => void) | void;\n\n/**\n * Run `setup(node)` now, and its returned teardown once — after `node` has been\n * connected and then leaves the document, or when the returned disposer is\n * called, whichever is first. Returns an idempotent disposer so a `mount()` /\n * `Scope` can drive teardown explicitly.\n */\nexport function attach(node: Element, setup: AttachSetup): () => void {\n const teardown = setup(node);\n let done = false;\n let hasConnected = node.isConnected;\n let observedRoot: Node | undefined;\n let connectionFrame = 0;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n observer.disconnect();\n if (typeof teardown === 'function') teardown();\n };\n\n const addedWithin = (candidate: Node): boolean => {\n let current: Node | null = node;\n while (current !== null) {\n if (current === candidate) return true;\n const root = current.getRootNode();\n current = current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);\n }\n return false;\n };\n\n const observer = new MutationObserver((records) => {\n if (done) return;\n\n if (!hasConnected) {\n // A node may be prepared before insertion. Its own detached root cannot\n // observe becoming someone else's child, so wait on ownerDocument until\n // it becomes connected; only a later disconnection is a teardown event.\n if (!node.isConnected) {\n const connectedAndRemovedInBatch = records.some((record) =>\n [...record.addedNodes].some(addedWithin));\n if (!connectedAndRemovedInBatch) return;\n hasConnected = true;\n finish();\n return;\n }\n hasConnected = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n } else if (!node.isConnected) {\n finish();\n return;\n }\n\n // A connected node can move between documents or shadow roots without\n // ending its lifecycle. Follow its live root so a later removal remains\n // observable after that move.\n const root = node.getRootNode();\n if (root !== observedRoot) observe(root);\n });\n\n const observe = (root: Node): void => {\n observer.disconnect();\n observedRoot = root;\n const options = { childList: true, subtree: true } as const;\n observer.observe(node.ownerDocument, options);\n // Document observation sees a shadow host leave the document, while shadow\n // observation sees the node (or one of its in-shadow ancestors) removed.\n // Both are required to cover the node's composed lifecycle.\n if (root !== node.ownerDocument) observer.observe(root, options);\n };\n\n const watchForConnection = (): void => {\n connectionFrame = globalThis.requestAnimationFrame(() => {\n if (done || hasConnected) return;\n if (node.isConnected) {\n hasConnected = true;\n observe(node.getRootNode());\n return;\n }\n watchForConnection();\n });\n };\n\n // Connected nodes are watched from their live document/shadow root. For a\n // disconnected node, watch ownerDocument plus animation frames: the document\n // catches ordinary insertion, while the frame check also catches direct\n // insertion across a shadow boundary that document observation cannot see.\n observe(hasConnected ? node.getRootNode() : node.ownerDocument);\n if (!hasConnected) watchForConnection();\n\n return finish;\n}\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"attach.js"}
@@ -11,4 +11,4 @@ function clearDevHooks() {
11
11
 
12
12
  export { clearDevHooks, devHooks, installDevHooks };
13
13
 
14
- //# sourceMappingURL=chunk-HW7KSM2Y.js.map
14
+ //# sourceMappingURL=chunk-3C6F6NM4.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev-hooks.ts"],"names":[],"mappings":";AA8IO,IAAM,WAAqB;AAS3B,SAAS,gBAAgB,KAAA,EAAuB;AACrD,EAAA,MAAA,CAAO,MAAA,CAAO,UAAU,KAAK,CAAA;AAC/B;AAOO,SAAS,aAAA,GAAsB;AACpC,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AACvC,IAAA,OAAQ,SAAqC,GAAG,CAAA;AAAA,EAClD;AACF","file":"chunk-HW7KSM2Y.js","sourcesContent":["/**\n * The dev-hook registry — kerf's single seam between production code and the\n * opt-in development diagnostics.\n *\n * ## Why this exists\n *\n * kerf used to INFER whether it was running in development, by reading\n * `globalThis.process?.env?.NODE_ENV` through `utils/devMode.ts`. That\n * inference cannot be made correct, and it was wrong in the most common case:\n * bundlers substitute the BARE `process.env.NODE_ENV` token and never create a\n * `globalThis.process` object for browser targets, so the read returned\n * `undefined` and `undefined !== 'production'` resolved to DEVELOPMENT inside\n * production browser bundles.\n *\n * It also could not be fixed by rewriting the expression. Only the\n * *production* answer can be made static: `X && false` folds to a constant for\n * any side-effect-free `X`, while `X && true` does not. So any form that a\n * bundler can eliminate is also a form that treats \"no `process` binding\" as\n * production — which silently disables every warning in the no-build/CDN path\n * and in browser dev bundles.\n *\n * The fix is to stop guessing. Every environment already has a correct,\n * statically-foldable dev flag; what none of them offers is a way to hand that\n * flag to a *library*. So the consumer writes the conditional, in their own\n * code, with their own flag:\n *\n * ```js\n * if (import.meta.env.DEV) await import('kerfjs/dev'); // Vite\n * if (process.env.NODE_ENV !== 'production') await import('kerfjs/dev');\n * ```\n *\n * Because that condition folds to `false` in the consumer's production build,\n * the entire statement is eliminated and the dev chunk is never emitted or\n * fetched. Installation IS the development signal — there is nothing left to\n * detect, and no environment kerf can be wrong about.\n *\n * ## The contract\n *\n * Core modules never import a `dev-*` module. They read a nullable slot off\n * `devHooks` and call through it:\n *\n * ```ts\n * devHooks.listRebind?.(id, marker.parentElement as Element);\n * ```\n *\n * When nothing is installed every slot is `undefined`, so the cost is one\n * property read per call site and the `dev-*` modules are unreachable from the\n * main entry — which is what lets a bundler drop them. This is why the gate\n * lives at the CALL SITE rather than inside each warner: an unconditional call\n * into a self-gating warner keeps the module reachable no matter how the gate\n * is written, so no amount of dead-code elimination can reclaim it.\n *\n * Slots ending in `Enabled` are predicates rather than warnings. They exist for\n * the handful of call sites that must decide whether to do *expensive\n * preparatory work* — capturing the previous render's binding list, allocating\n * a per-render `Map` — before there is anything to warn about. Core must check\n * those before paying the cost, exactly as it checked the old `isOptedIn()`\n * exports.\n *\n * Each opt-in warner keeps its own internal switch check through `devFlag()`;\n * an `enableWarnings()` override wins over the matching `KERF_DEV_WARN_*`\n * environment variable. Installation decides whether the diagnostics are\n * *present*; the opt-in warner decides whether it is *switched on*. Always-on\n * hooks skip that second gate.\n *\n * @see docs/11-dev-warnings.md\n */\n\nimport type { Binding } from './bindings.js';\nimport type { ListBinding } from './list-binding.js';\nimport type { Signal } from './reactive.js';\n\n/** Per-mount / per-store one-shot dedup flag, owned by the caller in core. */\nexport interface WarnOnceContext {\n warned: boolean;\n}\n\nexport interface DevHooks {\n // --- reactive.ts -------------------------------------------------------\n /**\n * Replaces `signal()`'s constructor so writes to never-subscribed signals can\n * warn. Resolved at signal-CREATION time, so signals created before the dev\n * entry is installed stay plain — see `signalsCreatedBeforeInstall`.\n */\n signalFactory?: <T>(value: T) => Signal<T>;\n /**\n * Wraps an `effect()` body so `delegate()` can detect that it is running\n * inside one. Returns the body to actually run.\n */\n wrapEffect?: (fn: () => void | (() => void)) => () => void | (() => void);\n\n // --- delegate.ts -------------------------------------------------------\n delegateInEffect?: (fn: 'delegate' | 'delegateCapture') => void;\n\n // --- store.ts ----------------------------------------------------------\n narrowSet?: (prev: unknown, next: unknown, ctx: WarnOnceContext) => void;\n /** Deep read-only proxy for the `get()` snapshot, so stray writes throw. */\n storeReadonly?: <T extends object>(state: T) => T;\n /** Unwraps a proxy handed back through `set({ ...get() })`. */\n storeToRaw?: <T>(next: T) => T;\n\n // --- mount.ts ----------------------------------------------------------\n listenerRebuild?: (rootEl: Element) => MutationObserver | null;\n listIdShift?: (id: string) => void;\n parserRepair?: (html: string) => void;\n staleBindingEnabled?: () => boolean;\n staleBinding?: (prevWired: readonly Binding[], current: readonly Binding[]) => void;\n listInvariantsEnabled?: () => boolean;\n listInvariants?: (\n rootEl: Element,\n bindings: ReadonlyMap<string, ListBinding>,\n expectedCounts?: ReadonlyMap<string, number>,\n ) => void;\n valueOnlyRerender?: (prevHtml: string, nextHtml: string, ctx: WarnOnceContext) => void;\n listRebind?: (id: string, liveParent: Element) => void;\n eachInMorphSkip?: (id: string, liveParent: Element, rootEl: Element) => void;\n missingRowKey?: (\n rowEl: Element,\n rowHtml: string,\n binding: { warnedMissingKey?: boolean },\n ) => void;\n\n // --- each.ts -----------------------------------------------------------\n staleIndexEnabled?: () => boolean;\n staleIndex?: (id: string) => void;\n duplicateCacheKeys?: (id: string, segItems: readonly { cacheKey: unknown }[]) => void;\n\n // --- utils/url-screen.ts -----------------------------------------------\n /**\n * When installed, a screened URL throws instead of warning-and-dropping.\n * A slot rather than a boolean so the check stays uniform with the rest.\n */\n urlScreenThrow?: (message: string) => never;\n}\n\n/**\n * The live slot table. Mutable by design — this is the fourth sanctioned\n * module-level mutable location (Design rule 5), and like `store.ts:REGISTRY`\n * it depends on there being exactly ONE copy at runtime. `tsup`'s\n * `splitting: true` guarantees that: shared modules are promoted into a single\n * chunk that both the main entry and the `kerfjs/dev` entry import.\n */\nexport const devHooks: DevHooks = {};\n\n/**\n * Install (or extend) the dev hooks. Called by the `kerfjs/dev` entry; not part\n * of the public API surface.\n *\n * Merges rather than replaces, so a consumer can install the standard bundle\n * and then override a single slot in a test.\n */\nexport function installDevHooks(hooks: DevHooks): void {\n Object.assign(devHooks, hooks);\n}\n\n/**\n * Remove every installed hook. Exists for test isolation — a suite that asserts\n * the not-installed (production-shaped) path needs to get back to a clean slate\n * without reloading modules.\n */\nexport function clearDevHooks(): void {\n for (const key of Object.keys(devHooks)) {\n delete (devHooks as Record<string, unknown>)[key];\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/dev-hooks.ts"],"names":[],"mappings":";AAoKO,IAAM,WAAqB;AAS3B,SAAS,gBAAgB,KAAA,EAAuB;AACrD,EAAA,MAAA,CAAO,MAAA,CAAO,UAAU,KAAK,CAAA;AAC/B;AAOO,SAAS,aAAA,GAAsB;AACpC,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AACvC,IAAA,OAAQ,SAAqC,GAAG,CAAA;AAAA,EAClD;AACF","file":"chunk-3C6F6NM4.js","sourcesContent":["/**\n * The dev-hook registry — kerf's single seam between production code and the\n * opt-in development diagnostics.\n *\n * ## Why this exists\n *\n * kerf used to INFER whether it was running in development, by reading\n * `globalThis.process?.env?.NODE_ENV` through `utils/devMode.ts`. That\n * inference cannot be made correct, and it was wrong in the most common case:\n * bundlers substitute the BARE `process.env.NODE_ENV` token and never create a\n * `globalThis.process` object for browser targets, so the read returned\n * `undefined` and `undefined !== 'production'` resolved to DEVELOPMENT inside\n * production browser bundles.\n *\n * It also could not be fixed by rewriting the expression. Only the\n * *production* answer can be made static: `X && false` folds to a constant for\n * any side-effect-free `X`, while `X && true` does not. So any form that a\n * bundler can eliminate is also a form that treats \"no `process` binding\" as\n * production — which silently disables every warning in the no-build/CDN path\n * and in browser dev bundles.\n *\n * The fix is to stop guessing. Every environment already has a correct,\n * statically-foldable dev flag; what none of them offers is a way to hand that\n * flag to a *library*. So the consumer writes the conditional, in their own\n * code, with their own flag:\n *\n * ```js\n * if (import.meta.env.DEV) await import('kerfjs/dev'); // Vite\n * if (process.env.NODE_ENV !== 'production') await import('kerfjs/dev');\n * ```\n *\n * Because that condition folds to `false` in the consumer's production build,\n * the entire statement is eliminated and the dev chunk is never emitted or\n * fetched. Installation IS the development signal — there is nothing left to\n * detect, and no environment kerf can be wrong about.\n *\n * ## The contract\n *\n * Core modules never import a `dev-*` module. They read a nullable slot off\n * `devHooks` and call through it:\n *\n * ```ts\n * devHooks.listRebind?.(id, marker.parentElement as Element);\n * ```\n *\n * When nothing is installed every slot is `undefined`, so the cost is one\n * property read per call site and the `dev-*` modules are unreachable from the\n * main entry — which is what lets a bundler drop them. This is why the gate\n * lives at the CALL SITE rather than inside each warner: an unconditional call\n * into a self-gating warner keeps the module reachable no matter how the gate\n * is written, so no amount of dead-code elimination can reclaim it.\n *\n * Slots ending in `Enabled` are predicates rather than warnings. They exist for\n * the handful of call sites that must decide whether to do *expensive\n * preparatory work* — capturing the previous render's binding list, allocating\n * a per-render `Map` — before there is anything to warn about. Core must check\n * those before paying the cost, exactly as it checked the old `isOptedIn()`\n * exports.\n *\n * Each opt-in warner keeps its own internal switch check through `devFlag()`;\n * an `enableWarnings()` override wins over the matching `KERF_DEV_WARN_*`\n * environment variable. Installation decides whether the diagnostics are\n * *present*; the opt-in warner decides whether it is *switched on*. Always-on\n * hooks skip that second gate.\n *\n * @see docs/11-dev-warnings.md\n */\n\nimport type { Binding } from './bindings.js';\nimport type { ListBinding } from './list-binding.js';\nimport type { Signal } from './reactive.js';\n\n/** Per-mount / per-store one-shot dedup flag, owned by the caller in core. */\nexport interface WarnOnceContext {\n warned: boolean;\n}\n\nexport interface DevHooks {\n // --- reactive.ts -------------------------------------------------------\n /**\n * Replaces `signal()`'s constructor so writes to never-subscribed signals can\n * warn. Resolved at signal-CREATION time, so signals created before the dev\n * entry is installed stay plain — see `signalsCreatedBeforeInstall`.\n */\n signalFactory?: <T>(value: T) => Signal<T>;\n /**\n * Wraps an `effect()` body so `delegate()` can detect that it is running\n * inside one. Returns the body to actually run. Resolved at effect-CREATION\n * time (the delegate-in-effect switch too), so an effect created before the\n * install or the opt-in is never wrapped.\n */\n wrapEffect?: (fn: () => void | (() => void)) => () => void | (() => void);\n\n // --- delegate.ts -------------------------------------------------------\n delegateInEffect?: (fn: 'delegate' | 'delegateCapture') => void;\n\n // --- store.ts ----------------------------------------------------------\n narrowSet?: (prev: unknown, next: unknown, ctx: WarnOnceContext) => void;\n /** Deep read-only proxy for the `get()` snapshot, so stray writes throw. */\n storeReadonly?: <T extends object>(state: T) => T;\n /** Unwraps a proxy handed back through `set({ ...get() })`. */\n storeToRaw?: <T>(next: T) => T;\n\n // --- mount.ts ----------------------------------------------------------\n /**\n * Attaches the rebuilt-listeners observer to a mount root. Called once per\n * `mount()`, at creation — and it returns `null` when the warning is switched\n * off then — so a mount created before the install or the opt-in is never\n * observed.\n */\n listenerRebuild?: (rootEl: Element) => MutationObserver | null;\n listIdShift?: (id: string) => void;\n parserRepair?: (html: string) => void;\n staleBindingEnabled?: () => boolean;\n staleBinding?: (\n prevWired: readonly Binding[],\n current: readonly Binding[],\n ) => void;\n listInvariantsEnabled?: () => boolean;\n listInvariants?: (\n rootEl: Element,\n bindings: ReadonlyMap<string, ListBinding>,\n expectedCounts?: ReadonlyMap<string, number>,\n ) => void;\n valueOnlyRerender?: (\n prevHtml: string,\n nextHtml: string,\n ctx: WarnOnceContext,\n ) => void;\n listRebind?: (id: string, liveParent: Element) => void;\n eachInMorphSkip?: (id: string, liveParent: Element, rootEl: Element) => void;\n missingRowKey?: (\n rowEl: Element,\n rowHtml: string,\n binding: { warnedMissingKey?: boolean },\n ) => void;\n\n // --- each.ts -----------------------------------------------------------\n staleIndexEnabled?: () => boolean;\n staleIndex?: (id: string) => void;\n duplicateCacheKeys?: (\n id: string,\n segItems: readonly { cacheKey: unknown }[],\n ) => void;\n\n // --- overlay-core.ts ---------------------------------------------------\n /** A surface opened over a modal `<dialog>` could not be made fully usable. */\n overlayBlockedByModal?: (reason: 'hidden' | 'inert') => void;\n\n // --- utils/url-screen.ts -----------------------------------------------\n /**\n * When installed, a screened URL throws instead of warning-and-dropping.\n * A slot rather than a boolean so the check stays uniform with the rest.\n */\n urlScreenThrow?: (message: string) => never;\n}\n\n/**\n * The live slot table. Mutable by design — this is the fourth sanctioned\n * module-level mutable location (Design rule 5), and like `store.ts:REGISTRY`\n * it depends on there being exactly ONE copy at runtime. `tsup`'s\n * `splitting: true` guarantees that: shared modules are promoted into a single\n * chunk that both the main entry and the `kerfjs/dev` entry import.\n */\nexport const devHooks: DevHooks = {};\n\n/**\n * Install (or extend) the dev hooks. Called by the `kerfjs/dev` entry; not part\n * of the public API surface.\n *\n * Merges rather than replaces, so a consumer can install the standard bundle\n * and then override a single slot in a test.\n */\nexport function installDevHooks(hooks: DevHooks): void {\n Object.assign(devHooks, hooks);\n}\n\n/**\n * Remove every installed hook. Exists for test isolation — a suite that asserts\n * the not-installed (production-shaped) path needs to get back to a clean slate\n * without reloading modules.\n */\nexport function clearDevHooks(): void {\n for (const key of Object.keys(devHooks)) {\n delete (devHooks as Record<string, unknown>)[key];\n }\n}\n"]}
@@ -10,7 +10,8 @@ function flatten(segment, withMarkers) {
10
10
  }
11
11
  function flattenWithoutListItems(segment) {
12
12
  if (segment.kind === "static") return segment.html;
13
- if (segment.kind === "list") return `<!--${LIST_MARKER_PREFIX}${segment.id}-->`;
13
+ if (segment.kind === "list")
14
+ return `<!--${LIST_MARKER_PREFIX}${segment.id}-->`;
14
15
  return segment.parts.map(flattenWithoutListItems).join("");
15
16
  }
16
17
  function collectLists(segment, out = /* @__PURE__ */ new Map()) {
@@ -68,6 +69,12 @@ function wrapWithTags(child, openTag, closeTag) {
68
69
  };
69
70
  }
70
71
 
71
- export { LIST_MARKER_PREFIX, collectLists, flatten, flattenWithoutListItems, mergeChildSegments, wrapWithTags };
72
+ // src/utils/overlay-host.ts
73
+ var OVERLAY_HOST_SELECTOR = "[data-kerf-overlay-host][data-morph-skip]";
74
+ function isOverlayHost(el) {
75
+ return el.matches(OVERLAY_HOST_SELECTOR);
76
+ }
77
+
78
+ export { LIST_MARKER_PREFIX, OVERLAY_HOST_SELECTOR, collectLists, flatten, flattenWithoutListItems, isOverlayHost, mergeChildSegments, wrapWithTags };
72
79
 
73
- //# sourceMappingURL=chunk-GY4XV2UV.js.map
80
+ //# sourceMappingURL=chunk-4TX6YJDM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/segment.ts","../src/utils/overlay-host.ts"],"names":[],"mappings":"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","file":"chunk-4TX6YJDM.js","sourcesContent":["/**\n * `Segment` — kerf's structured render output.\n *\n * The JSX runtime emits a `SafeHtml` wrapping a `Segment`. Most renders\n * produce a single static segment (just an HTML string), which behaves\n * exactly like a string for backward compatibility. When the tree\n * contains a list (`each()`) or a parent whose children include a list,\n * the runtime emits a structured segment that `mount()` can dispatch\n * on — running its native keyed reconciler for the list parts and\n * leaving the static surrounds to the general-purpose diff.\n *\n * Why have a structured form at all: the perf bottleneck for huge\n * keyed lists isn't the per-row JSX work (which `each()` already\n * memoizes). It's that flattening every render's whole tree to one\n * big HTML string forces a full `innerHTML` parse and a tree walk\n * over rows we know are unchanged. The segment shape lets mount()\n * skip both for the list parts.\n */\n\nimport type { Binding } from './bindings.js';\n\nexport type Segment = StaticSegment | ListSegment | MixedSegment;\n\nexport interface StaticSegment {\n kind: 'static';\n html: string;\n}\n\nexport interface ListItem {\n /**\n * The row's object identity. Used by the reconciler to match new items\n * against live DOM nodes across renders. Unchanged ref → reuse the\n * existing live node; replaced ref → build a fresh node.\n */\n ref: object;\n /**\n * KF-294: the row's fine-grained binding specs (signals in row attrs/text).\n * Undefined for granular-path rows (which snapshot in this spike). The\n * snapshot reconciler wires these to the fresh row node and disposes them\n * when the row is removed.\n */\n bindings?: Binding[];\n /**\n * Optional cache-invalidation key that captures external state affecting\n * this row's render (e.g. selection class). Different cacheKey on the\n * same `ref` triggers a cache miss for that row. `undefined` when the\n * user didn't pass a `key` callback to `each()`.\n */\n cacheKey: unknown;\n html: string;\n}\n\nexport interface ListSegment {\n kind: 'list';\n id: string;\n items: ListItem[];\n /**\n * Optional granular patches (KF-92). When present, the list reconciler\n * applies these directly to the existing binding instead of doing a\n * full classify+reconcile pass. Emitted by `each()` when bound to an\n * `arraySignal`. Mutually exclusive with the `items` snapshot in the\n * sense that the snapshot is treated as informational/fall-back when\n * patches are present.\n */\n patches?: ArrayPatchInternal[];\n /**\n * KF-388: the identity of the data this list renders — the `arraySignal`\n * instance, or `undefined` for a plain array.\n *\n * A list's `id` is its call-order index, so a render that changes how many\n * `each()` calls precede this one hands this segment a DIFFERENT list's\n * binding. Patches are only meaningful against the binding they were queued\n * for, so the reconciler compares this against the binding's recorded source\n * before trusting the patch queue. It is an identity check, not a value\n * check — the instance is never read.\n */\n source?: object;\n}\n\n/**\n * Internal patch shape used inside list segments. Mirrors `ArrayPatch<T>`\n * from `array-signal.ts` but typed against `object` so the segment layer\n * doesn't need to be generic. `update` / `insert` patches carry the row's\n * pre-rendered HTML — `each()` renders them at JSX-evaluation time inside a\n * try/catch so a throwing render falls back to the snapshot path (KF-99)\n * instead of leaving the signal and DOM divergent.\n */\nexport type ArrayPatchInternal =\n | {\n type: 'update';\n index: number;\n item: object;\n html: string;\n bindings?: Binding[];\n }\n | {\n type: 'insert';\n index: number;\n item: object;\n html: string;\n bindings?: Binding[];\n }\n | { type: 'remove'; index: number }\n | { type: 'move'; from: number; to: number }\n | { type: 'replace'; items: readonly object[] };\n\n/**\n * Narrowed patch aliases. Array indexing loses the union discriminant, so\n * the granular reconciler casts through these instead of restating the full\n * object type at every site — a field rename is then a one-place edit.\n */\nexport type UpdatePatch = Extract<ArrayPatchInternal, { type: 'update' }>;\nexport type InsertPatch = Extract<ArrayPatchInternal, { type: 'insert' }>;\n\nexport interface MixedSegment {\n kind: 'mixed';\n parts: Segment[];\n}\n\n/**\n * Flatten a segment to a complete HTML string. Used for first render\n * (bulk innerHTML), for SSR-style consumption via `toString()`, and\n * for diagnostics.\n *\n * If `withMarkers` is set, list segments are wrapped in\n * `<!--kf-list:{id}-->` comments so the post-parse walk can find each\n * list's live parent. Plain (non-marker) flatten is what JSX consumers\n * see when they call `.toString()` on the SafeHtml.\n */\n/**\n * Comment-marker prefix emitted before each list (`<!--kf-list:{id}-->`).\n * `mount()` consumes it when binding lists from the live DOM — the emitter\n * (here) and the consumer must agree byte-for-byte, so both import this one\n * constant. Part of the reserved marker namespace (see `bindings.ts`).\n */\nexport const LIST_MARKER_PREFIX = 'kf-list:';\n\nexport function flatten(segment: Segment, withMarkers: boolean): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') {\n const items = segment.items.map((i) => i.html).join('');\n return withMarkers\n ? `<!--${LIST_MARKER_PREFIX}${segment.id}-->${items}`\n : items;\n }\n return segment.parts.map((p) => flatten(p, withMarkers)).join('');\n}\n\n/**\n * Variant of `flatten` for the static-only diff path on subsequent\n * renders. Lists are reduced to a single marker comment with no items\n * inside — the actual list children stay in the live DOM and are\n * reconciled separately. Keeping list items out of this string is\n * what makes the morph cheap on huge lists where most rows are\n * unchanged.\n */\nexport function flattenWithoutListItems(segment: Segment): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list')\n return `<!--${LIST_MARKER_PREFIX}${segment.id}-->`;\n return segment.parts.map(flattenWithoutListItems).join('');\n}\n\n/** Collect every `ListSegment` in the tree, keyed by its id. */\nexport function collectLists(\n segment: Segment,\n out: Map<string, ListSegment> = new Map(),\n): Map<string, ListSegment> {\n if (segment.kind === 'list') out.set(segment.id, segment);\n else if (segment.kind === 'mixed') {\n for (const part of segment.parts) collectLists(part, out);\n }\n return out;\n}\n\n/**\n * Combine a list of child segments into the smallest equivalent\n * representation: collapses adjacent statics into one static, returns\n * a single static if everything is static, otherwise a mixed segment\n * with statics coalesced.\n */\nexport function mergeChildSegments(parts: Segment[]): Segment {\n if (parts.length === 0) return { kind: 'static', html: '' };\n if (parts.every((p) => p.kind === 'static')) {\n return {\n kind: 'static',\n html: parts.map((p) => (p as StaticSegment).html).join(''),\n };\n }\n const merged: Segment[] = [];\n let coalesced = '';\n for (const p of parts) {\n if (p.kind === 'static') {\n coalesced += p.html;\n } else {\n if (coalesced !== '') {\n merged.push({ kind: 'static', html: coalesced });\n coalesced = '';\n }\n merged.push(p);\n }\n }\n if (coalesced !== '') merged.push({ kind: 'static', html: coalesced });\n return { kind: 'mixed', parts: merged };\n}\n\n/**\n * Wrap a child segment with surrounding open/close tags from the\n * parent JSX element. Used by the JSX runtime when constructing\n * `_jsx(tag, ...)` output.\n */\nexport function wrapWithTags(\n child: Segment,\n openTag: string,\n closeTag: string,\n): Segment {\n if (child.kind === 'static') {\n return { kind: 'static', html: openTag + child.html + closeTag };\n }\n if (child.kind === 'mixed') {\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n ...child.parts,\n { kind: 'static', html: closeTag },\n ],\n };\n }\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n child,\n { kind: 'static', html: closeTag },\n ],\n };\n}\n","/**\n * The overlay host slot — the one sanctioned nested-mount boundary\n * (KF-FBHQEP: a popover opened over a modal `<dialog>` must render INSIDE the\n * dialog to be interactive, because every engine inerts content outside the\n * modal, yet the dialog is usually already a `mount()` root).\n *\n * An element carrying both `data-kerf-overlay-host` and `data-morph-skip` is a\n * boundary between two mounts: `kerfjs/overlay` mounts its surfaces inside it,\n * and the enclosing mount leaves it alone — its morph skips the subtree\n * (`data-morph-skip`), its nested-mount check stops there, and its binding and\n * list-marker scans never descend into it, so the inner mounts' markers can\n * never be mistaken for the outer mount's own. The opt-in rebuilt-listeners\n * dev observer stops there too: a removal inside the slot is the inner\n * mount's to report.\n */\nexport const OVERLAY_HOST_SELECTOR =\n '[data-kerf-overlay-host][data-morph-skip]';\n\n/** True when `el` is an overlay host slot (see the module comment). */\nexport function isOverlayHost(el: Element): boolean {\n return el.matches(OVERLAY_HOST_SELECTOR);\n}\n"]}
@@ -2,7 +2,8 @@
2
2
  var versions = /* @__PURE__ */ new WeakMap();
3
3
  var anyVersioned = false;
4
4
  function bumpItemVersion(item) {
5
- if (item === null || typeof item !== "object" && typeof item !== "function") return;
5
+ if (item === null || typeof item !== "object" && typeof item !== "function")
6
+ return;
6
7
  anyVersioned = true;
7
8
  versions.set(item, (versions.get(item) ?? 0) + 1);
8
9
  }
@@ -12,4 +13,4 @@ function itemVersion(item) {
12
13
 
13
14
  export { bumpItemVersion, itemVersion };
14
15
 
15
- //# sourceMappingURL=chunk-QIP723L4.js.map
16
+ //# sourceMappingURL=chunk-5WRGJZV6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/item-version.ts"],"names":[],"mappings":";AA8BA,IAAM,QAAA,uBAAe,OAAA,EAAwB;AAC7C,IAAI,YAAA,GAAe,KAAA;AAWZ,SAAS,gBAAgB,IAAA,EAAqB;AACnD,EAAA,IAAI,SAAS,IAAA,IAAS,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,KAAS,UAAA;AAChE,IAAA;AACF,EAAA,YAAA,GAAe,IAAA;AACf,EAAA,QAAA,CAAS,IAAI,IAAA,EAAA,CAAO,QAAA,CAAS,IAAI,IAAI,CAAA,IAAK,KAAK,CAAC,CAAA;AAClD;AAGO,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,OAAO,YAAA,GAAgB,QAAA,CAAS,GAAA,CAAI,IAAI,KAAK,CAAA,GAAK,CAAA;AACpD","file":"chunk-5WRGJZV6.js","sourcesContent":["/**\n * Per-item content version — makes a same-ref `arraySignal.update()` visible to\n * the row memo (KF-418).\n *\n * kerf memoizes `each()` rows by object IDENTITY (`WeakMap<item, CacheEntry>`),\n * so a same-ref mutation — `update(i, r => { r.x = 1; return r })` — changes the\n * row's content without changing its identity and is invisible to the memo. The\n * KF-414 fix repaired only the single list that drained the patch; every other\n * consumer (a second list over the same signal, a second `mount()`, a\n * plain-array `filter()` view of the same items) still trusted its own memo\n * entry, keyed on an identity that didn't change, and rendered stale forever.\n *\n * `arraySignal.update()` bumps the returned item's version here; each row's\n * `CacheEntry` records the version it rendered at, and a cache hit now requires\n * the version to still match. A same-ref update therefore re-renders the row in\n * EVERY consumer, because they all read the same version off the same item ref.\n *\n * Why a shared module rather than a field on `ArraySignal`:\n * - the main-bundle `each()` must read the version, and importing the\n * `kerfjs/array-signal` subpath into the main bundle would defeat KF-95\n * (arraySignal only ships when the consumer imports it). This module lives in\n * the main bundle; the subpath imports it, not the other way round.\n * - keying on the item object (not the signal) is what lets a plain-array\n * `filter()` view — which has no reference to the arraySignal — still see the\n * bump: it holds the same item refs.\n *\n * `anyVersioned` keeps the common case free: until some `update()` actually\n * bumps a version, every lookup returns 0 without touching the WeakMap, so a app\n * that never mutates in place pays nothing per row.\n */\nconst versions = new WeakMap<object, number>();\nlet anyVersioned = false;\n\n/**\n * Bump `item`'s content version. Called by `arraySignal.update()` on the item it\n * returns. A no-op for any value that isn't a valid WeakMap key — a primitive\n * item (an `arraySignal<number>` used purely as a standalone signal) can never be\n * an `each()` row, since rows are memoized by object identity, so it needs no\n * content version. Guarding here keeps `update()` from throwing `Invalid value\n * used as weak map key` and leaves `anyVersioned` false until a real object is\n * versioned (so the primitive path stays a zero-cost lookup).\n */\nexport function bumpItemVersion(item: unknown): void {\n if (item === null || (typeof item !== 'object' && typeof item !== 'function'))\n return;\n anyVersioned = true;\n versions.set(item, (versions.get(item) ?? 0) + 1);\n}\n\n/** `item`'s current content version — 0 if it has never been same-ref-updated. */\nexport function itemVersion(item: object): number {\n return anyVersioned ? (versions.get(item) ?? 0) : 0;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { signal } from './chunk-U6FK33SG.js';
2
- import { devHooks } from './chunk-HW7KSM2Y.js';
1
+ import { signal } from './chunk-IHMDBBUJ.js';
2
+ import { devHooks } from './chunk-3C6F6NM4.js';
3
3
 
4
4
  // src/store.ts
5
5
  var REGISTRY = [];
@@ -44,4 +44,4 @@ function clearStoreRegistry() {
44
44
 
45
45
  export { clearStoreRegistry, defineStore, resetAllStores };
46
46
 
47
- //# sourceMappingURL=chunk-KZJXHFIB.js.map
47
+ //# sourceMappingURL=chunk-BI4HGICV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts"],"names":[],"mappings":";;;;AAyCA,IAAM,WAAyC,EAAC;AAEzC,SAAS,YACd,IAAA,EACyB;AACzB,EAAA,MAAM,QAAA,GAA2B,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,CAAA;AAOtD,EAAA,IAAI,OAAA,GAAkC,IAAA;AAEtC,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAuB;AAMlC,IAAA,MAAM,QAAQ,QAAA,CAAS,UAAA;AACvB,IAAA,MAAM,GAAA,GAAM,KAAA,GAAQ,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAClC,IAAA,MAAM,YAAY,QAAA,CAAS,SAAA;AAC3B,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,OAAA,KAAY,EAAE,QAAQ,KAAA,EAAM;AAC5B,MAAA,SAAA,CAAU,QAAA,CAAS,KAAA,EAAO,GAAA,EAAK,OAAO,CAAA;AAAA,IACxC;AACA,IAAA,QAAA,CAAS,KAAA,GAAQ,GAAA;AAAA,EACnB,CAAA;AAOA,EAAA,MAAM,MAAM,MAAwB;AAClC,IAAA,MAAM,IAAI,QAAA,CAAS,KAAA;AACnB,IAAA,MAAM,WAAW,QAAA,CAAS,aAAA;AAC1B,IAAA,IAAI,QAAA,IAAY,CAAA,KAAM,IAAA,IAAQ,OAAO,MAAM,QAAA,EAAU;AACnD,MAAA,OAAO,SAAS,CAAoB,CAAA;AAAA,IACtC;AACA,IAAA,OAAO,CAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAA;AAErC,EAAA,MAAM,KAAA,GAAiC;AAAA,IACrC,KAAA,EAAO,QAAA;AAAA,IACP,OAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAK,OAAA,EAAQ;AAAA,IAChC;AAAA,GACF;AAEA,EAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,EAAA,OAAO,KAAA;AACT;AAOO,SAAS,cAAA,GAAuB;AACrC,EAAA,KAAA,MAAW,CAAA,IAAK,QAAA,EAAU,CAAA,CAAE,KAAA,EAAM;AACpC;AAMO,SAAS,kBAAA,GAA2B;AACzC,EAAA,QAAA,CAAS,MAAA,GAAS,CAAA;AACpB","file":"chunk-BI4HGICV.js","sourcesContent":["/**\n * `defineStore({ initial, actions })` — composable testable stores layered on\n * top of `reactive.ts`'s signals.\n *\n * Three rules:\n * 1. `state` is read-only. Consumers read via `state.value` or subscribe via\n * `effect()`. They cannot write directly.\n * 2. `actions` is the only mutation surface. All writes go through named\n * action functions. This is what makes stores testable — assert against\n * actions, not against arbitrary writes.\n * 3. `reset()` resets to `initial()`. Always defined; tests use it for\n * setup, lifecycle hooks (route change, sign-out, etc.) use it for\n * tear-down.\n *\n * A module-level registry tracks every store created via `defineStore()`;\n * `resetAllStores()` walks the registry and calls each `reset()`. Useful for\n * tests + project-switch / logout / route-reset scenarios where every piece\n * of client state should return to its initial shape.\n */\n\nimport { devHooks, type WarnOnceContext } from './dev-hooks.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport { signal } from './reactive.js';\n\nexport interface Store<TState, TActions> {\n /** Read-only reactive view. Consumers read `state.value` or subscribe via `effect()`. */\n readonly state: ReadonlySignal<TState>;\n /** Named mutators — the only way to change state. */\n readonly actions: TActions;\n /** Reset state to `initial()`. Used by tests and lifecycle hooks. */\n reset(): void;\n}\n\ninterface DefineStoreSpec<TState, TActions> {\n initial: () => TState;\n actions: (\n set: (next: TState) => void,\n get: () => Readonly<TState>,\n ) => TActions;\n}\n\nconst REGISTRY: Array<{ reset: () => void }> = [];\n\nexport function defineStore<TState, TActions>(\n spec: DefineStoreSpec<TState, TActions>,\n): Store<TState, TActions> {\n const internal: Signal<TState> = signal(spec.initial());\n // KF-212: per-store one-shot dedup for the opt-in narrow-set warning.\n // Default off; consumers opt in via `KERF_DEV_WARN_NARROW_SET=1` in dev.\n // Resolve the hook on each set(), so a store created before `kerfjs/dev` is\n // installed starts warning as soon as the diagnostics become available.\n // Allocate the context lazily — production never pays for an object it will\n // never read.\n let warnCtx: WarnOnceContext | null = null;\n\n const set = (next: TState): void => {\n // With `kerfjs/dev` installed, `next` may carry proxies handed back by the\n // `get()` trap (e.g. `set({ ...get(), count: 1 })`). Unwrap them so the\n // internal signal only ever holds a plain object — the narrow-set warning\n // and every consumer read see raw state, never a Proxy. Production stores\n // the bare reference.\n const toRaw = devHooks.storeToRaw;\n const raw = toRaw ? toRaw(next) : next;\n const narrowSet = devHooks.narrowSet;\n if (narrowSet) {\n warnCtx ??= { warned: false };\n narrowSet(internal.value, raw, warnCtx);\n }\n internal.value = raw;\n };\n // With the diagnostics installed, wrap the reference returned to actions in a\n // deep read-only Proxy so that `get().count = 42` / `get().nested.x = 1`\n // (documented Rule 8 violations) throw a `TypeError` instead of silently\n // landing on the underlying state without notifying subscribers. The live\n // state object is never frozen or mutated, so external references to it stay\n // writable. Production returns the bare reference — no proxy, no wrapping.\n const get = (): Readonly<TState> => {\n const v = internal.value;\n const readonly = devHooks.storeReadonly;\n if (readonly && v !== null && typeof v === 'object') {\n return readonly(v as TState & object);\n }\n return v;\n };\n\n const actions = spec.actions(set, get);\n\n const store: Store<TState, TActions> = {\n state: internal,\n actions,\n reset() {\n internal.value = spec.initial();\n },\n };\n\n REGISTRY.push(store);\n return store;\n}\n\n/**\n * Reset every store registered via `defineStore()` to its `initial()` value.\n * Used by tests and by application lifecycle hooks (project switch, logout,\n * route reset).\n */\nexport function resetAllStores(): void {\n for (const s of REGISTRY) s.reset();\n}\n\n/**\n * Test helper — clears the registry. Exposed via the `kerfjs/testing` subpath,\n * not the main `kerfjs` entry. Unit tests use it to isolate stores between cases.\n */\nexport function clearStoreRegistry(): void {\n REGISTRY.length = 0;\n}\n"]}
@@ -0,0 +1,70 @@
1
+ // src/attach.ts
2
+ function attach(node, setup) {
3
+ const teardown = setup(node);
4
+ let done = false;
5
+ let hasConnected = node.isConnected;
6
+ let observedRoot;
7
+ let connectionFrame = 0;
8
+ const finish = () => {
9
+ if (done) return;
10
+ done = true;
11
+ globalThis.cancelAnimationFrame(connectionFrame);
12
+ observer.disconnect();
13
+ if (typeof teardown === "function") teardown();
14
+ };
15
+ const addedWithin = (candidate) => {
16
+ let current = node;
17
+ while (current !== null) {
18
+ if (current === candidate) return true;
19
+ const root = current.getRootNode();
20
+ current = current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);
21
+ }
22
+ return false;
23
+ };
24
+ const observer = new MutationObserver((records) => {
25
+ if (done) return;
26
+ if (!hasConnected) {
27
+ if (!node.isConnected) {
28
+ const connectedAndRemovedInBatch = records.some(
29
+ (record) => [...record.addedNodes].some(addedWithin)
30
+ );
31
+ if (!connectedAndRemovedInBatch) return;
32
+ hasConnected = true;
33
+ finish();
34
+ return;
35
+ }
36
+ hasConnected = true;
37
+ globalThis.cancelAnimationFrame(connectionFrame);
38
+ } else if (!node.isConnected) {
39
+ finish();
40
+ return;
41
+ }
42
+ const root = node.getRootNode();
43
+ if (root !== observedRoot) observe(root);
44
+ });
45
+ const observe = (root) => {
46
+ observer.disconnect();
47
+ observedRoot = root;
48
+ const options = { childList: true, subtree: true };
49
+ observer.observe(node.ownerDocument, options);
50
+ if (root !== node.ownerDocument) observer.observe(root, options);
51
+ };
52
+ const watchForConnection = () => {
53
+ connectionFrame = globalThis.requestAnimationFrame(() => {
54
+ if (done || hasConnected) return;
55
+ if (node.isConnected) {
56
+ hasConnected = true;
57
+ observe(node.getRootNode());
58
+ return;
59
+ }
60
+ watchForConnection();
61
+ });
62
+ };
63
+ observe(hasConnected ? node.getRootNode() : node.ownerDocument);
64
+ if (!hasConnected) watchForConnection();
65
+ return finish;
66
+ }
67
+
68
+ export { attach };
69
+
70
+ //# sourceMappingURL=chunk-FM6N2B4N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attach.ts"],"names":[],"mappings":"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","file":"chunk-FM6N2B4N.js","sourcesContent":["/**\n * `kerfjs/attach` — bind a non-kerf widget's lifecycle to a single DOM node.\n *\n * `data-morph-skip` lets a library own a subtree so kerf won't touch it — but\n * nothing manages that widget's LIFECYCLE. You set it up imperatively after\n * render and must remember to tear it down when the node is replaced/removed\n * (dropping document-level listeners the widget added, etc.). `attach` closes\n * that seam: run a setup against one **existing** DOM node and auto-run its\n * teardown when that node leaves the document.\n *\n * import { attach } from 'kerfjs/attach';\n *\n * attach(canvasEl, (el) => {\n * const chart = D3.mount(el);\n * return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)\n * });\n *\n * `setup(node)` runs immediately — the node already exists, so there is nothing\n * to wait for (this is NOT React's `useEffect`: no dependency array, no re-run,\n * no render-phase or hook-order scoping; it is closer to a Web Component's\n * `connectedCallback`/`disconnectedCallback` pair, Svelte's\n * `onMount(() => () => cleanup)`, or Solid's `onCleanup`). A node may start\n * disconnected: setup still runs now, and teardown waits until it has first\n * connected and subsequently leaves the document. The returned teardown runs\n * once — whichever comes first — on that removal (detected by a\n * `MutationObserver`, so a morph swap, a `remountOn` replacement, or any removal\n * triggers it) or when the returned disposer is called. Re-creation is NOT\n * handled here: a fresh node is a fresh `attach()` call — pair it with\n * `kerfjs/remount`, which replaces the node and re-runs your render (and thus\n * this call) on the new one.\n *\n * Related: `kerfjs/scope`'s `observeRemovals` also auto-disposes on removal via a\n * `MutationObserver`, but scoped to a whole subtree's registered disposers rather\n * than one node's setup/teardown pair — reach for that when you're collecting\n * many disposers under an element, and for `attach` when you're binding one\n * widget's lifecycle to one node.\n */\n\n/** The setup callback for {@link attach}: run against `node`, optionally return a teardown. */\nexport type AttachSetup = (node: Element) => (() => void) | void;\n\n/**\n * Run `setup(node)` now, and its returned teardown once — after `node` has been\n * connected and then leaves the document, or when the returned disposer is\n * called, whichever is first. Returns an idempotent disposer so a `mount()` /\n * `Scope` can drive teardown explicitly.\n */\nexport function attach(node: Element, setup: AttachSetup): () => void {\n const teardown = setup(node);\n let done = false;\n let hasConnected = node.isConnected;\n let observedRoot: Node | undefined;\n let connectionFrame = 0;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n observer.disconnect();\n if (typeof teardown === 'function') teardown();\n };\n\n const addedWithin = (candidate: Node): boolean => {\n let current: Node | null = node;\n while (current !== null) {\n if (current === candidate) return true;\n const root = current.getRootNode();\n current =\n current.parentNode ?? (root instanceof ShadowRoot ? root.host : null);\n }\n return false;\n };\n\n const observer = new MutationObserver((records) => {\n if (done) return;\n\n if (!hasConnected) {\n // A node may be prepared before insertion. Its own detached root cannot\n // observe becoming someone else's child, so wait on ownerDocument until\n // it becomes connected; only a later disconnection is a teardown event.\n if (!node.isConnected) {\n const connectedAndRemovedInBatch = records.some((record) =>\n [...record.addedNodes].some(addedWithin),\n );\n if (!connectedAndRemovedInBatch) return;\n hasConnected = true;\n finish();\n return;\n }\n hasConnected = true;\n globalThis.cancelAnimationFrame(connectionFrame);\n } else if (!node.isConnected) {\n finish();\n return;\n }\n\n // A connected node can move between documents or shadow roots without\n // ending its lifecycle. Follow its live root so a later removal remains\n // observable after that move.\n const root = node.getRootNode();\n if (root !== observedRoot) observe(root);\n });\n\n const observe = (root: Node): void => {\n observer.disconnect();\n observedRoot = root;\n const options = { childList: true, subtree: true } as const;\n observer.observe(node.ownerDocument, options);\n // Document observation sees a shadow host leave the document, while shadow\n // observation sees the node (or one of its in-shadow ancestors) removed.\n // Both are required to cover the node's composed lifecycle.\n if (root !== node.ownerDocument) observer.observe(root, options);\n };\n\n const watchForConnection = (): void => {\n connectionFrame = globalThis.requestAnimationFrame(() => {\n if (done || hasConnected) return;\n if (node.isConnected) {\n hasConnected = true;\n observe(node.getRootNode());\n return;\n }\n watchForConnection();\n });\n };\n\n // Connected nodes are watched from their live document/shadow root. For a\n // disconnected node, watch ownerDocument plus animation frames: the document\n // catches ordinary insertion, while the frame check also catches direct\n // insertion across a shadow boundary that document observation cannot see.\n observe(hasConnected ? node.getRootNode() : node.ownerDocument);\n if (!hasConnected) watchForConnection();\n\n return finish;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { bumpItemVersion } from './chunk-QIP723L4.js';
2
- import { signal } from './chunk-U6FK33SG.js';
1
+ import { bumpItemVersion } from './chunk-5WRGJZV6.js';
2
+ import { signal } from './chunk-IHMDBBUJ.js';
3
3
 
4
4
  // src/array-signal.ts
5
5
  var ARRAY_SIGNAL_BRAND = /* @__PURE__ */ Symbol.for("kerfjs.ArraySignal");
@@ -106,4 +106,4 @@ function arraySignal(initial = []) {
106
106
 
107
107
  export { ARRAY_SIGNAL_BRAND, ArraySignal, arraySignal };
108
108
 
109
- //# sourceMappingURL=chunk-LVH3GC6B.js.map
109
+ //# sourceMappingURL=chunk-H7WTRM6F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/array-signal.ts"],"names":[],"mappings":"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","file":"chunk-H7WTRM6F.js","sourcesContent":["/**\n * `arraySignal(initial)` — granular collection signal.\n *\n * A keyed-list-friendly variant of `signal()` that emits typed patch events\n * for every mutation (update / insert / remove / move / replace). When such\n * a signal is bound to `each(...)` inside a `mount()`, the keyed list\n * reconciler applies just the patches against the live DOM — no per-item\n * iteration, no `classifyItems` Map build, no LIS pass over unchanged rows.\n *\n * const rows = arraySignal<Row>([]);\n *\n * rows.update(42, (r) => ({ ...r, label: 'changed' })); // 1 update event\n * rows.insert(0, { id: 'x', ... }); // 1 insert event\n * rows.remove(7); // 1 remove event\n * rows.move(3, 0); // 1 move event\n * rows.replace([...]); // falls back to snapshot reconcile\n *\n * Read-side semantics match a regular signal: `arraySig.value` is a\n * snapshot, and reads inside `effect()` / `computed()` register as\n * dependencies, so derived values keep working.\n */\n\nimport { bumpItemVersion } from './item-version.js';\nimport type { Signal } from './reactive.js';\nimport { signal } from './reactive.js';\n\n/** A single granular mutation event. */\nexport type ArrayPatch<T> =\n | { type: 'update'; index: number; item: T }\n | { type: 'insert'; index: number; item: T }\n | { type: 'remove'; index: number }\n | { type: 'move'; from: number; to: number }\n | { type: 'replace'; items: readonly T[] };\n\n/**\n * Cross-bundle brand for `ArraySignal` instances. `each()` and the\n * granular reconciler check for this brand instead of `instanceof\n * ArraySignal`, so the main `kerfjs` barrel can detect arraySignal\n * inputs without importing the class at runtime — the class lives\n * only in the `kerfjs/array-signal` subpath, so apps that don't need\n * granular collections shed ~1 KB.\n *\n * Same `Symbol.for(...)`-based pattern as `SafeHtml` (KF-14): cross-\n * bundle-safe, zero-cost runtime check.\n */\nexport const ARRAY_SIGNAL_BRAND = Symbol.for('kerfjs.ArraySignal');\n\nfunction isValidIndex(\n index: number,\n length: number,\n allowEnd = false,\n): boolean {\n return (\n Number.isInteger(index) &&\n index >= 0 &&\n (allowEnd ? index <= length : index < length)\n );\n}\n\nexport class ArraySignal<T> {\n private _items: T[];\n private _version: Signal<number>;\n private _patches: ArrayPatch<T>[];\n // Branded so `isArraySignal()` recognizes instances from any copy of this module.\n readonly [ARRAY_SIGNAL_BRAND] = true as const;\n\n constructor(initial: readonly T[] = []) {\n this._items = [...initial];\n this._version = signal(0);\n this._patches = [];\n }\n\n /** Read-only snapshot. Reads inside an effect/computed register a dependency. */\n get value(): readonly T[] {\n // Touch the version signal so signals-core treats reads as tracked.\n void this._version.value;\n return this._items;\n }\n\n /**\n * Replace the item at `index` with `fn(currentItem)`. Emits one `update`\n * patch. Both styles work: returning a fresh object (idiomatic) invalidates\n * the row by identity, and mutating `item` in place and returning it works\n * too — a per-item content version (KF-418) makes the same-ref change visible\n * to every consumer's row memo.\n */\n update(index: number, fn: (item: T) => T): void {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.update: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const next = fn(this._items[index]);\n this._items[index] = next;\n this._patches.push({ type: 'update', index, item: next });\n // KF-418: a same-ref update (fn mutates and returns the same object) is\n // invisible to the row memo, which is keyed on object identity. Bump the\n // item's content version so every consumer — this list, another list over\n // this signal, a second mount, a plain-array filter() view — re-renders it.\n // Non-object items (an arraySignal<number> used as a plain signal) are\n // skipped by bumpItemVersion — they can't be each() rows (KF-419).\n bumpItemVersion(next);\n this._version.value++;\n }\n\n /** Insert `item` at `index`. Existing items at index..N shift right. Emits one `insert` patch. */\n insert(index: number, item: T): void {\n if (!isValidIndex(index, this._items.length, true)) {\n throw new Error(\n `arraySignal.insert: index ${index} out of bounds [0, ${this._items.length}].`,\n );\n }\n this._items.splice(index, 0, item);\n this._patches.push({ type: 'insert', index, item });\n this._version.value++;\n }\n\n /** Append `item` at the end. Sugar for `insert(items.length, item)`. */\n push(item: T): void {\n this.insert(this._items.length, item);\n }\n\n /** Remove and return the item at `index`. Emits one `remove` patch. */\n remove(index: number): T {\n if (!isValidIndex(index, this._items.length)) {\n throw new Error(\n `arraySignal.remove: index ${index} out of bounds [0, ${this._items.length}).`,\n );\n }\n const [removed] = this._items.splice(index, 1);\n this._patches.push({ type: 'remove', index });\n this._version.value++;\n return removed;\n }\n\n /** Move the item at `from` to position `to`. Emits one `move` patch (no-op when from === to). */\n move(from: number, to: number): void {\n if (\n !isValidIndex(from, this._items.length) ||\n !isValidIndex(to, this._items.length)\n ) {\n throw new Error(\n `arraySignal.move: indices out of bounds (from=${from}, to=${to}, length=${this._items.length}).`,\n );\n }\n if (from === to) return;\n const [item] = this._items.splice(from, 1);\n this._items.splice(to, 0, item);\n this._patches.push({ type: 'move', from, to });\n this._version.value++;\n }\n\n /** Replace every item. Emits one `replace` patch — the granular reconciler falls back to a full keyed diff for this case. */\n replace(items: readonly T[]): void {\n this._items = [...items];\n this._patches.push({ type: 'replace', items: this._items });\n this._version.value++;\n }\n\n /**\n * @internal Used by `each()` when binding this signal to a list. Returns\n * the queue of granular patches issued since the previous call, then\n * clears the queue. Best paired with a single binding — a second consumer\n * in the same render gets an empty array (which forces the snapshot\n * fall-back path, which is correct but slower).\n */\n _consumePatches(): ArrayPatch<T>[] {\n const out = this._patches;\n this._patches = [];\n return out;\n }\n}\n\n/** Construct an array signal seeded with `initial`. */\nexport function arraySignal<T>(initial: readonly T[] = []): ArraySignal<T> {\n return new ArraySignal(initial);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { devHooks } from './chunk-HW7KSM2Y.js';
1
+ import { devHooks } from './chunk-3C6F6NM4.js';
2
2
  import { Signal, signal as signal$1, effect as effect$1 } from '@preact/signals-core';
3
3
  export { batch, computed } from '@preact/signals-core';
4
4
 
@@ -17,4 +17,4 @@ function effect(fn) {
17
17
 
18
18
  export { effect, isSignal, signal };
19
19
 
20
- //# sourceMappingURL=chunk-U6FK33SG.js.map
20
+ //# sourceMappingURL=chunk-IHMDBBUJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reactive.ts"],"names":["coreSignal","coreEffect"],"mappings":";;;;AAgDO,SAAS,SAAS,KAAA,EAA0C;AACjE,EAAA,OAAO,KAAA,YAAiB,MAAA;AAC1B;AAEO,SAAS,OAAU,KAAA,EAAsB;AAC9C,EAAA,MAAM,UAAU,QAAA,CAAS,aAAA;AACzB,EAAA,IAAI,OAAA,EAAS,OAAO,OAAA,CAAW,KAAU,CAAA;AACzC,EAAA,OAAOA,SAAW,KAAU,CAAA;AAC9B;AAEO,SAAS,OAAO,EAAA,EAA2C;AAChE,EAAA,MAAM,OAAO,QAAA,CAAS,UAAA;AACtB,EAAA,OAAOC,QAAA,CAAW,IAAA,GAAO,IAAA,CAAK,EAAE,IAAI,EAAE,CAAA;AACxC","file":"chunk-IHMDBBUJ.js","sourcesContent":["/**\n * Re-exports of `@preact/signals-core`. Lets the rest of the codebase depend\n * on `'./reactive.js'` without naming the underlying lib, so swapping it out\n * later (or fronting it with a hand-rolled implementation) is a one-file\n * change.\n *\n * Two dev hook slots sit in front of the bare re-exports:\n *\n * - `signalFactory` replaces the constructor so writes to never-subscribed\n * signals can warn (`KERF_DEV_WARN_UNTRACKED_SIGNALS=1`).\n *\n * - `wrapEffect` wraps the user body so `delegate()` can detect that it's\n * running inside an effect (`KERF_DEV_WARN_DELEGATE_IN_EFFECT=1`).\n *\n * Both are `undefined` unless the consumer imported `kerfjs/dev`, so production\n * sees the bare `@preact/signals-core` exports behind one property read.\n *\n * ORDERING: both slots are resolved at CREATION time. A signal created before\n * `kerfjs/dev` is installed (or before `untrackedSignals` is switched on) stays\n * plain, and an effect created before the install (or before\n * `delegateInEffect` is switched on) is never wrapped. Static imports hoist\n * above a `await import()`, so module-scope signals and effects in imported\n * modules are created first. `mount()`'s `listenerRebuild` hook is the third\n * creation-time boundary. See docs/11-dev-warnings.md §11.3.6.\n */\n\nimport {\n effect as coreEffect,\n Signal,\n signal as coreSignal,\n} from '@preact/signals-core';\n\nimport { devHooks } from './dev-hooks.js';\n\nexport {\n batch,\n computed,\n type ReadonlySignal,\n Signal,\n} from '@preact/signals-core';\n\n/**\n * Runtime type guard for a `@preact/signals-core` signal (both `signal()`\n * values and `computed()` values are `Signal` instances). Used by the JSX\n * runtime (KF-294) to detect a signal handed straight into an attribute or\n * text hole — the trigger for a fine-grained binding rather than a snapshot\n * stringify.\n */\nexport function isSignal(value: unknown): value is Signal<unknown> {\n return value instanceof Signal;\n}\n\nexport function signal<T>(value?: T): Signal<T> {\n const factory = devHooks.signalFactory;\n if (factory) return factory<T>(value as T);\n return coreSignal(value as T);\n}\n\nexport function effect(fn: () => void | (() => void)): () => void {\n const wrap = devHooks.wrapEffect;\n return coreEffect(wrap ? wrap(fn) : fn);\n}\n"]}
@@ -0,0 +1,86 @@
1
+ import { devHooks } from './chunk-3C6F6NM4.js';
2
+
3
+ // src/delegate.ts
4
+ var NON_BUBBLING = /* @__PURE__ */ new Set([
5
+ "focus",
6
+ "blur",
7
+ "scroll",
8
+ "load",
9
+ "error",
10
+ "mouseenter",
11
+ "mouseleave"
12
+ ]);
13
+ function assertValidSelector(selector, fn) {
14
+ try {
15
+ document.createElement("div").matches(selector);
16
+ } catch {
17
+ throw new Error(
18
+ `${fn}: invalid selector "${selector}". Pass a valid CSS selector (e.g. '[data-action="add"]', '.btn', 'input').`
19
+ );
20
+ }
21
+ }
22
+ var KEY = /* @__PURE__ */ Symbol();
23
+ function _delegate(rootEl, type, selector, bind, options, capture, fn) {
24
+ assertValidSelector(selector, fn);
25
+ devHooks.delegateInEffect?.(fn);
26
+ const direct = options?.match === "direct";
27
+ const resolve = (event) => {
28
+ const target = event.target;
29
+ if (event.type === type && target instanceof Element) {
30
+ const matched = direct ? target.matches(selector) ? target : null : target.closest(selector);
31
+ if (matched !== null) {
32
+ const run = bind(event, matched);
33
+ return () => {
34
+ if (rootEl.contains(matched)) run();
35
+ };
36
+ }
37
+ }
38
+ return void 0;
39
+ };
40
+ const listener = (event) => {
41
+ let snap = event[KEY];
42
+ if (!snap?.has(resolve)) {
43
+ snap = event[KEY] = /* @__PURE__ */ new Map();
44
+ for (const node of event.composedPath()) {
45
+ for (const other of node[KEY] ?? []) {
46
+ snap.set(other, other(event));
47
+ }
48
+ }
49
+ }
50
+ const run = snap.get(resolve);
51
+ snap.delete(resolve);
52
+ run?.();
53
+ };
54
+ const registry = (rootEl[KEY] ??= /* @__PURE__ */ new Set()).add(resolve);
55
+ rootEl.addEventListener(type, listener, capture);
56
+ return () => {
57
+ registry.delete(resolve);
58
+ rootEl.removeEventListener(type, listener, capture);
59
+ };
60
+ }
61
+ function delegate(rootEl, type, selector, handler, options) {
62
+ return _delegate(
63
+ rootEl,
64
+ type,
65
+ selector,
66
+ (event, matched) => () => handler(event, matched),
67
+ options,
68
+ NON_BUBBLING.has(type),
69
+ "delegate"
70
+ );
71
+ }
72
+ function delegateCapture(rootEl, type, selector, handler, options) {
73
+ return _delegate(
74
+ rootEl,
75
+ type,
76
+ selector,
77
+ (event, matched) => () => handler(event, matched),
78
+ options,
79
+ true,
80
+ "delegateCapture"
81
+ );
82
+ }
83
+
84
+ export { NON_BUBBLING, _delegate, delegate, delegateCapture };
85
+
86
+ //# sourceMappingURL=chunk-JN7EJM3B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/delegate.ts"],"names":[],"mappings":"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","file":"chunk-JN7EJM3B.js","sourcesContent":["/**\n * Tiny event-delegation helpers. Replace per-element `addEventListener` calls\n * (which don't survive morph re-renders for nodes the diff creates) with one\n * listener at the morph-root that dispatches via `closest()`.\n *\n * Three-tier listener model:\n *\n * - Tier 1 (bubbling events) — use `delegate()`.\n * click, input, change, submit, mousedown/up, keydown/up, pointerdown/up/move,\n * drag*, drop, contextmenu, wheel, copy/paste/cut, focusin/focusout.\n *\n * `delegate()` also auto-promotes the well-known non-bubbling event\n * types (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`,\n * `mouseleave`) to the capture phase under the hood, so the call site\n * looks identical for \"interactive thing happens on a descendant\"\n * regardless of whether that event bubbles. Selector matching stays\n * `closest()`-style — the same as for bubbling events — so a wrapper\n * selector like `'.field-row'` still matches when the event fires on\n * a descendant `<input>`.\n *\n * - Tier 2 (explicit capture) — use `delegateCapture()`.\n * The escape hatch for cases the auto-promotion list doesn't cover\n * (custom non-bubbling events) or when you want capture-phase\n * interception. Selector matching is `closest()`-style by default —\n * the same walk-up as `delegate()`, and it passes the matched ancestor\n * (not the raw target) to the handler — so a click on any descendant of\n * the selected element climbs to it. Pass `{ match: 'direct' }` to opt\n * into strict `matches()`-style matching (fire only when the event lands\n * on the exact element the selector identifies).\n *\n * - Tier 3 (per-element instances / library-owned subtrees) — mark the\n * host element with `data-morph-skip` and manage the library's\n * lifecycle directly. No delegation helper applies.\n */\n\nimport { devHooks } from './dev-hooks.js';\n\n/**\n * Event types that don't bubble and so wouldn't reach a root-level\n * bubble-phase listener. `delegate()` flips to capture for these; the\n * caller doesn't need to know or care.\n *\n * Membership is conservative — it covers the cases that \"should obviously\n * work\" with delegate() but otherwise don't. For exotic non-bubbling events\n * (custom events, less-common DOM events) the explicit `delegateCapture()`\n * remains the escape hatch.\n */\nexport const NON_BUBBLING = new Set<string>([\n 'focus',\n 'blur',\n 'scroll',\n 'load',\n 'error',\n 'mouseenter',\n 'mouseleave',\n]);\n\n/**\n * How the selector is matched against the event's target:\n *\n * - `'closest'` (the default for both helpers) — walk UP from `event.target`\n * via `closest(selector)`, firing for the nearest matching ancestor inside\n * `rootEl`. This is the delegation behavior you almost always want: a click\n * on an icon inside a button fires the button's handler.\n * - `'direct'` — strict `matches()` match: fire only when `event.target`\n * itself matches the selector, with no walk-up.\n */\nexport interface DelegateOptions {\n match?: 'closest' | 'direct';\n}\n\n/**\n * Validate a CSS selector at registration time, so a typo throws immediately\n * with the bad selector quoted instead of producing a cryptic DOMException\n * the first time a matching event fires.\n */\nfunction assertValidSelector(selector: string, fn: string): void {\n try {\n document.createElement('div').matches(selector);\n } catch {\n throw new Error(\n `${fn}: invalid selector \"${selector}\". ` +\n \"Pass a valid CSS selector (e.g. '[data-action=\\\"add\\\"]', '.btn', 'input').\",\n );\n }\n}\n\n/**\n * Per-root registry of the delegates installed on that root, and the per-event\n * dispatch snapshot. Both live ON the DOM objects, under one symbol key (a\n * root and an event are never the same object), rather than in module-level\n * maps — so they are collected with the root / event and add no module-level\n * mutable state (Design rule 5).\n */\nconst KEY = Symbol();\n\n/**\n * Resolves one delegate's selector against an event's target: the bound\n * handler call for a match, `undefined` otherwise.\n */\ntype Resolve = (event: Event) => (() => void) | undefined;\n\ninterface Registered {\n [KEY]?: Set<Resolve>;\n}\n\ninterface Snapshotted {\n [KEY]?: Map<Resolve, (() => void) | undefined>;\n}\n\n/**\n * Install one root-level delegate — the shared core of `delegate()`,\n * `delegateCapture()`, and `kerfjs/actions`' `delegateActions()`.\n *\n * The listener resolves the event's target to a matched element (walk-up\n * `closest()` or strict `matches()`, per `options.match`) that must lie inside\n * `rootEl`, then runs the call `bind(event, matched)` prepared. `bind` runs at\n * RESOLUTION time, so a caller can read matched-element state there (such as\n * `delegateActions`' action attribute) before any handler has mutated it.\n *\n * **Dispatch snapshot.** A handler that writes a signal makes `mount()` morph\n * synchronously, inside the same dispatch, and the morph can recycle the\n * clicked element in place into a control with a different selector identity\n * (same tag, same position, new `data-action`). Resolving later delegates\n * against that live DOM would fire one the user never clicked — typically one\n * that immediately undoes the first handler's state change. So the FIRST kerf\n * delegate listener to see an event resolves every kerf delegate on the\n * event's propagation path (fixed at dispatch start, per the DOM spec, and\n * listed by `composedPath()`) before any delegated handler runs, and stores\n * the results on the event; each listener then consumes its own entry. The\n * set of delegates an event reaches is fixed when it starts dispatching, like\n * per-element listeners. The matched element must also still be inside\n * `rootEl` when the listener runs: a handler that removed the target still\n * suppresses the later delegates, as before.\n *\n * A missing entry (a re-dispatch of the same event object, a delegate added\n * mid-dispatch, or a root hidden from the first listener by a closed shadow\n * boundary) takes a fresh snapshot. Accepted residuals: a same-path\n * re-dispatch after a stopped dispatch can reuse an unconsumed entry if all\n * earlier kerf listeners were disposed, and a closed shadow boundary can\n * hide delegates from the first listener's path view. See docs/5 §5.2.1.\n * Internal — exported for `kerfjs/actions`.\n */\nexport function _delegate(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n bind: (event: Event, matched: Element) => () => void,\n options: DelegateOptions | undefined,\n capture: boolean,\n fn: 'delegate' | 'delegateCapture',\n): () => void {\n assertValidSelector(selector, fn);\n devHooks.delegateInEffect?.(fn);\n const direct = options?.match === 'direct';\n const resolve: Resolve = (event) => {\n const target = event.target;\n if (event.type === type && target instanceof Element) {\n const matched = direct\n ? target.matches(selector)\n ? target\n : null\n : target.closest(selector);\n if (matched !== null) {\n const run = bind(event, matched);\n // Checked when the handler would run, not at resolution: a match\n // outside `rootEl` never fires, and neither does one an earlier\n // handler removed from `rootEl`.\n return () => {\n if (rootEl.contains(matched)) run();\n };\n }\n }\n return undefined;\n };\n const listener = (event: Event): void => {\n let snap = (event as Snapshotted)[KEY];\n if (!snap?.has(resolve)) {\n snap = (event as Snapshotted)[KEY] = new Map();\n for (const node of event.composedPath()) {\n for (const other of (node as Registered)[KEY] ?? []) {\n snap.set(other, other(event));\n }\n }\n }\n const run = snap.get(resolve);\n snap.delete(resolve);\n run?.();\n };\n const registry = ((rootEl as Registered)[KEY] ??= new Set()).add(resolve);\n rootEl.addEventListener(type, listener, capture);\n return () => {\n registry.delete(resolve);\n rootEl.removeEventListener(type, listener, capture);\n };\n}\n\n/**\n * Delegation that \"just works\" for both bubbling and the common non-bubbling\n * events. Installs ONE listener on `rootEl`; for known non-bubblers (see\n * `NON_BUBBLING` above) the listener is registered on the capture phase so\n * it actually reaches the target, otherwise on the bubble phase. Either way,\n * matching walks up from `event.target` via `closest(selector)` and fires\n * `handler(event, matched)` if the match is inside `rootEl`.\n *\n * Pass `{ match: 'direct' }` to fire only when `event.target` itself matches\n * the selector (no walk-up); the default is `'closest'`.\n *\n * The generic `T` narrows the second handler argument to the expected element\n * type — `delegate<HTMLButtonElement>(root, 'click', 'button', (e, btn) => btn.value)`\n * — so consumers can avoid casts. Defaults to `Element` for untyped calls.\n *\n * Returns a disposer that removes the listener.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegate(rootEl, 'click', '[data-action=\"add\"]', handlerFn);\n * delegate(rootEl, 'focus', 'input', handlerFn); // auto-capture\n */\nexport function delegate<T extends Element = Element>(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: (event: Event, target: T) => void,\n options?: DelegateOptions,\n): () => void {\n return _delegate(\n rootEl,\n type,\n selector,\n (event, matched) => () => handler(event, matched as T),\n options,\n NON_BUBBLING.has(type),\n 'delegate',\n );\n}\n\n/**\n * Capture-phase delegation — the escape hatch for custom non-bubbling events\n * (ones `delegate()`'s auto-promotion list doesn't know about) and for\n * capture-phase interception (run before any descendant's bubble-phase\n * handler). Reaches descendants of `rootEl` that match `selector` regardless\n * of how many times the diff has rebuilt them.\n *\n * Selector matching is `closest()`-style by default — the same walk-up as\n * `delegate()`, and it passes the matched ancestor (not the raw target) to\n * the handler — so a click on any descendant of the selected element climbs\n * to it. Pass `{ match: 'direct' }` to opt into strict `matches()`-style\n * matching (fire only when the event lands on the exact element the selector\n * identifies, with no walk-up).\n *\n * The generic `T` narrows the second handler argument to the expected element\n * type, mirroring `delegate<T>()`. Defaults to `Element` for untyped calls.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);\n * delegateCapture(rootEl, 'click', '.exact', handlerFn, { match: 'direct' });\n */\nexport function delegateCapture<T extends Element = Element>(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: (event: Event, target: T) => void,\n options?: DelegateOptions,\n): () => void {\n return _delegate(\n rootEl,\n type,\n selector,\n (event, matched) => () => handler(event, matched as T),\n options,\n true,\n 'delegateCapture',\n );\n}\n"]}
@@ -71,4 +71,4 @@ function attr(name, value) {
71
71
 
72
72
  export { attr };
73
73
 
74
- //# sourceMappingURL=chunk-UZJ6I4T6.js.map
74
+ //# sourceMappingURL=chunk-MK42GLPV.js.map