kerfjs 0.3.0 → 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 +36 -0
- package/README.md +68 -57
- package/dist/{chunk-ZLV35OHG.js → chunk-WK5D3OO7.js} +2 -2
- package/dist/{chunk-ZLV35OHG.js.map → chunk-WK5D3OO7.js.map} +1 -1
- package/dist/index.d.ts +26 -14
- package/dist/index.js +141 -80
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ 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
|
+
|
|
33
|
+
## [0.3.1] - 2026-05-08
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
- Removed stale `morphdom` references; the bundled native diff is now the only reconciler
|
|
37
|
+
|
|
7
38
|
## [0.3.0] - 2026-05-08
|
|
8
39
|
|
|
9
40
|
|
|
@@ -36,11 +67,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
36
67
|
|
|
37
68
|
### Fixed
|
|
38
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.
|
|
39
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`).
|
|
40
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.
|
|
41
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.
|
|
42
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.
|
|
43
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
|
+
|
|
44
80
|
### Added
|
|
45
81
|
|
|
46
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
|
-
|
|
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
|
-
>
|
|
5
|
+
<h1 align="center">Kerf</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center"><em>The smallest cut.</em></p>
|
|
6
8
|
|
|
7
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
## When to use Kerf
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
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,40 +106,39 @@ delegate(root, 'click', '[data-action="remove"]', (_e, btn) => {
|
|
|
94
106
|
});
|
|
95
107
|
```
|
|
96
108
|
|
|
97
|
-
##
|
|
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
|
-
|
|
111
|
+
```bash
|
|
112
|
+
npm install kerfjs
|
|
113
|
+
```
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
```jsonc
|
|
116
|
+
// tsconfig.json
|
|
117
|
+
{
|
|
118
|
+
"compilerOptions": {
|
|
119
|
+
"jsx": "react-jsx",
|
|
120
|
+
"jsxImportSource": "kerfjs"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
- [`llms.txt`](./llms.txt) — top-level index of every doc, in the [llmstxt.org](https://llmstxt.org) format.
|
|
125
|
+
## Links
|
|
117
126
|
|
|
118
|
-
|
|
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
|
-
|
|
133
|
+
## Why "kerf"?
|
|
121
134
|
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
|
130
|
-
v0.
|
|
141
|
+
v0.3.x — early. API may evolve. See [CHANGELOG.md](./CHANGELOG.md) for what's shipped.
|
|
131
142
|
|
|
132
143
|
## License
|
|
133
144
|
|
|
@@ -273,5 +273,5 @@ function Fragment({ children }) {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
export { Fragment, SafeHtml, collectLists, flatten, flattenWithoutListItems, isSafeHtml, jsx, listSafeHtml, raw };
|
|
276
|
-
//# sourceMappingURL=chunk-
|
|
277
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-WK5D3OO7.js.map
|
|
277
|
+
//# sourceMappingURL=chunk-WK5D3OO7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/segment.ts","../src/utils/escapeHtml.ts","../src/utils/jsx-attr-aliases.ts","../src/jsx-runtime.ts"],"names":[],"mappings":"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","file":"chunk-ZLV35OHG.js","sourcesContent":["/**\n * `Segment` — kerf's structured render output.\n *\n * The JSX runtime emits a `SafeHtml` wrapping a `Segment`. Most renders\n * produce a single static segment (just an HTML string), which behaves\n * exactly like a string for backward compatibility. When the tree\n * contains a list (`each()`) or a parent whose children include a list,\n * the runtime emits a structured segment that `mount()` can dispatch\n * on — running its native keyed reconciler for the list parts and\n * leaving the static surrounds to the general-purpose diff.\n *\n * Why have a structured form at all: the perf bottleneck for huge\n * keyed lists isn't the per-row JSX work (which `each()` already\n * memoises). It's that flattening every render's whole tree to one\n * big HTML string forces a full `innerHTML` parse and a tree walk\n * over rows we know are unchanged. The segment shape lets mount()\n * skip both for the list parts.\n */\n\nexport type Segment = StaticSegment | ListSegment | MixedSegment;\n\nexport interface StaticSegment {\n kind: 'static';\n html: string;\n}\n\nexport interface ListItem {\n /**\n * The row's object identity. Used by the reconciler to match new items\n * against live DOM nodes across renders. Unchanged ref → reuse the\n * existing live node; replaced ref → build a fresh node.\n */\n ref: object;\n /**\n * Optional cache-invalidation key that captures external state affecting\n * this row's render (e.g. selection class). Different cacheKey on the\n * same `ref` triggers a cache miss for that row. `undefined` when the\n * user didn't pass a `key` callback to `each()`.\n */\n cacheKey: unknown;\n html: string;\n}\n\nexport interface ListSegment {\n kind: 'list';\n id: string;\n items: ListItem[];\n}\n\nexport interface MixedSegment {\n kind: 'mixed';\n parts: Segment[];\n}\n\n/**\n * Flatten a segment to a complete HTML string. Used for first render\n * (bulk innerHTML), for SSR-style consumption via `toString()`, and\n * for diagnostics.\n *\n * If `withMarkers` is set, list segments are wrapped in\n * `<!--kf-list:{id}-->` comments so the post-parse walk can find each\n * list's live parent. Plain (non-marker) flatten is what JSX consumers\n * see when they call `.toString()` on the SafeHtml.\n */\nexport function flatten(segment: Segment, withMarkers: boolean): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') {\n const items = segment.items.map((i) => i.html).join('');\n return withMarkers ? `<!--kf-list:${segment.id}-->${items}` : items;\n }\n return segment.parts.map((p) => flatten(p, withMarkers)).join('');\n}\n\n/**\n * Variant of `flatten` for the static-only diff path on subsequent\n * renders. Lists are reduced to a single marker comment with no items\n * inside — the actual list children stay in the live DOM and are\n * reconciled separately. Keeping list items out of this string is\n * what makes the morph cheap on huge lists where most rows are\n * unchanged.\n */\nexport function flattenWithoutListItems(segment: Segment): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') return `<!--kf-list:${segment.id}-->`;\n return segment.parts.map(flattenWithoutListItems).join('');\n}\n\n/** Collect every `ListSegment` in the tree, keyed by its id. */\nexport function collectLists(\n segment: Segment,\n out: Map<string, ListSegment> = new Map(),\n): Map<string, ListSegment> {\n if (segment.kind === 'list') out.set(segment.id, segment);\n else if (segment.kind === 'mixed') {\n for (const part of segment.parts) collectLists(part, out);\n }\n return out;\n}\n\n/**\n * Combine a list of child segments into the smallest equivalent\n * representation: collapses adjacent statics into one static, returns\n * a single static if everything is static, otherwise a mixed segment\n * with statics coalesced.\n */\nexport function mergeChildSegments(parts: Segment[]): Segment {\n if (parts.length === 0) return { kind: 'static', html: '' };\n if (parts.every((p) => p.kind === 'static')) {\n return {\n kind: 'static',\n html: parts.map((p) => (p as StaticSegment).html).join(''),\n };\n }\n const merged: Segment[] = [];\n let coalesced = '';\n for (const p of parts) {\n if (p.kind === 'static') {\n coalesced += p.html;\n } else {\n if (coalesced !== '') {\n merged.push({ kind: 'static', html: coalesced });\n coalesced = '';\n }\n merged.push(p);\n }\n }\n if (coalesced !== '') merged.push({ kind: 'static', html: coalesced });\n return { kind: 'mixed', parts: merged };\n}\n\n/**\n * Wrap a child segment with surrounding open/close tags from the\n * parent JSX element. Used by the JSX runtime when constructing\n * `_jsx(tag, ...)` output.\n */\nexport function wrapWithTags(child: Segment, openTag: string, closeTag: string): Segment {\n if (child.kind === 'static') {\n return { kind: 'static', html: openTag + child.html + closeTag };\n }\n if (child.kind === 'mixed') {\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n ...child.parts,\n { kind: 'static', html: closeTag },\n ],\n };\n }\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n child,\n { kind: 'static', html: closeTag },\n ],\n };\n}\n","/**\n * HTML / attribute escaping for the JSX runtime. Identical to the helpers\n * used in any reasonable HTML emitter — included here so kerf has no extra\n * runtime dependencies beyond `@preact/signals-core` and `morphdom`.\n */\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","/**\n * JSX → HTML / SVG attribute name aliases.\n *\n * The JSX runtime translates camelCase attributes (React convention) to\n * the kebab-case / colon-form names the browser actually wants. Anything\n * not in this map is passed through verbatim — `data-*`, `aria-*`, and\n * any custom attribute work without ceremony.\n *\n * Lives in its own module so `src/jsx-runtime.ts` can stay under the\n * 200-LOC project guideline; the bulk of `jsx-runtime.ts` was this table.\n */\n\nexport const ATTR_ALIASES: Record<string, string> = {\n // HTML attributes\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n accessKey: 'accesskey',\n autoCapitalize: 'autocapitalize',\n autoComplete: 'autocomplete',\n autoFocus: 'autofocus',\n autoPlay: 'autoplay',\n colSpan: 'colspan',\n contentEditable: 'contenteditable',\n crossOrigin: 'crossorigin',\n dateTime: 'datetime',\n defaultChecked: 'checked',\n defaultValue: 'value',\n encType: 'enctype',\n formAction: 'formaction',\n formEncType: 'formenctype',\n formMethod: 'formmethod',\n formNoValidate: 'formnovalidate',\n formTarget: 'formtarget',\n hrefLang: 'hreflang',\n inputMode: 'inputmode',\n maxLength: 'maxlength',\n minLength: 'minlength',\n noModule: 'nomodule',\n noValidate: 'novalidate',\n readOnly: 'readonly',\n referrerPolicy: 'referrerpolicy',\n rowSpan: 'rowspan',\n spellCheck: 'spellcheck',\n srcDoc: 'srcdoc',\n srcLang: 'srclang',\n srcSet: 'srcset',\n tabIndex: 'tabindex',\n useMap: 'usemap',\n\n // SVG presentation attributes (camelCase → kebab-case)\n strokeWidth: 'stroke-width',\n strokeLinecap: 'stroke-linecap',\n strokeLinejoin: 'stroke-linejoin',\n strokeDasharray: 'stroke-dasharray',\n strokeDashoffset: 'stroke-dashoffset',\n strokeMiterlimit: 'stroke-miterlimit',\n strokeOpacity: 'stroke-opacity',\n fillOpacity: 'fill-opacity',\n fillRule: 'fill-rule',\n clipPath: 'clip-path',\n clipRule: 'clip-rule',\n colorInterpolation: 'color-interpolation',\n colorInterpolationFilters: 'color-interpolation-filters',\n floodColor: 'flood-color',\n floodOpacity: 'flood-opacity',\n lightingColor: 'lighting-color',\n stopColor: 'stop-color',\n stopOpacity: 'stop-opacity',\n shapeRendering: 'shape-rendering',\n imageRendering: 'image-rendering',\n textRendering: 'text-rendering',\n pointerEvents: 'pointer-events',\n vectorEffect: 'vector-effect',\n paintOrder: 'paint-order',\n\n // SVG text/font attributes\n fontFamily: 'font-family',\n fontSize: 'font-size',\n fontStyle: 'font-style',\n fontVariant: 'font-variant',\n fontWeight: 'font-weight',\n fontStretch: 'font-stretch',\n textAnchor: 'text-anchor',\n textDecoration: 'text-decoration',\n dominantBaseline: 'dominant-baseline',\n alignmentBaseline: 'alignment-baseline',\n baselineShift: 'baseline-shift',\n letterSpacing: 'letter-spacing',\n wordSpacing: 'word-spacing',\n writingMode: 'writing-mode',\n\n // SVG marker attributes\n markerStart: 'marker-start',\n markerMid: 'marker-mid',\n markerEnd: 'marker-end',\n\n // SVG xlink (legacy but still used)\n xlinkHref: 'xlink:href',\n xlinkShow: 'xlink:show',\n xlinkActuate: 'xlink:actuate',\n xlinkType: 'xlink:type',\n xlinkRole: 'xlink:role',\n xlinkTitle: 'xlink:title',\n xlinkArcrole: 'xlink:arcrole',\n xmlBase: 'xml:base',\n xmlLang: 'xml:lang',\n xmlSpace: 'xml:space',\n xmlnsXlink: 'xmlns:xlink',\n};\n","/**\n * kerf JSX runtime.\n *\n * JSX renders to `SafeHtml`, which wraps both:\n * - `__html`: the flattened HTML string (what `toString()` returns; what\n * legacy/SSR consumers care about)\n * - `__segment`: a structured representation that distinguishes \"static\n * html\", \"keyed list\", and \"mixed\" content.\n *\n * Most renders are pure-static and the segment is just `{kind:'static',html}`.\n * When the tree contains a list (via `each()`) or a parent whose children\n * include a non-static segment, the runtime threads that structure up so\n * `mount()` can dispatch on it — running its native keyed reconciler for\n * the list parts and leaving the static surrounds to the general-purpose\n * diff.\n *\n * Configure in your `tsconfig.json`:\n *\n * \"jsx\": \"react-jsx\",\n * \"jsxImportSource\": \"kerfjs\"\n *\n * Then write JSX as you normally would — kerf provides the `jsx` /\n * `jsxs` / `jsxDEV` / `Fragment` exports the JSX transform looks for.\n */\n\nimport {\n flatten,\n type ListSegment,\n mergeChildSegments,\n type Segment,\n wrapWithTags,\n} from './segment.js';\nimport { escapeAttr, escapeHtml } from './utils/escapeHtml.js';\nimport { ATTR_ALIASES } from './utils/jsx-attr-aliases.js';\n\n// Cross-realm/cross-bundle brand. Using `Symbol.for` (the global registry)\n// means two `SafeHtml` classes from different module copies still recognise\n// each other. Same approach React uses for `$$typeof: Symbol.for('react.element')`.\n// Without this, `instanceof SafeHtml` fails when the consumer's bundler ends\n// up loading two copies of kerf (separate barrel + jsx-runtime entries,\n// monorepo dedup misses, ESM/CJS interop, etc.).\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\n\nexport class SafeHtml {\n readonly __html: string;\n readonly __segment: Segment;\n // Branded so `isSafeHtml()` recognises instances from any copy of this module.\n readonly [SAFE_HTML_BRAND] = true as const;\n constructor(input: string | Segment) {\n if (typeof input === 'string') {\n this.__segment = { kind: 'static', html: input };\n this.__html = input;\n } else {\n this.__segment = input;\n this.__html = flatten(input, false);\n }\n }\n toString(): string {\n return this.__html;\n }\n}\n\n/**\n * Type guard for `SafeHtml`. Prefer this over `instanceof SafeHtml` — it works\n * across module copies (e.g. when the consumer's bundler loads kerf's barrel\n * and JSX-runtime entries as independent modules).\n */\nexport function isSafeHtml(value: unknown): value is SafeHtml {\n return typeof value === 'object'\n && value !== null\n && (value as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Inject a pre-escaped HTML string. Use sparingly — caller is responsible for escaping. */\nexport function raw(html: string): SafeHtml {\n return new SafeHtml(html);\n}\n\n/**\n * Internal: build a `SafeHtml` representing a list segment. Used by\n * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction.\n */\nexport function listSafeHtml(id: string, items: ListSegment['items']): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items });\n}\n\ntype Child = SafeHtml | string | number | boolean | null | undefined;\ntype Children = Child | Children[];\n\ninterface Props {\n children?: Children;\n [key: string]: unknown;\n}\n\nconst VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'source', 'track', 'wbr',\n]);\n\n/**\n * Convert a single JSX child into a Segment. Handles SafeHtml passthrough,\n * primitive coercion + escaping, arrays (recursive), and the nullish/false\n * skip cases.\n */\nfunction toSegment(child: Children): Segment {\n if (child == null || typeof child === 'boolean') return { kind: 'static', html: '' };\n if (isSafeHtml(child)) {\n // Cross-bundle SafeHtml shims (KF-14 case) may have only `__html`.\n return child.__segment ?? { kind: 'static', html: child.__html };\n }\n if (typeof child === 'string') return { kind: 'static', html: escapeHtml(child) };\n if (typeof child === 'number') return { kind: 'static', html: String(child) };\n if (Array.isArray(child)) return mergeChildSegments(child.map(toSegment));\n // Catch the common mistake of passing a DOM element (e.g. the result of\n // toElement(...)) as a JSX child. The runtime renders to HTML strings, so\n // DOM nodes can't be composed — they'd silently serialize to \"\" and their\n // event listeners would be lost. Throw loudly so this can't sneak in.\n const maybeNode = child as unknown;\n if (typeof maybeNode === 'object' && maybeNode !== null\n && ('nodeType' in maybeNode || 'outerHTML' in maybeNode)) {\n throw new Error(\n 'JSX: DOM elements cannot be passed as children (the JSX runtime renders to HTML strings). '\n + 'Build the tree in one JSX expression and use querySelector after toElement() to get element refs.',\n );\n }\n throw new Error(\n `JSX: unsupported child of type ${describeValue(child)}. `\n + 'Children must be SafeHtml, string, number, boolean, null, undefined, or an array of those. '\n + 'Common mistakes: passing a Signal/Store object directly (use signal.value or store.state.value), '\n + 'passing a function (call it first), or passing a Promise (await it before render).',\n );\n}\n\nfunction describeValue(v: unknown): string {\n if (Array.isArray(v)) return 'array';\n if (typeof v === 'object' && v !== null) {\n const ctor = (v as { constructor?: { name?: string } }).constructor?.name;\n return ctor && ctor !== 'Object' ? `object (${ctor})` : 'object';\n }\n return typeof v;\n}\n\nfunction renderAttr(key: string, value: unknown): string {\n const name = ATTR_ALIASES[key] ?? key;\n if (value == null || value === false) return '';\n if (value === true) return ` ${name}`;\n let strValue: string;\n if (isSafeHtml(value)) {\n strValue = value.__html;\n } else if (typeof value === 'number') {\n strValue = String(value);\n } else if (typeof value === 'string') {\n strValue = escapeAttr(value);\n } else {\n throw new Error(\n `JSX: unsupported value for attribute \"${key}\" — got ${describeValue(value)}. `\n + 'Attribute values must be string, number, boolean, null, undefined, or SafeHtml. '\n + 'Did you mean to read .value off a Signal, or stringify the object first?',\n );\n }\n return ` ${name}=\"${strValue}\"`;\n}\n\nexport function jsx(tag: string | ((props: Props) => SafeHtml), props: Props): SafeHtml {\n if (typeof tag === 'function') return tag(props);\n\n const { children, ...attrs } = props;\n const attrStr = Object.entries(attrs)\n .map(([k, v]) => renderAttr(k, v))\n .join('');\n\n if (VOID_TAGS.has(tag)) return new SafeHtml(`<${tag}${attrStr}>`);\n\n const childSegment: Segment = children != null\n ? toSegment(children)\n : { kind: 'static', html: '' };\n return new SafeHtml(wrapWithTags(childSegment, `<${tag}${attrStr}>`, `</${tag}>`));\n}\n\nexport { jsx as jsxs };\n// vitest's dev-mode JSX transform emits `jsxDEV(tag, props, ...)`; the\n// alias lets tests import this module without the production build pipeline\n// caring.\nexport { jsx as jsxDEV };\n\nexport function Fragment({ children }: { children?: Children }): SafeHtml {\n return new SafeHtml(children != null ? toSegment(children) : { kind: 'static', html: '' });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n export type Element = SafeHtml;\n export interface ElementChildrenAttribute {\n children: unknown;\n }\n export interface IntrinsicElements {\n [elemName: string]: Record<string, unknown>;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/segment.ts","../src/utils/escapeHtml.ts","../src/utils/jsx-attr-aliases.ts","../src/jsx-runtime.ts"],"names":[],"mappings":"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","file":"chunk-WK5D3OO7.js","sourcesContent":["/**\n * `Segment` — kerf's structured render output.\n *\n * The JSX runtime emits a `SafeHtml` wrapping a `Segment`. Most renders\n * produce a single static segment (just an HTML string), which behaves\n * exactly like a string for backward compatibility. When the tree\n * contains a list (`each()`) or a parent whose children include a list,\n * the runtime emits a structured segment that `mount()` can dispatch\n * on — running its native keyed reconciler for the list parts and\n * leaving the static surrounds to the general-purpose diff.\n *\n * Why have a structured form at all: the perf bottleneck for huge\n * keyed lists isn't the per-row JSX work (which `each()` already\n * memoises). It's that flattening every render's whole tree to one\n * big HTML string forces a full `innerHTML` parse and a tree walk\n * over rows we know are unchanged. The segment shape lets mount()\n * skip both for the list parts.\n */\n\nexport type Segment = StaticSegment | ListSegment | MixedSegment;\n\nexport interface StaticSegment {\n kind: 'static';\n html: string;\n}\n\nexport interface ListItem {\n /**\n * The row's object identity. Used by the reconciler to match new items\n * against live DOM nodes across renders. Unchanged ref → reuse the\n * existing live node; replaced ref → build a fresh node.\n */\n ref: object;\n /**\n * Optional cache-invalidation key that captures external state affecting\n * this row's render (e.g. selection class). Different cacheKey on the\n * same `ref` triggers a cache miss for that row. `undefined` when the\n * user didn't pass a `key` callback to `each()`.\n */\n cacheKey: unknown;\n html: string;\n}\n\nexport interface ListSegment {\n kind: 'list';\n id: string;\n items: ListItem[];\n}\n\nexport interface MixedSegment {\n kind: 'mixed';\n parts: Segment[];\n}\n\n/**\n * Flatten a segment to a complete HTML string. Used for first render\n * (bulk innerHTML), for SSR-style consumption via `toString()`, and\n * for diagnostics.\n *\n * If `withMarkers` is set, list segments are wrapped in\n * `<!--kf-list:{id}-->` comments so the post-parse walk can find each\n * list's live parent. Plain (non-marker) flatten is what JSX consumers\n * see when they call `.toString()` on the SafeHtml.\n */\nexport function flatten(segment: Segment, withMarkers: boolean): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') {\n const items = segment.items.map((i) => i.html).join('');\n return withMarkers ? `<!--kf-list:${segment.id}-->${items}` : items;\n }\n return segment.parts.map((p) => flatten(p, withMarkers)).join('');\n}\n\n/**\n * Variant of `flatten` for the static-only diff path on subsequent\n * renders. Lists are reduced to a single marker comment with no items\n * inside — the actual list children stay in the live DOM and are\n * reconciled separately. Keeping list items out of this string is\n * what makes the morph cheap on huge lists where most rows are\n * unchanged.\n */\nexport function flattenWithoutListItems(segment: Segment): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') return `<!--kf-list:${segment.id}-->`;\n return segment.parts.map(flattenWithoutListItems).join('');\n}\n\n/** Collect every `ListSegment` in the tree, keyed by its id. */\nexport function collectLists(\n segment: Segment,\n out: Map<string, ListSegment> = new Map(),\n): Map<string, ListSegment> {\n if (segment.kind === 'list') out.set(segment.id, segment);\n else if (segment.kind === 'mixed') {\n for (const part of segment.parts) collectLists(part, out);\n }\n return out;\n}\n\n/**\n * Combine a list of child segments into the smallest equivalent\n * representation: collapses adjacent statics into one static, returns\n * a single static if everything is static, otherwise a mixed segment\n * with statics coalesced.\n */\nexport function mergeChildSegments(parts: Segment[]): Segment {\n if (parts.length === 0) return { kind: 'static', html: '' };\n if (parts.every((p) => p.kind === 'static')) {\n return {\n kind: 'static',\n html: parts.map((p) => (p as StaticSegment).html).join(''),\n };\n }\n const merged: Segment[] = [];\n let coalesced = '';\n for (const p of parts) {\n if (p.kind === 'static') {\n coalesced += p.html;\n } else {\n if (coalesced !== '') {\n merged.push({ kind: 'static', html: coalesced });\n coalesced = '';\n }\n merged.push(p);\n }\n }\n if (coalesced !== '') merged.push({ kind: 'static', html: coalesced });\n return { kind: 'mixed', parts: merged };\n}\n\n/**\n * Wrap a child segment with surrounding open/close tags from the\n * parent JSX element. Used by the JSX runtime when constructing\n * `_jsx(tag, ...)` output.\n */\nexport function wrapWithTags(child: Segment, openTag: string, closeTag: string): Segment {\n if (child.kind === 'static') {\n return { kind: 'static', html: openTag + child.html + closeTag };\n }\n if (child.kind === 'mixed') {\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n ...child.parts,\n { kind: 'static', html: closeTag },\n ],\n };\n }\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n child,\n { kind: 'static', html: closeTag },\n ],\n };\n}\n","/**\n * HTML / attribute escaping for the JSX runtime. Identical to the helpers\n * used in any reasonable HTML emitter — included here so kerf has no extra\n * runtime dependencies beyond `@preact/signals-core`.\n */\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","/**\n * JSX → HTML / SVG attribute name aliases.\n *\n * The JSX runtime translates camelCase attributes (React convention) to\n * the kebab-case / colon-form names the browser actually wants. Anything\n * not in this map is passed through verbatim — `data-*`, `aria-*`, and\n * any custom attribute work without ceremony.\n *\n * Lives in its own module so `src/jsx-runtime.ts` can stay under the\n * 200-LOC project guideline; the bulk of `jsx-runtime.ts` was this table.\n */\n\nexport const ATTR_ALIASES: Record<string, string> = {\n // HTML attributes\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n accessKey: 'accesskey',\n autoCapitalize: 'autocapitalize',\n autoComplete: 'autocomplete',\n autoFocus: 'autofocus',\n autoPlay: 'autoplay',\n colSpan: 'colspan',\n contentEditable: 'contenteditable',\n crossOrigin: 'crossorigin',\n dateTime: 'datetime',\n defaultChecked: 'checked',\n defaultValue: 'value',\n encType: 'enctype',\n formAction: 'formaction',\n formEncType: 'formenctype',\n formMethod: 'formmethod',\n formNoValidate: 'formnovalidate',\n formTarget: 'formtarget',\n hrefLang: 'hreflang',\n inputMode: 'inputmode',\n maxLength: 'maxlength',\n minLength: 'minlength',\n noModule: 'nomodule',\n noValidate: 'novalidate',\n readOnly: 'readonly',\n referrerPolicy: 'referrerpolicy',\n rowSpan: 'rowspan',\n spellCheck: 'spellcheck',\n srcDoc: 'srcdoc',\n srcLang: 'srclang',\n srcSet: 'srcset',\n tabIndex: 'tabindex',\n useMap: 'usemap',\n\n // SVG presentation attributes (camelCase → kebab-case)\n strokeWidth: 'stroke-width',\n strokeLinecap: 'stroke-linecap',\n strokeLinejoin: 'stroke-linejoin',\n strokeDasharray: 'stroke-dasharray',\n strokeDashoffset: 'stroke-dashoffset',\n strokeMiterlimit: 'stroke-miterlimit',\n strokeOpacity: 'stroke-opacity',\n fillOpacity: 'fill-opacity',\n fillRule: 'fill-rule',\n clipPath: 'clip-path',\n clipRule: 'clip-rule',\n colorInterpolation: 'color-interpolation',\n colorInterpolationFilters: 'color-interpolation-filters',\n floodColor: 'flood-color',\n floodOpacity: 'flood-opacity',\n lightingColor: 'lighting-color',\n stopColor: 'stop-color',\n stopOpacity: 'stop-opacity',\n shapeRendering: 'shape-rendering',\n imageRendering: 'image-rendering',\n textRendering: 'text-rendering',\n pointerEvents: 'pointer-events',\n vectorEffect: 'vector-effect',\n paintOrder: 'paint-order',\n\n // SVG text/font attributes\n fontFamily: 'font-family',\n fontSize: 'font-size',\n fontStyle: 'font-style',\n fontVariant: 'font-variant',\n fontWeight: 'font-weight',\n fontStretch: 'font-stretch',\n textAnchor: 'text-anchor',\n textDecoration: 'text-decoration',\n dominantBaseline: 'dominant-baseline',\n alignmentBaseline: 'alignment-baseline',\n baselineShift: 'baseline-shift',\n letterSpacing: 'letter-spacing',\n wordSpacing: 'word-spacing',\n writingMode: 'writing-mode',\n\n // SVG marker attributes\n markerStart: 'marker-start',\n markerMid: 'marker-mid',\n markerEnd: 'marker-end',\n\n // SVG xlink (legacy but still used)\n xlinkHref: 'xlink:href',\n xlinkShow: 'xlink:show',\n xlinkActuate: 'xlink:actuate',\n xlinkType: 'xlink:type',\n xlinkRole: 'xlink:role',\n xlinkTitle: 'xlink:title',\n xlinkArcrole: 'xlink:arcrole',\n xmlBase: 'xml:base',\n xmlLang: 'xml:lang',\n xmlSpace: 'xml:space',\n xmlnsXlink: 'xmlns:xlink',\n};\n","/**\n * kerf JSX runtime.\n *\n * JSX renders to `SafeHtml`, which wraps both:\n * - `__html`: the flattened HTML string (what `toString()` returns; what\n * legacy/SSR consumers care about)\n * - `__segment`: a structured representation that distinguishes \"static\n * html\", \"keyed list\", and \"mixed\" content.\n *\n * Most renders are pure-static and the segment is just `{kind:'static',html}`.\n * When the tree contains a list (via `each()`) or a parent whose children\n * include a non-static segment, the runtime threads that structure up so\n * `mount()` can dispatch on it — running its native keyed reconciler for\n * the list parts and leaving the static surrounds to the general-purpose\n * diff.\n *\n * Configure in your `tsconfig.json`:\n *\n * \"jsx\": \"react-jsx\",\n * \"jsxImportSource\": \"kerfjs\"\n *\n * Then write JSX as you normally would — kerf provides the `jsx` /\n * `jsxs` / `jsxDEV` / `Fragment` exports the JSX transform looks for.\n */\n\nimport {\n flatten,\n type ListSegment,\n mergeChildSegments,\n type Segment,\n wrapWithTags,\n} from './segment.js';\nimport { escapeAttr, escapeHtml } from './utils/escapeHtml.js';\nimport { ATTR_ALIASES } from './utils/jsx-attr-aliases.js';\n\n// Cross-realm/cross-bundle brand. Using `Symbol.for` (the global registry)\n// means two `SafeHtml` classes from different module copies still recognise\n// each other. Same approach React uses for `$$typeof: Symbol.for('react.element')`.\n// Without this, `instanceof SafeHtml` fails when the consumer's bundler ends\n// up loading two copies of kerf (separate barrel + jsx-runtime entries,\n// monorepo dedup misses, ESM/CJS interop, etc.).\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\n\nexport class SafeHtml {\n readonly __html: string;\n readonly __segment: Segment;\n // Branded so `isSafeHtml()` recognises instances from any copy of this module.\n readonly [SAFE_HTML_BRAND] = true as const;\n constructor(input: string | Segment) {\n if (typeof input === 'string') {\n this.__segment = { kind: 'static', html: input };\n this.__html = input;\n } else {\n this.__segment = input;\n this.__html = flatten(input, false);\n }\n }\n toString(): string {\n return this.__html;\n }\n}\n\n/**\n * Type guard for `SafeHtml`. Prefer this over `instanceof SafeHtml` — it works\n * across module copies (e.g. when the consumer's bundler loads kerf's barrel\n * and JSX-runtime entries as independent modules).\n */\nexport function isSafeHtml(value: unknown): value is SafeHtml {\n return typeof value === 'object'\n && value !== null\n && (value as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Inject a pre-escaped HTML string. Use sparingly — caller is responsible for escaping. */\nexport function raw(html: string): SafeHtml {\n return new SafeHtml(html);\n}\n\n/**\n * Internal: build a `SafeHtml` representing a list segment. Used by\n * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction.\n */\nexport function listSafeHtml(id: string, items: ListSegment['items']): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items });\n}\n\ntype Child = SafeHtml | string | number | boolean | null | undefined;\ntype Children = Child | Children[];\n\ninterface Props {\n children?: Children;\n [key: string]: unknown;\n}\n\nconst VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'source', 'track', 'wbr',\n]);\n\n/**\n * Convert a single JSX child into a Segment. Handles SafeHtml passthrough,\n * primitive coercion + escaping, arrays (recursive), and the nullish/false\n * skip cases.\n */\nfunction toSegment(child: Children): Segment {\n if (child == null || typeof child === 'boolean') return { kind: 'static', html: '' };\n if (isSafeHtml(child)) {\n // Cross-bundle SafeHtml shims (KF-14 case) may have only `__html`.\n return child.__segment ?? { kind: 'static', html: child.__html };\n }\n if (typeof child === 'string') return { kind: 'static', html: escapeHtml(child) };\n if (typeof child === 'number') return { kind: 'static', html: String(child) };\n if (Array.isArray(child)) return mergeChildSegments(child.map(toSegment));\n // Catch the common mistake of passing a DOM element (e.g. the result of\n // toElement(...)) as a JSX child. The runtime renders to HTML strings, so\n // DOM nodes can't be composed — they'd silently serialize to \"\" and their\n // event listeners would be lost. Throw loudly so this can't sneak in.\n const maybeNode = child as unknown;\n if (typeof maybeNode === 'object' && maybeNode !== null\n && ('nodeType' in maybeNode || 'outerHTML' in maybeNode)) {\n throw new Error(\n 'JSX: DOM elements cannot be passed as children (the JSX runtime renders to HTML strings). '\n + 'Build the tree in one JSX expression and use querySelector after toElement() to get element refs.',\n );\n }\n throw new Error(\n `JSX: unsupported child of type ${describeValue(child)}. `\n + 'Children must be SafeHtml, string, number, boolean, null, undefined, or an array of those. '\n + 'Common mistakes: passing a Signal/Store object directly (use signal.value or store.state.value), '\n + 'passing a function (call it first), or passing a Promise (await it before render).',\n );\n}\n\nfunction describeValue(v: unknown): string {\n if (Array.isArray(v)) return 'array';\n if (typeof v === 'object' && v !== null) {\n const ctor = (v as { constructor?: { name?: string } }).constructor?.name;\n return ctor && ctor !== 'Object' ? `object (${ctor})` : 'object';\n }\n return typeof v;\n}\n\nfunction renderAttr(key: string, value: unknown): string {\n const name = ATTR_ALIASES[key] ?? key;\n if (value == null || value === false) return '';\n if (value === true) return ` ${name}`;\n let strValue: string;\n if (isSafeHtml(value)) {\n strValue = value.__html;\n } else if (typeof value === 'number') {\n strValue = String(value);\n } else if (typeof value === 'string') {\n strValue = escapeAttr(value);\n } else {\n throw new Error(\n `JSX: unsupported value for attribute \"${key}\" — got ${describeValue(value)}. `\n + 'Attribute values must be string, number, boolean, null, undefined, or SafeHtml. '\n + 'Did you mean to read .value off a Signal, or stringify the object first?',\n );\n }\n return ` ${name}=\"${strValue}\"`;\n}\n\nexport function jsx(tag: string | ((props: Props) => SafeHtml), props: Props): SafeHtml {\n if (typeof tag === 'function') return tag(props);\n\n const { children, ...attrs } = props;\n const attrStr = Object.entries(attrs)\n .map(([k, v]) => renderAttr(k, v))\n .join('');\n\n if (VOID_TAGS.has(tag)) return new SafeHtml(`<${tag}${attrStr}>`);\n\n const childSegment: Segment = children != null\n ? toSegment(children)\n : { kind: 'static', html: '' };\n return new SafeHtml(wrapWithTags(childSegment, `<${tag}${attrStr}>`, `</${tag}>`));\n}\n\nexport { jsx as jsxs };\n// vitest's dev-mode JSX transform emits `jsxDEV(tag, props, ...)`; the\n// alias lets tests import this module without the production build pipeline\n// caring.\nexport { jsx as jsxDEV };\n\nexport function Fragment({ children }: { children?: Children }): SafeHtml {\n return new SafeHtml(children != null ? toSegment(children) : { kind: 'static', html: '' });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n export type Element = SafeHtml;\n export interface ElementChildrenAttribute {\n children: unknown;\n }\n export interface IntrinsicElements {\n [elemName: string]: Record<string, unknown>;\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { S as Store, d as defineStore, r as resetAllStores } from './testing-CdM
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Tiny event-delegation helpers. Replace per-element `addEventListener` calls
|
|
8
|
-
* (which don't survive morph re-renders for nodes
|
|
8
|
+
* (which don't survive morph re-renders for nodes the diff creates) with one
|
|
9
9
|
* listener at the morph-root that dispatches via `closest()`.
|
|
10
10
|
*
|
|
11
11
|
* Three-tier listener model:
|
|
@@ -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
|
-
*
|
|
18
|
-
* focus
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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,21 +34,24 @@ 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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* the
|
|
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
|
/**
|
|
41
52
|
* Capture-phase delegation — for non-bubbling events (`focus`, `blur`,
|
|
42
53
|
* `scroll`, `load`, `error`). Reaches descendants of `rootEl` that match
|
|
43
|
-
* `selector` regardless of how many times
|
|
54
|
+
* `selector` regardless of how many times the diff has rebuilt them.
|
|
44
55
|
*
|
|
45
56
|
* Usage (pseudo-code — see examples for live ones):
|
|
46
57
|
* delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);
|
|
@@ -93,9 +104,10 @@ declare function each<T extends object>(items: readonly T[], render: (item: T, i
|
|
|
93
104
|
*
|
|
94
105
|
* Two phases per render:
|
|
95
106
|
*
|
|
96
|
-
* - Static surrounds (everything outside `each()` lists):
|
|
97
|
-
* a freshly-built template against the live
|
|
98
|
-
*
|
|
107
|
+
* - Static surrounds (everything outside `each()` lists): kerf's native
|
|
108
|
+
* `diff()` reconciler walks a freshly-built template against the live
|
|
109
|
+
* tree. Conventions: id/data-key matching, `data-morph-skip`, focus
|
|
110
|
+
* preservation.
|
|
99
111
|
*
|
|
100
112
|
* - List interiors (children of every `each()` parent): native keyed
|
|
101
113
|
* reconciler operates directly on the live parent's children. No
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { isSafeHtml, listSafeHtml, flatten, collectLists, flattenWithoutListItems } from './chunk-
|
|
2
|
-
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-
|
|
1
|
+
import { isSafeHtml, listSafeHtml, flatten, collectLists, flattenWithoutListItems } from './chunk-WK5D3OO7.js';
|
|
2
|
+
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-WK5D3OO7.js';
|
|
3
3
|
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
|
-
|
|
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,67 +216,37 @@ function preserveTextEntryState(fromEl, toEl) {
|
|
|
201
216
|
}
|
|
202
217
|
}
|
|
203
218
|
|
|
204
|
-
// src/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
let result;
|
|
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") {
|
|
214
228
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
const segment = isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: result };
|
|
220
|
-
if (isFirst) {
|
|
221
|
-
rootEl.innerHTML = flatten(segment, true);
|
|
222
|
-
bindListsFromMarkers(rootEl, segment, bindings);
|
|
223
|
-
isFirst = false;
|
|
224
|
-
} else {
|
|
225
|
-
const template = rootEl.cloneNode(false);
|
|
226
|
-
template.innerHTML = flattenWithoutListItems(segment);
|
|
227
|
-
const listParents = /* @__PURE__ */ new Set();
|
|
228
|
-
for (const b of bindings.values()) listParents.add(b.liveParent);
|
|
229
|
-
diff(rootEl, template, listParents);
|
|
230
|
-
bindListsFromMarkers(rootEl, segment, bindings);
|
|
231
|
-
}
|
|
232
|
-
for (const listSeg of collectLists(segment).values()) {
|
|
233
|
-
const binding = bindings.get(listSeg.id);
|
|
234
|
-
reconcileList(binding, listSeg);
|
|
229
|
+
selStart = el.selectionStart;
|
|
230
|
+
selEnd = el.selectionEnd;
|
|
231
|
+
} catch {
|
|
235
232
|
}
|
|
236
|
-
}
|
|
233
|
+
}
|
|
234
|
+
return { el, selStart, selEnd };
|
|
237
235
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const liveParent = marker.parentElement;
|
|
247
|
-
const items = [];
|
|
248
|
-
let next = marker.nextElementSibling;
|
|
249
|
-
for (let i = 0; i < listSeg.items.length && next !== null; i++) {
|
|
250
|
-
items.push({
|
|
251
|
-
ref: listSeg.items[i].ref,
|
|
252
|
-
cacheKey: listSeg.items[i].cacheKey,
|
|
253
|
-
html: listSeg.items[i].html,
|
|
254
|
-
node: next
|
|
255
|
-
});
|
|
256
|
-
next = next.nextElementSibling;
|
|
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 {
|
|
257
244
|
}
|
|
258
|
-
bindings.set(id, { liveParent, items });
|
|
259
|
-
marker.remove();
|
|
260
245
|
}
|
|
261
246
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
247
|
+
|
|
248
|
+
// src/list-reconcile.ts
|
|
249
|
+
function classifyItems(oldItems, listSeg) {
|
|
265
250
|
const oldByRef = /* @__PURE__ */ new Map();
|
|
266
251
|
const oldIndex = /* @__PURE__ */ new Map();
|
|
267
252
|
for (let i = 0; i < oldItems.length; i++) {
|
|
@@ -295,37 +280,39 @@ function reconcileList(binding, listSeg) {
|
|
|
295
280
|
freshIndices.push(i);
|
|
296
281
|
freshHtmls.push(ni.html);
|
|
297
282
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
283
|
+
for (const [, orphan] of oldByRef) replacedNodes.push(orphan.node);
|
|
284
|
+
return { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls };
|
|
285
|
+
}
|
|
286
|
+
function buildFreshNodes(newRecord, freshIndices, freshHtmls) {
|
|
287
|
+
if (freshHtmls.length === 0) return;
|
|
288
|
+
const tpl = document.createElement("template");
|
|
289
|
+
tpl.innerHTML = freshHtmls.join("");
|
|
290
|
+
let node = tpl.content.firstElementChild;
|
|
291
|
+
for (const idx of freshIndices) {
|
|
292
|
+
if (node === null) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`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)}`
|
|
295
|
+
);
|
|
311
296
|
}
|
|
297
|
+
const next = node.nextElementSibling;
|
|
298
|
+
newRecord[idx].node = node;
|
|
299
|
+
node = next;
|
|
312
300
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
301
|
+
}
|
|
302
|
+
function removeOldNodes(liveParent, replacedNodes) {
|
|
316
303
|
for (const node of replacedNodes) {
|
|
317
304
|
if (node.parentElement === liveParent) liveParent.removeChild(node);
|
|
318
305
|
}
|
|
319
|
-
|
|
306
|
+
}
|
|
307
|
+
function applyMoves(liveParent, newRecord, prevIdx, stable) {
|
|
320
308
|
let nextSibling = null;
|
|
321
309
|
for (let i = newRecord.length - 1; i >= 0; i--) {
|
|
322
310
|
const node = newRecord[i].node;
|
|
323
|
-
if (prevIdx[i]
|
|
311
|
+
if (prevIdx[i] === -1 || !stable.has(i)) {
|
|
324
312
|
liveParent.insertBefore(node, nextSibling);
|
|
325
313
|
}
|
|
326
314
|
nextSibling = node;
|
|
327
315
|
}
|
|
328
|
-
binding.items = newRecord;
|
|
329
316
|
}
|
|
330
317
|
function lis(arr) {
|
|
331
318
|
const tails = [];
|
|
@@ -356,10 +343,84 @@ function lis(arr) {
|
|
|
356
343
|
}
|
|
357
344
|
return out;
|
|
358
345
|
}
|
|
346
|
+
function reconcileList(binding, listSeg) {
|
|
347
|
+
const { liveParent } = binding;
|
|
348
|
+
const { newRecord, prevIdx, replacedNodes, freshIndices, freshHtmls } = classifyItems(binding.items, listSeg);
|
|
349
|
+
buildFreshNodes(newRecord, freshIndices, freshHtmls);
|
|
350
|
+
const focusSnap = captureFocus(liveParent);
|
|
351
|
+
removeOldNodes(liveParent, replacedNodes);
|
|
352
|
+
applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx));
|
|
353
|
+
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
354
|
+
binding.items = newRecord;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/mount.ts
|
|
358
|
+
var LIST_MARKER_PREFIX = "kf-list:";
|
|
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
|
+
}
|
|
365
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
366
|
+
const counter = { value: 0 };
|
|
367
|
+
let isFirst = true;
|
|
368
|
+
return effect(() => {
|
|
369
|
+
counter.value = 0;
|
|
370
|
+
_setListCounter(counter);
|
|
371
|
+
let result;
|
|
372
|
+
try {
|
|
373
|
+
result = render();
|
|
374
|
+
} finally {
|
|
375
|
+
_setListCounter(null);
|
|
376
|
+
}
|
|
377
|
+
const segment = isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: result };
|
|
378
|
+
if (isFirst) {
|
|
379
|
+
rootEl.innerHTML = flatten(segment, true);
|
|
380
|
+
bindListsFromMarkers(rootEl, segment, bindings);
|
|
381
|
+
isFirst = false;
|
|
382
|
+
} else {
|
|
383
|
+
const template = rootEl.cloneNode(false);
|
|
384
|
+
template.innerHTML = flattenWithoutListItems(segment);
|
|
385
|
+
const listParents = /* @__PURE__ */ new Set();
|
|
386
|
+
for (const b of bindings.values()) listParents.add(b.liveParent);
|
|
387
|
+
diff(rootEl, template, listParents);
|
|
388
|
+
bindListsFromMarkers(rootEl, segment, bindings);
|
|
389
|
+
}
|
|
390
|
+
for (const listSeg of collectLists(segment).values()) {
|
|
391
|
+
const binding = bindings.get(listSeg.id);
|
|
392
|
+
reconcileList(binding, listSeg);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function bindListsFromMarkers(rootEl, segment, bindings) {
|
|
397
|
+
const lists = collectLists(segment);
|
|
398
|
+
const found = [];
|
|
399
|
+
collectComments(rootEl, found);
|
|
400
|
+
for (const marker of found) {
|
|
401
|
+
if (!marker.data.startsWith(LIST_MARKER_PREFIX)) continue;
|
|
402
|
+
const id = marker.data.slice(LIST_MARKER_PREFIX.length);
|
|
403
|
+
const listSeg = lists.get(id);
|
|
404
|
+
const liveParent = marker.parentElement;
|
|
405
|
+
const items = [];
|
|
406
|
+
let next = marker.nextElementSibling;
|
|
407
|
+
for (let i = 0; i < listSeg.items.length && next !== null; i++) {
|
|
408
|
+
items.push({
|
|
409
|
+
ref: listSeg.items[i].ref,
|
|
410
|
+
cacheKey: listSeg.items[i].cacheKey,
|
|
411
|
+
html: listSeg.items[i].html,
|
|
412
|
+
node: next
|
|
413
|
+
});
|
|
414
|
+
next = next.nextElementSibling;
|
|
415
|
+
}
|
|
416
|
+
bindings.set(id, { liveParent, items });
|
|
417
|
+
marker.remove();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
359
420
|
function collectComments(node, out) {
|
|
360
421
|
for (let c = node.firstChild; c !== null; c = c.nextSibling) {
|
|
361
|
-
if (c.nodeType ===
|
|
362
|
-
else if (c.nodeType ===
|
|
422
|
+
if (c.nodeType === Node.COMMENT_NODE) out.push(c);
|
|
423
|
+
else if (c.nodeType === Node.ELEMENT_NODE) collectComments(c, out);
|
|
363
424
|
}
|
|
364
425
|
}
|
|
365
426
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/delegate.ts","../src/each.ts","../src/diff.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 morphdom 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 morphdom 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 * `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): morphdom diffs\n * a freshly-built template against the live tree. Same conventions as\n * before — id/data-key matching, `data-morph-skip`, focus 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 { effect } from './reactive.js';\nimport {\n collectLists,\n flatten,\n flattenWithoutListItems,\n type ListSegment,\n type Segment,\n} from './segment.js';\n\ninterface BoundItem {\n ref: object;\n cacheKey: unknown;\n html: string;\n node: Element;\n}\n\ninterface ListBinding {\n liveParent: Element;\n /**\n * One entry per item currently mounted under `liveParent`, in order.\n * Mirrors the current segment's `items` length after each reconcile.\n */\n items: BoundItem[];\n}\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\nfunction reconcileList(binding: ListBinding, listSeg: ListSegment): void {\n const { liveParent } = binding;\n const oldItems = binding.items;\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 // Build the new record. Per item, decide:\n // - \"stable\": cache hit (same ref, byte-identical html) — reuse the\n // existing live node. Captures its old position in `prevIdx` so the\n // LIS pass can decide whether the node also needs to move.\n // - \"replaced\": same ref but html changed — schedule a fresh node;\n // the old node will be removed before the move pass.\n // - \"new\": ref didn't exist before — schedule a fresh node.\n //\n // Fresh nodes aren't built one-at-a-time; instead we collect every fresh\n // item's HTML and parse them all in one `innerHTML` call below. For an\n // initial population of, say, 10k rows that's 1 parse instead of 10k.\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 // Placeholder; node is filled in by the bulk parse below.\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 if (freshHtmls.length > 0) {\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 // Remove orphans (refs that disappeared from the new list) and the old\n // nodes for replaced items. Both are out of `oldByRef` already; replaced\n // ones were captured separately.\n for (const orphan of oldByRef.values()) {\n if (orphan.node.parentElement === liveParent) liveParent.removeChild(orphan.node);\n }\n for (const node of replacedNodes) {\n if (node.parentElement === liveParent) liveParent.removeChild(node);\n }\n\n // Compute the longest increasing subsequence of `prevIdx`. New positions\n // whose index is in the LIS are already in the right relative order — we\n // skip moving them. Everything else (replaced, new, or relatively-out-of-\n // order stable items) gets `insertBefore`d in a single reverse pass.\n const stable = lis(prevIdx);\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 // In LIS — already in the right place.\n } else {\n liveParent.insertBefore(node, nextSibling);\n }\n nextSibling = node;\n }\n\n binding.items = newRecord;\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 * 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 === 8) out.push(c as Comment);\n else if (c.nodeType === 1) 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":"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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 * `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/dist/jsx-runtime.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Fragment, SafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-
|
|
1
|
+
export { Fragment, SafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-WK5D3OO7.js';
|
|
2
2
|
//# sourceMappingURL=jsx-runtime.js.map
|
|
3
3
|
//# sourceMappingURL=jsx-runtime.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kerfjs",
|
|
3
|
-
"version": "0.
|
|
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,
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"reactive",
|
|
19
19
|
"signals",
|
|
20
|
-
"morphdom",
|
|
21
20
|
"jsx",
|
|
22
21
|
"dom",
|
|
23
22
|
"ui-framework",
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
"fine-grained-reactivity"
|
|
26
25
|
],
|
|
27
26
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
27
|
+
"node": ">=22.12.0"
|
|
29
28
|
},
|
|
30
29
|
"main": "./dist/index.js",
|
|
31
30
|
"types": "./dist/index.d.ts",
|
|
@@ -71,7 +70,10 @@
|
|
|
71
70
|
"release:beta": "bash scripts/release.sh --beta",
|
|
72
71
|
"prepublishOnly": "npm run build",
|
|
73
72
|
"example:reactivity-demo": "cd examples/reactivity-demo && npm install && npm run dev",
|
|
74
|
-
"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"
|
|
75
77
|
},
|
|
76
78
|
"dependencies": {
|
|
77
79
|
"@preact/signals-core": "^1.14.1"
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
"@vitest/coverage-v8": "^3.0.0",
|
|
85
87
|
"eslint": "^9.16.0",
|
|
86
88
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
87
|
-
"happy-dom": "^
|
|
89
|
+
"happy-dom": "^20.9.0",
|
|
88
90
|
"husky": "^9.1.7",
|
|
89
91
|
"jsdom": "^29.1.1",
|
|
90
92
|
"tsup": "^8.3.0",
|