kerfjs 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to **kerf** are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.2] - 2026-05-09
8
+
9
+
10
+ - No user-facing changes in this release.
11
+
12
+ ## [0.4.1] - 2026-05-09
13
+
14
+
15
+ - Just fixing the build
16
+
17
+ ## [0.4.0] - 2026-05-09
18
+
19
+
20
+ - Auto-promote known non-bubbling events (focus, blur, scroll, etc.) to capture phase in `delegate()`
21
+
22
+ ## [0.4.0] - 2026-05-09
23
+
24
+
25
+ - `delegate()` now auto-promotes the seven well-known non-bubbling events (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`, `mouseleave`) to capture phase, with `closest()`-style selector matching preserved
26
+ - Fixed focus and caret position loss when reordering keyed `each()` rows on engines that drop focus on `insertBefore` (older Safari, happy-dom)
27
+ - `mount()` now throws a descriptive error when the root element is null/undefined instead of a generic "Cannot set properties of null"
28
+ - `each()` now throws a descriptive error naming the offending index when an item is a primitive (per-item cache requires objects)
29
+ - Minimum Node version bumped to 22.12+
30
+ - New Starlight-powered docs site at `/kerf/` with inline live examples and runnable complete apps; the reactivity demo moved to `/kerf/demo/`
31
+ - New kerf brand identity: production logo, full favicon set (SVG + PNG + ICO + Apple touch icon), and PWA manifest
32
+
7
33
  ## [0.3.1] - 2026-05-08
8
34
 
9
35
 
@@ -41,11 +67,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
41
67
 
42
68
  ### Fixed
43
69
 
70
+ - **Focus was sometimes lost on `each()` reorders (KF-65).** When the keyed list reconciler moved a row whose descendant held focus, `insertBefore` blurred the element on engines that don't preserve focus across DOM moves (older Safari, happy-dom). The element survived in the live tree, but `document.activeElement` reverted to `<body>` and the user's typing was interrupted. The reconciler now snapshots the active element + its selection range (when applicable) before the move pass and re-applies them after, so focus and caret position survive a reorder uniformly across engines. Engines that already preserve focus across moves see a no-op — the snapshot only takes effect when the active element changed. `docs/4-render.md` §4.4 and `docs/8-api-reference.md` updated. New regression tests in `tests/unit/mount.test.ts` cover reorder, top-insert, focused-row removal, non-text focused elements, selection-API rejection (e.g. `type=number`), and the "active element is outside the list" path.
44
71
  - **`Fragment` was missing from the `kerfjs` barrel (KF-24).** `Fragment` was implemented in `src/jsx-runtime.ts`, exported from `kerfjs/jsx-runtime`, and present in the shared chunk — but the barrel `src/index.ts` didn't re-export it. Importing `Fragment` from `'kerfjs'` resolved to `undefined`, so a manual `<Fragment>...</Fragment>` rendered as `<undefined>...</undefined>`. The `<>...</>` shorthand was unaffected because the JSX transform pulls `Fragment` from `kerfjs/jsx-runtime` directly. Added `Fragment` to the barrel re-export, and pinned the entire public-API contract with a new `tests/dist/barrel-completeness.test.ts` so any future omission fails CI loudly. Docs updated to list `Fragment` in the public API surface (`CLAUDE.md`, `llms.txt`, `docs/ai/usage-guide.md`, `docs/ai/code-summary.md`, `docs/6-jsx-runtime.md`, `docs/8-api-reference.md`).
45
72
  - **Focused contenteditable was being morphed, clobbering in-progress edits (KF-19).** The docs claimed contenteditable elements got focus + selection preservation alongside `<input>` and `<textarea>`, but the implementation only handled the latter two — a focused contenteditable's typed content was overwritten by morphdom on the next re-render. `mount()` now short-circuits the morph entirely when the active element is a contenteditable (same mechanism as `data-morph-skip`), so the user's edit, caret position, and any multi-range selection survive verbatim. Attribute updates are deferred until the next render after blur — that's the explicit trade-off, and matches what you want for in-progress rich-text editing. `docs/4-render.md` §4.4 and `docs/8-api-reference.md` §8.7 updated to describe the per-element-kind behaviour. The check uses the `contenteditable` attribute directly (the spec's source of truth) rather than the derived `isContentEditable` property, so test environments that don't populate the latter still get correct behaviour.
46
73
  - **`clearStoreRegistry` was a no-op in the published bundle (KF-15).** `dist/testing.js` shipped an empty function body. Root cause: `tsup` bundled each entry independently with `splitting: false`, so the testing entry tree-shook the module-level `REGISTRY` array out as unreferenced — leaving `REGISTRY.length = 0` as dead code. Same root cause as KF-14. Fixed by enabling `splitting: true` in `tsup.config.ts`: shared modules now live in chunk files that all entries import, so `defineStore`'s registry and `clearStoreRegistry`'s reference are the same array. Side benefit: the duplicate `SafeHtml` class definition is gone too — there's now exactly one copy across the whole dist. Build output now includes `dist/chunk-*.js` files (covered by the existing `"files": ["dist"]` in `package.json`). New regression test in `tests/dist/store-registry-shared.test.ts` exercises the cross-entry registry from the built bundles.
47
74
  - **`SafeHtml` cross-bundle identity (KF-14).** When a consumer's bundler ended up loading two copies of kerf — for example, the barrel (`kerfjs`) and the JSX-runtime entry (`kerfjs/jsx-runtime`) resolving as separate modules — `instanceof SafeHtml` failed inside the JSX runtime because the two `SafeHtml` classes were structurally identical but referentially distinct. The renderer would then throw `JSX: unsupported child of type object (SafeHtml)` on perfectly valid JSX. `SafeHtml` instances now carry a `Symbol.for('kerfjs.SafeHtml')` brand and the runtime checks for the brand instead of using `instanceof`. New unit tests simulate the duplicate-class scenario, and a new `npm run test:dist` job exercises the actual built bundles in CI.
48
75
 
76
+ ### Changed
77
+
78
+ - **`delegate()` now auto-promotes the well-known non-bubbling event types to capture phase (KF-56).** `focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`, `mouseleave` previously needed `delegateCapture()`; with auto-promotion the call site is identical to bubbling events. Selector matching stays `closest()`-style for every type, including the auto-promoted ones — so `delegate(root, 'focus', '.field-row', ...)` fires when a descendant `<input>` is focused, with the row as the matched element (not the input). `delegateCapture()` remains as the explicit-capture escape hatch with its `target.matches()`-style direct matching. No bundle-size change worth measuring. `docs/5-event-delegation.md` and `docs/8-api-reference.md` §8.4 updated. New unit tests in `tests/unit/delegate.test.ts` cover the `closest()` walk-up on auto-promoted events, the disposer path, and a phase-check for bubbling events to confirm the promotion is type-gated.
79
+
49
80
  ### Added
50
81
 
51
82
  - **`each(items, render, key?)` list primitive** exported from `kerfjs`. Keyed list iteration with per-item memoisation: skips re-running `render` for items whose object identity (and optional `key`) are unchanged since the previous call. Targets the partial-update / select-row / swap-rows perf path, where today's `mount()` re-runs the render for the full list on any signal change. On the js-framework-benchmark suite this drops kerfjs's partial-update from 87 → 64 ms (-27%), select-row from 69 → 42 ms (-38%), swap-rows from 86 → 58 ms (-33%), and remove-row from 49 → 35 ms (-29%); creates and bundle size are unaffected (+0.2 KB gz for the WeakMap memoiser). See `docs/8-api-reference.md` §8.3 and `bench/` for the benchmark harness.
package/README.md CHANGED
@@ -1,10 +1,18 @@
1
- # kerf
1
+ <p align="center">
2
+ <img src="./site/src/assets/logo-placeholder.svg" alt="Kerf logo placeholder" width="96" height="96" />
3
+ </p>
2
4
 
3
- > *kerf* — *noun* — the narrow strip of material a saw blade removes when cutting. The smallest possible cut.
5
+ <h1 align="center">Kerf</h1>
4
6
 
5
- A tiny reactive UI framework. Apply the smallest possible cut to update your DOM.
7
+ <p align="center"><em>The smallest cut.</em></p>
6
8
 
7
- **[Live demo →](https://brianwestphal.github.io/kerf/)** — seven sections exercising every primitive, no install required.
9
+ ---
10
+
11
+ > Introducing Kerf.
12
+ > The smallest cut.
13
+ >
14
+ > 6.6 KB. No virtual DOM. No compiler. No magic.
15
+ > Reactive UI that touches only the bytes that changed.
8
16
 
9
17
  ```ts
10
18
  import { signal, mount } from 'kerfjs';
@@ -19,41 +27,41 @@ mount(document.getElementById('app')!, () => (
19
27
  ));
20
28
  ```
21
29
 
22
- That's it. There's no virtual DOM, no compiler, no template language. Your JSX renders to HTML strings (with structured "list" segments where you use `each(...)`), kerf's native diff applies the minimum DOM mutations to make the live tree match, and signals re-run the render only when something they read actually changed.
30
+ That's it. Your JSX renders to HTML strings, kerf's native diff applies the minimum DOM mutations to make the live tree match, and signals re-run the render only when something they read actually changed.
23
31
 
24
- ## Why
32
+ ## Why Kerf
25
33
 
26
- Most reactive UI frameworks come with a lot of machinery: virtual DOMs, schedulers, reconcilers, compiler plugins, hook stacks, lifecycle hooks. kerf has none of that. You get four things:
34
+ 1. **Built for the AI-assisted era.** Tiny public surface (15 exports), no compiler magic, no hidden lifecycle. An LLM holds the framework in context and predicts behaviour — your AI agent generates code that works the first time. Ships [`llms.txt`](./llms.txt) and a dedicated AI usage guide.
27
35
 
28
- - **Signals** ([`@preact/signals-core`](https://github.com/preactjs/signals)) for fine-grained reactivity.
29
- - **Stores** built on signals — composable, testable units of state.
30
- - **Render** — a `mount(el, () => jsx)` helper that diffs the new HTML against the live DOM with kerf's native, segment-aware reconciler. Preserves focus, selection, in-flight pointer interactions, and event listeners on identity-preserved nodes. Lists rendered with `each(...)` go through a keyed reconciler that does O(changes) work, not O(rows).
31
- - **Event delegation** — small `delegate` / `delegateCapture` helpers that survive every re-render because they live on the morph root, not on individual nodes.
36
+ 2. **Smallest cut.** 6.6 KB gzipped including signals. Fine-grained reactivity re-runs only what changed; the diff touches only the DOM nodes that differ.
32
37
 
33
- The whole runtime is roughly 6.6 KB minified + gzipped, including `signals-core`.
38
+ 3. **No virtual DOM, no compiler.** JSX → HTML strings → native diff. DevTools shows the real DOM because it *is* the DOM.
34
39
 
35
- ## Install
40
+ 4. **Focus, selection, listeners survive re-renders.** We morph instead of rebuilding — your caret stays where you put it, your in-progress drag keeps moving, your delegated handlers keep firing.
36
41
 
37
- ```bash
38
- npm install kerfjs
39
- ```
42
+ 5. **Plain TS, plain JSX, plain ESM.** Drops into anything using esbuild / Vite / tsup. No plugin chain.
40
43
 
41
- Configure JSX:
44
+ ## When to use Kerf
42
45
 
43
- ```jsonc
44
- // tsconfig.json
45
- {
46
- "compilerOptions": {
47
- "jsx": "react-jsx",
48
- "jsxImportSource": "kerfjs"
49
- }
50
- }
51
- ```
46
+ - **AI-generated apps** — your LLM/agent holds the framework in context; no hallucinated APIs.
47
+ - **Hybrid desktop apps (Tauri / Electron)** — small bundle, predictable diff, debuggable runtime; ideal for the embedded webview.
48
+ - **Embedded widgets** — chat bubbles, comment boxes, dashboards dropped into someone else's page.
49
+ - **Server-rendered apps with islands** — Rails / Phoenix / Django / Hono. `mount` per island; `delegate` survives turbo-frame swaps.
50
+ - **Admin panels & internal tools** — reactivity without 200 KB of framework + state lib + router.
51
+ - **Replacing jQuery** — incremental migration; same delegation mental model, modern primitives.
52
+ - **Prototyping** — entire mental model on a postcard.
53
+
54
+ ### When to reach for something else
55
+
56
+ - Need a full ecosystem (router + forms + data + SSR streaming) → **Next.js / Remix / SolidStart**.
57
+ - Building a deeply componentised design-system app → **React / Solid / Svelte**.
58
+ - Need React Native / cross-platform mobile → **React** (Kerf + Tauri/Electron also covers many of these cases).
59
+ - Building a static site → **Astro** (we use it for *this* project's site).
52
60
 
53
61
  ## Quick tour
54
62
 
55
63
  ```ts
56
- import { signal, computed, effect, defineStore, mount, delegate } from 'kerfjs';
64
+ import { signal, computed, effect, defineStore, mount, each, delegate } from 'kerfjs';
57
65
 
58
66
  // 1. A signal — single piece of reactive state.
59
67
  const count = signal(0);
@@ -77,12 +85,16 @@ mount(root, () => (
77
85
  <div>
78
86
  <h1>Cart ({cart.state.value.items.length})</h1>
79
87
  <ul>
80
- {cart.state.value.items.map((item) => (
81
- <li data-key={item.id}>
82
- {item.name}
83
- <button data-action="remove" data-id={item.id}>×</button>
84
- </li>
85
- ))}
88
+ {each(
89
+ cart.state.value.items,
90
+ (item) => (
91
+ <li>
92
+ {item.name}
93
+ <button data-action="remove" data-id={item.id}>×</button>
94
+ </li>
95
+ ),
96
+ (item) => item.id,
97
+ )}
86
98
  </ul>
87
99
  <p>Doubled count: {doubled.value}</p>
88
100
  </div>
@@ -94,36 +106,35 @@ delegate(root, 'click', '[data-action="remove"]', (_e, btn) => {
94
106
  });
95
107
  ```
96
108
 
97
- ## Documentation
98
-
99
- The numbered docs in [`docs/`](./docs/) cover the design and rationale:
100
-
101
- 1. [Overview](./docs/1-overview.md) — what kerf is, what it isn't, when to use it.
102
- 2. [Reactivity](./docs/2-reactivity.md) — `signal`, `computed`, `effect`, `batch`.
103
- 3. [Stores](./docs/3-stores.md) — `defineStore`, `resetAllStores`.
104
- 4. [Render](./docs/4-render.md) — `mount`, segments, the native diff, and the list reconciler.
105
- 5. [Event delegation](./docs/5-event-delegation.md) — Tier 1 / Tier 2 / Tier 3 patterns.
106
- 6. [JSX runtime](./docs/6-jsx-runtime.md) — `SafeHtml`, `raw`, server-rendering.
107
- 7. [SVG handling](./docs/7-svg.md) — namespace propagation, `toElement`.
108
- 8. [API reference](./docs/8-api-reference.md) — every export, every option.
109
- 9. [Live demo](./docs/9-live-demo.md) — the GitHub Pages deploy of `examples/reactivity-demo`.
109
+ ## Install
110
110
 
111
- ### For AI coding assistants
111
+ ```bash
112
+ npm install kerfjs
113
+ ```
112
114
 
113
- If you are an AI assistant (Claude, Cursor, Copilot, etc.) helping a user choose or use kerf, start here:
115
+ ```jsonc
116
+ // tsconfig.json
117
+ {
118
+ "compilerOptions": {
119
+ "jsx": "react-jsx",
120
+ "jsxImportSource": "kerfjs"
121
+ }
122
+ }
123
+ ```
114
124
 
115
- - [`docs/ai/usage-guide.md`](./docs/ai/usage-guide.md) — when to recommend kerf, the full public API at a glance, the four core patterns, hard rules, and a common-errors → fixes table. Designed to be read once before writing kerf code.
116
- - [`llms.txt`](./llms.txt) — top-level index of every doc, in the [llmstxt.org](https://llmstxt.org) format.
125
+ ## Links
117
126
 
118
- ## Examples
127
+ - **Site:** [brianwestphal.github.io/kerf](https://brianwestphal.github.io/kerf/)
128
+ - **Docs:** [`docs/`](./docs/) — overview · reactivity · stores · render · events · jsx · svg · [API reference](./docs/8-api-reference.md)
129
+ - **AI guide:** [`docs/ai/usage-guide.md`](./docs/ai/usage-guide.md) — read once before writing kerf code with an LLM
130
+ - **Demo:** [live demo](https://brianwestphal.github.io/kerf/demo/) — seven sections exercising every primitive
131
+ - **Repo:** [github.com/brianwestphal/kerf](https://github.com/brianwestphal/kerf)
119
132
 
120
- [`examples/reactivity-demo/`](./examples/reactivity-demo) is a 7-section live demo exercising every primitive: counter, multi-consumer store, focus survival across re-renders, keyed list with identity preservation, morph-skip for library-owned subtrees, JSX-rendered SVG, and capture-phase event delegation.
133
+ ## Why "kerf"?
121
134
 
122
- Play with it live at **[brianwestphal.github.io/kerf](https://brianwestphal.github.io/kerf/)**, or run it locally:
135
+ A *kerf* is the narrow strip of material a saw blade removes when cutting — the smallest possible cut. The framework's job is the same: apply the smallest possible mutation to update your DOM.
123
136
 
124
- ```bash
125
- npm run example:reactivity-demo
126
- ```
137
+ (And yes, ~~kerformance~~ → *performance* jokes were written. They were also rejected.)
127
138
 
128
139
  ## Status
129
140
 
package/dist/index.d.ts CHANGED
@@ -14,11 +14,19 @@ export { S as Store, d as defineStore, r as resetAllStores } from './testing-CdM
14
14
  * click, input, change, submit, mousedown/up, keydown/up, pointerdown/up/move,
15
15
  * drag*, drop, contextmenu, wheel, copy/paste/cut, focusin/focusout.
16
16
  *
17
- * - Tier 2 (non-bubbling events) — use `delegateCapture()`.
18
- * focus, blur, scroll, load, error, mouseenter, mouseleave.
19
- * The capture phase fires on the way down from the root to the target,
20
- * so a root-level listener with `capture: true` reaches events that
21
- * wouldn't bubble back up.
17
+ * `delegate()` also auto-promotes the well-known non-bubbling event
18
+ * types (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`,
19
+ * `mouseleave`) to the capture phase under the hood, so the call site
20
+ * looks identical for "interactive thing happens on a descendant"
21
+ * regardless of whether that event bubbles. Selector matching stays
22
+ * `closest()`-style — the same as for bubbling events — so a wrapper
23
+ * selector like `'.field-row'` still matches when the event fires on
24
+ * a descendant `<input>`.
25
+ *
26
+ * - Tier 2 (explicit capture) — use `delegateCapture()`.
27
+ * The escape hatch for cases the auto-promotion list doesn't cover, or
28
+ * when you want capture-phase semantics and direct `matches()`-style
29
+ * selector matching (no walk-up).
22
30
  *
23
31
  * - Tier 3 (per-element instances / library-owned subtrees) — mark the
24
32
  * host element with `data-morph-skip` and manage the library's
@@ -26,15 +34,18 @@ export { S as Store, d as defineStore, r as resetAllStores } from './testing-CdM
26
34
  */
27
35
  type Handler = (event: Event, target: Element) => void;
28
36
  /**
29
- * Bubble-phase delegation. Installs ONE listener on `rootEl` for the given
30
- * event type. When the event fires, walks up from `event.target` to the root
31
- * looking for an element matching `selector`; if found, fires `handler` with
32
- * the matched element as the second arg.
37
+ * Delegation that "just works" for both bubbling and the common non-bubbling
38
+ * events. Installs ONE listener on `rootEl`; for known non-bubblers (see
39
+ * `NON_BUBBLING` above) the listener is registered on the capture phase so
40
+ * it actually reaches the target, otherwise on the bubble phase. Either way,
41
+ * matching walks up from `event.target` via `closest(selector)` and fires
42
+ * `handler(event, matched)` if the match is inside `rootEl`.
33
43
  *
34
44
  * Returns a disposer that removes the listener.
35
45
  *
36
46
  * Usage (pseudo-code — see examples for live ones):
37
47
  * delegate(rootEl, 'click', '[data-action="add"]', handlerFn);
48
+ * delegate(rootEl, 'focus', 'input', handlerFn); // auto-capture
38
49
  */
39
50
  declare function delegate(rootEl: HTMLElement, type: string, selector: string, handler: Handler): () => void;
40
51
  /**
package/dist/index.js CHANGED
@@ -4,6 +4,15 @@ import { effect } from './chunk-IZJIKRCE.js';
4
4
  export { batch, computed, defineStore, effect, resetAllStores, signal } from './chunk-IZJIKRCE.js';
5
5
 
6
6
  // src/delegate.ts
7
+ var NON_BUBBLING = /* @__PURE__ */ new Set([
8
+ "focus",
9
+ "blur",
10
+ "scroll",
11
+ "load",
12
+ "error",
13
+ "mouseenter",
14
+ "mouseleave"
15
+ ]);
7
16
  function assertValidSelector(selector, fn) {
8
17
  try {
9
18
  document.createElement("div").matches(selector);
@@ -23,9 +32,10 @@ function delegate(rootEl, type, selector, handler) {
23
32
  handler(event, matched);
24
33
  }
25
34
  };
26
- rootEl.addEventListener(type, listener);
35
+ const capture = NON_BUBBLING.has(type);
36
+ rootEl.addEventListener(type, listener, capture);
27
37
  return () => {
28
- rootEl.removeEventListener(type, listener);
38
+ rootEl.removeEventListener(type, listener, capture);
29
39
  };
30
40
  }
31
41
  function delegateCapture(rootEl, type, selector, handler) {
@@ -54,6 +64,11 @@ function each(items, render, key) {
54
64
  const segItems = new Array(items.length);
55
65
  for (let i = 0; i < items.length; i++) {
56
66
  const item = items[i];
67
+ if (typeof item !== "object" || item === null) {
68
+ throw new Error(
69
+ `each(): items must be objects (the per-item HTML cache is a WeakMap), got ${item === null ? "null" : typeof item} at index ${i}. Wrap primitives if you need to iterate them, e.g. items.map(v => ({ v })).`
70
+ );
71
+ }
57
72
  const k = key ? key(item, i) : void 0;
58
73
  const cached = ROW_CACHE.get(item);
59
74
  let html;
@@ -201,6 +216,35 @@ function preserveTextEntryState(fromEl, toEl) {
201
216
  }
202
217
  }
203
218
 
219
+ // src/list-reconcile-focus.ts
220
+ function captureFocus(liveParent) {
221
+ const active = document.activeElement;
222
+ if (active === null || active === document.body) return null;
223
+ if (!liveParent.contains(active)) return null;
224
+ const el = active;
225
+ let selStart = null;
226
+ let selEnd = null;
227
+ if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
228
+ try {
229
+ selStart = el.selectionStart;
230
+ selEnd = el.selectionEnd;
231
+ } catch {
232
+ }
233
+ }
234
+ return { el, selStart, selEnd };
235
+ }
236
+ function restoreFocus(snap) {
237
+ if (document.activeElement === snap.el) return;
238
+ if (!snap.el.isConnected) return;
239
+ snap.el.focus();
240
+ if (snap.selStart !== null && snap.selEnd !== null) {
241
+ try {
242
+ snap.el.setSelectionRange(snap.selStart, snap.selEnd);
243
+ } catch {
244
+ }
245
+ }
246
+ }
247
+
204
248
  // src/list-reconcile.ts
205
249
  function classifyItems(oldItems, listSeg) {
206
250
  const oldByRef = /* @__PURE__ */ new Map();
@@ -303,14 +347,21 @@ function reconcileList(binding, listSeg) {
303
347
  const { liveParent } = binding;
304
348
  const { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls } = classifyItems(binding.items, listSeg);
305
349
  buildFreshNodes(newRecord, freshIndices, freshHtmls);
350
+ const focusSnap = captureFocus(liveParent);
306
351
  removeOldNodes(liveParent, replacedNodes);
307
352
  applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx));
353
+ if (focusSnap !== null) restoreFocus(focusSnap);
308
354
  binding.items = newRecord;
309
355
  }
310
356
 
311
357
  // src/mount.ts
312
358
  var LIST_MARKER_PREFIX = "kf-list:";
313
359
  function mount(rootEl, render) {
360
+ if (rootEl == null) {
361
+ throw new Error(
362
+ 'mount: rootEl is null/undefined \u2014 pass the live element, e.g. mount(document.getElementById("app")!, render). A common cause is a typo in the id or selector that returns null at runtime even though the TypeScript types say HTMLElement.'
363
+ );
364
+ }
314
365
  const bindings = /* @__PURE__ */ new Map();
315
366
  const counter = { value: 0 };
316
367
  let isFirst = true;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/delegate.ts","../src/each.ts","../src/diff.ts","../src/list-reconcile.ts","../src/mount.ts","../src/toElement.ts"],"names":[],"mappings":"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","file":"index.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 * - Tier 2 (non-bubbling events) — use `delegateCapture()`.\n * focus, blur, scroll, load, error, mouseenter, mouseleave.\n * The capture phase fires on the way down from the root to the target,\n * so a root-level listener with `capture: true` reaches events that\n * wouldn't bubble back up.\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\ntype Handler = (event: Event, target: Element) => void;\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 * Bubble-phase delegation. Installs ONE listener on `rootEl` for the given\n * event type. When the event fires, walks up from `event.target` to the root\n * looking for an element matching `selector`; if found, fires `handler` with\n * the matched element as the second arg.\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 */\nexport function delegate(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: Handler,\n): () => void {\n assertValidSelector(selector, 'delegate');\n const listener = (event: Event): void => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const matched = target.closest(selector);\n if (matched !== null && rootEl.contains(matched)) {\n handler(event, matched);\n }\n };\n rootEl.addEventListener(type, listener);\n return () => {\n rootEl.removeEventListener(type, listener);\n };\n}\n\n/**\n * Capture-phase delegation — for non-bubbling events (`focus`, `blur`,\n * `scroll`, `load`, `error`). Reaches descendants of `rootEl` that match\n * `selector` regardless of how many times the diff has rebuilt them.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);\n */\nexport function delegateCapture(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: Handler,\n): () => void {\n assertValidSelector(selector, 'delegateCapture');\n const listener = (event: Event): void => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n if (target.matches(selector) && rootEl.contains(target)) {\n handler(event, target);\n }\n };\n rootEl.addEventListener(type, listener, true);\n return () => {\n rootEl.removeEventListener(type, listener, true);\n };\n}\n","/**\n * `each(items, render, key?)` — keyed list iteration with per-item memoisation.\n *\n * Drops in as the body of a list-rendering JSX expression inside a `mount()`\n * render function. Returns a `SafeHtml` carrying a structured list segment,\n * so `mount()` can run a native keyed reconciler instead of the general-\n * purpose morph for these children.\n *\n * Two layers of optimisation:\n *\n * 1. Per-item memoisation. `render(item)` is skipped for items whose object\n * identity (and optional `key`) are unchanged since the previous call.\n * Their HTML strings come from a `WeakMap` keyed by item reference. The\n * immutable-update style (\"replace the row object\" instead of \"mutate it\")\n * makes the cache work automatically.\n *\n * 2. Structural handoff. `mount()` recognises the list segment and bypasses\n * the parse-the-whole-table round trip: only fresh items get parsed (one\n * at a time, into the smallest detached element), and only changed rows\n * get patched in the live DOM. Unchanged rows are physically the same\n * nodes they were before — never visited.\n *\n * `key` covers the case where external state, not the item itself, drives\n * what the row should render (e.g. a \"currently selected\" id flips a CSS\n * class on one row). Same item identity but a different `key` value means\n * \"re-render this item.\" If you don't pass `key`, only identity changes\n * invalidate.\n *\n * Items must be objects (cache is a `WeakMap`); wrap primitives if you need\n * to iterate them. Each item's render output must produce exactly one\n * top-level element — the list reconciler binds one live DOM node per item.\n */\n\nimport type { SafeHtml } from './jsx-runtime.js';\nimport { isSafeHtml, listSafeHtml } from './jsx-runtime.js';\n\ninterface CacheEntry {\n key: unknown;\n html: string;\n}\n\nconst ROW_CACHE = new WeakMap<object, CacheEntry>();\n\n/**\n * Per-mount counter for assigning stable list ids across renders. `mount()`\n * sets this at the start of each render so that the n-th `each()` call\n * produces id \"n\" every render — the binding to the live parent persists.\n *\n * Outside a mount() render, calls to `each()` still work but get the\n * sentinel id \"orphan\"; their output flattens correctly via `toString()`\n * but the structural fast-path doesn't apply (no `mount()` is watching).\n */\nlet listCounter: { value: number } | null = null;\n\nexport function _setListCounter(c: { value: number } | null): void {\n listCounter = c;\n}\n\nexport function each<T extends object>(\n items: readonly T[],\n render: (item: T, index: number) => SafeHtml | string,\n key?: (item: T, index: number) => unknown,\n): SafeHtml {\n const id = listCounter !== null ? String(listCounter.value++) : 'orphan';\n const segItems = new Array<{ ref: object; cacheKey: unknown; html: string }>(items.length);\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key ? key(item, i) : undefined;\n const cached = ROW_CACHE.get(item);\n let html: string;\n if (cached !== undefined && cached.key === k) {\n html = cached.html;\n } else {\n const out = render(item, i);\n html = isSafeHtml(out) ? out.toString() : out;\n ROW_CACHE.set(item, { key: k, html });\n }\n segItems[i] = { ref: item, cacheKey: k, html };\n }\n return listSafeHtml(id, segItems);\n}\n","/**\n * `diff(liveRoot, templateRoot, listParents)` — minimum-mutation DOM\n * reconciliation.\n *\n * Replaces our previous dependency on `morphdom`. The algorithm is the\n * classic two-tree walk: match children by key (id, then data-key, then\n * positional same-tag), morph matches in place, insert / remove / clone\n * the rest. Specialised for what kerf needs:\n *\n * - `childrenOnly` is always true; the live root is never replaced.\n * - Three short-circuit paths on each element:\n * 1. `data-morph-skip`: subtree is library-owned, leave verbatim.\n * 2. `isEqualNode`: subtree is byte-identical, no work needed.\n * 3. `listParents` membership: a kerf list reconciler owns this\n * element's children, so we morph attributes only and stop.\n * - Focused text inputs (`<input>`/`<textarea>`) keep their value +\n * selection across the morph; focused `[contenteditable]` keeps its\n * entire subtree (typed content + caret + multi-range selection).\n *\n * Algorithm credit: based on the design of\n * https://github.com/patrick-steele-idem/morphdom by Patrick Steele-Idem\n * (MIT licensed). Reimplemented here so kerf can specialise the hot paths\n * (segment-aware list dispatch, lighter callback surface) and drop the\n * runtime dependency. Original copyright preserved in `LICENSE`.\n */\n\nconst ID_KEY_PREFIX = 'id:';\nconst DATA_KEY_PREFIX = 'data-key:';\nconst ELEMENT_NODE = 1;\nconst TEXT_NODE = 3;\nconst COMMENT_NODE = 8;\n\nfunction getNodeKey(node: Node): string | undefined {\n if (node.nodeType !== ELEMENT_NODE) return undefined;\n const el = node as HTMLElement;\n if (el.id !== '') return `${ID_KEY_PREFIX}${el.id}`;\n if (el.dataset !== undefined && el.dataset.key !== undefined) {\n return `${DATA_KEY_PREFIX}${el.dataset.key}`;\n }\n return undefined;\n}\n\n/**\n * Reconcile the children of `liveRoot` to match the children of\n * `templateRoot`. `listParents` is the set of live elements whose children\n * are managed by `each()`'s reconciler — they get attribute morphing but\n * not children diffing.\n */\nexport function diff(\n liveRoot: Element,\n templateRoot: Element,\n listParents: ReadonlySet<Element>,\n): void {\n diffChildren(liveRoot, templateRoot, listParents);\n}\n\nfunction diffChildren(\n fromParent: Element,\n toParent: Element,\n listParents: ReadonlySet<Element>,\n): void {\n // Build a keyed lookup from the live children so we can match by key\n // even after reorders.\n const keyed = new Map<string, Element>();\n for (let c = fromParent.firstChild; c !== null; c = c.nextSibling) {\n const k = getNodeKey(c);\n if (k !== undefined) keyed.set(k, c as Element);\n }\n\n let fromChild: Node | null = fromParent.firstChild;\n let toChild: Node | null = toParent.firstChild;\n\n while (toChild !== null) {\n const toNext = toChild.nextSibling;\n let matched: Node | null = null;\n\n // 1. Try key match.\n const toKey = getNodeKey(toChild);\n if (toKey !== undefined && keyed.has(toKey)) {\n matched = keyed.get(toKey) as Element;\n keyed.delete(toKey);\n if (matched !== fromChild) {\n fromParent.insertBefore(matched, fromChild);\n } else {\n fromChild = fromChild.nextSibling;\n }\n }\n\n // 2. Fall back to positional match — same nodeType, same tag (for\n // elements), and the live node has no key (a keyed node only\n // matches by key, never positionally, so reorders work).\n if (matched === null && fromChild !== null\n && fromChild.nodeType === toChild.nodeType\n && (toChild.nodeType !== ELEMENT_NODE\n || ((fromChild as Element).tagName === (toChild as Element).tagName\n && getNodeKey(fromChild) === undefined))) {\n matched = fromChild;\n fromChild = fromChild.nextSibling;\n }\n\n if (matched !== null) {\n morphNode(matched, toChild, listParents);\n } else {\n // 3. No match — clone the new node and insert.\n const cloned = toChild.cloneNode(true);\n fromParent.insertBefore(cloned, fromChild);\n }\n\n toChild = toNext;\n }\n\n // 4. Anything past the cursor is unmatched — remove. Any keyed node that\n // wasn't matched in the toParent walk falls into this trailing range\n // by construction (matched keyed nodes get moved before the cursor;\n // unmatched ones stay at or after it), so we don't need a separate\n // orphan pass.\n while (fromChild !== null) {\n const next = fromChild.nextSibling;\n fromParent.removeChild(fromChild);\n fromChild = next;\n }\n}\n\nfunction morphNode(\n fromNode: Node,\n toNode: Node,\n listParents: ReadonlySet<Element>,\n): void {\n if (fromNode.nodeType === ELEMENT_NODE) {\n morphElement(fromNode as Element, toNode as Element, listParents);\n return;\n }\n if (fromNode.nodeType === TEXT_NODE || fromNode.nodeType === COMMENT_NODE) {\n const fromText = fromNode as CharacterData;\n const toText = toNode as CharacterData;\n if (fromText.data !== toText.data) fromText.data = toText.data;\n }\n}\n\nfunction morphElement(\n fromEl: Element,\n toEl: Element,\n listParents: ReadonlySet<Element>,\n): void {\n if (fromEl.tagName !== toEl.tagName) {\n const replacement = toEl.cloneNode(true);\n fromEl.parentNode?.replaceChild(replacement, fromEl);\n return;\n }\n // 1. Library-owned subtree — leave verbatim.\n if ((fromEl as HTMLElement).dataset.morphSkip !== undefined) return;\n // 2. Byte-identical — nothing to do.\n if (fromEl.isEqualNode(toEl)) return;\n // 3. Focused contenteditable — preserve user's in-progress edit.\n if (fromEl === document.activeElement) {\n const ce = fromEl.getAttribute('contenteditable');\n if (ce !== null && ce.toLowerCase() !== 'false') return;\n if (isTextInputOrTextarea(fromEl)) preserveTextEntryState(fromEl, toEl);\n }\n morphAttributes(fromEl, toEl);\n // 4. List parent — its children are managed by the each() reconciler;\n // diff stops here. We still ran morphAttributes above, so attribute\n // changes on the parent itself (id, class, data-* …) propagate.\n if (listParents.has(fromEl)) return;\n diffChildren(fromEl, toEl, listParents);\n}\n\nfunction morphAttributes(fromEl: Element, toEl: Element): void {\n // Set/update every attribute on toEl.\n const toAttrs = toEl.attributes;\n for (let i = 0; i < toAttrs.length; i++) {\n const attr = toAttrs[i];\n const ns = attr.namespaceURI;\n const name = attr.localName;\n const value = attr.value;\n if (ns !== null) {\n if (fromEl.getAttributeNS(ns, name) !== value) {\n fromEl.setAttributeNS(ns, attr.name, value);\n }\n } else if (fromEl.getAttribute(name) !== value) {\n fromEl.setAttribute(name, value);\n }\n }\n // Remove attributes that are no longer present on toEl.\n const fromAttrs = fromEl.attributes;\n for (let i = fromAttrs.length - 1; i >= 0; i--) {\n const attr = fromAttrs[i];\n const ns = attr.namespaceURI;\n const name = attr.localName;\n if (ns !== null) {\n if (!toEl.hasAttributeNS(ns, name)) fromEl.removeAttributeNS(ns, name);\n } else if (!toEl.hasAttribute(name)) {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nfunction isTextInputOrTextarea(el: Element): boolean {\n if (el.tagName === 'TEXTAREA') return true;\n if (el.tagName === 'INPUT') {\n const type = (el as HTMLInputElement).type;\n return type === 'text' || type === 'search' || type === 'url' || type === 'email'\n || type === 'tel' || type === 'password' || type === '';\n }\n return false;\n}\n\nfunction preserveTextEntryState(fromEl: Element, toEl: Element): void {\n if (fromEl.tagName === 'TEXTAREA' || fromEl.tagName === 'INPUT') {\n const fromInput = fromEl as HTMLInputElement;\n const toInput = toEl as HTMLInputElement;\n toInput.value = fromInput.value;\n try {\n toInput.setSelectionRange(fromInput.selectionStart, fromInput.selectionEnd);\n } catch {\n // Some input types (number, range, color, …) reject selection APIs.\n }\n }\n}\n","/**\n * Keyed list reconciler — the engine behind `each(...)` inside `mount()`.\n *\n * Given a `ListBinding` (the live parent + the items currently mounted under\n * it) and a fresh `ListSegment` from this render, mutate the live DOM with\n * the minimum number of operations:\n *\n * 1. Classify each new item as `stable` (cache hit on identity + html),\n * `replaced` (same ref, html changed), or `new` (never seen before).\n * 2. Bulk-parse every fresh row's HTML in ONE `innerHTML` call — for an\n * initial population of 10k rows that's 1 parse instead of 10k.\n * 3. Remove orphan refs (gone from the new list) + replaced rows' old nodes.\n * 4. Compute a longest-increasing-subsequence over old positions; items in\n * the LIS are already in the right relative order, so they don't move.\n * 5. Reverse-pass over the new record, `insertBefore` only the items that\n * didn't anchor the LIS.\n *\n * Lives in its own file so `mount.ts` can stay an orchestrator under the\n * 200-LOC guideline. Internal to kerf — not part of the public API.\n */\n\nimport type { ListSegment } from './segment.js';\n\nexport interface BoundItem {\n ref: object;\n cacheKey: unknown;\n html: string;\n node: Element;\n}\n\nexport interface ListBinding {\n liveParent: Element;\n /**\n * One entry per item currently mounted under `liveParent`, in order.\n * Mirrors the segment's `items` length after each reconcile.\n */\n items: BoundItem[];\n}\n\ninterface Classification {\n newRecord: BoundItem[];\n prevIdx: number[];\n replacedNodes: Element[];\n freshIndices: number[];\n freshHtmls: string[];\n}\n\n/**\n * Classify each item in the new segment against the old binding. Items with\n * a cache-hit (same ref + same html) are reused; replaced/new items get a\n * placeholder `BoundItem` whose `node` is filled in by the bulk parse below.\n */\nfunction classifyItems(oldItems: BoundItem[], listSeg: ListSegment): Classification {\n const oldByRef = new Map<object, BoundItem>();\n const oldIndex = new Map<object, number>();\n for (let i = 0; i < oldItems.length; i++) {\n oldByRef.set(oldItems[i].ref, oldItems[i]);\n oldIndex.set(oldItems[i].ref, i);\n }\n\n const newRecord: BoundItem[] = new Array(listSeg.items.length);\n const prevIdx = new Array<number>(listSeg.items.length);\n const replacedNodes: Element[] = [];\n const freshIndices: number[] = [];\n const freshHtmls: string[] = [];\n\n for (let i = 0; i < listSeg.items.length; i++) {\n const ni = listSeg.items[i];\n const oi = oldByRef.get(ni.ref);\n if (oi !== undefined) {\n oldByRef.delete(ni.ref);\n if (oi.html === ni.html) {\n newRecord[i] = oi;\n prevIdx[i] = oldIndex.get(ni.ref) as number;\n continue;\n }\n replacedNodes.push(oi.node);\n }\n newRecord[i] = {\n ref: ni.ref, cacheKey: ni.cacheKey, html: ni.html, node: null as unknown as Element,\n };\n prevIdx[i] = -1;\n freshIndices.push(i);\n freshHtmls.push(ni.html);\n }\n\n // Whatever's left in oldByRef is an orphan ref that disappeared from the\n // new list. The caller removes those nodes.\n for (const [, orphan] of oldByRef) replacedNodes.push(orphan.node);\n // Distinguish: replacedNodes here mixes \"old node for replaced row\" and\n // \"orphan node\". That's fine — the caller removes both unconditionally.\n\n return { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls };\n}\n\n/**\n * Bulk-parse every fresh row's HTML in one `innerHTML` call, then walk the\n * parsed children in order and fill in each placeholder's `node` field.\n */\nfunction buildFreshNodes(\n newRecord: BoundItem[],\n freshIndices: number[],\n freshHtmls: string[],\n): void {\n if (freshHtmls.length === 0) return;\n const tpl = document.createElement('template');\n tpl.innerHTML = freshHtmls.join('');\n let node = tpl.content.firstElementChild;\n for (const idx of freshIndices) {\n if (node === null) {\n throw new Error(\n `each(): row render produced no top-level element. Each item's render must return exactly one element. Got HTML: ${newRecord[idx].html.slice(0, 120)}`,\n );\n }\n const next = node.nextElementSibling;\n newRecord[idx].node = node;\n node = next;\n }\n}\n\n/**\n * Remove every replaced/orphan node still attached to the live parent.\n */\nfunction removeOldNodes(liveParent: Element, replacedNodes: Element[]): void {\n for (const node of replacedNodes) {\n if (node.parentElement === liveParent) liveParent.removeChild(node);\n }\n}\n\n/**\n * `insertBefore` everything that's not in the LIS. Walks the new record in\n * reverse so each move's anchor (`nextSibling`) is already in its final\n * position by the time we reach earlier items.\n */\nfunction applyMoves(\n liveParent: Element,\n newRecord: BoundItem[],\n prevIdx: number[],\n stable: ReadonlySet<number>,\n): void {\n let nextSibling: Element | null = null;\n for (let i = newRecord.length - 1; i >= 0; i--) {\n const node = newRecord[i].node;\n if (prevIdx[i] === -1 || !stable.has(i)) {\n liveParent.insertBefore(node, nextSibling);\n }\n nextSibling = node;\n }\n}\n\n/**\n * Patience-sort LIS: returns the *set of indices* in `arr` that participate\n * in a longest strictly-increasing subsequence. `-1` entries (representing\n * brand-new items in the new list) are ignored — they can't anchor the\n * subsequence and shouldn't count as stable.\n */\nfunction lis(arr: ReadonlyArray<number>): ReadonlySet<number> {\n const tails: number[] = [];\n const tailIdx: number[] = [];\n const prev = new Array<number>(arr.length);\n for (let i = 0; i < arr.length; i++) {\n const v = arr[i];\n if (v === -1) {\n prev[i] = -1;\n continue;\n }\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (tails[mid] < v) lo = mid + 1;\n else hi = mid;\n }\n prev[i] = lo > 0 ? tailIdx[lo - 1] : -1;\n tails[lo] = v;\n tailIdx[lo] = i;\n }\n const out = new Set<number>();\n let k = tailIdx.length > 0 ? tailIdx[tailIdx.length - 1] : -1;\n while (k !== -1) {\n out.add(k);\n k = prev[k];\n }\n return out;\n}\n\n/**\n * Reconcile `binding`'s live parent against `listSeg`. Mutates `binding.items`\n * to mirror the new segment when done.\n */\nexport function reconcileList(binding: ListBinding, listSeg: ListSegment): void {\n const { liveParent } = binding;\n const { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls }\n = classifyItems(binding.items, listSeg);\n buildFreshNodes(newRecord, freshIndices, freshHtmls);\n removeOldNodes(liveParent, replacedNodes);\n applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx));\n binding.items = newRecord;\n}\n","/**\n * `mount(rootEl, render)` — kerf's render primitive.\n *\n * Wraps `effect()` so that whenever any signal read inside `render()`\n * changes, we re-run `render()` and apply the minimum DOM mutations against\n * the live tree. Element identity (and thus focus, selection, in-flight\n * pointer interactions, and event listeners on preserved nodes) is preserved\n * wherever the keyed/positional diff matches.\n *\n * Two phases per render:\n *\n * - Static surrounds (everything outside `each()` lists): kerf's native\n * `diff()` reconciler walks a freshly-built template against the live\n * tree. Conventions: id/data-key matching, `data-morph-skip`, focus\n * preservation.\n *\n * - List interiors (children of every `each()` parent): native keyed\n * reconciler operates directly on the live parent's children. No\n * re-parse, no morph walk for cache-hit rows. Cost is O(changes), not\n * O(rows).\n *\n * Compared to a `replaceChildren(...rows.map(toElement))` rebuild pattern,\n * the user-visible win is that an `<input>` the user is typing into\n * survives an unrelated re-render — its DOM node, focus state, and cursor\n * position are not destroyed and recreated on each tick.\n */\n\nimport { diff } from './diff.js';\nimport { _setListCounter } from './each.js';\nimport type { SafeHtml } from './jsx-runtime.js';\nimport { isSafeHtml } from './jsx-runtime.js';\nimport {\n type BoundItem,\n type ListBinding,\n reconcileList,\n} from './list-reconcile.js';\nimport { effect } from './reactive.js';\nimport {\n collectLists,\n flatten,\n flattenWithoutListItems,\n type ListSegment,\n type Segment,\n} from './segment.js';\n\nconst LIST_MARKER_PREFIX = 'kf-list:';\n\n/**\n * Bind `render()` to the children of `rootEl`. Re-runs whenever any signal\n * read inside `render()` changes. Returns a disposer that tears down the\n * effect; call it when the host element is removed from the DOM.\n *\n * Conventions:\n *\n * - Diff keys: `id` and `data-key` are matched across the morph by key\n * rather than positionally, so list reorders move existing nodes instead\n * of churning unrelated siblings.\n * - `data-morph-skip`: any element with this attribute is left untouched\n * inside on subsequent renders. Used for library-owned subtrees (xterm-\n * style widgets, charts, third-party editors) where the library's own\n * lifecycle manages the children.\n * - Focused text-entry inputs (`<input>` of typing kinds, `<textarea>`)\n * keep their current value + selection range across morphs while focused.\n * The user never sees their cursor jump mid-keystroke.\n * - Focused `[contenteditable]` elements have their entire subtree\n * skipped (same mechanism as `data-morph-skip`). The user's in-progress\n * edit — typed content, caret position, multi-range selections, anything\n * else they did to the DOM — survives verbatim. The next render after\n * blur catches up.\n */\nexport function mount(rootEl: HTMLElement, render: () => SafeHtml | string): () => void {\n const bindings = new Map<string, ListBinding>();\n const counter = { value: 0 };\n let isFirst = true;\n\n return effect(() => {\n counter.value = 0;\n _setListCounter(counter);\n let result: SafeHtml | string;\n try {\n result = render();\n } finally {\n _setListCounter(null);\n }\n\n const segment: Segment = isSafeHtml(result)\n ? (result.__segment ?? { kind: 'static', html: result.__html })\n : { kind: 'static', html: result };\n\n if (isFirst) {\n // Bulk-render with items inlined and a marker per list. The marker walk\n // afterwards binds each list to the rows already in the DOM, so the\n // first-render reconcile is a no-op (every item is a cache hit).\n rootEl.innerHTML = flatten(segment, true);\n bindListsFromMarkers(rootEl, segment, bindings);\n isFirst = false;\n } else {\n // Static-surrounds-only render: lists become marker-only in the\n // template. `diff()` skips bound list parents' children entirely\n // (so existing rows stay) and inserts the marker for any list that\n // didn't exist before. The marker walk afterwards binds those new\n // lists; the per-list reconcile below patches every list's items.\n const template = rootEl.cloneNode(false) as HTMLElement;\n template.innerHTML = flattenWithoutListItems(segment);\n const listParents = new Set<Element>();\n for (const b of bindings.values()) listParents.add(b.liveParent);\n diff(rootEl, template, listParents);\n bindListsFromMarkers(rootEl, segment, bindings);\n }\n\n for (const listSeg of collectLists(segment).values()) {\n const binding = bindings.get(listSeg.id) as ListBinding;\n reconcileList(binding, listSeg);\n }\n });\n}\n\n/**\n * Walk the live tree's comment nodes; every `<!--kf-list:{id}-->` marker\n * is the first child of a list parent. Record the binding (parent + the\n * already-rendered item nodes that follow the marker) and then remove the\n * marker — bindings live in JS, not in the DOM.\n */\nfunction bindListsFromMarkers(\n rootEl: Element,\n segment: Segment,\n bindings: Map<string, ListBinding>,\n): void {\n const lists = collectLists(segment);\n const found: Comment[] = [];\n collectComments(rootEl, found);\n for (const marker of found) {\n if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;\n const id = marker.data.slice(LIST_MARKER_PREFIX.length);\n // Markers are only emitted by `flatten(..., true)` paired with a list\n // segment in the same render, so both lookups always succeed here.\n const listSeg = lists.get(id) as ListSegment;\n const liveParent = marker.parentElement as Element;\n // On first render, items are inlined right after the marker (so the\n // first element sibling is `items[0]`, the next is `items[1]`, etc.).\n // On subsequent renders for a list newly appearing, the list parent\n // is empty after the marker; the loop falls through with `items=[]`\n // and the reconcile phase below builds the items.\n const items: BoundItem[] = [];\n let next: Element | null = marker.nextElementSibling;\n for (let i = 0; i < listSeg.items.length && next !== null; i++) {\n items.push({\n ref: listSeg.items[i].ref,\n cacheKey: listSeg.items[i].cacheKey,\n html: listSeg.items[i].html,\n node: next,\n });\n next = next.nextElementSibling;\n }\n bindings.set(id, { liveParent, items });\n marker.remove();\n }\n}\n\n/**\n * Recursive collector for comment nodes — happy-dom's `TreeWalker` doesn't\n * surface `Node.COMMENT_NODE` despite accepting `NodeFilter.SHOW_COMMENT`,\n * so we walk children directly. Cheap (O(elements)) and portable.\n */\nfunction collectComments(node: Node, out: Comment[]): void {\n for (let c: Node | null = node.firstChild; c !== null; c = c.nextSibling) {\n if (c.nodeType === Node.COMMENT_NODE) out.push(c as Comment);\n else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);\n }\n}\n\n\n","/**\n * `toElement(jsx)` — JSX → DOM, with SVG-aware namespace handling.\n *\n * The naive implementation parses JSX through a `<template>` element's\n * `innerHTML`. That works for HTML and for SVG fragments whose root tag is\n * `<svg>` (the parser switches to \"foreign content\" mode). It silently\n * fails for SVG fragments WITHOUT an `<svg>` wrapper — descendants come out\n * as `HTMLUnknownElement` and never paint.\n *\n * `toElement` detects SVG content and routes through `DOMParser` with the\n * `image/svg+xml` MIME, which guarantees correct namespacing for all\n * descendants. HTML content takes the original `<template>` path unchanged.\n */\n\nimport type { SafeHtml } from './jsx-runtime.js';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nconst SVG_FRAGMENT_TAGS = new Set([\n 'g', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'ellipse',\n 'text', 'tspan', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern',\n 'filter', 'marker', 'linearGradient', 'radialGradient', 'stop', 'image',\n 'foreignObject',\n]);\n\nconst EXCERPT_MAX_LEN = 100;\n\nfunction leadingTag(html: string): string | null {\n const match = /^\\s*<([a-zA-Z][a-zA-Z0-9]*)\\b/.exec(html);\n return match !== null ? match[1] : null;\n}\n\nfunction excerpt(html: string): string {\n const trimmed = html.trim();\n return trimmed.length > EXCERPT_MAX_LEN ? `${trimmed.slice(0, EXCERPT_MAX_LEN)}…` : trimmed;\n}\n\nfunction parseSvgOrThrow(html: string, label: string, originalHtml: string): Document {\n const doc = new DOMParser().parseFromString(html, 'image/svg+xml');\n const err = doc.querySelector('parsererror');\n if (err !== null) {\n throw new Error(`toElement: ${label} parse error — ${err.textContent}\\n input: ${excerpt(originalHtml)}`);\n }\n return doc;\n}\n\nexport function toElement(jsx: SafeHtml | string): Element {\n const html = typeof jsx === 'string' ? jsx : jsx.toString();\n const tag = leadingTag(html);\n\n if (tag === 'svg') {\n // SVG root — parse as XML to guarantee namespace propagation.\n return parseSvgOrThrow(html, 'SVG', html).documentElement;\n }\n\n if (tag !== null && SVG_FRAGMENT_TAGS.has(tag)) {\n // SVG fragment without an <svg> wrapper — wrap, parse, unwrap.\n const wrapped = `<svg xmlns=\"${SVG_NS}\">${html}</svg>`;\n const doc = parseSvgOrThrow(wrapped, 'SVG fragment', html);\n const first = doc.documentElement.firstElementChild;\n /* c8 ignore next 2 — defensive: a successful XML parse of a wrapped svg always yields ≥1 child. */\n if (first === null) throw new Error(`toElement: SVG fragment produced no element\\n input: ${excerpt(html)}`);\n return first;\n }\n\n // HTML — `<template>`-based parse.\n const t = document.createElement('template');\n t.innerHTML = html;\n const child = t.content.firstElementChild;\n if (child === null) throw new Error(`toElement: produced no element\\n input: ${excerpt(html)}`);\n return child;\n}\n"]}
1
+ {"version":3,"sources":["../src/delegate.ts","../src/each.ts","../src/diff.ts","../src/list-reconcile-focus.ts","../src/list-reconcile.ts","../src/mount.ts","../src/toElement.ts"],"names":[],"mappings":";;;;;;AA0CA,IAAM,YAAA,uBAAmB,GAAA,CAAY;AAAA,EACnC,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAC,CAAA;AAOD,SAAS,mBAAA,CAAoB,UAAkB,EAAA,EAAkB;AAC/D,EAAA,IAAI;AACF,IAAA,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,EAChD,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,EAAG,EAAE,CAAA,oBAAA,EAAuB,QAAQ,CAAA,2EAAA;AAAA,KAEtC;AAAA,EACF;AACF;AAgBO,SAAS,QAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,UAAU,CAAA;AACxC,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAuB;AACvC,IAAA,MAAM,SAAS,KAAA,CAAM,MAAA;AACrB,IAAA,IAAI,EAAE,kBAAkB,OAAA,CAAA,EAAU;AAClC,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AACvC,IAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,EAAG;AAChD,MAAA,OAAA,CAAQ,OAAO,OAAO,CAAA;AAAA,IACxB;AAAA,EACF,CAAA;AACA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAC/C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAAA,EACpD,CAAA;AACF;AAUO,SAAS,eAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,iBAAiB,CAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAuB;AACvC,IAAA,MAAM,SAAS,KAAA,CAAM,MAAA;AACrB,IAAA,IAAI,EAAE,kBAAkB,OAAA,CAAA,EAAU;AAClC,IAAA,IAAI,OAAO,OAAA,CAAQ,QAAQ,KAAK,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAG;AACvD,MAAA,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA,IACvB;AAAA,EACF,CAAA;AACA,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAC5C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAAA,EACjD,CAAA;AACF;;;ACzFA,IAAM,SAAA,uBAAgB,OAAA,EAA4B;AAWlD,IAAI,WAAA,GAAwC,IAAA;AAErC,SAAS,gBAAgB,CAAA,EAAmC;AACjE,EAAA,WAAA,GAAc,CAAA;AAChB;AAEO,SAAS,IAAA,CACd,KAAA,EACA,MAAA,EACA,GAAA,EACU;AACV,EAAA,MAAM,KAAK,WAAA,KAAgB,IAAA,GAAO,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA,GAAI,QAAA;AAChE,EAAA,MAAM,QAAA,GAAW,IAAI,KAAA,CAAwD,KAAA,CAAM,MAAM,CAAA;AACzF,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,IAAA,EAAM;AAC7C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,6EAA6E,IAAA,KAAS,IAAA,GAAO,SAAS,OAAO,IAAI,aAAa,CAAC,CAAA,4EAAA;AAAA,OAEjI;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,GAAA,GAAM,GAAA,CAAI,IAAA,EAAM,CAAC,CAAA,GAAI,MAAA;AAC/B,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AACjC,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,GAAA,KAAQ,CAAA,EAAG;AAC5C,MAAA,IAAA,GAAO,MAAA,CAAO,IAAA;AAAA,IAChB,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,IAAA,EAAM,CAAC,CAAA;AAC1B,MAAA,IAAA,GAAO,UAAA,CAAW,GAAG,CAAA,GAAI,GAAA,CAAI,UAAS,GAAI,GAAA;AAC1C,MAAA,SAAA,CAAU,IAAI,IAAA,EAAM,EAAE,GAAA,EAAK,CAAA,EAAG,MAAM,CAAA;AAAA,IACtC;AACA,IAAA,QAAA,CAAS,CAAC,CAAA,GAAI,EAAE,KAAK,IAAA,EAAM,QAAA,EAAU,GAAG,IAAA,EAAK;AAAA,EAC/C;AACA,EAAA,OAAO,YAAA,CAAa,IAAI,QAAQ,CAAA;AAClC;;;AC5DA,IAAM,aAAA,GAAgB,KAAA;AACtB,IAAM,eAAA,GAAkB,WAAA;AACxB,IAAM,YAAA,GAAe,CAAA;AACrB,IAAM,SAAA,GAAY,CAAA;AAClB,IAAM,YAAA,GAAe,CAAA;AAErB,SAAS,WAAW,IAAA,EAAgC;AAClD,EAAA,IAAI,IAAA,CAAK,QAAA,KAAa,YAAA,EAAc,OAAO,MAAA;AAC3C,EAAA,MAAM,EAAA,GAAK,IAAA;AACX,EAAA,IAAI,EAAA,CAAG,OAAO,EAAA,EAAI,OAAO,GAAG,aAAa,CAAA,EAAG,GAAG,EAAE,CAAA,CAAA;AACjD,EAAA,IAAI,GAAG,OAAA,KAAY,MAAA,IAAa,EAAA,CAAG,OAAA,CAAQ,QAAQ,MAAA,EAAW;AAC5D,IAAA,OAAO,CAAA,EAAG,eAAe,CAAA,EAAG,EAAA,CAAG,QAAQ,GAAG,CAAA,CAAA;AAAA,EAC5C;AACA,EAAA,OAAO,MAAA;AACT;AAQO,SAAS,IAAA,CACd,QAAA,EACA,YAAA,EACA,WAAA,EACM;AACN,EAAA,YAAA,CAAa,QAAA,EAAU,cAAc,WAAW,CAAA;AAClD;AAEA,SAAS,YAAA,CACP,UAAA,EACA,QAAA,EACA,WAAA,EACM;AAGN,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAqB;AACvC,EAAA,KAAA,IAAS,IAAI,UAAA,CAAW,UAAA,EAAY,MAAM,IAAA,EAAM,CAAA,GAAI,EAAE,WAAA,EAAa;AACjE,IAAA,MAAM,CAAA,GAAI,WAAW,CAAC,CAAA;AACtB,IAAA,IAAI,CAAA,KAAM,MAAA,EAAW,KAAA,CAAM,GAAA,CAAI,GAAG,CAAY,CAAA;AAAA,EAChD;AAEA,EAAA,IAAI,YAAyB,UAAA,CAAW,UAAA;AACxC,EAAA,IAAI,UAAuB,QAAA,CAAS,UAAA;AAEpC,EAAA,OAAO,YAAY,IAAA,EAAM;AACvB,IAAA,MAAM,SAAS,OAAA,CAAQ,WAAA;AACvB,IAAA,IAAI,OAAA,GAAuB,IAAA;AAG3B,IAAA,MAAM,KAAA,GAAQ,WAAW,OAAO,CAAA;AAChC,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,GAAA,CAAI,KAAK,CAAA,EAAG;AAC3C,MAAA,OAAA,GAAU,KAAA,CAAM,IAAI,KAAK,CAAA;AACzB,MAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAClB,MAAA,IAAI,YAAY,SAAA,EAAW;AACzB,QAAA,UAAA,CAAW,YAAA,CAAa,SAAS,SAAS,CAAA;AAAA,MAC5C,CAAA,MAAO;AACL,QAAA,SAAA,GAAY,SAAA,CAAU,WAAA;AAAA,MACxB;AAAA,IACF;AAKA,IAAA,IAAI,YAAY,IAAA,IAAQ,SAAA,KAAc,QAC/B,SAAA,CAAU,QAAA,KAAa,QAAQ,QAAA,KAC9B,OAAA,CAAQ,QAAA,KAAa,YAAA,IAChB,UAAsB,OAAA,KAAa,OAAA,CAAoB,WACrD,UAAA,CAAW,SAAS,MAAM,MAAA,CAAA,EAAa;AACpD,MAAA,OAAA,GAAU,SAAA;AACV,MAAA,SAAA,GAAY,SAAA,CAAU,WAAA;AAAA,IACxB;AAEA,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,SAAA,CAAU,OAAA,EAAS,SAAS,WAAW,CAAA;AAAA,IACzC,CAAA,MAAO;AAEL,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,SAAA,CAAU,IAAI,CAAA;AACrC,MAAA,UAAA,CAAW,YAAA,CAAa,QAAQ,SAAS,CAAA;AAAA,IAC3C;AAEA,IAAA,OAAA,GAAU,MAAA;AAAA,EACZ;AAOA,EAAA,OAAO,cAAc,IAAA,EAAM;AACzB,IAAA,MAAM,OAAO,SAAA,CAAU,WAAA;AACvB,IAAA,UAAA,CAAW,YAAY,SAAS,CAAA;AAChC,IAAA,SAAA,GAAY,IAAA;AAAA,EACd;AACF;AAEA,SAAS,SAAA,CACP,QAAA,EACA,MAAA,EACA,WAAA,EACM;AACN,EAAA,IAAI,QAAA,CAAS,aAAa,YAAA,EAAc;AACtC,IAAA,YAAA,CAAa,QAAA,EAAqB,QAAmB,WAAW,CAAA;AAChE,IAAA;AAAA,EACF;AACA,EAAA,IAAI,QAAA,CAAS,QAAA,KAAa,SAAA,IAAa,QAAA,CAAS,aAAa,YAAA,EAAc;AACzE,IAAA,MAAM,QAAA,GAAW,QAAA;AACjB,IAAA,MAAM,MAAA,GAAS,MAAA;AACf,IAAA,IAAI,SAAS,IAAA,KAAS,MAAA,CAAO,IAAA,EAAM,QAAA,CAAS,OAAO,MAAA,CAAO,IAAA;AAAA,EAC5D;AACF;AAEA,SAAS,YAAA,CACP,MAAA,EACA,IAAA,EACA,WAAA,EACM;AACN,EAAA,IAAI,MAAA,CAAO,OAAA,KAAY,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AACvC,IAAA,MAAA,CAAO,UAAA,EAAY,YAAA,CAAa,WAAA,EAAa,MAAM,CAAA;AACnD,IAAA;AAAA,EACF;AAEA,EAAA,IAAK,MAAA,CAAuB,OAAA,CAAQ,SAAA,KAAc,MAAA,EAAW;AAE7D,EAAA,IAAI,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA,EAAG;AAE9B,EAAA,IAAI,MAAA,KAAW,SAAS,aAAA,EAAe;AACrC,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,YAAA,CAAa,iBAAiB,CAAA;AAChD,IAAA,IAAI,EAAA,KAAO,IAAA,IAAQ,EAAA,CAAG,WAAA,OAAkB,OAAA,EAAS;AACjD,IAAA,IAAI,qBAAA,CAAsB,MAAM,CAAA,EAAG,sBAAA,CAAuB,QAAQ,IAAI,CAAA;AAAA,EACxE;AACA,EAAA,eAAA,CAAgB,QAAQ,IAAI,CAAA;AAI5B,EAAA,IAAI,WAAA,CAAY,GAAA,CAAI,MAAM,CAAA,EAAG;AAC7B,EAAA,YAAA,CAAa,MAAA,EAAQ,MAAM,WAAW,CAAA;AACxC;AAEA,SAAS,eAAA,CAAgB,QAAiB,IAAA,EAAqB;AAE7D,EAAA,MAAM,UAAU,IAAA,CAAK,UAAA;AACrB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACvC,IAAA,MAAM,IAAA,GAAO,QAAQ,CAAC,CAAA;AACtB,IAAA,MAAM,KAAK,IAAA,CAAK,YAAA;AAChB,IAAA,MAAM,OAAO,IAAA,CAAK,SAAA;AAClB,IAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,IAAA,IAAI,OAAO,IAAA,EAAM;AACf,MAAA,IAAI,MAAA,CAAO,cAAA,CAAe,EAAA,EAAI,IAAI,MAAM,KAAA,EAAO;AAC7C,QAAA,MAAA,CAAO,cAAA,CAAe,EAAA,EAAI,IAAA,CAAK,IAAA,EAAM,KAAK,CAAA;AAAA,MAC5C;AAAA,IACF,CAAA,MAAA,IAAW,MAAA,CAAO,YAAA,CAAa,IAAI,MAAM,KAAA,EAAO;AAC9C,MAAA,MAAA,CAAO,YAAA,CAAa,MAAM,KAAK,CAAA;AAAA,IACjC;AAAA,EACF;AAEA,EAAA,MAAM,YAAY,MAAA,CAAO,UAAA;AACzB,EAAA,KAAA,IAAS,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC9C,IAAA,MAAM,IAAA,GAAO,UAAU,CAAC,CAAA;AACxB,IAAA,MAAM,KAAK,IAAA,CAAK,YAAA;AAChB,IAAA,MAAM,OAAO,IAAA,CAAK,SAAA;AAClB,IAAA,IAAI,OAAO,IAAA,EAAM;AACf,MAAA,IAAI,CAAC,KAAK,cAAA,CAAe,EAAA,EAAI,IAAI,CAAA,EAAG,MAAA,CAAO,iBAAA,CAAkB,EAAA,EAAI,IAAI,CAAA;AAAA,IACvE,CAAA,MAAA,IAAW,CAAC,IAAA,CAAK,YAAA,CAAa,IAAI,CAAA,EAAG;AACnC,MAAA,MAAA,CAAO,gBAAgB,IAAI,CAAA;AAAA,IAC7B;AAAA,EACF;AACF;AAEA,SAAS,sBAAsB,EAAA,EAAsB;AACnD,EAAA,IAAI,EAAA,CAAG,OAAA,KAAY,UAAA,EAAY,OAAO,IAAA;AACtC,EAAA,IAAI,EAAA,CAAG,YAAY,OAAA,EAAS;AAC1B,IAAA,MAAM,OAAQ,EAAA,CAAwB,IAAA;AACtC,IAAA,OAAO,IAAA,KAAS,MAAA,IAAU,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,KAAA,IAAS,IAAA,KAAS,OAAA,IACrE,IAAA,KAAS,KAAA,IAAS,IAAA,KAAS,UAAA,IAAc,IAAA,KAAS,EAAA;AAAA,EACzD;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,sBAAA,CAAuB,QAAiB,IAAA,EAAqB;AACpE,EAAA,IAAI,MAAA,CAAO,OAAA,KAAY,UAAA,IAAc,MAAA,CAAO,YAAY,OAAA,EAAS;AAC/D,IAAA,MAAM,SAAA,GAAY,MAAA;AAClB,IAAA,MAAM,OAAA,GAAU,IAAA;AAChB,IAAA,OAAA,CAAQ,QAAQ,SAAA,CAAU,KAAA;AAC1B,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,iBAAA,CAAkB,SAAA,CAAU,cAAA,EAAgB,SAAA,CAAU,YAAY,CAAA;AAAA,IAC5E,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC/LO,SAAS,aAAa,UAAA,EAA2C;AACtE,EAAA,MAAM,SAAS,QAAA,CAAS,aAAA;AACxB,EAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,MAAA,KAAW,QAAA,CAAS,MAAM,OAAO,IAAA;AACxD,EAAA,IAAI,CAAC,UAAA,CAAW,QAAA,CAAS,MAAM,GAAG,OAAO,IAAA;AACzC,EAAA,MAAM,EAAA,GAAK,MAAA;AACX,EAAA,IAAI,QAAA,GAA0B,IAAA;AAC9B,EAAA,IAAI,MAAA,GAAwB,IAAA;AAC5B,EAAA,IAAI,EAAA,CAAG,OAAA,KAAY,OAAA,IAAW,EAAA,CAAG,YAAY,UAAA,EAAY;AACvD,IAAA,IAAI;AACF,MAAA,QAAA,GAAY,EAAA,CAAwB,cAAA;AACpC,MAAA,MAAA,GAAU,EAAA,CAAwB,YAAA;AAAA,IACpC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,QAAA,EAAU,MAAA,EAAO;AAChC;AAEO,SAAS,aAAa,IAAA,EAA2B;AACtD,EAAA,IAAI,QAAA,CAAS,aAAA,KAAkB,IAAA,CAAK,EAAA,EAAI;AACxC,EAAA,IAAI,CAAC,IAAA,CAAK,EAAA,CAAG,WAAA,EAAa;AAC1B,EAAA,IAAA,CAAK,GAAG,KAAA,EAAM;AACd,EAAA,IAAI,IAAA,CAAK,QAAA,KAAa,IAAA,IAAQ,IAAA,CAAK,WAAW,IAAA,EAAM;AAClD,IAAA,IAAI;AACF,MAAC,KAAK,EAAA,CAAwB,iBAAA,CAAkB,IAAA,CAAK,QAAA,EAAU,KAAK,MAAM,CAAA;AAAA,IAC5E,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF;;;ACHA,SAAS,aAAA,CAAc,UAAuB,OAAA,EAAsC;AAClF,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAuB;AAC5C,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAoB;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,QAAA,CAAS,IAAI,QAAA,CAAS,CAAC,EAAE,GAAA,EAAK,QAAA,CAAS,CAAC,CAAC,CAAA;AACzC,IAAA,QAAA,CAAS,GAAA,CAAI,QAAA,CAAS,CAAC,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,EACjC;AAEA,EAAA,MAAM,SAAA,GAAyB,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,MAAM,CAAA;AAC7D,EAAA,MAAM,OAAA,GAAU,IAAI,KAAA,CAAc,OAAA,CAAQ,MAAM,MAAM,CAAA;AACtD,EAAA,MAAM,gBAA2B,EAAC;AAClC,EAAA,MAAM,eAAyB,EAAC;AAChC,EAAA,MAAM,aAAuB,EAAC;AAE9B,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AAC7C,IAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA;AAC1B,IAAA,MAAM,EAAA,GAAK,QAAA,CAAS,GAAA,CAAI,EAAA,CAAG,GAAG,CAAA;AAC9B,IAAA,IAAI,OAAO,MAAA,EAAW;AACpB,MAAA,QAAA,CAAS,MAAA,CAAO,GAAG,GAAG,CAAA;AACtB,MAAA,IAAI,EAAA,CAAG,IAAA,KAAS,EAAA,CAAG,IAAA,EAAM;AACvB,QAAA,SAAA,CAAU,CAAC,CAAA,GAAI,EAAA;AACf,QAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,QAAA,CAAS,GAAA,CAAI,GAAG,GAAG,CAAA;AAChC,QAAA;AAAA,MACF;AACA,MAAA,aAAA,CAAc,IAAA,CAAK,GAAG,IAAI,CAAA;AAAA,IAC5B;AACA,IAAA,SAAA,CAAU,CAAC,CAAA,GAAI;AAAA,MACb,KAAK,EAAA,CAAG,GAAA;AAAA,MAAK,UAAU,EAAA,CAAG,QAAA;AAAA,MAAU,MAAM,EAAA,CAAG,IAAA;AAAA,MAAM,IAAA,EAAM;AAAA,KAC3D;AACA,IAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,EAAA;AACb,IAAA,YAAA,CAAa,KAAK,CAAC,CAAA;AACnB,IAAA,UAAA,CAAW,IAAA,CAAK,GAAG,IAAI,CAAA;AAAA,EACzB;AAIA,EAAA,KAAA,MAAW,GAAG,MAAM,CAAA,IAAK,UAAU,aAAA,CAAc,IAAA,CAAK,OAAO,IAAI,CAAA;AAIjE,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,aAAA,EAAe,cAAc,UAAA,EAAW;AACvE;AAMA,SAAS,eAAA,CACP,SAAA,EACA,YAAA,EACA,UAAA,EACM;AACN,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC7B,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,aAAA,CAAc,UAAU,CAAA;AAC7C,EAAA,GAAA,CAAI,SAAA,GAAY,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA;AAClC,EAAA,IAAI,IAAA,GAAO,IAAI,OAAA,CAAQ,iBAAA;AACvB,EAAA,KAAA,MAAW,OAAO,YAAA,EAAc;AAC9B,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gHAAA,EAAmH,UAAU,GAAG,CAAA,CAAE,KAAK,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,OACtJ;AAAA,IACF;AACA,IAAA,MAAM,OAAO,IAAA,CAAK,kBAAA;AAClB,IAAA,SAAA,CAAU,GAAG,EAAE,IAAA,GAAO,IAAA;AACtB,IAAA,IAAA,GAAO,IAAA;AAAA,EACT;AACF;AAKA,SAAS,cAAA,CAAe,YAAqB,aAAA,EAAgC;AAC3E,EAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,IAAA,IAAI,IAAA,CAAK,aAAA,KAAkB,UAAA,EAAY,UAAA,CAAW,YAAY,IAAI,CAAA;AAAA,EACpE;AACF;AAOA,SAAS,UAAA,CACP,UAAA,EACA,SAAA,EACA,OAAA,EACA,MAAA,EACM;AACN,EAAA,IAAI,WAAA,GAA8B,IAAA;AAClC,EAAA,KAAA,IAAS,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC9C,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA;AAC1B,IAAA,IAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAA,IAAM,CAAC,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG;AACvC,MAAA,UAAA,CAAW,YAAA,CAAa,MAAM,WAAW,CAAA;AAAA,IAC3C;AACA,IAAA,WAAA,GAAc,IAAA;AAAA,EAChB;AACF;AAQA,SAAS,IAAI,GAAA,EAAiD;AAC5D,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,IAAA,GAAO,IAAI,KAAA,CAAc,GAAA,CAAI,MAAM,CAAA;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,MAAM,CAAA,GAAI,IAAI,CAAC,CAAA;AACf,IAAA,IAAI,MAAM,EAAA,EAAI;AACZ,MAAA,IAAA,CAAK,CAAC,CAAA,GAAI,EAAA;AACV,MAAA;AAAA,IACF;AACA,IAAA,IAAI,EAAA,GAAK,CAAA;AACT,IAAA,IAAI,KAAK,KAAA,CAAM,MAAA;AACf,IAAA,OAAO,KAAK,EAAA,EAAI;AACd,MAAA,MAAM,GAAA,GAAO,KAAK,EAAA,IAAO,CAAA;AACzB,MAAA,IAAI,KAAA,CAAM,GAAG,CAAA,GAAI,CAAA,OAAQ,GAAA,GAAM,CAAA;AAAA,WAC1B,EAAA,GAAK,GAAA;AAAA,IACZ;AACA,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,EAAA,GAAK,IAAI,OAAA,CAAQ,EAAA,GAAK,CAAC,CAAA,GAAI,EAAA;AACrC,IAAA,KAAA,CAAM,EAAE,CAAA,GAAI,CAAA;AACZ,IAAA,OAAA,CAAQ,EAAE,CAAA,GAAI,CAAA;AAAA,EAChB;AACA,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,EAAA,IAAI,CAAA,GAAI,QAAQ,MAAA,GAAS,CAAA,GAAI,QAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,GAAI,EAAA;AAC3D,EAAA,OAAO,MAAM,EAAA,EAAI;AACf,IAAA,GAAA,CAAI,IAAI,CAAC,CAAA;AACT,IAAA,CAAA,GAAI,KAAK,CAAC,CAAA;AAAA,EACZ;AACA,EAAA,OAAO,GAAA;AACT;AAWO,SAAS,aAAA,CAAc,SAAsB,OAAA,EAA4B;AAC9E,EAAA,MAAM,EAAE,YAAW,GAAI,OAAA;AACvB,EAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,aAAA,EAAe,YAAA,EAAc,YAAW,GAChE,aAAA,CAAc,OAAA,CAAQ,KAAA,EAAO,OAAO,CAAA;AACxC,EAAA,eAAA,CAAgB,SAAA,EAAW,cAAc,UAAU,CAAA;AACnD,EAAA,MAAM,SAAA,GAAY,aAAa,UAAU,CAAA;AACzC,EAAA,cAAA,CAAe,YAAY,aAAa,CAAA;AACxC,EAAA,UAAA,CAAW,UAAA,EAAY,SAAA,EAAW,OAAA,EAAS,GAAA,CAAI,OAAO,CAAC,CAAA;AACvD,EAAA,IAAI,SAAA,KAAc,IAAA,EAAM,YAAA,CAAa,SAAS,CAAA;AAC9C,EAAA,OAAA,CAAQ,KAAA,GAAQ,SAAA;AAClB;;;ACjKA,IAAM,kBAAA,GAAqB,UAAA;AAyBpB,SAAS,KAAA,CAAM,QAAqB,MAAA,EAA6C;AACtF,EAAA,IAAI,UAAU,IAAA,EAAM;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAyB;AAC9C,EAAA,MAAM,OAAA,GAAU,EAAE,KAAA,EAAO,CAAA,EAAE;AAC3B,EAAA,IAAI,OAAA,GAAU,IAAA;AAEd,EAAA,OAAO,OAAO,MAAM;AAClB,IAAA,OAAA,CAAQ,KAAA,GAAQ,CAAA;AAChB,IAAA,eAAA,CAAgB,OAAO,CAAA;AACvB,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAS,MAAA,EAAO;AAAA,IAClB,CAAA,SAAE;AACA,MAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,IACtB;AAEA,IAAA,MAAM,UAAmB,UAAA,CAAW,MAAM,CAAA,GACrC,MAAA,CAAO,aAAa,EAAE,IAAA,EAAM,QAAA,EAAU,IAAA,EAAM,OAAO,MAAA,EAAO,GAC3D,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,MAAA,EAAO;AAEnC,IAAA,IAAI,OAAA,EAAS;AAIX,MAAA,MAAA,CAAO,SAAA,GAAY,OAAA,CAAQ,OAAA,EAAS,IAAI,CAAA;AACxC,MAAA,oBAAA,CAAqB,MAAA,EAAQ,SAAS,QAAQ,CAAA;AAC9C,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ,CAAA,MAAO;AAML,MAAA,MAAM,QAAA,GAAW,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA;AACvC,MAAA,QAAA,CAAS,SAAA,GAAY,wBAAwB,OAAO,CAAA;AACpD,MAAA,MAAM,WAAA,uBAAkB,GAAA,EAAa;AACrC,MAAA,KAAA,MAAW,KAAK,QAAA,CAAS,MAAA,IAAU,WAAA,CAAY,GAAA,CAAI,EAAE,UAAU,CAAA;AAC/D,MAAA,IAAA,CAAK,MAAA,EAAQ,UAAU,WAAW,CAAA;AAClC,MAAA,oBAAA,CAAqB,MAAA,EAAQ,SAAS,QAAQ,CAAA;AAAA,IAChD;AAEA,IAAA,KAAA,MAAW,OAAA,IAAW,YAAA,CAAa,OAAO,CAAA,CAAE,QAAO,EAAG;AACpD,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,EAAE,CAAA;AACvC,MAAA,aAAA,CAAc,SAAS,OAAO,CAAA;AAAA,IAChC;AAAA,EACF,CAAC,CAAA;AACH;AAQA,SAAS,oBAAA,CACP,MAAA,EACA,OAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,KAAA,GAAQ,aAAa,OAAO,CAAA;AAClC,EAAA,MAAM,QAAmB,EAAC;AAC1B,EAAA,eAAA,CAAgB,QAAQ,KAAK,CAAA;AAC7B,EAAA,KAAA,MAAW,UAAU,KAAA,EAAO;AAC1B,IAAA,IAAI,CAAC,MAAA,CAAO,IAAA,CAAK,UAAA,CAAW,kBAAkB,CAAA,EAAG;AACjD,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,mBAAmB,MAAM,CAAA;AAGtD,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,GAAA,CAAI,EAAE,CAAA;AAC5B,IAAA,MAAM,aAAa,MAAA,CAAO,aAAA;AAM1B,IAAA,MAAM,QAAqB,EAAC;AAC5B,IAAA,IAAI,OAAuB,MAAA,CAAO,kBAAA;AAClC,IAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,GAAI,OAAA,CAAQ,MAAM,MAAA,IAAU,IAAA,KAAS,MAAM,CAAA,EAAA,EAAK;AAC9D,MAAA,KAAA,CAAM,IAAA,CAAK;AAAA,QACT,GAAA,EAAK,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,GAAA;AAAA,QACtB,QAAA,EAAU,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,QAAA;AAAA,QAC3B,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,IAAA;AAAA,QACvB,IAAA,EAAM;AAAA,OACP,CAAA;AACD,MAAA,IAAA,GAAO,IAAA,CAAK,kBAAA;AAAA,IACd;AACA,IAAA,QAAA,CAAS,GAAA,CAAI,EAAA,EAAI,EAAE,UAAA,EAAY,OAAO,CAAA;AACtC,IAAA,MAAA,CAAO,MAAA,EAAO;AAAA,EAChB;AACF;AAOA,SAAS,eAAA,CAAgB,MAAY,GAAA,EAAsB;AACzD,EAAA,KAAA,IAAS,IAAiB,IAAA,CAAK,UAAA,EAAY,MAAM,IAAA,EAAM,CAAA,GAAI,EAAE,WAAA,EAAa;AACxE,IAAA,IAAI,EAAE,QAAA,KAAa,IAAA,CAAK,YAAA,EAAc,GAAA,CAAI,KAAK,CAAY,CAAA;AAAA,SAAA,IAClD,EAAE,QAAA,KAAa,IAAA,CAAK,YAAA,EAAc,eAAA,CAAgB,GAAG,GAAG,CAAA;AAAA,EACnE;AACF;;;AC/JA,IAAM,MAAA,GAAS,4BAAA;AAEf,IAAM,iBAAA,uBAAwB,GAAA,CAAI;AAAA,EAChC,GAAA;AAAA,EAAK,MAAA;AAAA,EAAQ,QAAA;AAAA,EAAU,MAAA;AAAA,EAAQ,MAAA;AAAA,EAAQ,SAAA;AAAA,EAAW,UAAA;AAAA,EAAY,SAAA;AAAA,EAC9D,MAAA;AAAA,EAAQ,OAAA;AAAA,EAAS,MAAA;AAAA,EAAQ,KAAA;AAAA,EAAO,QAAA;AAAA,EAAU,UAAA;AAAA,EAAY,MAAA;AAAA,EAAQ,SAAA;AAAA,EAC9D,QAAA;AAAA,EAAU,QAAA;AAAA,EAAU,gBAAA;AAAA,EAAkB,gBAAA;AAAA,EAAkB,MAAA;AAAA,EAAQ,OAAA;AAAA,EAChE;AACF,CAAC,CAAA;AAED,IAAM,eAAA,GAAkB,GAAA;AAExB,SAAS,WAAW,IAAA,EAA6B;AAC/C,EAAA,MAAM,KAAA,GAAQ,+BAAA,CAAgC,IAAA,CAAK,IAAI,CAAA;AACvD,EAAA,OAAO,KAAA,KAAU,IAAA,GAAO,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA;AACrC;AAEA,SAAS,QAAQ,IAAA,EAAsB;AACrC,EAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,EAAA,OAAO,OAAA,CAAQ,SAAS,eAAA,GAAkB,CAAA,EAAG,QAAQ,KAAA,CAAM,CAAA,EAAG,eAAe,CAAC,CAAA,MAAA,CAAA,GAAM,OAAA;AACtF;AAEA,SAAS,eAAA,CAAgB,IAAA,EAAc,KAAA,EAAe,YAAA,EAAgC;AACpF,EAAA,MAAM,MAAM,IAAI,SAAA,EAAU,CAAE,eAAA,CAAgB,MAAM,eAAe,CAAA;AACjE,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,aAAA,CAAc,aAAa,CAAA;AAC3C,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,WAAA,EAAc,KAAK,CAAA,oBAAA,EAAkB,IAAI,WAAW;AAAA,SAAA,EAAc,OAAA,CAAQ,YAAY,CAAC,CAAA,CAAE,CAAA;AAAA,EAC3G;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,UAAU,GAAA,EAAiC;AACzD,EAAA,MAAM,OAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM,IAAI,QAAA,EAAS;AAC1D,EAAA,MAAM,GAAA,GAAM,WAAW,IAAI,CAAA;AAE3B,EAAA,IAAI,QAAQ,KAAA,EAAO;AAEjB,IAAA,OAAO,eAAA,CAAgB,IAAA,EAAM,KAAA,EAAO,IAAI,CAAA,CAAE,eAAA;AAAA,EAC5C;AAEA,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,iBAAA,CAAkB,GAAA,CAAI,GAAG,CAAA,EAAG;AAE9C,IAAA,MAAM,OAAA,GAAU,CAAA,YAAA,EAAe,MAAM,CAAA,EAAA,EAAK,IAAI,CAAA,MAAA,CAAA;AAC9C,IAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,OAAA,EAAS,cAAA,EAAgB,IAAI,CAAA;AACzD,IAAA,MAAM,KAAA,GAAQ,IAAI,eAAA,CAAgB,iBAAA;AAElC,IAAA,IAAI,KAAA,KAAU,IAAA,EAAM,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,SAAA,EAAyD,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAE,CAAA;AAC5G,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,UAAU,CAAA;AAC3C,EAAA,CAAA,CAAE,SAAA,GAAY,IAAA;AACd,EAAA,MAAM,KAAA,GAAQ,EAAE,OAAA,CAAQ,iBAAA;AACxB,EAAA,IAAI,KAAA,KAAU,IAAA,EAAM,MAAM,IAAI,KAAA,CAAM,CAAA;AAAA,SAAA,EAA4C,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAE,CAAA;AAC/F,EAAA,OAAO,KAAA;AACT","file":"index.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, or\n * when you want capture-phase semantics and direct `matches()`-style\n * selector matching (no walk-up).\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\ntype Handler = (event: Event, target: Element) => void;\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 */\nconst NON_BUBBLING = new Set<string>([\n 'focus',\n 'blur',\n 'scroll',\n 'load',\n 'error',\n 'mouseenter',\n 'mouseleave',\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 * 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 * 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(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: Handler,\n): () => void {\n assertValidSelector(selector, 'delegate');\n const listener = (event: Event): void => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const matched = target.closest(selector);\n if (matched !== null && rootEl.contains(matched)) {\n handler(event, matched);\n }\n };\n const capture = NON_BUBBLING.has(type);\n rootEl.addEventListener(type, listener, capture);\n return () => {\n rootEl.removeEventListener(type, listener, capture);\n };\n}\n\n/**\n * Capture-phase delegation — for non-bubbling events (`focus`, `blur`,\n * `scroll`, `load`, `error`). Reaches descendants of `rootEl` that match\n * `selector` regardless of how many times the diff has rebuilt them.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);\n */\nexport function delegateCapture(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: Handler,\n): () => void {\n assertValidSelector(selector, 'delegateCapture');\n const listener = (event: Event): void => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n if (target.matches(selector) && rootEl.contains(target)) {\n handler(event, target);\n }\n };\n rootEl.addEventListener(type, listener, true);\n return () => {\n rootEl.removeEventListener(type, listener, true);\n };\n}\n","/**\n * `each(items, render, key?)` — keyed list iteration with per-item memoisation.\n *\n * Drops in as the body of a list-rendering JSX expression inside a `mount()`\n * render function. Returns a `SafeHtml` carrying a structured list segment,\n * so `mount()` can run a native keyed reconciler instead of the general-\n * purpose morph for these children.\n *\n * Two layers of optimisation:\n *\n * 1. Per-item memoisation. `render(item)` is skipped for items whose object\n * identity (and optional `key`) are unchanged since the previous call.\n * Their HTML strings come from a `WeakMap` keyed by item reference. The\n * immutable-update style (\"replace the row object\" instead of \"mutate it\")\n * makes the cache work automatically.\n *\n * 2. Structural handoff. `mount()` recognises the list segment and bypasses\n * the parse-the-whole-table round trip: only fresh items get parsed (one\n * at a time, into the smallest detached element), and only changed rows\n * get patched in the live DOM. Unchanged rows are physically the same\n * nodes they were before — never visited.\n *\n * `key` covers the case where external state, not the item itself, drives\n * what the row should render (e.g. a \"currently selected\" id flips a CSS\n * class on one row). Same item identity but a different `key` value means\n * \"re-render this item.\" If you don't pass `key`, only identity changes\n * invalidate.\n *\n * Items must be objects (cache is a `WeakMap`); wrap primitives if you need\n * to iterate them. Each item's render output must produce exactly one\n * top-level element — the list reconciler binds one live DOM node per item.\n */\n\nimport type { SafeHtml } from './jsx-runtime.js';\nimport { isSafeHtml, listSafeHtml } from './jsx-runtime.js';\n\ninterface CacheEntry {\n key: unknown;\n html: string;\n}\n\nconst ROW_CACHE = new WeakMap<object, CacheEntry>();\n\n/**\n * Per-mount counter for assigning stable list ids across renders. `mount()`\n * sets this at the start of each render so that the n-th `each()` call\n * produces id \"n\" every render — the binding to the live parent persists.\n *\n * Outside a mount() render, calls to `each()` still work but get the\n * sentinel id \"orphan\"; their output flattens correctly via `toString()`\n * but the structural fast-path doesn't apply (no `mount()` is watching).\n */\nlet listCounter: { value: number } | null = null;\n\nexport function _setListCounter(c: { value: number } | null): void {\n listCounter = c;\n}\n\nexport function each<T extends object>(\n items: readonly T[],\n render: (item: T, index: number) => SafeHtml | string,\n key?: (item: T, index: number) => unknown,\n): SafeHtml {\n const id = listCounter !== null ? String(listCounter.value++) : 'orphan';\n const segItems = new Array<{ ref: object; cacheKey: unknown; html: string }>(items.length);\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n if (typeof item !== 'object' || item === null) {\n throw new Error(\n `each(): items must be objects (the per-item HTML cache is a WeakMap), got ${item === null ? 'null' : typeof item} at index ${i}. `\n + 'Wrap primitives if you need to iterate them, e.g. items.map(v => ({ v })).',\n );\n }\n const k = key ? key(item, i) : undefined;\n const cached = ROW_CACHE.get(item);\n let html: string;\n if (cached !== undefined && cached.key === k) {\n html = cached.html;\n } else {\n const out = render(item, i);\n html = isSafeHtml(out) ? out.toString() : out;\n ROW_CACHE.set(item, { key: k, html });\n }\n segItems[i] = { ref: item, cacheKey: k, html };\n }\n return listSafeHtml(id, segItems);\n}\n","/**\n * `diff(liveRoot, templateRoot, listParents)` — minimum-mutation DOM\n * reconciliation.\n *\n * Replaces our previous dependency on `morphdom`. The algorithm is the\n * classic two-tree walk: match children by key (id, then data-key, then\n * positional same-tag), morph matches in place, insert / remove / clone\n * the rest. Specialised for what kerf needs:\n *\n * - `childrenOnly` is always true; the live root is never replaced.\n * - Three short-circuit paths on each element:\n * 1. `data-morph-skip`: subtree is library-owned, leave verbatim.\n * 2. `isEqualNode`: subtree is byte-identical, no work needed.\n * 3. `listParents` membership: a kerf list reconciler owns this\n * element's children, so we morph attributes only and stop.\n * - Focused text inputs (`<input>`/`<textarea>`) keep their value +\n * selection across the morph; focused `[contenteditable]` keeps its\n * entire subtree (typed content + caret + multi-range selection).\n *\n * Algorithm credit: based on the design of\n * https://github.com/patrick-steele-idem/morphdom by Patrick Steele-Idem\n * (MIT licensed). Reimplemented here so kerf can specialise the hot paths\n * (segment-aware list dispatch, lighter callback surface) and drop the\n * runtime dependency. Original copyright preserved in `LICENSE`.\n */\n\nconst ID_KEY_PREFIX = 'id:';\nconst DATA_KEY_PREFIX = 'data-key:';\nconst ELEMENT_NODE = 1;\nconst TEXT_NODE = 3;\nconst COMMENT_NODE = 8;\n\nfunction getNodeKey(node: Node): string | undefined {\n if (node.nodeType !== ELEMENT_NODE) return undefined;\n const el = node as HTMLElement;\n if (el.id !== '') return `${ID_KEY_PREFIX}${el.id}`;\n if (el.dataset !== undefined && el.dataset.key !== undefined) {\n return `${DATA_KEY_PREFIX}${el.dataset.key}`;\n }\n return undefined;\n}\n\n/**\n * Reconcile the children of `liveRoot` to match the children of\n * `templateRoot`. `listParents` is the set of live elements whose children\n * are managed by `each()`'s reconciler — they get attribute morphing but\n * not children diffing.\n */\nexport function diff(\n liveRoot: Element,\n templateRoot: Element,\n listParents: ReadonlySet<Element>,\n): void {\n diffChildren(liveRoot, templateRoot, listParents);\n}\n\nfunction diffChildren(\n fromParent: Element,\n toParent: Element,\n listParents: ReadonlySet<Element>,\n): void {\n // Build a keyed lookup from the live children so we can match by key\n // even after reorders.\n const keyed = new Map<string, Element>();\n for (let c = fromParent.firstChild; c !== null; c = c.nextSibling) {\n const k = getNodeKey(c);\n if (k !== undefined) keyed.set(k, c as Element);\n }\n\n let fromChild: Node | null = fromParent.firstChild;\n let toChild: Node | null = toParent.firstChild;\n\n while (toChild !== null) {\n const toNext = toChild.nextSibling;\n let matched: Node | null = null;\n\n // 1. Try key match.\n const toKey = getNodeKey(toChild);\n if (toKey !== undefined && keyed.has(toKey)) {\n matched = keyed.get(toKey) as Element;\n keyed.delete(toKey);\n if (matched !== fromChild) {\n fromParent.insertBefore(matched, fromChild);\n } else {\n fromChild = fromChild.nextSibling;\n }\n }\n\n // 2. Fall back to positional match — same nodeType, same tag (for\n // elements), and the live node has no key (a keyed node only\n // matches by key, never positionally, so reorders work).\n if (matched === null && fromChild !== null\n && fromChild.nodeType === toChild.nodeType\n && (toChild.nodeType !== ELEMENT_NODE\n || ((fromChild as Element).tagName === (toChild as Element).tagName\n && getNodeKey(fromChild) === undefined))) {\n matched = fromChild;\n fromChild = fromChild.nextSibling;\n }\n\n if (matched !== null) {\n morphNode(matched, toChild, listParents);\n } else {\n // 3. No match — clone the new node and insert.\n const cloned = toChild.cloneNode(true);\n fromParent.insertBefore(cloned, fromChild);\n }\n\n toChild = toNext;\n }\n\n // 4. Anything past the cursor is unmatched — remove. Any keyed node that\n // wasn't matched in the toParent walk falls into this trailing range\n // by construction (matched keyed nodes get moved before the cursor;\n // unmatched ones stay at or after it), so we don't need a separate\n // orphan pass.\n while (fromChild !== null) {\n const next = fromChild.nextSibling;\n fromParent.removeChild(fromChild);\n fromChild = next;\n }\n}\n\nfunction morphNode(\n fromNode: Node,\n toNode: Node,\n listParents: ReadonlySet<Element>,\n): void {\n if (fromNode.nodeType === ELEMENT_NODE) {\n morphElement(fromNode as Element, toNode as Element, listParents);\n return;\n }\n if (fromNode.nodeType === TEXT_NODE || fromNode.nodeType === COMMENT_NODE) {\n const fromText = fromNode as CharacterData;\n const toText = toNode as CharacterData;\n if (fromText.data !== toText.data) fromText.data = toText.data;\n }\n}\n\nfunction morphElement(\n fromEl: Element,\n toEl: Element,\n listParents: ReadonlySet<Element>,\n): void {\n if (fromEl.tagName !== toEl.tagName) {\n const replacement = toEl.cloneNode(true);\n fromEl.parentNode?.replaceChild(replacement, fromEl);\n return;\n }\n // 1. Library-owned subtree — leave verbatim.\n if ((fromEl as HTMLElement).dataset.morphSkip !== undefined) return;\n // 2. Byte-identical — nothing to do.\n if (fromEl.isEqualNode(toEl)) return;\n // 3. Focused contenteditable — preserve user's in-progress edit.\n if (fromEl === document.activeElement) {\n const ce = fromEl.getAttribute('contenteditable');\n if (ce !== null && ce.toLowerCase() !== 'false') return;\n if (isTextInputOrTextarea(fromEl)) preserveTextEntryState(fromEl, toEl);\n }\n morphAttributes(fromEl, toEl);\n // 4. List parent — its children are managed by the each() reconciler;\n // diff stops here. We still ran morphAttributes above, so attribute\n // changes on the parent itself (id, class, data-* …) propagate.\n if (listParents.has(fromEl)) return;\n diffChildren(fromEl, toEl, listParents);\n}\n\nfunction morphAttributes(fromEl: Element, toEl: Element): void {\n // Set/update every attribute on toEl.\n const toAttrs = toEl.attributes;\n for (let i = 0; i < toAttrs.length; i++) {\n const attr = toAttrs[i];\n const ns = attr.namespaceURI;\n const name = attr.localName;\n const value = attr.value;\n if (ns !== null) {\n if (fromEl.getAttributeNS(ns, name) !== value) {\n fromEl.setAttributeNS(ns, attr.name, value);\n }\n } else if (fromEl.getAttribute(name) !== value) {\n fromEl.setAttribute(name, value);\n }\n }\n // Remove attributes that are no longer present on toEl.\n const fromAttrs = fromEl.attributes;\n for (let i = fromAttrs.length - 1; i >= 0; i--) {\n const attr = fromAttrs[i];\n const ns = attr.namespaceURI;\n const name = attr.localName;\n if (ns !== null) {\n if (!toEl.hasAttributeNS(ns, name)) fromEl.removeAttributeNS(ns, name);\n } else if (!toEl.hasAttribute(name)) {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nfunction isTextInputOrTextarea(el: Element): boolean {\n if (el.tagName === 'TEXTAREA') return true;\n if (el.tagName === 'INPUT') {\n const type = (el as HTMLInputElement).type;\n return type === 'text' || type === 'search' || type === 'url' || type === 'email'\n || type === 'tel' || type === 'password' || type === '';\n }\n return false;\n}\n\nfunction preserveTextEntryState(fromEl: Element, toEl: Element): void {\n if (fromEl.tagName === 'TEXTAREA' || fromEl.tagName === 'INPUT') {\n const fromInput = fromEl as HTMLInputElement;\n const toInput = toEl as HTMLInputElement;\n toInput.value = fromInput.value;\n try {\n toInput.setSelectionRange(fromInput.selectionStart, fromInput.selectionEnd);\n } catch {\n // Some input types (number, range, color, …) reject selection APIs.\n }\n }\n}\n","/**\n * Focus snapshot/restore for the keyed list reconciler.\n *\n * Some engines (older Safari, happy-dom) drop focus state on `insertBefore`\n * even when the focused element survives the move connected to the document.\n * The reconciler snapshots the active element + selection range before its\n * move pass, then re-applies them afterwards. Engines that already preserve\n * focus across DOM moves see a no-op; engines that don't get a transparent\n * fix.\n *\n * Lives in its own file (rather than inside `list-reconcile.ts`) to keep that\n * file under the 200-LOC project guideline and to isolate the engine-quirk\n * handling described in `docs/4-render.md` §4.4.\n */\n\nexport interface FocusSnapshot {\n el: HTMLElement;\n selStart: number | null;\n selEnd: number | null;\n}\n\n/**\n * Capture focus + selection on a focused descendant of `liveParent`.\n *\n * Returns null when the active element is outside the list (the diff path\n * handles those cases) or when there's no useful focus state to capture.\n */\nexport function captureFocus(liveParent: Element): FocusSnapshot | null {\n const active = document.activeElement;\n if (active === null || active === document.body) return null;\n if (!liveParent.contains(active)) return null;\n const el = active as HTMLElement;\n let selStart: number | null = null;\n let selEnd: number | null = null;\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n try {\n selStart = (el as HTMLInputElement).selectionStart;\n selEnd = (el as HTMLInputElement).selectionEnd;\n } catch {\n // Some input types (number, range, color, …) reject selection APIs.\n }\n }\n return { el, selStart, selEnd };\n}\n\nexport function restoreFocus(snap: FocusSnapshot): void {\n if (document.activeElement === snap.el) return;\n if (!snap.el.isConnected) return;\n snap.el.focus();\n if (snap.selStart !== null && snap.selEnd !== null) {\n try {\n (snap.el as HTMLInputElement).setSelectionRange(snap.selStart, snap.selEnd);\n } catch {\n // Selection may have been clobbered by .focus(); not fatal.\n }\n }\n}\n","/**\n * Keyed list reconciler — the engine behind `each(...)` inside `mount()`.\n *\n * Given a `ListBinding` (the live parent + the items currently mounted under\n * it) and a fresh `ListSegment` from this render, mutate the live DOM with\n * the minimum number of operations:\n *\n * 1. Classify each new item as `stable` (cache hit on identity + html),\n * `replaced` (same ref, html changed), or `new` (never seen before).\n * 2. Bulk-parse every fresh row's HTML in ONE `innerHTML` call — for an\n * initial population of 10k rows that's 1 parse instead of 10k.\n * 3. Remove orphan refs (gone from the new list) + replaced rows' old nodes.\n * 4. Compute a longest-increasing-subsequence over old positions; items in\n * the LIS are already in the right relative order, so they don't move.\n * 5. Reverse-pass over the new record, `insertBefore` only the items that\n * didn't anchor the LIS.\n *\n * Lives in its own file so `mount.ts` can stay an orchestrator under the\n * 200-LOC guideline. Internal to kerf — not part of the public API.\n */\n\nimport { captureFocus, restoreFocus } from './list-reconcile-focus.js';\nimport type { ListSegment } from './segment.js';\n\nexport interface BoundItem {\n ref: object;\n cacheKey: unknown;\n html: string;\n node: Element;\n}\n\nexport interface ListBinding {\n liveParent: Element;\n /**\n * One entry per item currently mounted under `liveParent`, in order.\n * Mirrors the segment's `items` length after each reconcile.\n */\n items: BoundItem[];\n}\n\ninterface Classification {\n newRecord: BoundItem[];\n prevIdx: number[];\n replacedNodes: Element[];\n freshIndices: number[];\n freshHtmls: string[];\n}\n\n/**\n * Classify each item in the new segment against the old binding. Items with\n * a cache-hit (same ref + same html) are reused; replaced/new items get a\n * placeholder `BoundItem` whose `node` is filled in by the bulk parse below.\n */\nfunction classifyItems(oldItems: BoundItem[], listSeg: ListSegment): Classification {\n const oldByRef = new Map<object, BoundItem>();\n const oldIndex = new Map<object, number>();\n for (let i = 0; i < oldItems.length; i++) {\n oldByRef.set(oldItems[i].ref, oldItems[i]);\n oldIndex.set(oldItems[i].ref, i);\n }\n\n const newRecord: BoundItem[] = new Array(listSeg.items.length);\n const prevIdx = new Array<number>(listSeg.items.length);\n const replacedNodes: Element[] = [];\n const freshIndices: number[] = [];\n const freshHtmls: string[] = [];\n\n for (let i = 0; i < listSeg.items.length; i++) {\n const ni = listSeg.items[i];\n const oi = oldByRef.get(ni.ref);\n if (oi !== undefined) {\n oldByRef.delete(ni.ref);\n if (oi.html === ni.html) {\n newRecord[i] = oi;\n prevIdx[i] = oldIndex.get(ni.ref) as number;\n continue;\n }\n replacedNodes.push(oi.node);\n }\n newRecord[i] = {\n ref: ni.ref, cacheKey: ni.cacheKey, html: ni.html, node: null as unknown as Element,\n };\n prevIdx[i] = -1;\n freshIndices.push(i);\n freshHtmls.push(ni.html);\n }\n\n // Whatever's left in oldByRef is an orphan ref that disappeared from the\n // new list. The caller removes those nodes.\n for (const [, orphan] of oldByRef) replacedNodes.push(orphan.node);\n // Distinguish: replacedNodes here mixes \"old node for replaced row\" and\n // \"orphan node\". That's fine — the caller removes both unconditionally.\n\n return { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls };\n}\n\n/**\n * Bulk-parse every fresh row's HTML in one `innerHTML` call, then walk the\n * parsed children in order and fill in each placeholder's `node` field.\n */\nfunction buildFreshNodes(\n newRecord: BoundItem[],\n freshIndices: number[],\n freshHtmls: string[],\n): void {\n if (freshHtmls.length === 0) return;\n const tpl = document.createElement('template');\n tpl.innerHTML = freshHtmls.join('');\n let node = tpl.content.firstElementChild;\n for (const idx of freshIndices) {\n if (node === null) {\n throw new Error(\n `each(): row render produced no top-level element. Each item's render must return exactly one element. Got HTML: ${newRecord[idx].html.slice(0, 120)}`,\n );\n }\n const next = node.nextElementSibling;\n newRecord[idx].node = node;\n node = next;\n }\n}\n\n/**\n * Remove every replaced/orphan node still attached to the live parent.\n */\nfunction removeOldNodes(liveParent: Element, replacedNodes: Element[]): void {\n for (const node of replacedNodes) {\n if (node.parentElement === liveParent) liveParent.removeChild(node);\n }\n}\n\n/**\n * `insertBefore` everything that's not in the LIS. Walks the new record in\n * reverse so each move's anchor (`nextSibling`) is already in its final\n * position by the time we reach earlier items.\n */\nfunction applyMoves(\n liveParent: Element,\n newRecord: BoundItem[],\n prevIdx: number[],\n stable: ReadonlySet<number>,\n): void {\n let nextSibling: Element | null = null;\n for (let i = newRecord.length - 1; i >= 0; i--) {\n const node = newRecord[i].node;\n if (prevIdx[i] === -1 || !stable.has(i)) {\n liveParent.insertBefore(node, nextSibling);\n }\n nextSibling = node;\n }\n}\n\n/**\n * Patience-sort LIS: returns the *set of indices* in `arr` that participate\n * in a longest strictly-increasing subsequence. `-1` entries (representing\n * brand-new items in the new list) are ignored — they can't anchor the\n * subsequence and shouldn't count as stable.\n */\nfunction lis(arr: ReadonlyArray<number>): ReadonlySet<number> {\n const tails: number[] = [];\n const tailIdx: number[] = [];\n const prev = new Array<number>(arr.length);\n for (let i = 0; i < arr.length; i++) {\n const v = arr[i];\n if (v === -1) {\n prev[i] = -1;\n continue;\n }\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (tails[mid] < v) lo = mid + 1;\n else hi = mid;\n }\n prev[i] = lo > 0 ? tailIdx[lo - 1] : -1;\n tails[lo] = v;\n tailIdx[lo] = i;\n }\n const out = new Set<number>();\n let k = tailIdx.length > 0 ? tailIdx[tailIdx.length - 1] : -1;\n while (k !== -1) {\n out.add(k);\n k = prev[k];\n }\n return out;\n}\n\n/**\n * Reconcile `binding`'s live parent against `listSeg`. Mutates `binding.items`\n * to mirror the new segment when done.\n *\n * Focus capture/restore lives in `list-reconcile-focus.ts` — `insertBefore`\n * of a focused descendant's ancestor blurs the element in some engines\n * (happy-dom, older Safari) even when it stays connected; the snapshot fixes\n * those engines and is a no-op on engines that already preserve focus.\n */\nexport function reconcileList(binding: ListBinding, listSeg: ListSegment): void {\n const { liveParent } = binding;\n const { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls }\n = classifyItems(binding.items, listSeg);\n buildFreshNodes(newRecord, freshIndices, freshHtmls);\n const focusSnap = captureFocus(liveParent);\n removeOldNodes(liveParent, replacedNodes);\n applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx));\n if (focusSnap !== null) restoreFocus(focusSnap);\n binding.items = newRecord;\n}\n","/**\n * `mount(rootEl, render)` — kerf's render primitive.\n *\n * Wraps `effect()` so that whenever any signal read inside `render()`\n * changes, we re-run `render()` and apply the minimum DOM mutations against\n * the live tree. Element identity (and thus focus, selection, in-flight\n * pointer interactions, and event listeners on preserved nodes) is preserved\n * wherever the keyed/positional diff matches.\n *\n * Two phases per render:\n *\n * - Static surrounds (everything outside `each()` lists): kerf's native\n * `diff()` reconciler walks a freshly-built template against the live\n * tree. Conventions: id/data-key matching, `data-morph-skip`, focus\n * preservation.\n *\n * - List interiors (children of every `each()` parent): native keyed\n * reconciler operates directly on the live parent's children. No\n * re-parse, no morph walk for cache-hit rows. Cost is O(changes), not\n * O(rows).\n *\n * Compared to a `replaceChildren(...rows.map(toElement))` rebuild pattern,\n * the user-visible win is that an `<input>` the user is typing into\n * survives an unrelated re-render — its DOM node, focus state, and cursor\n * position are not destroyed and recreated on each tick.\n */\n\nimport { diff } from './diff.js';\nimport { _setListCounter } from './each.js';\nimport type { SafeHtml } from './jsx-runtime.js';\nimport { isSafeHtml } from './jsx-runtime.js';\nimport {\n type BoundItem,\n type ListBinding,\n reconcileList,\n} from './list-reconcile.js';\nimport { effect } from './reactive.js';\nimport {\n collectLists,\n flatten,\n flattenWithoutListItems,\n type ListSegment,\n type Segment,\n} from './segment.js';\n\nconst LIST_MARKER_PREFIX = 'kf-list:';\n\n/**\n * Bind `render()` to the children of `rootEl`. Re-runs whenever any signal\n * read inside `render()` changes. Returns a disposer that tears down the\n * effect; call it when the host element is removed from the DOM.\n *\n * Conventions:\n *\n * - Diff keys: `id` and `data-key` are matched across the morph by key\n * rather than positionally, so list reorders move existing nodes instead\n * of churning unrelated siblings.\n * - `data-morph-skip`: any element with this attribute is left untouched\n * inside on subsequent renders. Used for library-owned subtrees (xterm-\n * style widgets, charts, third-party editors) where the library's own\n * lifecycle manages the children.\n * - Focused text-entry inputs (`<input>` of typing kinds, `<textarea>`)\n * keep their current value + selection range across morphs while focused.\n * The user never sees their cursor jump mid-keystroke.\n * - Focused `[contenteditable]` elements have their entire subtree\n * skipped (same mechanism as `data-morph-skip`). The user's in-progress\n * edit — typed content, caret position, multi-range selections, anything\n * else they did to the DOM — survives verbatim. The next render after\n * blur catches up.\n */\nexport function mount(rootEl: HTMLElement, render: () => SafeHtml | string): () => void {\n if (rootEl == null) {\n throw new Error(\n 'mount: rootEl is null/undefined — pass the live element, e.g. mount(document.getElementById(\"app\")!, render). '\n + 'A common cause is a typo in the id or selector that returns null at runtime even though the TypeScript types say HTMLElement.',\n );\n }\n const bindings = new Map<string, ListBinding>();\n const counter = { value: 0 };\n let isFirst = true;\n\n return effect(() => {\n counter.value = 0;\n _setListCounter(counter);\n let result: SafeHtml | string;\n try {\n result = render();\n } finally {\n _setListCounter(null);\n }\n\n const segment: Segment = isSafeHtml(result)\n ? (result.__segment ?? { kind: 'static', html: result.__html })\n : { kind: 'static', html: result };\n\n if (isFirst) {\n // Bulk-render with items inlined and a marker per list. The marker walk\n // afterwards binds each list to the rows already in the DOM, so the\n // first-render reconcile is a no-op (every item is a cache hit).\n rootEl.innerHTML = flatten(segment, true);\n bindListsFromMarkers(rootEl, segment, bindings);\n isFirst = false;\n } else {\n // Static-surrounds-only render: lists become marker-only in the\n // template. `diff()` skips bound list parents' children entirely\n // (so existing rows stay) and inserts the marker for any list that\n // didn't exist before. The marker walk afterwards binds those new\n // lists; the per-list reconcile below patches every list's items.\n const template = rootEl.cloneNode(false) as HTMLElement;\n template.innerHTML = flattenWithoutListItems(segment);\n const listParents = new Set<Element>();\n for (const b of bindings.values()) listParents.add(b.liveParent);\n diff(rootEl, template, listParents);\n bindListsFromMarkers(rootEl, segment, bindings);\n }\n\n for (const listSeg of collectLists(segment).values()) {\n const binding = bindings.get(listSeg.id) as ListBinding;\n reconcileList(binding, listSeg);\n }\n });\n}\n\n/**\n * Walk the live tree's comment nodes; every `<!--kf-list:{id}-->` marker\n * is the first child of a list parent. Record the binding (parent + the\n * already-rendered item nodes that follow the marker) and then remove the\n * marker — bindings live in JS, not in the DOM.\n */\nfunction bindListsFromMarkers(\n rootEl: Element,\n segment: Segment,\n bindings: Map<string, ListBinding>,\n): void {\n const lists = collectLists(segment);\n const found: Comment[] = [];\n collectComments(rootEl, found);\n for (const marker of found) {\n if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;\n const id = marker.data.slice(LIST_MARKER_PREFIX.length);\n // Markers are only emitted by `flatten(..., true)` paired with a list\n // segment in the same render, so both lookups always succeed here.\n const listSeg = lists.get(id) as ListSegment;\n const liveParent = marker.parentElement as Element;\n // On first render, items are inlined right after the marker (so the\n // first element sibling is `items[0]`, the next is `items[1]`, etc.).\n // On subsequent renders for a list newly appearing, the list parent\n // is empty after the marker; the loop falls through with `items=[]`\n // and the reconcile phase below builds the items.\n const items: BoundItem[] = [];\n let next: Element | null = marker.nextElementSibling;\n for (let i = 0; i < listSeg.items.length && next !== null; i++) {\n items.push({\n ref: listSeg.items[i].ref,\n cacheKey: listSeg.items[i].cacheKey,\n html: listSeg.items[i].html,\n node: next,\n });\n next = next.nextElementSibling;\n }\n bindings.set(id, { liveParent, items });\n marker.remove();\n }\n}\n\n/**\n * Recursive collector for comment nodes — happy-dom's `TreeWalker` doesn't\n * surface `Node.COMMENT_NODE` despite accepting `NodeFilter.SHOW_COMMENT`,\n * so we walk children directly. Cheap (O(elements)) and portable.\n */\nfunction collectComments(node: Node, out: Comment[]): void {\n for (let c: Node | null = node.firstChild; c !== null; c = c.nextSibling) {\n if (c.nodeType === Node.COMMENT_NODE) out.push(c as Comment);\n else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);\n }\n}\n\n\n","/**\n * `toElement(jsx)` — JSX → DOM, with SVG-aware namespace handling.\n *\n * The naive implementation parses JSX through a `<template>` element's\n * `innerHTML`. That works for HTML and for SVG fragments whose root tag is\n * `<svg>` (the parser switches to \"foreign content\" mode). It silently\n * fails for SVG fragments WITHOUT an `<svg>` wrapper — descendants come out\n * as `HTMLUnknownElement` and never paint.\n *\n * `toElement` detects SVG content and routes through `DOMParser` with the\n * `image/svg+xml` MIME, which guarantees correct namespacing for all\n * descendants. HTML content takes the original `<template>` path unchanged.\n */\n\nimport type { SafeHtml } from './jsx-runtime.js';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nconst SVG_FRAGMENT_TAGS = new Set([\n 'g', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'ellipse',\n 'text', 'tspan', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern',\n 'filter', 'marker', 'linearGradient', 'radialGradient', 'stop', 'image',\n 'foreignObject',\n]);\n\nconst EXCERPT_MAX_LEN = 100;\n\nfunction leadingTag(html: string): string | null {\n const match = /^\\s*<([a-zA-Z][a-zA-Z0-9]*)\\b/.exec(html);\n return match !== null ? match[1] : null;\n}\n\nfunction excerpt(html: string): string {\n const trimmed = html.trim();\n return trimmed.length > EXCERPT_MAX_LEN ? `${trimmed.slice(0, EXCERPT_MAX_LEN)}…` : trimmed;\n}\n\nfunction parseSvgOrThrow(html: string, label: string, originalHtml: string): Document {\n const doc = new DOMParser().parseFromString(html, 'image/svg+xml');\n const err = doc.querySelector('parsererror');\n if (err !== null) {\n throw new Error(`toElement: ${label} parse error — ${err.textContent}\\n input: ${excerpt(originalHtml)}`);\n }\n return doc;\n}\n\nexport function toElement(jsx: SafeHtml | string): Element {\n const html = typeof jsx === 'string' ? jsx : jsx.toString();\n const tag = leadingTag(html);\n\n if (tag === 'svg') {\n // SVG root — parse as XML to guarantee namespace propagation.\n return parseSvgOrThrow(html, 'SVG', html).documentElement;\n }\n\n if (tag !== null && SVG_FRAGMENT_TAGS.has(tag)) {\n // SVG fragment without an <svg> wrapper — wrap, parse, unwrap.\n const wrapped = `<svg xmlns=\"${SVG_NS}\">${html}</svg>`;\n const doc = parseSvgOrThrow(wrapped, 'SVG fragment', html);\n const first = doc.documentElement.firstElementChild;\n /* c8 ignore next 2 — defensive: a successful XML parse of a wrapped svg always yields ≥1 child. */\n if (first === null) throw new Error(`toElement: SVG fragment produced no element\\n input: ${excerpt(html)}`);\n return first;\n }\n\n // HTML — `<template>`-based parse.\n const t = document.createElement('template');\n t.innerHTML = html;\n const child = t.content.firstElementChild;\n if (child === null) throw new Error(`toElement: produced no element\\n input: ${excerpt(html)}`);\n return child;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kerfjs",
3
- "version": "0.3.1",
3
+ "version": "0.4.2",
4
4
  "description": "Tiny reactive UI framework — fine-grained signals + DOM morphing + JSX. Apply the smallest possible cut to update your DOM.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,7 +24,7 @@
24
24
  "fine-grained-reactivity"
25
25
  ],
26
26
  "engines": {
27
- "node": ">=20"
27
+ "node": ">=22.12.0"
28
28
  },
29
29
  "main": "./dist/index.js",
30
30
  "types": "./dist/index.d.ts",
@@ -70,7 +70,10 @@
70
70
  "release:beta": "bash scripts/release.sh --beta",
71
71
  "prepublishOnly": "npm run build",
72
72
  "example:reactivity-demo": "cd examples/reactivity-demo && npm install && npm run dev",
73
- "example:reactivity-demo:build": "cd examples/reactivity-demo && npm install && npm run build"
73
+ "example:reactivity-demo:build": "cd examples/reactivity-demo && npm install && npm run build",
74
+ "site:dev": "cd site && npm install && npm run dev",
75
+ "site:dev:hmr": "cd site && npm install && npm run dev:hmr",
76
+ "site:build": "cd site && npm install && npm run build"
74
77
  },
75
78
  "dependencies": {
76
79
  "@preact/signals-core": "^1.14.1"
@@ -83,7 +86,7 @@
83
86
  "@vitest/coverage-v8": "^3.0.0",
84
87
  "eslint": "^9.16.0",
85
88
  "eslint-plugin-simple-import-sort": "^12.1.1",
86
- "happy-dom": "^15.11.0",
89
+ "happy-dom": "^20.9.0",
87
90
  "husky": "^9.1.7",
88
91
  "jsdom": "^29.1.1",
89
92
  "tsup": "^8.3.0",