kerfjs 4.1.1 → 4.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/actions.d.ts +72 -0
- package/dist/actions.js +26 -0
- package/dist/actions.js.map +1 -0
- package/dist/array-signal.js +3 -104
- package/dist/array-signal.js.map +1 -1
- package/dist/async.d.ts +59 -0
- package/dist/async.js +55 -0
- package/dist/async.js.map +1 -0
- package/dist/attrSelector-Cmu2ZoGO.d.ts +79 -0
- package/dist/chunk-4MY2656S.js +1395 -0
- package/dist/chunk-4MY2656S.js.map +1 -0
- package/dist/{chunk-JXAR5J54.js → chunk-FSAQR6IU.js} +7 -4
- package/dist/chunk-FSAQR6IU.js.map +1 -0
- package/dist/chunk-KEZTD6H4.js +54 -0
- package/dist/chunk-KEZTD6H4.js.map +1 -0
- package/dist/chunk-MRYM3O3V.js +106 -0
- package/dist/chunk-MRYM3O3V.js.map +1 -0
- package/dist/chunk-U32TFTGZ.js +74 -0
- package/dist/chunk-U32TFTGZ.js.map +1 -0
- package/dist/delegate-CL9VTZFb.d.ts +93 -0
- package/dist/html.js +2 -2
- package/dist/imperative.d.ts +34 -0
- package/dist/imperative.js +20 -0
- package/dist/imperative.js.map +1 -0
- package/dist/index.d.ts +21 -219
- package/dist/index.js +13 -1511
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +13 -1
- package/dist/jsx-runtime.js +2 -2
- package/dist/list.d.ts +39 -0
- package/dist/list.js +146 -0
- package/dist/list.js.map +1 -0
- package/dist/mount-Bo2qOx25.d.ts +57 -0
- package/dist/overlay.d.ts +204 -0
- package/dist/overlay.js +406 -0
- package/dist/overlay.js.map +1 -0
- package/dist/remount.d.ts +52 -0
- package/dist/remount.js +45 -0
- package/dist/remount.js.map +1 -0
- package/dist/scope.d.ts +67 -0
- package/dist/scope.js +71 -0
- package/dist/scope.js.map +1 -0
- package/dist/timing.d.ts +65 -0
- package/dist/timing.js +80 -0
- package/dist/timing.js.map +1 -0
- package/package.json +34 -1
- package/dist/chunk-JXAR5J54.js.map +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kerfjs/imperative` — bind a non-kerf widget's lifecycle to a single DOM node.
|
|
3
|
+
*
|
|
4
|
+
* `data-morph-skip` lets a library own a subtree so kerf won't touch it — but
|
|
5
|
+
* nothing manages that widget's LIFECYCLE. You set it up imperatively after
|
|
6
|
+
* render and must remember to tear it down when the node is replaced/removed
|
|
7
|
+
* (dropping document-level listeners the widget added, etc.). `imperative` closes
|
|
8
|
+
* that seam: it's a `useEffect`-with-cleanup bound to one node.
|
|
9
|
+
*
|
|
10
|
+
* import { imperative } from 'kerfjs/imperative';
|
|
11
|
+
*
|
|
12
|
+
* imperative(canvasEl, (el) => {
|
|
13
|
+
* const chart = D3.mount(el);
|
|
14
|
+
* return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* `setup(node)` runs immediately and may return a teardown function. The teardown
|
|
18
|
+
* runs once — whichever comes first — when the node leaves the document (detected
|
|
19
|
+
* by a `MutationObserver`, so a morph swap, a `remountOn` replacement, or any
|
|
20
|
+
* removal triggers it) or when the returned disposer is called. Re-creation is
|
|
21
|
+
* NOT handled here: a fresh node is a fresh `imperative()` call — pair it with
|
|
22
|
+
* `kerfjs/remount`, which replaces the node and re-runs your render (and thus
|
|
23
|
+
* this call) on the new one.
|
|
24
|
+
*/
|
|
25
|
+
/** The setup callback for {@link imperative}: run against `node`, optionally return a teardown. */
|
|
26
|
+
type ImperativeSetup = (node: Element) => (() => void) | void;
|
|
27
|
+
/**
|
|
28
|
+
* Run `setup(node)` now, and its returned teardown once — when `node` leaves the
|
|
29
|
+
* document, or when the returned disposer is called, whichever is first. Returns
|
|
30
|
+
* a disposer (idempotent) so a `mount()` / `Scope` can drive teardown explicitly.
|
|
31
|
+
*/
|
|
32
|
+
declare function imperative(node: Element, setup: ImperativeSetup): () => void;
|
|
33
|
+
|
|
34
|
+
export { type ImperativeSetup, imperative };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/imperative.ts
|
|
2
|
+
function imperative(node, setup) {
|
|
3
|
+
const teardown = setup(node);
|
|
4
|
+
let done = false;
|
|
5
|
+
const finish = () => {
|
|
6
|
+
if (done) return;
|
|
7
|
+
done = true;
|
|
8
|
+
observer.disconnect();
|
|
9
|
+
if (typeof teardown === "function") teardown();
|
|
10
|
+
};
|
|
11
|
+
const observer = new MutationObserver(() => {
|
|
12
|
+
if (!node.isConnected) finish();
|
|
13
|
+
});
|
|
14
|
+
observer.observe(node.getRootNode(), { childList: true, subtree: true });
|
|
15
|
+
return finish;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { imperative };
|
|
19
|
+
//# sourceMappingURL=imperative.js.map
|
|
20
|
+
//# sourceMappingURL=imperative.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/imperative.ts"],"names":[],"mappings":";AAiCO,SAAS,UAAA,CAAW,MAAe,KAAA,EAAoC;AAC5E,EAAA,MAAM,QAAA,GAAW,MAAM,IAAI,CAAA;AAC3B,EAAA,IAAI,IAAA,GAAO,KAAA;AAEX,EAAA,MAAM,SAAS,MAAY;AACzB,IAAA,IAAI,IAAA,EAAM;AACV,IAAA,IAAA,GAAO,IAAA;AACP,IAAA,QAAA,CAAS,UAAA,EAAW;AACpB,IAAA,IAAI,OAAO,QAAA,KAAa,UAAA,EAAY,QAAA,EAAS;AAAA,EAC/C,CAAA;AAMA,EAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,CAAiB,MAAM;AAC1C,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAa,MAAA,EAAO;AAAA,EAChC,CAAC,CAAA;AACD,EAAA,QAAA,CAAS,OAAA,CAAQ,KAAK,WAAA,EAAY,EAAG,EAAE,SAAA,EAAW,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,CAAA;AAEvE,EAAA,OAAO,MAAA;AACT","file":"imperative.js","sourcesContent":["/**\n * `kerfjs/imperative` — bind a non-kerf widget's lifecycle to a single DOM node.\n *\n * `data-morph-skip` lets a library own a subtree so kerf won't touch it — but\n * nothing manages that widget's LIFECYCLE. You set it up imperatively after\n * render and must remember to tear it down when the node is replaced/removed\n * (dropping document-level listeners the widget added, etc.). `imperative` closes\n * that seam: it's a `useEffect`-with-cleanup bound to one node.\n *\n * import { imperative } from 'kerfjs/imperative';\n *\n * imperative(canvasEl, (el) => {\n * const chart = D3.mount(el);\n * return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)\n * });\n *\n * `setup(node)` runs immediately and may return a teardown function. The teardown\n * runs once — whichever comes first — when the node leaves the document (detected\n * by a `MutationObserver`, so a morph swap, a `remountOn` replacement, or any\n * removal triggers it) or when the returned disposer is called. Re-creation is\n * NOT handled here: a fresh node is a fresh `imperative()` call — pair it with\n * `kerfjs/remount`, which replaces the node and re-runs your render (and thus\n * this call) on the new one.\n */\n\n/** The setup callback for {@link imperative}: run against `node`, optionally return a teardown. */\nexport type ImperativeSetup = (node: Element) => (() => void) | void;\n\n/**\n * Run `setup(node)` now, and its returned teardown once — when `node` leaves the\n * document, or when the returned disposer is called, whichever is first. Returns\n * a disposer (idempotent) so a `mount()` / `Scope` can drive teardown explicitly.\n */\nexport function imperative(node: Element, setup: ImperativeSetup): () => void {\n const teardown = setup(node);\n let done = false;\n\n const finish = (): void => {\n if (done) return;\n done = true;\n observer.disconnect();\n if (typeof teardown === 'function') teardown();\n };\n\n // Observe the node's live tree (the document when connected) with subtree, so\n // an ANCESTOR removal — not just a direct one — is caught. Each mutation just\n // re-checks `node.isConnected`, which is true until the node (or an ancestor)\n // is removed, so a morph swap / remountOn replacement / manual removal all fire.\n const observer = new MutationObserver(() => {\n if (!node.isConnected) finish();\n });\n observer.observe(node.getRootNode(), { childList: true, subtree: true });\n\n return finish;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export { A as AttrSpec, a as attr } from './attrSelector-Cmu2ZoGO.js';
|
|
2
|
+
export { D as DelegateOptions, d as delegate, a as delegateCapture } from './delegate-CL9VTZFb.js';
|
|
1
3
|
import { ArraySignal } from './array-signal.js';
|
|
2
4
|
import { SafeHtml } from './jsx-runtime.js';
|
|
3
|
-
export { Fragment, isSafeHtml, raw } from './jsx-runtime.js';
|
|
5
|
+
export { Fragment, isSafeHtml, raw, trustedRaw } from './jsx-runtime.js';
|
|
6
|
+
export { M as MountResult, m as mount } from './mount-Bo2qOx25.js';
|
|
4
7
|
import { Signal } from '@preact/signals-core';
|
|
5
8
|
export { ReadonlySignal, Signal, batch, computed } from '@preact/signals-core';
|
|
6
9
|
export { S as Store, d as defineStore, r as resetAllStores } from './testing-DNEY7wi3.js';
|
|
@@ -33,176 +36,6 @@ import './bindings-CYwoJpQb.js';
|
|
|
33
36
|
declare function signal<T>(value?: T): Signal<T>;
|
|
34
37
|
declare function effect(fn: () => void | (() => void)): () => void;
|
|
35
38
|
|
|
36
|
-
/**
|
|
37
|
-
* `attr(name, value)` — create a pre-computed attribute descriptor (static form).
|
|
38
|
-
* `attr(name)` — create a per-render factory for dynamic attribute values (dynamic form).
|
|
39
|
-
*
|
|
40
|
-
* **Static form** — best for fixed action names, filter keys, role values, etc.
|
|
41
|
-
* Escapes once at module-load time; produces a full {@link AttrSpec} with
|
|
42
|
-
* `.name`, `.value`, `.selector`, and `.attrs`.
|
|
43
|
-
*
|
|
44
|
-
* const ACTIONS = {
|
|
45
|
-
* toggle: attr('data-action', 'toggle'),
|
|
46
|
-
* remove: attr('data-action', 'remove'),
|
|
47
|
-
* } as const satisfies Record<string, AttrSpec<'data-action'>>;
|
|
48
|
-
*
|
|
49
|
-
* // In JSX — spread .attrs (rename-safe; no hardcoded attribute name):
|
|
50
|
-
* <button {...ACTIONS.toggle.attrs}>Toggle</button>
|
|
51
|
-
*
|
|
52
|
-
* // In delegate — use the pre-computed selector:
|
|
53
|
-
* delegate(root, 'click', ACTIONS.toggle.selector, handler);
|
|
54
|
-
*
|
|
55
|
-
* **Dynamic form** — best for per-row data like `data-id`, where the value
|
|
56
|
-
* changes per item but the attribute name is constant.
|
|
57
|
-
* The name is validated and pre-escaped at definition time; calling the
|
|
58
|
-
* returned factory is cheap (it just freezes a one-key object — the value is
|
|
59
|
-
* escaped later by the JSX attribute renderer when the result is spread).
|
|
60
|
-
*
|
|
61
|
-
* const ITEM = { id: attr('data-id') } as const;
|
|
62
|
-
*
|
|
63
|
-
* // In JSX — call the factory inline:
|
|
64
|
-
* <li {...ITEM.id(String(item.id))}>…</li>
|
|
65
|
-
*
|
|
66
|
-
* For ad-hoc compound selectors, concatenate `.selector` strings:
|
|
67
|
-
*
|
|
68
|
-
* delegate(root, 'click',
|
|
69
|
-
* ACTIONS.toggle.selector + attr('data-id', id).selector,
|
|
70
|
-
* handler);
|
|
71
|
-
*
|
|
72
|
-
* Escaping:
|
|
73
|
-
* - Attribute name: escaped as a CSS identifier via `cssEscapeIdent`, which is
|
|
74
|
-
* an SSR-safe (no `CSS.escape`) adaptation of the Mathias Bynens polyfill
|
|
75
|
-
* (https://github.com/mathiasbynens/CSS.escape, MIT licensed — see the
|
|
76
|
-
* Acknowledgements section of LICENSE). Handles
|
|
77
|
-
* control chars, leading digits, non-ASCII, and CSS metacharacters.
|
|
78
|
-
* - Attribute value: embedded in double quotes as a CSS string. Backslashes and
|
|
79
|
-
* double-quote characters are backslash-escaped; control characters are
|
|
80
|
-
* hex-escaped per CSS Syntax Level 3 §3.4.
|
|
81
|
-
*
|
|
82
|
-
* Throws on an empty attribute name (not a valid CSS identifier).
|
|
83
|
-
*/
|
|
84
|
-
/** Descriptor created by the static {@link attr} overload. */
|
|
85
|
-
interface AttrSpec<N extends string = string, V extends string = string> {
|
|
86
|
-
/** The raw attribute name passed to `attr()`. */
|
|
87
|
-
readonly name: N;
|
|
88
|
-
/** The raw attribute value passed to `attr()`. */
|
|
89
|
-
readonly value: V;
|
|
90
|
-
/** Pre-computed `[name="value"]` CSS selector string, safe to pass to `delegate()`. */
|
|
91
|
-
readonly selector: string;
|
|
92
|
-
/** Spreadable JSX object — `{ [name]: value }` — keeps the attribute name out of JSX literals. */
|
|
93
|
-
readonly attrs: {
|
|
94
|
-
readonly [K in N]: V;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Static overload — pre-computes the full descriptor at definition time.
|
|
99
|
-
* Returns an {@link AttrSpec} with `.name`, `.value`, `.selector`, and `.attrs`.
|
|
100
|
-
*/
|
|
101
|
-
declare function attr<N extends string, V extends string>(name: N, value: V): AttrSpec<N, V>;
|
|
102
|
-
/**
|
|
103
|
-
* Dynamic overload — pre-validates and pre-escapes the attribute name, returns a
|
|
104
|
-
* factory that accepts a per-render value and produces a frozen spreadable object.
|
|
105
|
-
* Use for per-row attributes like `data-id` where the value changes per item.
|
|
106
|
-
* The optional `V` generic constrains which values the factory accepts:
|
|
107
|
-
* `attr<'data-id', 'a'|'b'>('data-id')` → `(value: 'a'|'b') => { 'data-id': 'a'|'b' }`.
|
|
108
|
-
* Leaving both generics off infers `N` from the argument and defaults `V` to `string`.
|
|
109
|
-
*/
|
|
110
|
-
declare function attr<N extends string, V extends string = string>(name: N): (value: V) => {
|
|
111
|
-
readonly [K in N]: V;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Tiny event-delegation helpers. Replace per-element `addEventListener` calls
|
|
116
|
-
* (which don't survive morph re-renders for nodes the diff creates) with one
|
|
117
|
-
* listener at the morph-root that dispatches via `closest()`.
|
|
118
|
-
*
|
|
119
|
-
* Three-tier listener model:
|
|
120
|
-
*
|
|
121
|
-
* - Tier 1 (bubbling events) — use `delegate()`.
|
|
122
|
-
* click, input, change, submit, mousedown/up, keydown/up, pointerdown/up/move,
|
|
123
|
-
* drag*, drop, contextmenu, wheel, copy/paste/cut, focusin/focusout.
|
|
124
|
-
*
|
|
125
|
-
* `delegate()` also auto-promotes the well-known non-bubbling event
|
|
126
|
-
* types (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`,
|
|
127
|
-
* `mouseleave`) to the capture phase under the hood, so the call site
|
|
128
|
-
* looks identical for "interactive thing happens on a descendant"
|
|
129
|
-
* regardless of whether that event bubbles. Selector matching stays
|
|
130
|
-
* `closest()`-style — the same as for bubbling events — so a wrapper
|
|
131
|
-
* selector like `'.field-row'` still matches when the event fires on
|
|
132
|
-
* a descendant `<input>`.
|
|
133
|
-
*
|
|
134
|
-
* - Tier 2 (explicit capture) — use `delegateCapture()`.
|
|
135
|
-
* The escape hatch for cases the auto-promotion list doesn't cover
|
|
136
|
-
* (custom non-bubbling events) or when you want capture-phase
|
|
137
|
-
* interception. Selector matching is `closest()`-style by default —
|
|
138
|
-
* the same walk-up as `delegate()`, and it passes the matched ancestor
|
|
139
|
-
* (not the raw target) to the handler — so a click on any descendant of
|
|
140
|
-
* the selected element climbs to it. Pass `{ match: 'direct' }` to opt
|
|
141
|
-
* into strict `matches()`-style matching (fire only when the event lands
|
|
142
|
-
* on the exact element the selector identifies).
|
|
143
|
-
*
|
|
144
|
-
* - Tier 3 (per-element instances / library-owned subtrees) — mark the
|
|
145
|
-
* host element with `data-morph-skip` and manage the library's
|
|
146
|
-
* lifecycle directly. No delegation helper applies.
|
|
147
|
-
*/
|
|
148
|
-
/**
|
|
149
|
-
* How the selector is matched against the event's target:
|
|
150
|
-
*
|
|
151
|
-
* - `'closest'` (the default for both helpers) — walk UP from `event.target`
|
|
152
|
-
* via `closest(selector)`, firing for the nearest matching ancestor inside
|
|
153
|
-
* `rootEl`. This is the delegation behavior you almost always want: a click
|
|
154
|
-
* on an icon inside a button fires the button's handler.
|
|
155
|
-
* - `'direct'` — strict `matches()` match: fire only when `event.target`
|
|
156
|
-
* itself matches the selector, with no walk-up.
|
|
157
|
-
*/
|
|
158
|
-
interface DelegateOptions {
|
|
159
|
-
match?: 'closest' | 'direct';
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Delegation that "just works" for both bubbling and the common non-bubbling
|
|
163
|
-
* events. Installs ONE listener on `rootEl`; for known non-bubblers (see
|
|
164
|
-
* `NON_BUBBLING` above) the listener is registered on the capture phase so
|
|
165
|
-
* it actually reaches the target, otherwise on the bubble phase. Either way,
|
|
166
|
-
* matching walks up from `event.target` via `closest(selector)` and fires
|
|
167
|
-
* `handler(event, matched)` if the match is inside `rootEl`.
|
|
168
|
-
*
|
|
169
|
-
* Pass `{ match: 'direct' }` to fire only when `event.target` itself matches
|
|
170
|
-
* the selector (no walk-up); the default is `'closest'`.
|
|
171
|
-
*
|
|
172
|
-
* The generic `T` narrows the second handler argument to the expected element
|
|
173
|
-
* type — `delegate<HTMLButtonElement>(root, 'click', 'button', (e, btn) => btn.value)`
|
|
174
|
-
* — so consumers can avoid casts. Defaults to `Element` for untyped calls.
|
|
175
|
-
*
|
|
176
|
-
* Returns a disposer that removes the listener.
|
|
177
|
-
*
|
|
178
|
-
* Usage (pseudo-code — see examples for live ones):
|
|
179
|
-
* delegate(rootEl, 'click', '[data-action="add"]', handlerFn);
|
|
180
|
-
* delegate(rootEl, 'focus', 'input', handlerFn); // auto-capture
|
|
181
|
-
*/
|
|
182
|
-
declare function delegate<T extends Element = Element>(rootEl: HTMLElement, type: string, selector: string, handler: (event: Event, target: T) => void, options?: DelegateOptions): () => void;
|
|
183
|
-
/**
|
|
184
|
-
* Capture-phase delegation — the escape hatch for custom non-bubbling events
|
|
185
|
-
* (ones `delegate()`'s auto-promotion list doesn't know about) and for
|
|
186
|
-
* capture-phase interception (run before any descendant's bubble-phase
|
|
187
|
-
* handler). Reaches descendants of `rootEl` that match `selector` regardless
|
|
188
|
-
* of how many times the diff has rebuilt them.
|
|
189
|
-
*
|
|
190
|
-
* Selector matching is `closest()`-style by default — the same walk-up as
|
|
191
|
-
* `delegate()`, and it passes the matched ancestor (not the raw target) to
|
|
192
|
-
* the handler — so a click on any descendant of the selected element climbs
|
|
193
|
-
* to it. Pass `{ match: 'direct' }` to opt into strict `matches()`-style
|
|
194
|
-
* matching (fire only when the event lands on the exact element the selector
|
|
195
|
-
* identifies, with no walk-up).
|
|
196
|
-
*
|
|
197
|
-
* The generic `T` narrows the second handler argument to the expected element
|
|
198
|
-
* type, mirroring `delegate<T>()`. Defaults to `Element` for untyped calls.
|
|
199
|
-
*
|
|
200
|
-
* Usage (pseudo-code — see examples for live ones):
|
|
201
|
-
* delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);
|
|
202
|
-
* delegateCapture(rootEl, 'click', '.exact', handlerFn, { match: 'direct' });
|
|
203
|
-
*/
|
|
204
|
-
declare function delegateCapture<T extends Element = Element>(rootEl: HTMLElement, type: string, selector: string, handler: (event: Event, target: T) => void, options?: DelegateOptions): () => void;
|
|
205
|
-
|
|
206
39
|
/**
|
|
207
40
|
* `each(items, render, cacheKey?)` — keyed list iteration with per-item memoization.
|
|
208
41
|
*
|
|
@@ -338,58 +171,27 @@ declare function each<T extends object>(items: readonly T[] | ArraySignal<T>, re
|
|
|
338
171
|
declare function morph(liveRoot: Element, template: Element | SafeHtml | string, ownedItems?: ReadonlySet<Element>): void;
|
|
339
172
|
|
|
340
173
|
/**
|
|
341
|
-
* `
|
|
342
|
-
*
|
|
343
|
-
* Wraps `effect()` so that whenever any signal read inside `render()`
|
|
344
|
-
* changes, we re-run `render()` and apply the minimum DOM mutations against
|
|
345
|
-
* the live tree. Element identity (and thus focus, selection, in-flight
|
|
346
|
-
* pointer interactions, and event listeners on preserved nodes) is preserved
|
|
347
|
-
* wherever the keyed/positional diff matches.
|
|
174
|
+
* `renderDocument(node)` — prepend the doctype to a rendered document.
|
|
348
175
|
*
|
|
349
|
-
*
|
|
176
|
+
* Every kerf SSR app ends its routes with the identical concat
|
|
177
|
+
* `"<!DOCTYPE html>" + page.toString()`. This is that one line, blessed, so the
|
|
178
|
+
* doctype isn't reinvented (or forgotten) per route.
|
|
350
179
|
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* tree. Conventions: id/data-key matching, `data-morph-skip`, focus
|
|
354
|
-
* preservation.
|
|
180
|
+
* import { renderDocument } from 'kerfjs';
|
|
181
|
+
* return c.html(renderDocument(<Page />)); // "<!DOCTYPE html><html>…"
|
|
355
182
|
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* O(rows).
|
|
360
|
-
*
|
|
361
|
-
* Compared to a `replaceChildren(...rows.map(toElement))` rebuild pattern,
|
|
362
|
-
* the user-visible win is that an `<input>` the user is typing into
|
|
363
|
-
* survives an unrelated re-render — its DOM node, focus state, and cursor
|
|
364
|
-
* position are not destroyed and recreated on each tick.
|
|
183
|
+
* `node` is a `SafeHtml` (from JSX / the `html` tagged template) or a raw string
|
|
184
|
+
* — both are stringified via `.toString()`. The optional `doctype` overrides the
|
|
185
|
+
* default `'html'`.
|
|
365
186
|
*/
|
|
366
187
|
|
|
367
|
-
/**
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
* Conventions:
|
|
375
|
-
*
|
|
376
|
-
* - Diff keys: `id` and `data-key` are matched across the morph by key
|
|
377
|
-
* rather than positionally, so list reorders move existing nodes instead
|
|
378
|
-
* of churning unrelated siblings.
|
|
379
|
-
* - `data-morph-skip`: any element with this attribute is left untouched
|
|
380
|
-
* inside on subsequent renders. Used for library-owned subtrees (xterm-
|
|
381
|
-
* style widgets, charts, third-party editors) where the library's own
|
|
382
|
-
* lifecycle manages the children.
|
|
383
|
-
* - Focused text-entry inputs (`<input>` of typing kinds, `<textarea>`)
|
|
384
|
-
* keep their current value + selection range across morphs while focused.
|
|
385
|
-
* The user never sees their cursor jump mid-keystroke.
|
|
386
|
-
* - Focused `[contenteditable]` elements have their entire subtree
|
|
387
|
-
* skipped (same mechanism as `data-morph-skip`). The user's in-progress
|
|
388
|
-
* edit — typed content, caret position, multi-range selections, anything
|
|
389
|
-
* else they did to the DOM — survives verbatim. The next render after
|
|
390
|
-
* blur catches up.
|
|
391
|
-
*/
|
|
392
|
-
declare function mount(rootEl: HTMLElement, render: () => MountResult): () => void;
|
|
188
|
+
/** Options for {@link renderDocument}. */
|
|
189
|
+
interface RenderDocumentOptions {
|
|
190
|
+
/** The doctype name. Default `'html'` → `<!DOCTYPE html>`. */
|
|
191
|
+
doctype?: string;
|
|
192
|
+
}
|
|
193
|
+
/** Prepend `<!DOCTYPE …>` to a rendered `SafeHtml` (or string) document and return the full HTML string. */
|
|
194
|
+
declare function renderDocument(node: SafeHtml | string, options?: RenderDocumentOptions): string;
|
|
393
195
|
|
|
394
196
|
/**
|
|
395
197
|
* `toElement(jsx)` — JSX → DOM, with SVG-aware namespace handling.
|
|
@@ -417,4 +219,4 @@ declare function mount(rootEl: HTMLElement, render: () => MountResult): () => vo
|
|
|
417
219
|
|
|
418
220
|
declare function toElement(jsx: SafeHtml | string): Element | DocumentFragment;
|
|
419
221
|
|
|
420
|
-
export { type
|
|
222
|
+
export { type RenderDocumentOptions, SafeHtml, each, effect, morph, renderDocument, signal, toElement };
|