kerfjs 4.2.0-beta.4 → 4.2.0-beta.6
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 +4 -5
- package/dist/attach.d.ts +45 -0
- package/dist/{imperative.js → attach.js} +5 -5
- package/dist/attach.js.map +1 -0
- package/dist/{chunk-4MY2656S.js → chunk-LKWAKC2X.js} +3 -3
- package/dist/{chunk-4MY2656S.js.map → chunk-LKWAKC2X.js.map} +1 -1
- package/dist/{chunk-FSAQR6IU.js → chunk-SUPUPSBE.js} +3 -6
- package/dist/chunk-SUPUPSBE.js.map +1 -0
- package/dist/html.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/jsx-runtime.d.ts +24 -12
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.d.ts +97 -7
- package/dist/list.js +180 -23
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +9 -2
- package/dist/overlay.js +18 -13
- package/dist/overlay.js.map +1 -1
- package/dist/remount.d.ts +3 -3
- package/dist/remount.js +2 -2
- package/dist/remount.js.map +1 -1
- package/dist/scope.js +2 -2
- package/package.json +4 -4
- package/dist/chunk-FSAQR6IU.js.map +0 -1
- package/dist/imperative.d.ts +0 -34
- package/dist/imperative.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,15 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
- **`trustedRaw(html)`** (main barrel) — the intention-revealing, lint-exempt escape hatch for injecting a **server-trusted dynamic** value (a CSRF token, a trusted `<script src>`, a server-issued id). Identical to `raw()` at runtime, but because it isn't named `raw`, the `kerfjs/no-raw-with-dynamic-arg` rule leaves it alone — replacing scattered `eslint-disable` comments with one explicit call. Not a sanitizer; only pass values you control.
|
|
10
9
|
- **`renderDocument(node, options?)`** (main barrel) — a tiny SSR helper that prepends the doctype to a rendered document, so server routes stop reinventing `"<!DOCTYPE html>" + page.toString()`. Takes a `SafeHtml` or string; optional `{ doctype }` (default `'html'`). Pure string work, no DOM dependency.
|
|
11
|
-
- **New `kerfjs/list` subpath** — `bindList(parent, source, options)`, a keyed list distinct from `each()`: each row is individually `mount()`ed, so a signal a row reads updates just that row (fine-grained, no full-list pass), and it can **virtualize** the viewport (`virtualize: { rowHeight }` renders only visible rows, padding keeps `scrollHeight` honest). `render(item)` returns a `MountResult` (**content mode** — kerf creates the row element and mounts your content inside it) **or** an `HTMLElement` / `{ el, update?, dispose? }` (**element mode** — the element you return IS the row, so you own its tag / class / `data-*` / listeners; kerf **keys / moves / reuses** it — the SAME element survives an append, a remove elsewhere, a reorder, or a fresh item object at the same key, preserving focus / scroll / listeners — and runs your `dispose` only on genuine removal; return an `update(item)` to refresh a reused element's content). A list may mix the two. `source` is a `signal<readonly T[]>` or an `arraySignal<T>` — a non-virtualized `arraySignal` source applies its structural patches **granularly** (O(patches)), everything else uses a keyed diff (transparent optimization). Reach for it for surgical per-row updates, app-owned row elements, or long/windowed lists; `each()` stays the default for item-owned-state lists rendered to HTML strings. Optional and tree-shakeable.
|
|
10
|
+
- **New `kerfjs/list` subpath** — `bindList(parent, source, options)`, a keyed list distinct from `each()`: each row is individually `mount()`ed, so a signal a row reads updates just that row (fine-grained, no full-list pass), and it can **virtualize** the viewport (`virtualize: { rowHeight }` renders only the visible rows, padding keeps `scrollHeight` honest). `rowHeight` is a fixed `number`, a `(item, index) => number` for **app-declared variable** row heights (kerf builds a prefix sum — rebuilt when the source changes, not per scroll frame — and binary-searches it for the window), **or** `{ estimate }` for **measured** heights: kerf sizes an unmeasured row by `estimate` and the app reports real heights via the returned handle's **`setHeight(key, px)`** (keyed by the list key, so reports survive reorders) or the new **`observeRowHeights(handle)`** helper (one `ResizeObserver` over the visible rows → `setHeight`); kerf anchor-corrects `scrollTop` when an above-viewport row is remeasured so on-screen content doesn't jump. `bindList` now returns a `BindListHandle` (`(() => void) & { setHeight }`) — still callable as the disposer. `render(item)` returns a `MountResult` (**content mode** — kerf creates the row element and mounts your content inside it) **or** an `HTMLElement` / `{ el, update?, dispose? }` (**element mode** — the element you return IS the row, so you own its tag / class / `data-*` / listeners; kerf **keys / moves / reuses** it — the SAME element survives an append, a remove elsewhere, a reorder, or a fresh item object at the same key, preserving focus / scroll / listeners — and runs your `dispose` only on genuine removal; return an `update(item)` to refresh a reused element's content). A list may mix the two. `source` is a `signal<readonly T[]>` or an `arraySignal<T>` — a non-virtualized `arraySignal` source applies its structural patches **granularly** (O(patches)), everything else uses a keyed diff (transparent optimization). A **`before`** option (a `Node` or `() => Node | null`) keeps the rows as a contiguous block ending just before a fixed trailing sibling, so a list can share its `parent` with an "add" button or an indicator instead of assuming exclusive ownership. Three virtualization ergonomics from real adoption: **`virtualize.minRows`** renders every row (no windowing) while the list is shorter than it — one DOM structure whether short or long, so the call site never branches, and a short list stays visible to find-in-page / screen readers / DOM-count tests; **`virtualize.containerClass` / `containerId`** (and **`handle.container`**) name and expose the inner sizer kerf creates, replacing `parent.lastElementChild` guesswork; and kerf now re-windows on a **`ResizeObserver`** over `parent` (where available), so a list mounted before layout (`clientHeight` 0) fills in once sized and a resized container re-windows. Reach for it for surgical per-row updates, app-owned row elements, or long/windowed lists; `each()` stays the default for item-owned-state lists rendered to HTML strings. Optional and tree-shakeable.
|
|
12
11
|
- **New `kerfjs/async` subpath** — `resource<T, I = void>()` models async state (`{ status, data, error, progress, input }`) with the stale-response guard built in. You write the fetch (Node `fetch` for SSR, browser `fetch` client-side); `.run(fetcher)` drives `idle` → `running` → `completed`/`failed` and drops out-of-order responses (only the latest run resolves the state). It never rejects — a failure lands in `value.error` — keeps previous data across a re-run (stale-while-revalidate), and supports opt-in progress via a callback the fetcher receives. The `.run(input, fetcher)` form threads the run's `input` to `value.input` for `running`/`completed`/`failed` (latest-wins under the stale guard), so a failure handler can recover **which request failed** (e.g. an inline error keyed by `value.input.fileId`) without reintroducing module-scope bookkeeping. Pass `resource({ cacheKey, equals })` for a real SWR section: **`cacheKey(input)`** keeps the last value **per key** (revisiting a loaded key paints its cached slice instantly while it revalidates; `reset()` clears the cache), and **`value.revision`** bumps only when `data` actually changes (by `equals`, default `Object.is`) so a consumer can skip a redundant paint (a poll returning identical data leaves it untouched). The cache is readable and evictable without running a key: **`cached(key)`** / **`cachedKeys()`** view it, and **`clearCache(key?)`** evicts one key (or all) without touching `value`. `value` is a tracking read. Signals only (no render core); tiny.
|
|
13
|
-
- **New `kerfjs/
|
|
14
|
-
- **New `kerfjs/remount` subpath** — `remountOn(parent, key, render)`, the opposite of kerf's morph-by-default: **replace** a subtree wholesale when `key` changes instead of morphing it. Names the hand-rolled `data-key={`gen-${n}`}` + `data-morph-skip` counter trick, for library-owned subtrees (a highlighted diff, a chart, an editor) that must tear down and re-initialize on fresh DOM. `key` is a signal or a thunk `() => K`; an unchanged key (including a thunk whose inputs moved but whose value stayed equal) leaves the subtree alone, so per-row reactivity inside `render` still updates in place. An optional `onMount(root)` callback runs after each (re)mount with the live subtree — the place to bind an imperative widget (`kerfjs/
|
|
12
|
+
- **New `kerfjs/attach` subpath** — `attach(node, setup)`, which binds a non-kerf widget's lifecycle to a single **existing** DOM node, purpose-built for the `data-morph-skip` escape hatch. `setup(node)` runs immediately (the node already exists — this is **not** React's `useEffect`: no dependency array, no re-run, no render-phase/hook-order scoping; it's closer to a Web Component's `connectedCallback`/`disconnectedCallback` pair, Svelte's `onMount(() => () => cleanup)`, or Solid's `onCleanup`) and may return a teardown; the teardown runs once when the node leaves the document (detected by a `MutationObserver`, so a morph swap, a `remountOn` replacement, or any removal triggers it) or when the returned idempotent disposer is called. Turns the "library owns this subtree" convention into a supported seam with real lifecycle guarantees. Re-creation is handled by pairing with `kerfjs/remount`. DOM only (no signals, no render core) — the smallest subpath. Optional and tree-shakeable.
|
|
13
|
+
- **New `kerfjs/remount` subpath** — `remountOn(parent, key, render)`, the opposite of kerf's morph-by-default: **replace** a subtree wholesale when `key` changes instead of morphing it. Names the hand-rolled `data-key={`gen-${n}`}` + `data-morph-skip` counter trick, for library-owned subtrees (a highlighted diff, a chart, an editor) that must tear down and re-initialize on fresh DOM. `key` is a signal or a thunk `() => K`; an unchanged key (including a thunk whose inputs moved but whose value stayed equal) leaves the subtree alone, so per-row reactivity inside `render` still updates in place. An optional `onMount(root)` callback runs after each (re)mount with the live subtree — the place to bind an imperative widget (`kerfjs/attach`) to the fresh DOM; returning `attach`'s disposer from it makes teardown synchronous. `remountOn` owns `parent`'s children and returns a disposer. Optional and tree-shakeable.
|
|
15
14
|
- **New `kerfjs/timing` subpath** — the `let timer; clearTimeout(timer); timer = setTimeout(…)` pattern every app hand-rolls, blessed with disposer-shaped ergonomics. `debounce(fn, ms)` is trailing-edge (runs once `ms` after calls stop, latest args); `throttle(fn, ms)` is leading-plus-trailing (fires immediately, then at most once per `ms`, collapsing a burst to one trailing call). Both return a callable with `cancel()` / `flush()`. `debouncedSignal(source, ms)` is a read-only signal that trails `source` by `ms` so it composes inside the reactive graph (`computed`/`effect`/`mount`) instead of beside it. `debounce`/`throttle` are dependency-free; `debouncedSignal` pulls in signals only (no render core). Optional and tree-shakeable; tiny.
|
|
16
15
|
- **New `kerfjs/scope` subpath** — tie disposers to a DOM element's lifetime, so append-heavy UIs stop leaking detached-but-subscribed effects/listeners. `disposeScope(el)` returns a WeakMap-keyed, accumulating scope whose `add(disposer)` (plus convenience `mount` / `effect` / `delegate` wrappers that register their own disposer) collects teardown; `dispose()` runs it all best-effort and idempotently. `disposeSubtree(root)` sweeps a subtree before removal; `observeRemovals(root)` installs one `MutationObserver` that auto-disposes on removal. No module-level mutable state. Optional and tree-shakeable.
|
|
17
|
-
- **New `kerfjs/overlay` subpath** — the blessed modal/overlay + dismiss manager that every real kerf app hand-rolls. `overlay(content, options?)` appends a wrapper, `mount()`s content inside it (owning the disposal), wires dismissals (Escape / backdrop / outside-click, with `outsideIgnore`), a focus trap (`role="dialog"` / `aria-modal`, Tab wrap-around, restore-focus-on-close), and returns `{ el, close(result?), result }`. `confirm(message, options?)` is a promise-based `window.confirm` replacement, and `prompt(message, options?)` → `Promise<string | null>` its `window.prompt` counterpart (both globals are no-ops in Tauri webviews); `form(fields, options?)` → `Promise<Record<string, string> | null>` collects a two-or-three-field dialog. `choice<R>(message, actions, options?)` → `Promise<R | null>` is the **N-way** sibling of `confirm` — one button per action resolves that action's `value` (or `null` on dismissal), and `options.defaultValue` makes **Enter** (anywhere in the dialog) resolve a default action (the "global Enter-to-confirm" model) without holding the overlay handle. `prompt`/`form` submit on Enter and take an inline `validate`; all auto-escape their content. `confirm` / `prompt` / `form` also accept a **`render` slot option** for design-system teams — return your own markup and spread the provided `ok`/`cancel` (+ `input`/`error`) wiring onto it; kerf keeps owning the promise, `validate`, Enter-submit, dismiss, focus-trap, and focus-restore, so you adopt the batteries-included dialogs without a CSS rewrite. `popover(anchor, content, options?)` → `OverlayHandle` is a non-modal **anchored** overlay: it positions the content relative to `anchor` (below by default, flipping above on viewport overflow, clamped horizontally), defaults to dismiss-on-outside with the anchor exempt, and repositions on scroll / resize. `popover`'s placement core is also exported standalone: **`positionAnchored(el, anchor, options?)`** (one-shot) and **`autoReposition(el, anchor, options?)`** (keeps an element positioned on scroll/resize, returns a disposer) position *your own* element with no overlay lifecycle. **`tooltip(anchor, content, options?)`** is a hover/focus-triggered, non-modal, auto-hiding tooltip built on them. `toast(content, options?)` now returns a **`ToastHandle` (`{ el, dismiss }`)** instead of a bare dismiss function, so callers can inspect the node, wire an action button, or run entrance/exit transitions; new options: `mode: 'replace'` (collapse a rapid sequence to the latest) with `collapse: 'fade'` (default — run the prior toast's exit transition, good for a stacking region) or `'instant'` (remove it synchronously, what a single centered slot wants so messages never cross-fade in place), `variant` (`'info'`/`'success'`/`'warning'` → a `${className}--${variant}` accent class), `enterClass` (added on the next animation frame for a CSS entrance) and `exitClass` + `exitDuration` (CSS owns the exit — on dismiss the `enterClass` is **removed** so `exitClass` needn't out-specify it, and a symmetric single-class fade works with just `enterClass` + `exitDuration`; the node is removed after the delay). **Breaking (beta):** `toast()`'s return type changed from `() => void` to `{ el, dismiss }` — call `toast(...).dismiss()` or destructure `{ dismiss }`. Structural only — kerf ships no CSS. Optional and tree-shakeable; shares the core with the main barrel via code-splitting.
|
|
16
|
+
- **New `kerfjs/overlay` subpath** — the blessed modal/overlay + dismiss manager that every real kerf app hand-rolls. `overlay(content, options?)` appends a wrapper, `mount()`s content inside it (owning the disposal), wires dismissals (Escape / backdrop / outside-click, with `outsideIgnore`), a focus trap (`role="dialog"` / `aria-modal`, Tab wrap-around, restore-focus-on-close), and returns `{ el, close(result?), result }`. `confirm(message, options?)` is a promise-based `window.confirm` replacement, and `prompt(message, options?)` → `Promise<string | null>` its `window.prompt` counterpart (both globals are no-ops in Tauri webviews); `form(fields, options?)` → `Promise<Record<string, string> | null>` collects a two-or-three-field dialog. `choice<R>(message, actions, options?)` → `Promise<R | null>` is the **N-way** sibling of `confirm` — one button per action resolves that action's `value` (or `null` on dismissal), and `options.defaultValue` makes **Enter** (anywhere in the dialog) resolve a default action (the "global Enter-to-confirm" model) without holding the overlay handle. `prompt`/`form` submit on Enter and take an inline `validate`; all auto-escape their content. `confirm` / `prompt` / `form` also accept a **`render` slot option** for design-system teams — return your own markup and spread the provided `ok`/`cancel` (+ `input`/`error`) wiring onto it; kerf keeps owning the promise, `validate`, Enter-submit, dismiss, focus-trap, and focus-restore, so you adopt the batteries-included dialogs without a CSS rewrite. `popover(anchor, content, options?)` → `OverlayHandle` is a non-modal **anchored** overlay: it positions the content relative to `anchor` (below by default, flipping above on viewport overflow, clamped horizontally), defaults to dismiss-on-outside with the anchor exempt, and repositions on scroll / resize. `popover`'s placement core is also exported standalone: **`positionAnchored(el, anchor, options?)`** (one-shot) and **`autoReposition(el, anchor, options?)`** (keeps an element positioned on scroll/resize, returns a disposer) position *your own* element with no overlay lifecycle. **`tooltip(anchor, content, options?)`** is a hover/focus-triggered, non-modal, auto-hiding tooltip built on them. `toast(content, options?)` now returns a **`ToastHandle` (`{ el, dismiss }`)** instead of a bare dismiss function, so callers can inspect the node, wire an action button, or run entrance/exit transitions; new options: `mode: 'replace'` (collapse a rapid sequence to the latest) with `collapse: 'fade'` (default — run the prior toast's exit transition, good for a stacking region) or `'instant'` (remove it synchronously, what a single centered slot wants so messages never cross-fade in place), `variant` (`'info'`/`'success'`/`'warning'` → a `${className}--${variant}` accent class), `enterClass` (added on the next animation frame for a CSS entrance) and `exitClass` + `exitDuration` (CSS owns the exit — on dismiss the `enterClass` is **removed** so `exitClass` needn't out-specify it, and a symmetric single-class fade works with just `enterClass` + `exitDuration`; the node is removed after the delay). `dismiss({ instant: true })` removes the toast **synchronously** (skipping the exit transition), and `mode: 'replace'` `collapse: 'instant'` also force-removes a toast that is already mid-fade — so an action button that dismisses itself and shows a replacement in a single centered slot doesn't cross-fade the two. **Breaking (beta):** `toast()`'s return type changed from `() => void` to `{ el, dismiss }` — call `toast(...).dismiss()` or destructure `{ dismiss }`. Structural only — kerf ships no CSS. Optional and tree-shakeable; shares the core with the main barrel via code-splitting.
|
|
18
17
|
- **New `kerfjs/actions` subpath** — the blessed delegated action-table helper. `action(value)` returns a `data-action` `AttrSpec` (a thin specialization of `attr()`); `delegateActions(root, eventType, table, options?)` wires a whole table of `data-action` handlers with one delegated listener (built on `delegate()`) and returns a disposer. Formalizes the most-reinvented idiom in real kerf apps — one `attr('data-action', …)` table as the single source of truth for both the JSX attribute and the delegate dispatch. Optional and tree-shakeable; adds nothing to the main barrel.
|
|
19
18
|
|
|
20
19
|
## [4.1.1] - 2026-08-14
|
package/dist/attach.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kerfjs/attach` — 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.). `attach` closes
|
|
8
|
+
* that seam: run a setup against one **existing** DOM node and auto-run its
|
|
9
|
+
* teardown when that node leaves the document.
|
|
10
|
+
*
|
|
11
|
+
* import { attach } from 'kerfjs/attach';
|
|
12
|
+
*
|
|
13
|
+
* attach(canvasEl, (el) => {
|
|
14
|
+
* const chart = D3.mount(el);
|
|
15
|
+
* return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* `setup(node)` runs immediately — the node already exists, so there is nothing
|
|
19
|
+
* to wait for (this is NOT React's `useEffect`: no dependency array, no re-run,
|
|
20
|
+
* no render-phase or hook-order scoping; it is closer to a Web Component's
|
|
21
|
+
* `connectedCallback`/`disconnectedCallback` pair, Svelte's
|
|
22
|
+
* `onMount(() => () => cleanup)`, or Solid's `onCleanup`). The returned teardown
|
|
23
|
+
* runs once — whichever comes first — when the node leaves the document (detected
|
|
24
|
+
* by a `MutationObserver`, so a morph swap, a `remountOn` replacement, or any
|
|
25
|
+
* removal triggers it) or when the returned disposer is called. Re-creation is
|
|
26
|
+
* NOT handled here: a fresh node is a fresh `attach()` call — pair it with
|
|
27
|
+
* `kerfjs/remount`, which replaces the node and re-runs your render (and thus
|
|
28
|
+
* this call) on the new one.
|
|
29
|
+
*
|
|
30
|
+
* Related: `kerfjs/scope`'s `observeRemovals` also auto-disposes on removal via a
|
|
31
|
+
* `MutationObserver`, but scoped to a whole subtree's registered disposers rather
|
|
32
|
+
* than one node's setup/teardown pair — reach for that when you're collecting
|
|
33
|
+
* many disposers under an element, and for `attach` when you're binding one
|
|
34
|
+
* widget's lifecycle to one node.
|
|
35
|
+
*/
|
|
36
|
+
/** The setup callback for {@link attach}: run against `node`, optionally return a teardown. */
|
|
37
|
+
type AttachSetup = (node: Element) => (() => void) | void;
|
|
38
|
+
/**
|
|
39
|
+
* Run `setup(node)` now, and its returned teardown once — when `node` leaves the
|
|
40
|
+
* document, or when the returned disposer is called, whichever is first. Returns
|
|
41
|
+
* a disposer (idempotent) so a `mount()` / `Scope` can drive teardown explicitly.
|
|
42
|
+
*/
|
|
43
|
+
declare function attach(node: Element, setup: AttachSetup): () => void;
|
|
44
|
+
|
|
45
|
+
export { type AttachSetup, attach };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
function
|
|
1
|
+
// src/attach.ts
|
|
2
|
+
function attach(node, setup) {
|
|
3
3
|
const teardown = setup(node);
|
|
4
4
|
let done = false;
|
|
5
5
|
const finish = () => {
|
|
@@ -15,6 +15,6 @@ function imperative(node, setup) {
|
|
|
15
15
|
return finish;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export {
|
|
19
|
-
//# sourceMappingURL=
|
|
20
|
-
//# sourceMappingURL=
|
|
18
|
+
export { attach };
|
|
19
|
+
//# sourceMappingURL=attach.js.map
|
|
20
|
+
//# sourceMappingURL=attach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/attach.ts"],"names":[],"mappings":";AA4CO,SAAS,MAAA,CAAO,MAAe,KAAA,EAAgC;AACpE,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":"attach.js","sourcesContent":["/**\n * `kerfjs/attach` — bind a non-kerf widget's lifecycle to a single DOM node.\n *\n * `data-morph-skip` lets a library own a subtree so kerf won't touch it — but\n * nothing manages that widget's LIFECYCLE. You set it up imperatively after\n * render and must remember to tear it down when the node is replaced/removed\n * (dropping document-level listeners the widget added, etc.). `attach` closes\n * that seam: run a setup against one **existing** DOM node and auto-run its\n * teardown when that node leaves the document.\n *\n * import { attach } from 'kerfjs/attach';\n *\n * attach(canvasEl, (el) => {\n * const chart = D3.mount(el);\n * return () => chart.destroy(); // runs when el leaves the DOM (or on dispose)\n * });\n *\n * `setup(node)` runs immediately — the node already exists, so there is nothing\n * to wait for (this is NOT React's `useEffect`: no dependency array, no re-run,\n * no render-phase or hook-order scoping; it is closer to a Web Component's\n * `connectedCallback`/`disconnectedCallback` pair, Svelte's\n * `onMount(() => () => cleanup)`, or Solid's `onCleanup`). The returned 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 `attach()` call — pair it with\n * `kerfjs/remount`, which replaces the node and re-runs your render (and thus\n * this call) on the new one.\n *\n * Related: `kerfjs/scope`'s `observeRemovals` also auto-disposes on removal via a\n * `MutationObserver`, but scoped to a whole subtree's registered disposers rather\n * than one node's setup/teardown pair — reach for that when you're collecting\n * many disposers under an element, and for `attach` when you're binding one\n * widget's lifecycle to one node.\n */\n\n/** The setup callback for {@link attach}: run against `node`, optionally return a teardown. */\nexport type AttachSetup = (node: Element) => (() => void) | void;\n\n/**\n * Run `setup(node)` now, and its returned teardown once — 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 attach(node: Element, setup: AttachSetup): () => 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"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { itemVersion } from './chunk-QIP723L4.js';
|
|
2
2
|
import { parseRowTemplate, rowContractError, parseSingleRow, collectTemplateChildren } from './chunk-YHH7OUFA.js';
|
|
3
|
-
import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, wireRowBindings, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-
|
|
3
|
+
import { captureRowBindings, listSafeHtml, boundTextNodeOf, syncFormProp, newBindingContext, wireBindings, disposeRowBindings, isSafeHtml, wireRowBindings, _setBindingContext, TEXT_MARKER_PREFIX, ROW_TEXT_PREFIX, carryOrRewireRowBindings, granularListSafeHtml } from './chunk-SUPUPSBE.js';
|
|
4
4
|
import { effect } from './chunk-3APBEVHF.js';
|
|
5
5
|
import { LIST_MARKER_PREFIX, flattenWithoutListItems, collectLists, flatten } from './chunk-GY4XV2UV.js';
|
|
6
6
|
import { devHooks } from './chunk-VVDJLWMP.js';
|
|
@@ -1391,5 +1391,5 @@ function collectComments(node, out) {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
|
|
1393
1393
|
export { each, morph, mount };
|
|
1394
|
-
//# sourceMappingURL=chunk-
|
|
1395
|
-
//# sourceMappingURL=chunk-
|
|
1394
|
+
//# sourceMappingURL=chunk-LKWAKC2X.js.map
|
|
1395
|
+
//# sourceMappingURL=chunk-LKWAKC2X.js.map
|