kerfjs 5.0.0-beta.52 → 5.0.0-beta.53
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 +329 -0
- package/README.md +4 -3
- package/ai/cursorrules +6 -5
- package/ai/manifest.json +9 -7
- package/ai/skill.md +6 -5
- package/dist/actions.js +3 -3
- package/dist/actions.js.map +1 -1
- package/dist/array-signal.js +1 -1
- package/dist/async.js +1 -1
- package/dist/attach.js +1 -68
- package/dist/attach.js.map +1 -1
- package/dist/{chunk-CEQMZYLR.js → chunk-3C6F6NM4.js} +1 -1
- package/dist/{chunk-CEQMZYLR.js.map → chunk-3C6F6NM4.js.map} +1 -1
- package/dist/{chunk-ZOIERTUW.js → chunk-BI4HGICV.js} +3 -3
- package/dist/{chunk-ZOIERTUW.js.map → chunk-BI4HGICV.js.map} +1 -1
- package/dist/{chunk-3GSWKMBZ.js → chunk-DU35YGKW.js} +3 -3
- package/dist/{chunk-3GSWKMBZ.js.map → chunk-DU35YGKW.js.map} +1 -1
- package/dist/chunk-FM6N2B4N.js +70 -0
- package/dist/chunk-FM6N2B4N.js.map +1 -0
- package/dist/{chunk-ELXVRKY2.js → chunk-H7WTRM6F.js} +2 -2
- package/dist/{chunk-ELXVRKY2.js.map → chunk-H7WTRM6F.js.map} +1 -1
- package/dist/{chunk-H3TTM7PQ.js → chunk-HTXQR2KL.js} +108 -106
- package/dist/chunk-HTXQR2KL.js.map +1 -0
- package/dist/{chunk-Y2FOYPBV.js → chunk-IHMDBBUJ.js} +2 -2
- package/dist/chunk-IHMDBBUJ.js.map +1 -0
- package/dist/{chunk-E5R5GNKE.js → chunk-X6OQ2HTR.js} +2 -2
- package/dist/{chunk-E5R5GNKE.js.map → chunk-X6OQ2HTR.js.map} +1 -1
- package/dist/dev.d.ts +31 -13
- package/dist/dev.js +34 -18
- package/dist/dev.js.map +1 -1
- package/dist/html.js +2 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/list.d.ts +3 -2
- package/dist/list.js +27 -13
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +0 -22
- package/dist/overlay.js +107 -30
- package/dist/overlay.js.map +1 -1
- package/dist/remount.js +2 -2
- package/dist/router.d.ts +13 -4
- package/dist/router.js +8 -4
- package/dist/router.js.map +1 -1
- package/dist/scope.d.ts +10 -2
- package/dist/scope.js +22 -28
- package/dist/scope.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/timing.d.ts +3 -2
- package/dist/timing.js +1 -1
- package/dist/timing.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/chunk-H3TTM7PQ.js.map +0 -1
- package/dist/chunk-Y2FOYPBV.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { devHooks } from './chunk-
|
|
1
|
+
import { devHooks } from './chunk-3C6F6NM4.js';
|
|
2
2
|
|
|
3
3
|
// src/delegate.ts
|
|
4
4
|
var NON_BUBBLING = /* @__PURE__ */ new Set([
|
|
@@ -61,4 +61,4 @@ function delegateCapture(rootEl, type, selector, handler, options) {
|
|
|
61
61
|
|
|
62
62
|
export { delegate, delegateCapture };
|
|
63
63
|
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
64
|
+
//# sourceMappingURL=chunk-X6OQ2HTR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/delegate.ts"],"names":[],"mappings":";;;AA+CA,IAAM,YAAA,uBAAmB,GAAA,CAAY;AAAA,EACnC,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAC,CAAA;AAqBD,SAAS,mBAAA,CAAoB,UAAkB,EAAA,EAAkB;AAC/D,EAAA,IAAI;AACF,IAAA,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,EAChD,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,EAAG,EAAE,CAAA,oBAAA,EAAuB,QAAQ,CAAA,2EAAA;AAAA,KAEtC;AAAA,EACF;AACF;AAQA,SAAS,YAAA,CACP,MAAA,EACA,QAAA,EACA,OAAA,EACA,KAAA,EACwB;AACxB,EAAA,OAAO,CAAC,KAAA,KAAuB;AAC7B,IAAA,MAAM,SAAS,KAAA,CAAM,MAAA;AACrB,IAAA,IAAI,EAAE,kBAAkB,OAAA,CAAA,EAAU;AAClC,IAAA,MAAM,OAAA,GACJ,KAAA,KAAU,QAAA,GACN,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,GACrB,MAAA,GACA,IAAA,GACF,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AAC7B,IAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,EAAG;AAChD,MAAA,OAAA,CAAQ,OAAO,OAAY,CAAA;AAAA,IAC7B;AAAA,EACF,CAAA;AACF;AAuBO,SAAS,QAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,SACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,UAAU,CAAA;AACxC,EAAA,QAAA,CAAS,mBAAmB,UAAU,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IACf,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAS,KAAA,IAAS;AAAA,GACpB;AACA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAC/C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAAA,EACpD,CAAA;AACF;AAuBO,SAAS,eAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,SACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,iBAAiB,CAAA;AAC/C,EAAA,QAAA,CAAS,mBAAmB,iBAAiB,CAAA;AAC7C,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IACf,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAS,KAAA,IAAS;AAAA,GACpB;AACA,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAC5C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAAA,EACjD,CAAA;AACF","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/delegate.ts"],"names":[],"mappings":";;;AA+CA,IAAM,YAAA,uBAAmB,GAAA,CAAY;AAAA,EACnC,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAC,CAAA;AAqBD,SAAS,mBAAA,CAAoB,UAAkB,EAAA,EAAkB;AAC/D,EAAA,IAAI;AACF,IAAA,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,EAChD,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,EAAG,EAAE,CAAA,oBAAA,EAAuB,QAAQ,CAAA,2EAAA;AAAA,KAEtC;AAAA,EACF;AACF;AAQA,SAAS,YAAA,CACP,MAAA,EACA,QAAA,EACA,OAAA,EACA,KAAA,EACwB;AACxB,EAAA,OAAO,CAAC,KAAA,KAAuB;AAC7B,IAAA,MAAM,SAAS,KAAA,CAAM,MAAA;AACrB,IAAA,IAAI,EAAE,kBAAkB,OAAA,CAAA,EAAU;AAClC,IAAA,MAAM,OAAA,GACJ,KAAA,KAAU,QAAA,GACN,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,GACrB,MAAA,GACA,IAAA,GACF,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AAC7B,IAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,EAAG;AAChD,MAAA,OAAA,CAAQ,OAAO,OAAY,CAAA;AAAA,IAC7B;AAAA,EACF,CAAA;AACF;AAuBO,SAAS,QAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,SACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,UAAU,CAAA;AACxC,EAAA,QAAA,CAAS,mBAAmB,UAAU,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IACf,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAS,KAAA,IAAS;AAAA,GACpB;AACA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAC/C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,OAAO,CAAA;AAAA,EACpD,CAAA;AACF;AAuBO,SAAS,eAAA,CACd,MAAA,EACA,IAAA,EACA,QAAA,EACA,SACA,OAAA,EACY;AACZ,EAAA,mBAAA,CAAoB,UAAU,iBAAiB,CAAA;AAC/C,EAAA,QAAA,CAAS,mBAAmB,iBAAiB,CAAA;AAC7C,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IACf,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAS,KAAA,IAAS;AAAA,GACpB;AACA,EAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAC5C,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAAA,EACjD,CAAA;AACF","file":"chunk-X6OQ2HTR.js","sourcesContent":["/**\n * Tiny event-delegation helpers. Replace per-element `addEventListener` calls\n * (which don't survive morph re-renders for nodes the diff creates) with one\n * listener at the morph-root that dispatches via `closest()`.\n *\n * Three-tier listener model:\n *\n * - Tier 1 (bubbling events) — use `delegate()`.\n * click, input, change, submit, mousedown/up, keydown/up, pointerdown/up/move,\n * drag*, drop, contextmenu, wheel, copy/paste/cut, focusin/focusout.\n *\n * `delegate()` also auto-promotes the well-known non-bubbling event\n * types (`focus`, `blur`, `scroll`, `load`, `error`, `mouseenter`,\n * `mouseleave`) to the capture phase under the hood, so the call site\n * looks identical for \"interactive thing happens on a descendant\"\n * regardless of whether that event bubbles. Selector matching stays\n * `closest()`-style — the same as for bubbling events — so a wrapper\n * selector like `'.field-row'` still matches when the event fires on\n * a descendant `<input>`.\n *\n * - Tier 2 (explicit capture) — use `delegateCapture()`.\n * The escape hatch for cases the auto-promotion list doesn't cover\n * (custom non-bubbling events) or when you want capture-phase\n * interception. Selector matching is `closest()`-style by default —\n * the same walk-up as `delegate()`, and it passes the matched ancestor\n * (not the raw target) to the handler — so a click on any descendant of\n * the selected element climbs to it. Pass `{ match: 'direct' }` to opt\n * into strict `matches()`-style matching (fire only when the event lands\n * on the exact element the selector identifies).\n *\n * - Tier 3 (per-element instances / library-owned subtrees) — mark the\n * host element with `data-morph-skip` and manage the library's\n * lifecycle directly. No delegation helper applies.\n */\n\nimport { devHooks } from './dev-hooks.js';\n\n/**\n * Event types that don't bubble and so wouldn't reach a root-level\n * bubble-phase listener. `delegate()` flips to capture for these; the\n * caller doesn't need to know or care.\n *\n * Membership is conservative — it covers the cases that \"should obviously\n * work\" with delegate() but otherwise don't. For exotic non-bubbling events\n * (custom events, less-common DOM events) the explicit `delegateCapture()`\n * remains the escape hatch.\n */\nconst NON_BUBBLING = new Set<string>([\n 'focus',\n 'blur',\n 'scroll',\n 'load',\n 'error',\n 'mouseenter',\n 'mouseleave',\n]);\n\n/**\n * How the selector is matched against the event's target:\n *\n * - `'closest'` (the default for both helpers) — walk UP from `event.target`\n * via `closest(selector)`, firing for the nearest matching ancestor inside\n * `rootEl`. This is the delegation behavior you almost always want: a click\n * on an icon inside a button fires the button's handler.\n * - `'direct'` — strict `matches()` match: fire only when `event.target`\n * itself matches the selector, with no walk-up.\n */\nexport interface DelegateOptions {\n match?: 'closest' | 'direct';\n}\n\n/**\n * Validate a CSS selector at registration time, so a typo throws immediately\n * with the bad selector quoted instead of producing a cryptic DOMException\n * the first time a matching event fires.\n */\nfunction assertValidSelector(selector: string, fn: string): void {\n try {\n document.createElement('div').matches(selector);\n } catch {\n throw new Error(\n `${fn}: invalid selector \"${selector}\". ` +\n \"Pass a valid CSS selector (e.g. '[data-action=\\\"add\\\"]', '.btn', 'input').\",\n );\n }\n}\n\n/**\n * Build the shared root-level listener used by both helpers. Resolves the\n * event's target to a matched element (walk-up `closest()` or strict\n * `matches()`, per `match`), requires the match to be inside `rootEl`, then\n * fires `handler(event, matched)`.\n */\nfunction makeListener<T extends Element>(\n rootEl: HTMLElement,\n selector: string,\n handler: (event: Event, target: T) => void,\n match: 'closest' | 'direct',\n): (event: Event) => void {\n return (event: Event): void => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const matched =\n match === 'direct'\n ? target.matches(selector)\n ? target\n : null\n : target.closest(selector);\n if (matched !== null && rootEl.contains(matched)) {\n handler(event, matched as T);\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 * Pass `{ match: 'direct' }` to fire only when `event.target` itself matches\n * the selector (no walk-up); the default is `'closest'`.\n *\n * The generic `T` narrows the second handler argument to the expected element\n * type — `delegate<HTMLButtonElement>(root, 'click', 'button', (e, btn) => btn.value)`\n * — so consumers can avoid casts. Defaults to `Element` for untyped calls.\n *\n * Returns a disposer that removes the listener.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegate(rootEl, 'click', '[data-action=\"add\"]', handlerFn);\n * delegate(rootEl, 'focus', 'input', handlerFn); // auto-capture\n */\nexport function delegate<T extends Element = Element>(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: (event: Event, target: T) => void,\n options?: DelegateOptions,\n): () => void {\n assertValidSelector(selector, 'delegate');\n devHooks.delegateInEffect?.('delegate');\n const listener = makeListener(\n rootEl,\n selector,\n handler,\n options?.match ?? 'closest',\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 — the escape hatch for custom non-bubbling events\n * (ones `delegate()`'s auto-promotion list doesn't know about) and for\n * capture-phase interception (run before any descendant's bubble-phase\n * handler). Reaches descendants of `rootEl` that match `selector` regardless\n * of how many times the diff has rebuilt them.\n *\n * Selector matching is `closest()`-style by default — the same walk-up as\n * `delegate()`, and it passes the matched ancestor (not the raw target) to\n * the handler — so a click on any descendant of the selected element climbs\n * to it. Pass `{ match: 'direct' }` to opt into strict `matches()`-style\n * matching (fire only when the event lands on the exact element the selector\n * identifies, with no walk-up).\n *\n * The generic `T` narrows the second handler argument to the expected element\n * type, mirroring `delegate<T>()`. Defaults to `Element` for untyped calls.\n *\n * Usage (pseudo-code — see examples for live ones):\n * delegateCapture(rootEl, 'focus', 'input, textarea', handlerFn);\n * delegateCapture(rootEl, 'click', '.exact', handlerFn, { match: 'direct' });\n */\nexport function delegateCapture<T extends Element = Element>(\n rootEl: HTMLElement,\n type: string,\n selector: string,\n handler: (event: Event, target: T) => void,\n options?: DelegateOptions,\n): () => void {\n assertValidSelector(selector, 'delegateCapture');\n devHooks.delegateInEffect?.('delegateCapture');\n const listener = makeListener(\n rootEl,\n selector,\n handler,\n options?.match ?? 'closest',\n );\n rootEl.addEventListener(type, listener, true);\n return () => {\n rootEl.removeEventListener(type, listener, true);\n };\n}\n"]}
|
package/dist/dev.d.ts
CHANGED
|
@@ -137,7 +137,9 @@ interface DevHooks {
|
|
|
137
137
|
signalFactory?: <T>(value: T) => Signal<T>;
|
|
138
138
|
/**
|
|
139
139
|
* Wraps an `effect()` body so `delegate()` can detect that it is running
|
|
140
|
-
* inside one. Returns the body to actually run.
|
|
140
|
+
* inside one. Returns the body to actually run. Resolved at effect-CREATION
|
|
141
|
+
* time (the delegate-in-effect switch too), so an effect created before the
|
|
142
|
+
* install or the opt-in is never wrapped.
|
|
141
143
|
*/
|
|
142
144
|
wrapEffect?: (fn: () => void | (() => void)) => () => void | (() => void);
|
|
143
145
|
delegateInEffect?: (fn: 'delegate' | 'delegateCapture') => void;
|
|
@@ -146,6 +148,12 @@ interface DevHooks {
|
|
|
146
148
|
storeReadonly?: <T extends object>(state: T) => T;
|
|
147
149
|
/** Unwraps a proxy handed back through `set({ ...get() })`. */
|
|
148
150
|
storeToRaw?: <T>(next: T) => T;
|
|
151
|
+
/**
|
|
152
|
+
* Attaches the rebuilt-listeners observer to a mount root. Called once per
|
|
153
|
+
* `mount()`, at creation — and it returns `null` when the warning is switched
|
|
154
|
+
* off then — so a mount created before the install or the opt-in is never
|
|
155
|
+
* observed.
|
|
156
|
+
*/
|
|
149
157
|
listenerRebuild?: (rootEl: Element) => MutationObserver | null;
|
|
150
158
|
listIdShift?: (id: string) => void;
|
|
151
159
|
parserRepair?: (html: string) => void;
|
|
@@ -164,6 +172,8 @@ interface DevHooks {
|
|
|
164
172
|
duplicateCacheKeys?: (id: string, segItems: readonly {
|
|
165
173
|
cacheKey: unknown;
|
|
166
174
|
}[]) => void;
|
|
175
|
+
/** A surface opened over a modal `<dialog>` could not be made fully usable. */
|
|
176
|
+
overlayBlockedByModal?: (reason: 'hidden' | 'inert') => void;
|
|
167
177
|
/**
|
|
168
178
|
* When installed, a screened URL throws instead of warning-and-dropping.
|
|
169
179
|
* A slot rather than a boolean so the check stays uniform with the rest.
|
|
@@ -282,17 +292,23 @@ interface DevWarningOptions {
|
|
|
282
292
|
*
|
|
283
293
|
* ## Install ordering
|
|
284
294
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
295
|
+
* Most hooks are read at CALL time (render, reconcile, `set()`, `delegate()`).
|
|
296
|
+
* Three are decided once, when their host is CREATED, so both installation and
|
|
297
|
+
* the warning's switch must precede it:
|
|
298
|
+
*
|
|
299
|
+
* - `signalFactory` — `signal()` picks its constructor (untracked signals).
|
|
300
|
+
* - `wrapEffect` — `effect()` decides whether to wrap its body with the
|
|
301
|
+
* effect-depth counter (delegate-in-effect).
|
|
302
|
+
* - `listenerRebuild` — `mount()` decides whether to attach its
|
|
303
|
+
* rebuilt-listeners `MutationObserver`.
|
|
287
304
|
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* dev-only entry file, then load the rest of your app.
|
|
305
|
+
* Static imports are hoisted above a top-level `await import()`, so
|
|
306
|
+
* module-scope signals and effects in imported modules are created before this
|
|
307
|
+
* module runs. To cover those, make `import 'kerfjs/dev'` the FIRST STATIC
|
|
308
|
+
* import of a dev-only entry file (calling `enableWarnings()` there), then load
|
|
309
|
+
* the rest of your app.
|
|
294
310
|
*
|
|
295
|
-
* Opting into
|
|
311
|
+
* Opting into the untracked-signal warning prints its boundary once, so the gap is loud
|
|
296
312
|
* rather than silent. It cannot be closed by retro-fitting existing signals:
|
|
297
313
|
* `Signal.prototype`'s `value` accessor is non-configurable, and reaching live
|
|
298
314
|
* instances would require a per-signal registry that production would pay for.
|
|
@@ -326,9 +342,11 @@ interface DevWarningOptions {
|
|
|
326
342
|
* goes through `globalThis.process` into a local binding), so the environment
|
|
327
343
|
* variables are unreachable there.
|
|
328
344
|
*
|
|
329
|
-
* Call it as early as you can.
|
|
330
|
-
*
|
|
331
|
-
*
|
|
345
|
+
* Call it as early as you can. Most diagnostics read their switch at call
|
|
346
|
+
* time, but three are decided when their host is CREATED: `untrackedSignals`
|
|
347
|
+
* per `signal()`, `delegateInEffect` per `effect()`, and `rebuiltListeners` per
|
|
348
|
+
* `mount()`. A signal, effect, or mount created before this call stays
|
|
349
|
+
* uncovered. Enabling `untrackedSignals` prints its coverage boundary once.
|
|
332
350
|
*/
|
|
333
351
|
declare function enableWarnings(options: DevWarningOptions): void;
|
|
334
352
|
/**
|
package/dist/dev.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { truncateRowHtml } from './chunk-WKIPLNVO.js';
|
|
2
2
|
import { LIST_MARKER_PREFIX } from './chunk-BDX3R4OM.js';
|
|
3
|
-
import { installDevHooks } from './chunk-
|
|
4
|
-
export { clearDevHooks, devHooks, installDevHooks } from './chunk-
|
|
3
|
+
import { installDevHooks } from './chunk-3C6F6NM4.js';
|
|
4
|
+
export { clearDevHooks, devHooks, installDevHooks } from './chunk-3C6F6NM4.js';
|
|
5
5
|
import { signal, Signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
// src/dev-warn-config.ts
|
|
@@ -36,6 +36,9 @@ function devFlag(envName) {
|
|
|
36
36
|
const proc = globalThis.process;
|
|
37
37
|
return proc?.env?.[envName];
|
|
38
38
|
}
|
|
39
|
+
function silenceHint(key) {
|
|
40
|
+
return `Silence it with enableWarnings({ ${key}: false }) from kerfjs/dev, or set ${ENV_NAME[key]}=0 (or unset it) under Node.`;
|
|
41
|
+
}
|
|
39
42
|
|
|
40
43
|
// src/dev-binding-warn.ts
|
|
41
44
|
var warnedHoles = /* @__PURE__ */ new Set();
|
|
@@ -54,7 +57,7 @@ function maybeWarnStaleBinding(prevWired, current) {
|
|
|
54
57
|
if (warnedHoles.has(cur.id)) continue;
|
|
55
58
|
warnedHoles.add(cur.id);
|
|
56
59
|
console.warn(
|
|
57
|
-
`kerf: fine-grained binding ${describeHole(cur)} switched to a different signal instance on a render whose static-surrounds HTML was byte-for-byte unchanged. On that fast path kerf keeps the original binding effect and does NOT re-bind, so this hole is now stale \u2014 it still tracks the FIRST signal instance and will not reflect the new one. Bind one computed that switches internally (e.g. class={computed(() => cond.value ? sigA.value : sigB.value)}) instead of switching which signal instance you bind (see docs/2-reactivity \xA72.9).
|
|
60
|
+
`kerf: fine-grained binding ${describeHole(cur)} switched to a different signal instance on a render whose static-surrounds HTML was byte-for-byte unchanged. On that fast path kerf keeps the original binding effect and does NOT re-bind, so this hole is now stale \u2014 it still tracks the FIRST signal instance and will not reflect the new one. Bind one computed that switches internally (e.g. class={computed(() => cond.value ? sigA.value : sigB.value)}) instead of switching which signal instance you bind (see docs/2-reactivity \xA72.9). ` + silenceHint("staleBinding")
|
|
58
61
|
);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -80,7 +83,7 @@ function warnIfInsideEffect(fn) {
|
|
|
80
83
|
if (warned) return;
|
|
81
84
|
warned = true;
|
|
82
85
|
console.warn(
|
|
83
|
-
`kerf: ${fn}() was called inside an effect() body. Every effect re-run installs a fresh root listener; the effect disposer cleans up the reactive subscription but not the listeners, so listener count grows linearly with signal churn and each listener pins its handler closure. Register the delegate once at module or setup scope and gate behavior on the signal *inside the handler* where the read is free. See docs/5-event-delegation.md \xA75.3 "When capturing the disposer still isn't enough".
|
|
86
|
+
`kerf: ${fn}() was called inside an effect() body. Every effect re-run installs a fresh root listener; the effect disposer cleans up the reactive subscription but not the listeners, so listener count grows linearly with signal churn and each listener pins its handler closure. Register the delegate once at module or setup scope and gate behavior on the signal *inside the handler* where the read is free. See docs/5-event-delegation.md \xA75.3 "When capturing the disposer still isn't enough". ` + silenceHint("delegateInEffect")
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -103,7 +106,7 @@ function maybeWarnEachInMorphSkip(id, liveParent, rootEl) {
|
|
|
103
106
|
if (!hasMorphSkipAncestor(liveParent, rootEl)) return;
|
|
104
107
|
warnedIds.add(id);
|
|
105
108
|
console.warn(
|
|
106
|
-
`kerf: each() list '${id}' is inside a data-morph-skip subtree. The keyed reconciler still updates the list rows, but any static signal-reactive JSX inside the same skipped ancestor (e.g. <p>{count.value}</p>) is frozen \u2014 the morph never visits it. Remove data-morph-skip from any element that contains reactive JSX content and reserve it for truly library-owned hosts.
|
|
109
|
+
`kerf: each() list '${id}' is inside a data-morph-skip subtree. The keyed reconciler still updates the list rows, but any static signal-reactive JSX inside the same skipped ancestor (e.g. <p>{count.value}</p>) is frozen \u2014 the morph never visits it. Remove data-morph-skip from any element that contains reactive JSX content and reserve it for truly library-owned hosts. ` + silenceHint("eachInMorphSkip")
|
|
107
110
|
);
|
|
108
111
|
}
|
|
109
112
|
var warnedDupIds = /* @__PURE__ */ new Set();
|
|
@@ -118,7 +121,7 @@ function maybeWarnDuplicateCacheKeys(id, segItems) {
|
|
|
118
121
|
if (seen.has(si.cacheKey)) {
|
|
119
122
|
warnedDupIds.add(id);
|
|
120
123
|
console.warn(
|
|
121
|
-
`kerf: each() list '${id}' has duplicate cacheKey values (duplicate: ${String(si.cacheKey)}). The cacheKey function should return a unique value per row so kerf can tell apart items for memoization \u2014 duplicate values cause some rows to return stale cached HTML when external state that affects their render changes.
|
|
124
|
+
`kerf: each() list '${id}' has duplicate cacheKey values (duplicate: ${String(si.cacheKey)}). The cacheKey function should return a unique value per row so kerf can tell apart items for memoization \u2014 duplicate values cause some rows to return stale cached HTML when external state that affects their render changes. ` + silenceHint("duplicateEachKeys")
|
|
122
125
|
);
|
|
123
126
|
return;
|
|
124
127
|
}
|
|
@@ -269,7 +272,7 @@ function maybeWarnStaleIndex(id) {
|
|
|
269
272
|
if (warnedIds2.has(id)) return;
|
|
270
273
|
warnedIds2.add(id);
|
|
271
274
|
console.warn(
|
|
272
|
-
`kerf: each() list '${id}' reused a memoized row at a new index this render, but its render function takes an \`index\` argument. Rows are memoized by object identity, not by position, so a reorder or an insert/remove ahead of a surviving row serves that row's cached HTML \u2014 computed at its OLD index. A numbered list, zebra striping, or an "N of M" label will silently show the wrong value. If the row output depends on the index, fold it into the memo key: each(items, render, { cacheKey: (_, i) => i }) (combine with your own key if you have one). If the index is not used in the output, ignore this.
|
|
275
|
+
`kerf: each() list '${id}' reused a memoized row at a new index this render, but its render function takes an \`index\` argument. Rows are memoized by object identity, not by position, so a reorder or an insert/remove ahead of a surviving row serves that row's cached HTML \u2014 computed at its OLD index. A numbered list, zebra striping, or an "N of M" label will silently show the wrong value. If the row output depends on the index, fold it into the memo key: each(items, render, { cacheKey: (_, i) => i }) (combine with your own key if you have one). If the index is not used in the output, ignore this. ` + silenceHint("staleIndex")
|
|
273
276
|
);
|
|
274
277
|
}
|
|
275
278
|
|
|
@@ -290,14 +293,13 @@ function maybeWarnListRebind(id, liveParent) {
|
|
|
290
293
|
if (warnedIds3.has(id)) return;
|
|
291
294
|
warnedIds3.add(id);
|
|
292
295
|
console.warn(
|
|
293
|
-
`kerf: each() list '${id}' had its container (<${liveParent.tagName.toLowerCase()}>) rebuilt by the morph this render \u2014 an ancestor's tag changed so the subtree was replaced, or a same-tag sibling positionally took the container's place. The list re-binds and repopulates automatically, but its rows were re-created from scratch: focus, scroll positions, in-progress IME composition, and any imperative listeners on the old row nodes are lost. If the rows should survive, give the LIST'S OWN container a stable id/data-key (keying the conditional sibling instead only helps when it is removed, not when it reappears) and keep the tags of the list's ancestors stable across renders.
|
|
296
|
+
`kerf: each() list '${id}' had its container (<${liveParent.tagName.toLowerCase()}>) rebuilt by the morph this render \u2014 an ancestor's tag changed so the subtree was replaced, or a same-tag sibling positionally took the container's place. The list re-binds and repopulates automatically, but its rows were re-created from scratch: focus, scroll positions, in-progress IME composition, and any imperative listeners on the old row nodes are lost. If the rows should survive, give the LIST'S OWN container a stable id/data-key (keying the conditional sibling instead only helps when it is removed, not when it reappears) and keep the tags of the list's ancestors stable across renders. ` + silenceHint("listRebind")
|
|
294
297
|
);
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
// src/dev-listener-warn.ts
|
|
298
301
|
var LISTENER_MARKER = /* @__PURE__ */ Symbol.for("kerfjs.devListener");
|
|
299
302
|
var patched = false;
|
|
300
|
-
var warned2 = false;
|
|
301
303
|
function isOptedIn5() {
|
|
302
304
|
return devFlag("KERF_DEV_WARN_REBUILT_LISTENERS") === "1";
|
|
303
305
|
}
|
|
@@ -335,22 +337,22 @@ function hasMarkedListener(el) {
|
|
|
335
337
|
return false;
|
|
336
338
|
}
|
|
337
339
|
function emitWarning() {
|
|
338
|
-
if (warned2) return;
|
|
339
|
-
warned2 = true;
|
|
340
340
|
console.warn(
|
|
341
|
-
"kerf: a node inside a mount()-managed tree was removed/rebuilt while carrying an imperative addEventListener listener. The listener is gone with the old node. Use `delegate(rootEl, 'click', '[data-action=\"...\"]', handler)` so the listener lives on a stable ancestor and survives re-renders, or wrap the host in `data-morph-skip` if the subtree is library-owned (Monaco, xterm, D3 charts).
|
|
341
|
+
"kerf: a node inside a mount()-managed tree was removed/rebuilt while carrying an imperative addEventListener listener. The listener is gone with the old node. Use `delegate(rootEl, 'click', '[data-action=\"...\"]', handler)` so the listener lives on a stable ancestor and survives re-renders, or wrap the host in `data-morph-skip` if the subtree is library-owned (Monaco, xterm, D3 charts). " + silenceHint("rebuiltListeners")
|
|
342
342
|
);
|
|
343
343
|
}
|
|
344
344
|
function installListenerRebuildWarn(rootEl) {
|
|
345
345
|
if (!isOptedIn5()) return null;
|
|
346
346
|
patchAddEventListenerOnce();
|
|
347
|
+
let warned3 = false;
|
|
347
348
|
const observer = new MutationObserver((mutations) => {
|
|
348
|
-
if (
|
|
349
|
+
if (warned3) return;
|
|
349
350
|
for (const m of mutations) {
|
|
350
351
|
for (let i = 0; i < m.removedNodes.length; i++) {
|
|
351
352
|
const removed = m.removedNodes[i];
|
|
352
353
|
if (!(removed instanceof Element)) continue;
|
|
353
354
|
if (hasMarkedListener(removed)) {
|
|
355
|
+
warned3 = true;
|
|
354
356
|
emitWarning();
|
|
355
357
|
return;
|
|
356
358
|
}
|
|
@@ -361,6 +363,18 @@ function installListenerRebuildWarn(rootEl) {
|
|
|
361
363
|
return observer;
|
|
362
364
|
}
|
|
363
365
|
|
|
366
|
+
// src/dev-overlay-warn.ts
|
|
367
|
+
var warned2 = /* @__PURE__ */ new Set();
|
|
368
|
+
var MESSAGES = {
|
|
369
|
+
hidden: "kerf overlay(): a surface was opened while a modal <dialog> is open, and this engine lacks the <dialog>.showModal() / Popover API needed to lift it into the top layer. It renders beneath the dialog and is inert, so the user can neither see nor use it. Render the content inside the dialog's own markup, or open the dialog without `native: true`.",
|
|
370
|
+
inert: "kerf overlay(): a non-modal surface opened while a modal <dialog> is open was lifted into the top layer so it is visible, but browsers keep everything outside a modal dialog inert \u2014 its focusable controls cannot be focused or clicked. Render interactive content inside the dialog's own markup (for example a <details> menu), or open the dialog without `native: true`."
|
|
371
|
+
};
|
|
372
|
+
function maybeWarnOverlayBlockedByModal(reason) {
|
|
373
|
+
if (warned2.has(reason)) return;
|
|
374
|
+
warned2.add(reason);
|
|
375
|
+
console.warn(MESSAGES[reason]);
|
|
376
|
+
}
|
|
377
|
+
|
|
364
378
|
// src/dev-parser-repair-warn.ts
|
|
365
379
|
var BLOCK_TAGS = [
|
|
366
380
|
"address",
|
|
@@ -425,7 +439,7 @@ function maybeWarnParserRepair(html) {
|
|
|
425
439
|
if (warnedPairs.has(pair)) return;
|
|
426
440
|
warnedPairs.add(pair);
|
|
427
441
|
console.warn(
|
|
428
|
-
`kerf: a <${tag}> inside a <p> will not survive parsing. <p> may contain only phrasing content, so the HTML parser closes it before a block-level child \u2014 your <p> ends up EMPTY and the <${tag}> (plus everything after it) becomes its sibling instead of its child. kerf then reconciles that repaired tree correctly, so updates still work; what you lose is the structure you wrote, along with any CSS or querySelector that assumed it. Use a <div> (or a phrasing element like <span>) in place of the <p>, or move the block content outside it.
|
|
442
|
+
`kerf: a <${tag}> inside a <p> will not survive parsing. <p> may contain only phrasing content, so the HTML parser closes it before a block-level child \u2014 your <p> ends up EMPTY and the <${tag}> (plus everything after it) becomes its sibling instead of its child. kerf then reconciles that repaired tree correctly, so updates still work; what you lose is the structure you wrote, along with any CSS or querySelector that assumed it. Use a <div> (or a phrasing element like <span>) in place of the <p>, or move the block content outside it. ` + silenceHint("parserRepair")
|
|
429
443
|
);
|
|
430
444
|
}
|
|
431
445
|
|
|
@@ -464,7 +478,7 @@ function maybeWarnValueOnlyRerender(prevHtml, nextHtml, ctx) {
|
|
|
464
478
|
if (!_isValueOnlyDiff(a.content, b.content)) return;
|
|
465
479
|
ctx.warned = true;
|
|
466
480
|
console.warn(
|
|
467
|
-
"kerf: this re-render changed only text content and attribute values \u2014 no structural change \u2014 so every changed hole could be a fine-grained binding instead. Values bind, structure re-renders: pass the signal/computed itself ({count}, class={sig}) rather than reading .value in the hole, and each change updates just that node with no render re-run (a mount whose render reads no .value never re-renders at all). See docs/2-reactivity \xA72.9.
|
|
481
|
+
"kerf: this re-render changed only text content and attribute values \u2014 no structural change \u2014 so every changed hole could be a fine-grained binding instead. Values bind, structure re-renders: pass the signal/computed itself ({count}, class={sig}) rather than reading .value in the hole, and each change updates just that node with no render re-run (a mount whose render reads no .value never re-renders at all). See docs/2-reactivity \xA72.9. " + silenceHint("valueOnlyRerender")
|
|
468
482
|
);
|
|
469
483
|
}
|
|
470
484
|
|
|
@@ -477,7 +491,7 @@ function maybeWarnMissingRowKey(rowEl, rowHtml, binding) {
|
|
|
477
491
|
`kerf each(): the first row has no \`id\` or \`data-key\` attribute. Without one, rows match positionally \u2014 an insert/remove at the head shifts every row's identity, so focused inputs jump to the wrong row, mid-edit textareas swap content with their neighbor, and any per-row state silently follows the wrong item. Add \`data-key={item.id}\` (or set \`id\`) to the top-level element returned by the row render. Row HTML: ${JSON.stringify(truncateRowHtml(rowHtml))}`
|
|
478
492
|
);
|
|
479
493
|
}
|
|
480
|
-
var WARNING_MESSAGE = "kerf: signal was written but has no subscribers. Did you read `.value` outside of a render fn / effect()? Hoisted reads do not subscribe, so subsequent writes will not re-render. Move the read inside mount()'s render fn or effect() callback.
|
|
494
|
+
var WARNING_MESSAGE = "kerf: signal was written but has no subscribers. Did you read `.value` outside of a render fn / effect()? Hoisted reads do not subscribe, so subsequent writes will not re-render. Move the read inside mount()'s render fn or effect() callback. " + silenceHint("untrackedSignals");
|
|
481
495
|
var DevSignal = class extends Signal {
|
|
482
496
|
__hasSubscriber = false;
|
|
483
497
|
__warned = false;
|
|
@@ -509,13 +523,13 @@ function noteUntrackedCoverage() {
|
|
|
509
523
|
if (coverageNoticeShown) return;
|
|
510
524
|
coverageNoticeShown = true;
|
|
511
525
|
console.warn(
|
|
512
|
-
"kerf: KERF_DEV_WARN_UNTRACKED_SIGNALS only covers signals created AFTER kerfjs/dev is installed. Static imports are hoisted above `await import('kerfjs/dev')`, so module-scope signals in the modules you import are created first and this warning cannot see them \u2014 you may get no warnings even where the bug exists. To cover them, make `import 'kerfjs/dev'` the FIRST STATIC import of a dev-only entry file (static imports evaluate in order), then load the rest of your app.
|
|
526
|
+
"kerf: KERF_DEV_WARN_UNTRACKED_SIGNALS only covers signals created AFTER kerfjs/dev is installed. Static imports are hoisted above `await import('kerfjs/dev')`, so module-scope signals in the modules you import are created first and this warning cannot see them \u2014 you may get no warnings even where the bug exists. To cover them, make `import 'kerfjs/dev'` the FIRST STATIC import of a dev-only entry file (static imports evaluate in order), then load the rest of your app. " + silenceHint("untrackedSignals")
|
|
513
527
|
);
|
|
514
528
|
}
|
|
515
529
|
|
|
516
530
|
// src/dev-store-warn.ts
|
|
517
531
|
var WARNING_PREFIX = "kerf: defineStore.set() called with keys missing from the current state \u2014 ";
|
|
518
|
-
var WARNING_SUFFIX = ". set() REPLACES state; the missing keys will be undefined after this call. Use `set({ ...get(), ...next })` to merge instead, or update each call site to pass the full state.
|
|
532
|
+
var WARNING_SUFFIX = ". set() REPLACES state; the missing keys will be undefined after this call. Use `set({ ...get(), ...next })` to merge instead, or update each call site to pass the full state. " + silenceHint("narrowSet");
|
|
519
533
|
function isOptedIn8() {
|
|
520
534
|
return devFlag("KERF_DEV_WARN_NARROW_SET") === "1";
|
|
521
535
|
}
|
|
@@ -641,6 +655,8 @@ var DEV_HOOKS = {
|
|
|
641
655
|
staleIndexEnabled: isOptedInStaleIndex,
|
|
642
656
|
staleIndex: maybeWarnStaleIndex,
|
|
643
657
|
duplicateCacheKeys: maybeWarnDuplicateCacheKeys,
|
|
658
|
+
// --- overlay ----------------------------------------------------------
|
|
659
|
+
overlayBlockedByModal: maybeWarnOverlayBlockedByModal,
|
|
644
660
|
// --- urlScreen --------------------------------------------------------
|
|
645
661
|
urlScreenThrow: (message) => {
|
|
646
662
|
throw new Error(message);
|