@llui/dom 0.0.39 → 0.1.0
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/README.md +22 -21
- package/dist/binding.d.ts +5 -0
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +1 -0
- package/dist/binding.js.map +1 -1
- package/dist/combine.d.ts +67 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +76 -0
- package/dist/combine.js.map +1 -0
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +15 -5
- package/dist/devtools.js.map +1 -1
- package/dist/el-split.d.ts +1 -1
- package/dist/el-split.d.ts.map +1 -1
- package/dist/el-split.js +20 -2
- package/dist/el-split.js.map +1 -1
- package/dist/el-template.d.ts +8 -2
- package/dist/el-template.d.ts.map +1 -1
- package/dist/el-template.js +2 -1
- package/dist/el-template.js.map +1 -1
- package/dist/escape-hatch.d.ts +47 -0
- package/dist/escape-hatch.d.ts.map +1 -0
- package/dist/escape-hatch.js +63 -0
- package/dist/escape-hatch.js.map +1 -0
- package/dist/hmr.d.ts +2 -1
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +3 -2
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/primitives/branch.d.ts.map +1 -1
- package/dist/primitives/branch.js +1 -0
- package/dist/primitives/branch.js.map +1 -1
- package/dist/primitives/each.d.ts.map +1 -1
- package/dist/primitives/each.js +14 -4
- package/dist/primitives/each.js.map +1 -1
- package/dist/primitives/memo.d.ts +2 -2
- package/dist/primitives/memo.d.ts.map +1 -1
- package/dist/primitives/memo.js +13 -5
- package/dist/primitives/memo.js.map +1 -1
- package/dist/primitives/unsafe-html.d.ts.map +1 -1
- package/dist/primitives/unsafe-html.js +1 -0
- package/dist/primitives/unsafe-html.js.map +1 -1
- package/dist/primitives/virtual-each.d.ts.map +1 -1
- package/dist/primitives/virtual-each.js +10 -0
- package/dist/primitives/virtual-each.js.map +1 -1
- package/dist/structural.d.ts +8 -4
- package/dist/structural.d.ts.map +1 -1
- package/dist/structural.js.map +1 -1
- package/dist/types.d.ts +21 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-loop.d.ts +14 -6
- package/dist/update-loop.d.ts.map +1 -1
- package/dist/update-loop.js +130 -34
- package/dist/update-loop.js.map +1 -1
- package/package.json +5 -1
package/dist/types.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ export interface ComponentDef<S, M, E = never, D = void> {
|
|
|
9
9
|
send: Send<M>;
|
|
10
10
|
signal: AbortSignal;
|
|
11
11
|
}) => void;
|
|
12
|
-
propsMsg?: (props: Record<string, unknown>) => M;
|
|
13
|
-
receives?: Record<string, (params: unknown) => M>;
|
|
14
12
|
}
|
|
15
13
|
export type Send<M> = (msg: M) => void;
|
|
16
14
|
/**
|
|
@@ -51,9 +49,8 @@ export interface AnyComponentDef {
|
|
|
51
49
|
update(state: unknown, msg: unknown): [unknown, unknown[]];
|
|
52
50
|
view(h: unknown): Node[];
|
|
53
51
|
onEffect?: unknown;
|
|
54
|
-
propsMsg?: unknown;
|
|
55
|
-
receives?: unknown;
|
|
56
52
|
__dirty?: unknown;
|
|
53
|
+
__prefixes?: unknown;
|
|
57
54
|
__renderToString?: unknown;
|
|
58
55
|
__msgSchema?: unknown;
|
|
59
56
|
__msgAnnotations?: unknown;
|
|
@@ -84,9 +81,8 @@ export interface LazyDef<D = void> {
|
|
|
84
81
|
update(state: unknown, msg: unknown): [unknown, unknown[]];
|
|
85
82
|
view(h: unknown): Node[];
|
|
86
83
|
onEffect?: unknown;
|
|
87
|
-
propsMsg?: unknown;
|
|
88
|
-
receives?: unknown;
|
|
89
84
|
__dirty?: unknown;
|
|
85
|
+
__prefixes?: unknown;
|
|
90
86
|
__renderToString?: unknown;
|
|
91
87
|
__msgSchema?: unknown;
|
|
92
88
|
__msgAnnotations?: unknown;
|
|
@@ -125,8 +121,9 @@ export interface AppHandle {
|
|
|
125
121
|
* normal view-bound `send` channel. Useful for adapter layers that
|
|
126
122
|
* need to push updates into a long-lived instance — e.g.
|
|
127
123
|
* `@llui/vike`'s persistent-layout chain pushes layout-data updates
|
|
128
|
-
* into surviving layer instances on client navigation
|
|
129
|
-
* `
|
|
124
|
+
* into surviving layer instances on client navigation through the
|
|
125
|
+
* user-supplied `onLayerDataChange` callback, which then calls
|
|
126
|
+
* `handle.send(msg)` with the layout's chosen state-update message.
|
|
130
127
|
*
|
|
131
128
|
* Messages are queued through the same path as `view`-side `send`
|
|
132
129
|
* calls — they batch into the next microtask and process via the
|
|
@@ -283,14 +280,26 @@ export interface LifetimeNode {
|
|
|
283
280
|
* `'text' | 'prop' | 'attr' | 'class' | 'style'` write their accessor's
|
|
284
281
|
* return value to the DOM. `'effect'` is a side-effect-only watcher:
|
|
285
282
|
* the accessor is invoked every Phase 2 tick its mask is hit, but its
|
|
286
|
-
* return value is discarded and `applyBinding` is a no-op.
|
|
287
|
-
*
|
|
288
|
-
* without
|
|
289
|
-
* detached anchor node every render.
|
|
283
|
+
* return value is discarded and `applyBinding` is a no-op. Reserved
|
|
284
|
+
* for primitives that need to fire a side-effect on each binding
|
|
285
|
+
* evaluation without writing to the DOM.
|
|
290
286
|
*/
|
|
291
287
|
export type BindingKind = 'text' | 'prop' | 'attr' | 'class' | 'style' | 'effect';
|
|
292
288
|
export interface Binding {
|
|
289
|
+
/**
|
|
290
|
+
* Bits 0..30 of the binding's dirty mask. For components with ≤31
|
|
291
|
+
* reactive prefixes this is the entire mask; for components with
|
|
292
|
+
* 32..61 prefixes, the bits beyond 30 live in {@link maskHi}.
|
|
293
|
+
*/
|
|
293
294
|
mask: number;
|
|
295
|
+
/**
|
|
296
|
+
* Bits 31..61 of the binding's dirty mask, encoded with `1 <<
|
|
297
|
+
* (pos - 31)`. Zero for ≤31-path components — the runtime gates
|
|
298
|
+
* still AND against it, but `0 & dirtyHi` is always 0 so the gate
|
|
299
|
+
* collapses to the single-word check. Compiler-emitted only when
|
|
300
|
+
* the component actually reads paths past bit 30.
|
|
301
|
+
*/
|
|
302
|
+
maskHi: number;
|
|
294
303
|
accessor: (state: unknown) => unknown;
|
|
295
304
|
lastValue: unknown;
|
|
296
305
|
kind: BindingKind;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAK7C,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IACtC,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IAC/B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qEAAqE","sourcesContent":["// ── Component Definition ──────────────────────────────────────────\n\nimport type { View } from './view-helpers.js'\nimport type { StructuralBlock } from './structural.js'\nimport type { ComponentInstance } from './update-loop.js'\nimport type { DisposerEvent } from './tracking/disposer-log.js'\n\nexport interface ComponentDef<S, M, E = never, D = void> {\n name: string\n init: (data: D) => [S, E[]]\n update: (state: S, msg: M) => [S, E[]]\n view: (h: View<S, M>) => Node[]\n onEffect?: (ctx: { effect: E; send: Send<M>; signal: AbortSignal }) => void\n\n // Level 2 composition\n propsMsg?: (props: Record<string, unknown>) => M\n receives?: Record<string, (params: unknown) => M>\n\n /** @internal Compiler-injected */\n __dirty?: (oldState: S, newState: S) => number | [number, number]\n /** @internal Compiler-injected */\n __renderToString?: (state: S) => string\n /** @internal Compiler-injected */\n __msgSchema?: object\n /** @internal Compiler-injected; keyed by Msg discriminant → annotations. See agent spec §5.1. */\n __msgAnnotations?: Record<\n string,\n {\n intent: string | null\n alwaysAffordable: boolean\n requiresConfirm: boolean\n humanOnly: boolean\n }\n >\n /** @internal Compiler-emitted; one entry per send() call site in view(). See agent spec §5.2. */\n __bindingDescriptors?: Array<{ variant: string }>\n /** @internal Compiler-injected; 32-char hex SHA-256 of schemas + annotations. See agent spec §12.3. */\n __schemaHash?: string\n /** @internal Compiler-injected — maps top-level state field → dirty-mask bit(s) */\n __maskLegend?: Record<string, number>\n /** @internal Compiler-injected — source-file location of the component() call */\n __componentMeta?: { file: string; line: number }\n /** @internal Compiler-injected — shape of the State type (for introspection) */\n __stateSchema?: object\n /** @internal Compiler-injected — Effect union schema (for introspection) */\n __effectSchema?: object\n /** @internal Compiler-injected — replaces generic Phase 1 + Phase 2 loop */\n __update?: (\n state: S,\n dirty: number,\n bindings: Binding[],\n blocks: StructuralBlock[],\n bindingsBeforePhase1: number,\n ) => void\n /** @internal Compiler-injected — per-message-type specialized handlers.\n * Bypass the entire processMessages pipeline for single-message updates. */\n __handlers?: Record<string, (inst: object, msg: unknown) => [S, E[]]>\n}\n\nexport type Send<M> = (msg: M) => void\n\n/**\n * Type-erased component definition for use at module boundaries where\n * the consumer's `S`, `M`, `E` are internal and invisible to the caller.\n *\n * Why this exists: `ComponentDef<S, M, E, D>` uses property syntax for\n * its callable fields (`init: (data: D) => ...`), and TypeScript\n * checks property syntax with strict (contravariant) variance. That's\n * the right call for user-facing type safety when authoring a\n * component (a narrower `Msg` type can't accidentally satisfy the\n * `update`'s param), but it means `ComponentDef<MyState, MyMsg, MyEffect, MyData>`\n * is NOT structurally assignable to `ComponentDef<unknown, unknown, unknown, unknown>`\n * — the `init: (data: MyData) => ...` field is contravariant in `MyData`,\n * so widening to `unknown` is rejected.\n *\n * `AnyComponentDef` (and `LazyDef<D>` below) declare the same fields\n * using **method syntax** (`init(data: D): ...`), which TypeScript\n * checks bivariantly. Concrete `ComponentDef<S, M, E, D>` assigns into\n * these structurally without any `widenDef` helper at the callsite.\n *\n * Used by every API that accepts an opaque component definition at a\n * module boundary:\n *\n * - `child({ def })`\n * - `lazy({ loader })` — returns `LazyDef<D>` so the loader's `D` survives\n * - `createOnRenderClient({ Layout })` (from `@llui/vike`)\n * - `createOnRenderHtml({ Layout })` (from `@llui/vike`)\n *\n * The `D` parameter is `unknown` here — `child()` doesn't pass init\n * data through, and `Layout` callers pass route-supplied data whose\n * shape is unknown at the type-erased boundary. Use `LazyDef<D>` when\n * you need to preserve the init data shape (the lazy loader case).\n */\nexport interface AnyComponentDef {\n name: string\n init(data: unknown): [unknown, unknown[]]\n update(state: unknown, msg: unknown): [unknown, unknown[]]\n view(h: unknown): Node[]\n onEffect?: unknown\n propsMsg?: unknown\n receives?: unknown\n __dirty?: unknown\n __renderToString?: unknown\n __msgSchema?: unknown\n __msgAnnotations?: unknown\n __bindingDescriptors?: unknown\n __schemaHash?: unknown\n __maskLegend?: unknown\n __componentMeta?: unknown\n __stateSchema?: unknown\n __effectSchema?: unknown\n __update?: unknown\n __handlers?: unknown\n}\n\n/**\n * Type-erased component definition for use at module boundaries where the\n * loaded component's S, M, E are internal and invisible to the caller.\n * Only `D` (init data) survives because the caller provides it.\n *\n * `ComponentDef<S, M, E, D>` is structurally assignable to `LazyDef<D>`\n * for any S, M, E — `view: (h: unknown) => Node[]` accepts any View via\n * contravariance, and all other fields widen to `unknown` return types.\n *\n * Used by `lazy()` as the loader's return type. Use `AnyComponentDef`\n * (above) when D is also opaque — most other adapter-layer APIs.\n */\nexport interface LazyDef<D = void> {\n name: string\n // Method syntax — TypeScript checks methods bivariantly, so\n // ComponentDef<S, M, E, D>'s concrete (state: S, msg: M) => ...\n // assigns here even though S/M ≠ unknown. Property syntax would\n // be contravariant and reject the assignment.\n init(data: D): [unknown, unknown[]]\n update(state: unknown, msg: unknown): [unknown, unknown[]]\n view(h: unknown): Node[]\n onEffect?: unknown\n propsMsg?: unknown\n receives?: unknown\n __dirty?: unknown\n __renderToString?: unknown\n __msgSchema?: unknown\n __msgAnnotations?: unknown\n __bindingDescriptors?: unknown\n __schemaHash?: unknown\n __maskLegend?: unknown\n __componentMeta?: unknown\n __stateSchema?: unknown\n __effectSchema?: unknown\n __update?: unknown\n __handlers?: unknown\n}\n\n/**\n * Maps a value shape to a reactive-props shape: every field becomes an accessor\n * `(s: S) => V`. Use for Level-1 view function signatures.\n *\n * ```ts\n * type ToolbarData = { tools: Tool[]; theme: 'light' | 'dark' }\n *\n * export function toolbar<S>(props: Props<ToolbarData, S>, send: Send<Msg>) {\n * return [div({ class: props.theme }, [each({ items: props.tools, ... })])]\n * }\n *\n * // Caller — TypeScript enforces per-field accessors; passing a raw value errors:\n * toolbar({ tools: (s: State) => s.tools, theme: (s) => s.theme }, send)\n * ```\n */\nexport type Props<T, S> = {\n [K in keyof T]: (s: S) => T[K]\n}\n\n// ── App Handle ────────────────────────────────────────────────────\n\nexport interface AppHandle {\n dispose(): void\n flush(): void\n /**\n * Dispatch a message into the mounted instance from outside its\n * normal view-bound `send` channel. Useful for adapter layers that\n * need to push updates into a long-lived instance — e.g.\n * `@llui/vike`'s persistent-layout chain pushes layout-data updates\n * into surviving layer instances on client navigation when their\n * `propsMsg` translates the new data into a state-update message.\n *\n * Messages are queued through the same path as `view`-side `send`\n * calls — they batch into the next microtask and process via the\n * normal update loop. Calling `send` after `dispose` is a no-op.\n */\n send(msg: unknown): void\n /**\n * Read the current state snapshot. Safe to call from anywhere —\n * event handlers, async callbacks, adapter `send` wrappers, or any\n * imperative context where the render context is not live.\n *\n * Unlike `sample()` (a view primitive that requires an active\n * render context and throws outside of `view()`), `getState` is\n * the sanctioned escape hatch for \"I need to know the current\n * state to decide what to dispatch.\" Typical shape:\n *\n * ```ts\n * const handle = mountApp(root, MyApp)\n * container.addEventListener('drop', () => {\n * const { mode } = handle.getState() as AppState\n * if (mode === 'drag') handle.send({ type: 'commit' })\n * })\n * ```\n *\n * Throws after `dispose()` — stale reads are silent bugs; a thrown\n * error pinpoints the callsite that forgot to detach.\n *\n * The return type is `unknown` because `AppHandle` is state-type\n * erased at this boundary; cast to your app's state type at the\n * call site.\n */\n getState(): unknown\n /**\n * Register a listener called synchronously after every update cycle\n * completes. The listener receives the new state. Returns an\n * unsubscribe function. Safe after dispose (no-op; returns a no-op\n * unsubscribe). See agent spec §10.5 (state-update frames).\n */\n subscribe(listener: (state: unknown) => void): () => void\n /**\n * Snapshot the Msg variants currently dispatchable from rendered UI.\n * Each entry corresponds to one or more event-handler bindings the\n * compiler tagged with literal `send({type: '<variant>'})` calls;\n * the registry tracks live mounts via lifetime refcounts so the\n * snapshot reflects what the user can click *right now*, not the\n * static catalogue of every variant the app could ever accept.\n *\n * Used by `@llui/agent` to populate `list_actions`. Apps that don't\n * use the LLui compiler tagger pass (or whose views contain no\n * literal sends) get an empty array — agents fall back to the Msg\n * schema for affordance discovery.\n */\n getBindingDescriptors(): Array<{ variant: string }>\n /**\n * Hot-swap the component's `update` function (and optionally\n * `onEffect`) without rebuilding the DOM. Pending messages are\n * drained against the OLD `update` first so a half-applied\n * transition can't leave the state ill-formed; subsequent\n * dispatches go through the new function.\n *\n * This is the lightweight HMR escape hatch for cases where the\n * full `replaceComponent` flow (in `@llui/dom/hmr`) is overkill —\n * pure `update.ts` edits don't change the view, so disposing the\n * root lifetime and re-running the view just to install a new\n * function is wasteful and loses focus / scroll / transient DOM\n * state. Wire it via `import.meta.hot`:\n *\n * ```ts\n * // main.ts\n * const handle = mountApp(root, App)\n * if (import.meta.hot) {\n * import.meta.hot.accept('./app/update', (mod) => {\n * if (mod) handle.swapUpdate(mod.update)\n * })\n * }\n * ```\n *\n * Caveats:\n * - Doesn't compensate for State shape changes — if the new\n * `update` reads a field the old state doesn't have, you'll\n * see runtime errors. Hard-reload after structural state\n * changes.\n * - Doesn't swap `view` or compiler-emitted `__update`/`__dirty`\n * — for those, prefer the auto-injected component-level HMR\n * accept callback (which routes through `replaceComponent`).\n */\n swapUpdate(\n newUpdate: (state: unknown, msg: unknown) => [unknown, unknown[]],\n newOnEffect?: unknown,\n ): void\n /**\n * Run the reducer in isolation against the current state. Returns\n * `[newState, effects]` — the reducer's literal output without any\n * commit, flush, or effect-handler invocation. Used by the agent's\n * `would_dispatch` tool to predict what a candidate dispatch would\n * do before committing to it.\n *\n * Pure-reducer assumption: TEA mandates `update` be pure. LLui's\n * runtime treats it that way too (no speculative re-runs for any\n * other reason). Apps whose reducers branch on `Date.now()` or\n * read `localStorage` will see prediction drift from real dispatch\n * by exactly that amount of impurity — surface as documented at\n * the agent's call site, not silently corrected here.\n *\n * **No effects fire.** Effect descriptors are returned for the\n * agent to inspect; `onEffect` is not called. This is the entire\n * point of the API.\n */\n runReducer(msg: unknown): { state: unknown; effects: unknown[] } | null\n /**\n * Install a hook called whenever a binding's accessor throws during\n * the update cycle. The runtime catches the throw, leaves the\n * binding's `lastValue` unchanged (DOM stays at its previous value\n * rather than blanking), continues with sibling bindings, and\n * notifies this hook with `{kind, key?, message, stack?}`.\n *\n * Used by `@llui/agent` to fold binding-evaluation throws into the\n * dispatch envelope's `drain.errors` — so an LLM dispatching a Msg\n * that breaks scoring doesn't get HTTP 500 + a frozen UI; instead\n * the dispatch reports as `{status: 'dispatched', drain: {errors:\n * [...]}}` with the rest of the page rendering normally.\n *\n * Pass `null` to remove the hook. Without a hook, throws fall back\n * to `console.error` (dev mode) or `console.warn` (prod) so they\n * aren't silently swallowed.\n */\n setOnBindingError(\n hook: ((info: { kind: string; key?: string; message: string; stack?: string }) => void) | null,\n ): void\n}\n\n// ── Lifetime ─────────────────────────────────────────────────────────\n\nexport interface Lifetime {\n id: number\n parent: Lifetime | null\n children: Lifetime[]\n disposers: Array<() => void>\n bindings: Binding[]\n /** Per-item updaters — called directly by each() when item changes, bypassing Phase 2 */\n itemUpdaters: Array<() => void>\n /**\n * @internal dev-only back-reference to the owning ComponentInstance.\n * Populated on the root scope by `installDevTools` so `disposeLifetime`\n * can walk up the scope chain and emit DisposerEvents into the\n * instance's `_disposerLog`. Undefined in production.\n */\n instance?: ComponentInstance\n /**\n * @internal dev-only cause hint. Structural primitives (branch, each,\n * child, mountApp teardown) set this field immediately before calling\n * `disposeLifetime`; the dispose path reads it once to stamp the emitted\n * DisposerEvent. Left undefined, `disposeLifetime` falls back to\n * `'component-unmount'`. Undefined in production.\n */\n disposalCause?: DisposerEvent['cause']\n /** @internal dev-only — populated by structural primitives for scope-tree classification */\n _kind?: 'root' | 'show' | 'each' | 'branch' | 'scope' | 'child' | 'portal' | 'foreign'\n}\n\nexport interface LifetimeNode {\n scopeId: string\n kind: 'root' | 'show' | 'each' | 'branch' | 'scope' | 'child' | 'portal' | 'foreign'\n active: boolean\n children: LifetimeNode[]\n}\n\n// ── Binding ───────────────────────────────────────────────────────\n\n/**\n * Binding output kinds.\n *\n * `'text' | 'prop' | 'attr' | 'class' | 'style'` write their accessor's\n * return value to the DOM. `'effect'` is a side-effect-only watcher:\n * the accessor is invoked every Phase 2 tick its mask is hit, but its\n * return value is discarded and `applyBinding` is a no-op. Used by\n * `child()` to fire the prop-diff/propsMsg cascade on parent updates\n * without the cost of stringifying the returned props bag onto a\n * detached anchor node every render.\n */\nexport type BindingKind = 'text' | 'prop' | 'attr' | 'class' | 'style' | 'effect'\n\nexport interface Binding {\n mask: number\n accessor: (state: unknown) => unknown\n lastValue: unknown\n kind: BindingKind\n node: Node\n key?: string\n ownerLifetime: Lifetime\n perItem: boolean\n dead: boolean\n}\n\n// ── Structural Primitives ─────────────────────────────────────────\n\nexport interface TransitionOptions {\n enter?: (nodes: Node[]) => void | Promise<void>\n leave?: (nodes: Node[]) => void | Promise<void>\n onTransition?: (ctx: { entering: Node[]; leaving: Node[]; parent: Node }) => void | Promise<void>\n}\n\ninterface BranchOptionsBase extends TransitionOptions {\n /**\n * @internal Set by `show()` / `scope()` sugar when delegating to\n * `branch()`, so the dev-only disposer log can report `'show-hide'` /\n * `'scope-rebuild'` instead of the default `'branch-swap'` for the\n * leaving arm. User code should not set this directly.\n */\n __disposalCause?: DisposerEvent['cause']\n /** @internal Compiler-injected mask of paths read by `on`. */\n __mask?: number\n}\n\n/**\n * Discriminant accessor — reads a string-keyed value either from the\n * current state (`(s) => …`) or from a closed-over item accessor /\n * memo (`() => item.kind()`). Both forms re-evaluate on every commit.\n * The zero-arg form is the canonical shape for branching on per-row\n * fields inside an `each.render` callback.\n */\ntype Discriminant<S, K extends string> = ((s: S) => K) | (() => K)\n\n/**\n * All cases covered by `cases` — no default allowed (would be dead code).\n */\ntype BranchOptionsExhaustive<S, M, K extends string> = BranchOptionsBase & {\n on: Discriminant<S, K>\n cases: { [P in K]: (h: View<S, M>) => Node[] }\n default?: never\n}\n\n/**\n * `cases` may cover some but not all keys; `default` handles the rest.\n */\ntype BranchOptionsNonExhaustive<S, M, K extends string> = BranchOptionsBase & {\n on: Discriminant<S, K>\n cases?: { [P in K]?: (h: View<S, M>) => Node[] }\n default: (h: View<S, M>) => Node[]\n}\n\n/**\n * `on` returns a wide `string` — exhaustiveness cannot be verified at\n * compile time (the key domain is infinite). Lenient: `default` is\n * optional so existing call sites that predate exhaustiveness typing\n * continue to compile. Authors who want the gate opt in by narrowing\n * `on`'s return type to a literal union.\n */\ntype BranchOptionsWide<S, M> = BranchOptionsBase & {\n on: Discriminant<S, string>\n cases?: Record<string, (h: View<S, M>) => Node[]>\n default?: (h: View<S, M>) => Node[]\n}\n\n/**\n * Options for `branch()`.\n *\n * When `on` returns a literal string union (e.g. `'idle' | 'loading'\n * | 'done'`), TypeScript enforces exhaustiveness: either every union\n * member has a case (and `default` is disallowed as unreachable), or\n * `default` is required to cover the remainder. When `on` returns a\n * wide `string`, `default` stays optional — exhaustiveness isn't\n * meaningful for an unbounded domain.\n *\n * `cases` is optional when `default` is present; `branch({ on, default })`\n * is the canonical dynamic-rebuild shape — `scope()` sugar wraps\n * exactly this form.\n */\nexport type BranchOptions<S, M = unknown, K extends string = string> = string extends K\n ? BranchOptionsWide<S, M>\n : BranchOptionsExhaustive<S, M, K> | BranchOptionsNonExhaustive<S, M, K>\n\nexport interface ShowOptions<S, M = unknown> extends TransitionOptions {\n /**\n * Predicate evaluated on every commit. `(s) => …` reads from\n * component state; `() => …` (zero-arg) reads from a closed-over\n * source — typically `item.field()` inside an `each.render`\n * callback. Both forms are reactive: the predicate runs against\n * each new state and the rendered subtree mounts/unmounts on the\n * boolean transition.\n */\n when: ((s: S) => boolean) | (() => boolean)\n render: (h: View<S, M>) => Node[]\n fallback?: (h: View<S, M>) => Node[]\n}\n\n/**\n * Options for `scope()` — rebuilds a subtree when `on(state)` changes.\n *\n * Sugar over `branch({ on, cases: {}, default: render, __disposalCause: 'scope-rebuild' })`.\n * Use when the key is dynamic (e.g. an epoch counter bumped from the\n * outside) and you want a fresh arm — fresh lifetime, fresh bindings —\n * each time it changes. Combine with `sample()` inside `render` for a\n * one-shot current-state read.\n */\nexport interface ScopeOptions<S, M = unknown> extends TransitionOptions {\n /**\n * Key accessor evaluated on every commit. `(s) => …` reads from\n * component state; `() => …` (zero-arg) reads from a closed-over\n * source. The render callback rebuilds whenever the returned key\n * changes — fresh lifetime, fresh bindings.\n */\n on: ((s: S) => string) | (() => string)\n render: (h: View<S, M>) => Node[]\n /** @internal Compiler-injected mask of paths read by `on`. */\n __mask?: number\n}\n\n/**\n * Options for `each()`. The inherited `enter` / `leave` callbacks fire **per item**:\n * `enter(nodes)` runs after an item's DOM is inserted (including initial mount);\n * `leave(nodes)` runs before an item's DOM is removed and may return a Promise\n * to hold the DOM until the animation resolves. Setting `leave` disables the\n * bulk-clear / full-replace fast paths.\n */\n/**\n * Per-item accessor. Two access forms:\n * - `item.field` — shorthand, returns accessor for `item.current[field]`\n * - `item(t => t.expr)` — computed expressions\n *\n * In both cases the returned value is a `() => V` accessor.\n * Invoke it (`item.field()`) to read the current value imperatively.\n */\nexport type ItemAccessor<T> = {\n <R>(selector: (t: T) => R): () => R\n} & {\n [K in keyof T]-?: () => T[K]\n} & {\n /**\n * Read the whole current item. Needed when T is a primitive (where the\n * field-map branch collapses to method names like `toString`) or when\n * you want to sample the entire record rather than a single field.\n *\n * Shadows any literal `current` field on T — if T has such a field,\n * use `item(r => r.current)` to disambiguate.\n */\n current(): T\n}\n\nexport interface EachOptions<S, T, M = unknown> extends TransitionOptions {\n items: (s: S) => T[]\n key: (item: T) => string | number\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n /**\n * Plain (non-Proxy) accessor factory. Compiler-output path; avoid in user code\n * (use `item.field` / `item(fn)` — more ergonomic and bypasses Proxy when compiled).\n */\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n /**\n * The component's View bag (`h.text`, `h.show`, `h.branch`,\n * `h.scope`, `h.sample`, …). Each-render callers used to reach\n * for the top-level imports; the bag form is symmetric with how\n * `branch.cases[k]`, `show.render`, and `scope.render` receive it.\n * Both still work — destructure whichever is more convenient.\n */\n h: View<S, M>\n /** @internal Compiler-injected — entry reference for row factory */\n entry?: Record<string, unknown>\n }) => Node[]\n}\n\nexport interface PortalOptions {\n target: HTMLElement | string\n render: () => Node[]\n}\n\nexport interface ForeignOptions<S, M, T extends Record<string, unknown>, Instance> {\n /**\n * Construct the imperative instance. Can be async — return a\n * `Promise<Instance>` to defer construction until e.g. a dynamic\n * `import()` resolves. While the promise is pending:\n *\n * - The container element is in the DOM immediately (so layout\n * doesn't shift when the instance arrives).\n * - `sync` is NOT called. State changes are tracked by the\n * primitive and the latest props are applied as the initial\n * sync right after the promise resolves.\n * - If the owning scope disposes before the promise resolves,\n * `destroy(instance)` runs on resolution — no matter how long\n * the promise takes.\n */\n mount: (ctx: { container: HTMLElement; send: Send<M> }) => Instance | Promise<Instance>\n props: (s: S) => T\n sync:\n | ((ctx: { instance: Instance; props: T; prev: T | undefined }) => void)\n | {\n [K in keyof T]?: (ctx: { instance: Instance; value: T[K]; prev: T[K] | undefined }) => void\n }\n destroy: (instance: Instance) => void\n container?: { tag?: string; attrs?: Record<string, string> }\n}\n\nexport interface ChildOptions<S, ChildM> {\n // Type-erased via AnyComponentDef so callers can pass any concrete\n // ComponentDef<S, M, E, D> without a widening helper. The runtime\n // narrows the message shape via the user-supplied `onMsg` callback,\n // which keeps `ChildM` typed at this boundary even though the def\n // itself is opaque.\n def: AnyComponentDef\n key: string | number\n props: (s: S) => Record<string, unknown>\n onMsg?: (msg: ChildM) => unknown | null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qEAAqE","sourcesContent":["// ── Component Definition ──────────────────────────────────────────\n\nimport type { View } from './view-helpers.js'\nimport type { StructuralBlock } from './structural.js'\nimport type { ComponentInstance } from './update-loop.js'\nimport type { DisposerEvent } from './tracking/disposer-log.js'\n\nexport interface ComponentDef<S, M, E = never, D = void> {\n name: string\n init: (data: D) => [S, E[]]\n update: (state: S, msg: M) => [S, E[]]\n view: (h: View<S, M>) => Node[]\n onEffect?: (ctx: { effect: E; send: Send<M>; signal: AbortSignal }) => void\n\n /**\n * @internal Compiler-injected — **path-keyed reactivity** prefix table.\n *\n * Each entry is a stable closure `(s: S) => unknown` that the compiler\n * hoists at module scope, one per distinct *minimal reference-stable\n * prefix* read across this component's accessors. The position of\n * each entry in this array IS its bit position: positions 0..30 map\n * to the low-word `mask`, positions 31..61 map to the high-word\n * `maskHi`. The runtime computes `combinedDirty` (and\n * `combinedDirtyHi` for >31-prefix components) by reference-comparing\n * `prefix(prev)` vs `prefix(next)` for each entry.\n *\n * Hand-authoring this is supported but unusual — `@llui/vite-plugin`\n * emits it automatically from accessor analysis. The previous\n * `__dirty` bitmask emission was removed; user-supplied `__dirty`\n * functions throw at mount.\n */\n __prefixes?: ReadonlyArray<(state: S) => unknown>\n /** @internal Compiler-injected */\n __renderToString?: (state: S) => string\n /** @internal Compiler-injected */\n __msgSchema?: object\n /** @internal Compiler-injected; keyed by Msg discriminant → annotations. See agent spec §5.1. */\n __msgAnnotations?: Record<\n string,\n {\n intent: string | null\n alwaysAffordable: boolean\n requiresConfirm: boolean\n humanOnly: boolean\n }\n >\n /** @internal Compiler-emitted; one entry per send() call site in view(). See agent spec §5.2. */\n __bindingDescriptors?: Array<{ variant: string }>\n /** @internal Compiler-injected; 32-char hex SHA-256 of schemas + annotations. See agent spec §12.3. */\n __schemaHash?: string\n /** @internal Compiler-injected — maps top-level state field → dirty-mask bit(s) */\n __maskLegend?: Record<string, number>\n /** @internal Compiler-injected — source-file location of the component() call */\n __componentMeta?: { file: string; line: number }\n /** @internal Compiler-injected — shape of the State type (for introspection) */\n __stateSchema?: object\n /** @internal Compiler-injected — Effect union schema (for introspection) */\n __effectSchema?: object\n /**\n * @internal Compiler-injected — replaces generic Phase 1 + Phase 2 loop.\n *\n * The trailing `dirtyHi` is the high-word dirty mask used by\n * 32..61-prefix components. Appended (not inserted) so old compiled\n * bundles emitted with a 5-param signature remain callable — the\n * runtime always passes 6 args; old bundles ignore the extra one\n * and ≤31-prefix components have `dirtyHi === 0` anyway.\n */\n __update?: (\n state: S,\n dirty: number,\n bindings: Binding[],\n blocks: StructuralBlock[],\n bindingsBeforePhase1: number,\n dirtyHi?: number,\n ) => void\n /** @internal Compiler-injected — per-message-type specialized handlers.\n * Bypass the entire processMessages pipeline for single-message updates. */\n __handlers?: Record<string, (inst: object, msg: unknown) => [S, E[]]>\n}\n\nexport type Send<M> = (msg: M) => void\n\n/**\n * Type-erased component definition for use at module boundaries where\n * the consumer's `S`, `M`, `E` are internal and invisible to the caller.\n *\n * Why this exists: `ComponentDef<S, M, E, D>` uses property syntax for\n * its callable fields (`init: (data: D) => ...`), and TypeScript\n * checks property syntax with strict (contravariant) variance. That's\n * the right call for user-facing type safety when authoring a\n * component (a narrower `Msg` type can't accidentally satisfy the\n * `update`'s param), but it means `ComponentDef<MyState, MyMsg, MyEffect, MyData>`\n * is NOT structurally assignable to `ComponentDef<unknown, unknown, unknown, unknown>`\n * — the `init: (data: MyData) => ...` field is contravariant in `MyData`,\n * so widening to `unknown` is rejected.\n *\n * `AnyComponentDef` (and `LazyDef<D>` below) declare the same fields\n * using **method syntax** (`init(data: D): ...`), which TypeScript\n * checks bivariantly. Concrete `ComponentDef<S, M, E, D>` assigns into\n * these structurally without any `widenDef` helper at the callsite.\n *\n * Used by every API that accepts an opaque component definition at a\n * module boundary:\n *\n * - `child({ def })`\n * - `lazy({ loader })` — returns `LazyDef<D>` so the loader's `D` survives\n * - `createOnRenderClient({ Layout })` (from `@llui/vike`)\n * - `createOnRenderHtml({ Layout })` (from `@llui/vike`)\n *\n * The `D` parameter is `unknown` here — `child()` doesn't pass init\n * data through, and `Layout` callers pass route-supplied data whose\n * shape is unknown at the type-erased boundary. Use `LazyDef<D>` when\n * you need to preserve the init data shape (the lazy loader case).\n */\nexport interface AnyComponentDef {\n name: string\n init(data: unknown): [unknown, unknown[]]\n update(state: unknown, msg: unknown): [unknown, unknown[]]\n view(h: unknown): Node[]\n onEffect?: unknown\n __dirty?: unknown\n __prefixes?: unknown\n __renderToString?: unknown\n __msgSchema?: unknown\n __msgAnnotations?: unknown\n __bindingDescriptors?: unknown\n __schemaHash?: unknown\n __maskLegend?: unknown\n __componentMeta?: unknown\n __stateSchema?: unknown\n __effectSchema?: unknown\n __update?: unknown\n __handlers?: unknown\n}\n\n/**\n * Type-erased component definition for use at module boundaries where the\n * loaded component's S, M, E are internal and invisible to the caller.\n * Only `D` (init data) survives because the caller provides it.\n *\n * `ComponentDef<S, M, E, D>` is structurally assignable to `LazyDef<D>`\n * for any S, M, E — `view: (h: unknown) => Node[]` accepts any View via\n * contravariance, and all other fields widen to `unknown` return types.\n *\n * Used by `lazy()` as the loader's return type. Use `AnyComponentDef`\n * (above) when D is also opaque — most other adapter-layer APIs.\n */\nexport interface LazyDef<D = void> {\n name: string\n // Method syntax — TypeScript checks methods bivariantly, so\n // ComponentDef<S, M, E, D>'s concrete (state: S, msg: M) => ...\n // assigns here even though S/M ≠ unknown. Property syntax would\n // be contravariant and reject the assignment.\n init(data: D): [unknown, unknown[]]\n update(state: unknown, msg: unknown): [unknown, unknown[]]\n view(h: unknown): Node[]\n onEffect?: unknown\n __dirty?: unknown\n __prefixes?: unknown\n __renderToString?: unknown\n __msgSchema?: unknown\n __msgAnnotations?: unknown\n __bindingDescriptors?: unknown\n __schemaHash?: unknown\n __maskLegend?: unknown\n __componentMeta?: unknown\n __stateSchema?: unknown\n __effectSchema?: unknown\n __update?: unknown\n __handlers?: unknown\n}\n\n/**\n * Maps a value shape to a reactive-props shape: every field becomes an accessor\n * `(s: S) => V`. Use for Level-1 view function signatures.\n *\n * ```ts\n * type ToolbarData = { tools: Tool[]; theme: 'light' | 'dark' }\n *\n * export function toolbar<S>(props: Props<ToolbarData, S>, send: Send<Msg>) {\n * return [div({ class: props.theme }, [each({ items: props.tools, ... })])]\n * }\n *\n * // Caller — TypeScript enforces per-field accessors; passing a raw value errors:\n * toolbar({ tools: (s: State) => s.tools, theme: (s) => s.theme }, send)\n * ```\n */\nexport type Props<T, S> = {\n [K in keyof T]: (s: S) => T[K]\n}\n\n// ── App Handle ────────────────────────────────────────────────────\n\nexport interface AppHandle {\n dispose(): void\n flush(): void\n /**\n * Dispatch a message into the mounted instance from outside its\n * normal view-bound `send` channel. Useful for adapter layers that\n * need to push updates into a long-lived instance — e.g.\n * `@llui/vike`'s persistent-layout chain pushes layout-data updates\n * into surviving layer instances on client navigation through the\n * user-supplied `onLayerDataChange` callback, which then calls\n * `handle.send(msg)` with the layout's chosen state-update message.\n *\n * Messages are queued through the same path as `view`-side `send`\n * calls — they batch into the next microtask and process via the\n * normal update loop. Calling `send` after `dispose` is a no-op.\n */\n send(msg: unknown): void\n /**\n * Read the current state snapshot. Safe to call from anywhere —\n * event handlers, async callbacks, adapter `send` wrappers, or any\n * imperative context where the render context is not live.\n *\n * Unlike `sample()` (a view primitive that requires an active\n * render context and throws outside of `view()`), `getState` is\n * the sanctioned escape hatch for \"I need to know the current\n * state to decide what to dispatch.\" Typical shape:\n *\n * ```ts\n * const handle = mountApp(root, MyApp)\n * container.addEventListener('drop', () => {\n * const { mode } = handle.getState() as AppState\n * if (mode === 'drag') handle.send({ type: 'commit' })\n * })\n * ```\n *\n * Throws after `dispose()` — stale reads are silent bugs; a thrown\n * error pinpoints the callsite that forgot to detach.\n *\n * The return type is `unknown` because `AppHandle` is state-type\n * erased at this boundary; cast to your app's state type at the\n * call site.\n */\n getState(): unknown\n /**\n * Register a listener called synchronously after every update cycle\n * completes. The listener receives the new state. Returns an\n * unsubscribe function. Safe after dispose (no-op; returns a no-op\n * unsubscribe). See agent spec §10.5 (state-update frames).\n */\n subscribe(listener: (state: unknown) => void): () => void\n /**\n * Snapshot the Msg variants currently dispatchable from rendered UI.\n * Each entry corresponds to one or more event-handler bindings the\n * compiler tagged with literal `send({type: '<variant>'})` calls;\n * the registry tracks live mounts via lifetime refcounts so the\n * snapshot reflects what the user can click *right now*, not the\n * static catalogue of every variant the app could ever accept.\n *\n * Used by `@llui/agent` to populate `list_actions`. Apps that don't\n * use the LLui compiler tagger pass (or whose views contain no\n * literal sends) get an empty array — agents fall back to the Msg\n * schema for affordance discovery.\n */\n getBindingDescriptors(): Array<{ variant: string }>\n /**\n * Hot-swap the component's `update` function (and optionally\n * `onEffect`) without rebuilding the DOM. Pending messages are\n * drained against the OLD `update` first so a half-applied\n * transition can't leave the state ill-formed; subsequent\n * dispatches go through the new function.\n *\n * This is the lightweight HMR escape hatch for cases where the\n * full `replaceComponent` flow (in `@llui/dom/hmr`) is overkill —\n * pure `update.ts` edits don't change the view, so disposing the\n * root lifetime and re-running the view just to install a new\n * function is wasteful and loses focus / scroll / transient DOM\n * state. Wire it via `import.meta.hot`:\n *\n * ```ts\n * // main.ts\n * const handle = mountApp(root, App)\n * if (import.meta.hot) {\n * import.meta.hot.accept('./app/update', (mod) => {\n * if (mod) handle.swapUpdate(mod.update)\n * })\n * }\n * ```\n *\n * Caveats:\n * - Doesn't compensate for State shape changes — if the new\n * `update` reads a field the old state doesn't have, you'll\n * see runtime errors. Hard-reload after structural state\n * changes.\n * - Doesn't swap `view` or compiler-emitted `__update`/`__dirty`\n * — for those, prefer the auto-injected component-level HMR\n * accept callback (which routes through `replaceComponent`).\n */\n swapUpdate(\n newUpdate: (state: unknown, msg: unknown) => [unknown, unknown[]],\n newOnEffect?: unknown,\n ): void\n /**\n * Run the reducer in isolation against the current state. Returns\n * `[newState, effects]` — the reducer's literal output without any\n * commit, flush, or effect-handler invocation. Used by the agent's\n * `would_dispatch` tool to predict what a candidate dispatch would\n * do before committing to it.\n *\n * Pure-reducer assumption: TEA mandates `update` be pure. LLui's\n * runtime treats it that way too (no speculative re-runs for any\n * other reason). Apps whose reducers branch on `Date.now()` or\n * read `localStorage` will see prediction drift from real dispatch\n * by exactly that amount of impurity — surface as documented at\n * the agent's call site, not silently corrected here.\n *\n * **No effects fire.** Effect descriptors are returned for the\n * agent to inspect; `onEffect` is not called. This is the entire\n * point of the API.\n */\n runReducer(msg: unknown): { state: unknown; effects: unknown[] } | null\n /**\n * Install a hook called whenever a binding's accessor throws during\n * the update cycle. The runtime catches the throw, leaves the\n * binding's `lastValue` unchanged (DOM stays at its previous value\n * rather than blanking), continues with sibling bindings, and\n * notifies this hook with `{kind, key?, message, stack?}`.\n *\n * Used by `@llui/agent` to fold binding-evaluation throws into the\n * dispatch envelope's `drain.errors` — so an LLM dispatching a Msg\n * that breaks scoring doesn't get HTTP 500 + a frozen UI; instead\n * the dispatch reports as `{status: 'dispatched', drain: {errors:\n * [...]}}` with the rest of the page rendering normally.\n *\n * Pass `null` to remove the hook. Without a hook, throws fall back\n * to `console.error` (dev mode) or `console.warn` (prod) so they\n * aren't silently swallowed.\n */\n setOnBindingError(\n hook: ((info: { kind: string; key?: string; message: string; stack?: string }) => void) | null,\n ): void\n}\n\n// ── Lifetime ─────────────────────────────────────────────────────────\n\nexport interface Lifetime {\n id: number\n parent: Lifetime | null\n children: Lifetime[]\n disposers: Array<() => void>\n bindings: Binding[]\n /** Per-item updaters — called directly by each() when item changes, bypassing Phase 2 */\n itemUpdaters: Array<() => void>\n /**\n * @internal dev-only back-reference to the owning ComponentInstance.\n * Populated on the root scope by `installDevTools` so `disposeLifetime`\n * can walk up the scope chain and emit DisposerEvents into the\n * instance's `_disposerLog`. Undefined in production.\n */\n instance?: ComponentInstance\n /**\n * @internal dev-only cause hint. Structural primitives (branch, each,\n * child, mountApp teardown) set this field immediately before calling\n * `disposeLifetime`; the dispose path reads it once to stamp the emitted\n * DisposerEvent. Left undefined, `disposeLifetime` falls back to\n * `'component-unmount'`. Undefined in production.\n */\n disposalCause?: DisposerEvent['cause']\n /** @internal dev-only — populated by structural primitives for scope-tree classification */\n _kind?: 'root' | 'show' | 'each' | 'branch' | 'scope' | 'child' | 'portal' | 'foreign'\n}\n\nexport interface LifetimeNode {\n scopeId: string\n kind: 'root' | 'show' | 'each' | 'branch' | 'scope' | 'child' | 'portal' | 'foreign'\n active: boolean\n children: LifetimeNode[]\n}\n\n// ── Binding ───────────────────────────────────────────────────────\n\n/**\n * Binding output kinds.\n *\n * `'text' | 'prop' | 'attr' | 'class' | 'style'` write their accessor's\n * return value to the DOM. `'effect'` is a side-effect-only watcher:\n * the accessor is invoked every Phase 2 tick its mask is hit, but its\n * return value is discarded and `applyBinding` is a no-op. Reserved\n * for primitives that need to fire a side-effect on each binding\n * evaluation without writing to the DOM.\n */\nexport type BindingKind = 'text' | 'prop' | 'attr' | 'class' | 'style' | 'effect'\n\nexport interface Binding {\n /**\n * Bits 0..30 of the binding's dirty mask. For components with ≤31\n * reactive prefixes this is the entire mask; for components with\n * 32..61 prefixes, the bits beyond 30 live in {@link maskHi}.\n */\n mask: number\n /**\n * Bits 31..61 of the binding's dirty mask, encoded with `1 <<\n * (pos - 31)`. Zero for ≤31-path components — the runtime gates\n * still AND against it, but `0 & dirtyHi` is always 0 so the gate\n * collapses to the single-word check. Compiler-emitted only when\n * the component actually reads paths past bit 30.\n */\n maskHi: number\n accessor: (state: unknown) => unknown\n lastValue: unknown\n kind: BindingKind\n node: Node\n key?: string\n ownerLifetime: Lifetime\n perItem: boolean\n dead: boolean\n}\n\n// ── Structural Primitives ─────────────────────────────────────────\n\nexport interface TransitionOptions {\n enter?: (nodes: Node[]) => void | Promise<void>\n leave?: (nodes: Node[]) => void | Promise<void>\n onTransition?: (ctx: { entering: Node[]; leaving: Node[]; parent: Node }) => void | Promise<void>\n}\n\ninterface BranchOptionsBase extends TransitionOptions {\n /**\n * @internal Set by `show()` / `scope()` sugar when delegating to\n * `branch()`, so the dev-only disposer log can report `'show-hide'` /\n * `'scope-rebuild'` instead of the default `'branch-swap'` for the\n * leaving arm. User code should not set this directly.\n */\n __disposalCause?: DisposerEvent['cause']\n /** @internal Compiler-injected mask of paths read by `on`. */\n __mask?: number\n}\n\n/**\n * Discriminant accessor — reads a string-keyed value either from the\n * current state (`(s) => …`) or from a closed-over item accessor /\n * memo (`() => item.kind()`). Both forms re-evaluate on every commit.\n * The zero-arg form is the canonical shape for branching on per-row\n * fields inside an `each.render` callback.\n */\ntype Discriminant<S, K extends string> = ((s: S) => K) | (() => K)\n\n/**\n * All cases covered by `cases` — no default allowed (would be dead code).\n */\ntype BranchOptionsExhaustive<S, M, K extends string> = BranchOptionsBase & {\n on: Discriminant<S, K>\n cases: { [P in K]: (h: View<S, M>) => Node[] }\n default?: never\n}\n\n/**\n * `cases` may cover some but not all keys; `default` handles the rest.\n */\ntype BranchOptionsNonExhaustive<S, M, K extends string> = BranchOptionsBase & {\n on: Discriminant<S, K>\n cases?: { [P in K]?: (h: View<S, M>) => Node[] }\n default: (h: View<S, M>) => Node[]\n}\n\n/**\n * `on` returns a wide `string` — exhaustiveness cannot be verified at\n * compile time (the key domain is infinite). Lenient: `default` is\n * optional so existing call sites that predate exhaustiveness typing\n * continue to compile. Authors who want the gate opt in by narrowing\n * `on`'s return type to a literal union.\n */\ntype BranchOptionsWide<S, M> = BranchOptionsBase & {\n on: Discriminant<S, string>\n cases?: Record<string, (h: View<S, M>) => Node[]>\n default?: (h: View<S, M>) => Node[]\n}\n\n/**\n * Options for `branch()`.\n *\n * When `on` returns a literal string union (e.g. `'idle' | 'loading'\n * | 'done'`), TypeScript enforces exhaustiveness: either every union\n * member has a case (and `default` is disallowed as unreachable), or\n * `default` is required to cover the remainder. When `on` returns a\n * wide `string`, `default` stays optional — exhaustiveness isn't\n * meaningful for an unbounded domain.\n *\n * `cases` is optional when `default` is present; `branch({ on, default })`\n * is the canonical dynamic-rebuild shape — `scope()` sugar wraps\n * exactly this form.\n */\nexport type BranchOptions<S, M = unknown, K extends string = string> = string extends K\n ? BranchOptionsWide<S, M>\n : BranchOptionsExhaustive<S, M, K> | BranchOptionsNonExhaustive<S, M, K>\n\nexport interface ShowOptions<S, M = unknown> extends TransitionOptions {\n /**\n * Predicate evaluated on every commit. `(s) => …` reads from\n * component state; `() => …` (zero-arg) reads from a closed-over\n * source — typically `item.field()` inside an `each.render`\n * callback. Both forms are reactive: the predicate runs against\n * each new state and the rendered subtree mounts/unmounts on the\n * boolean transition.\n */\n when: ((s: S) => boolean) | (() => boolean)\n render: (h: View<S, M>) => Node[]\n fallback?: (h: View<S, M>) => Node[]\n}\n\n/**\n * Options for `scope()` — rebuilds a subtree when `on(state)` changes.\n *\n * Sugar over `branch({ on, cases: {}, default: render, __disposalCause: 'scope-rebuild' })`.\n * Use when the key is dynamic (e.g. an epoch counter bumped from the\n * outside) and you want a fresh arm — fresh lifetime, fresh bindings —\n * each time it changes. Combine with `sample()` inside `render` for a\n * one-shot current-state read.\n */\nexport interface ScopeOptions<S, M = unknown> extends TransitionOptions {\n /**\n * Key accessor evaluated on every commit. `(s) => …` reads from\n * component state; `() => …` (zero-arg) reads from a closed-over\n * source. The render callback rebuilds whenever the returned key\n * changes — fresh lifetime, fresh bindings.\n */\n on: ((s: S) => string) | (() => string)\n render: (h: View<S, M>) => Node[]\n /** @internal Compiler-injected mask of paths read by `on`. */\n __mask?: number\n}\n\n/**\n * Options for `each()`. The inherited `enter` / `leave` callbacks fire **per item**:\n * `enter(nodes)` runs after an item's DOM is inserted (including initial mount);\n * `leave(nodes)` runs before an item's DOM is removed and may return a Promise\n * to hold the DOM until the animation resolves. Setting `leave` disables the\n * bulk-clear / full-replace fast paths.\n */\n/**\n * Per-item accessor. Two access forms:\n * - `item.field` — shorthand, returns accessor for `item.current[field]`\n * - `item(t => t.expr)` — computed expressions\n *\n * In both cases the returned value is a `() => V` accessor.\n * Invoke it (`item.field()`) to read the current value imperatively.\n */\nexport type ItemAccessor<T> = {\n <R>(selector: (t: T) => R): () => R\n} & {\n [K in keyof T]-?: () => T[K]\n} & {\n /**\n * Read the whole current item. Needed when T is a primitive (where the\n * field-map branch collapses to method names like `toString`) or when\n * you want to sample the entire record rather than a single field.\n *\n * Shadows any literal `current` field on T — if T has such a field,\n * use `item(r => r.current)` to disambiguate.\n */\n current(): T\n}\n\nexport interface EachOptions<S, T, M = unknown> extends TransitionOptions {\n items: (s: S) => T[]\n key: (item: T) => string | number\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n /**\n * Plain (non-Proxy) accessor factory. Compiler-output path; avoid in user code\n * (use `item.field` / `item(fn)` — more ergonomic and bypasses Proxy when compiled).\n */\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n /**\n * The component's View bag (`h.text`, `h.show`, `h.branch`,\n * `h.scope`, `h.sample`, …). Each-render callers used to reach\n * for the top-level imports; the bag form is symmetric with how\n * `branch.cases[k]`, `show.render`, and `scope.render` receive it.\n * Both still work — destructure whichever is more convenient.\n */\n h: View<S, M>\n /** @internal Compiler-injected — entry reference for row factory */\n entry?: Record<string, unknown>\n }) => Node[]\n}\n\nexport interface PortalOptions {\n target: HTMLElement | string\n render: () => Node[]\n}\n\nexport interface ForeignOptions<S, M, T extends Record<string, unknown>, Instance> {\n /**\n * Construct the imperative instance. Can be async — return a\n * `Promise<Instance>` to defer construction until e.g. a dynamic\n * `import()` resolves. While the promise is pending:\n *\n * - The container element is in the DOM immediately (so layout\n * doesn't shift when the instance arrives).\n * - `sync` is NOT called. State changes are tracked by the\n * primitive and the latest props are applied as the initial\n * sync right after the promise resolves.\n * - If the owning scope disposes before the promise resolves,\n * `destroy(instance)` runs on resolution — no matter how long\n * the promise takes.\n */\n mount: (ctx: { container: HTMLElement; send: Send<M> }) => Instance | Promise<Instance>\n props: (s: S) => T\n sync:\n | ((ctx: { instance: Instance; props: T; prev: T | undefined }) => void)\n | {\n [K in keyof T]?: (ctx: { instance: Instance; value: T[K]; prev: T[K] | undefined }) => void\n }\n destroy: (instance: Instance) => void\n container?: { tag?: string; attrs?: Record<string, string> }\n}\n\nexport interface ChildOptions<S, ChildM> {\n // Type-erased via AnyComponentDef so callers can pass any concrete\n // ComponentDef<S, M, E, D> without a widening helper. The runtime\n // narrows the message shape via the user-supplied `onMsg` callback,\n // which keeps `ChildM` typed at this boundary even though the def\n // itself is opaque.\n def: AnyComponentDef\n key: string | number\n props: (s: S) => Record<string, unknown>\n onMsg?: (msg: ChildM) => unknown | null\n}\n"]}
|
package/dist/update-loop.d.ts
CHANGED
|
@@ -2,10 +2,6 @@ import type { ComponentDef, Lifetime, Binding } from './types.js';
|
|
|
2
2
|
import type { StructuralBlock } from './structural.js';
|
|
3
3
|
import { type DomEnv } from './dom-env.js';
|
|
4
4
|
export declare const FULL_MASK: number;
|
|
5
|
-
export declare function setAddressedDispatcher(fn: (eff: {
|
|
6
|
-
__targetKey: string | number;
|
|
7
|
-
__msg: unknown;
|
|
8
|
-
}) => void): void;
|
|
9
5
|
export interface ComponentInstance<S = unknown, M = unknown, E = unknown> {
|
|
10
6
|
def: ComponentDef<S, M, E>;
|
|
11
7
|
state: S;
|
|
@@ -37,14 +33,26 @@ export declare function _forceState<S, M, E>(inst: ComponentInstance<S, M, E>, n
|
|
|
37
33
|
*
|
|
38
34
|
* @param method 0=reconcile, 1=reconcileItems, 2=reconcileClear, 3=reconcileRemove, -1=skip blocks
|
|
39
35
|
* @public — used by compiler-generated `__handlers`
|
|
36
|
+
*
|
|
37
|
+
* Backward-compat: pre-multi-word compiled bundles call this with 4
|
|
38
|
+
* args (no `dirtyHi`). The default `dirtyHi = 0` keeps those calls
|
|
39
|
+
* correct for ≤31-prefix components — the high gate always evaluates
|
|
40
|
+
* to 0 so the runtime falls back to the single-word check. Components
|
|
41
|
+
* with >31 prefixes need a fresh compile to start passing `dirtyHi`.
|
|
40
42
|
*/
|
|
41
|
-
export declare function _handleMsg(inst: ComponentInstance, msg: unknown, dirty: number, method: number): [unknown, unknown[]];
|
|
43
|
+
export declare function _handleMsg(inst: ComponentInstance, msg: unknown, dirty: number, method: number, dirtyHi?: number): [unknown, unknown[]];
|
|
42
44
|
/**
|
|
43
45
|
* Phase 2: compact dead bindings + update live bindings.
|
|
44
46
|
* Shared between genericUpdate and compiler-generated __update.
|
|
45
47
|
* @public — used by compiler-generated `__update` functions
|
|
48
|
+
*
|
|
49
|
+
* `dirtyHi` defaults to 0 for backward-compat with pre-multi-word
|
|
50
|
+
* compiled bundles that pass only the single-word `dirty`. Bindings
|
|
51
|
+
* read paths 0..30 in `binding.mask` and paths 31..61 in
|
|
52
|
+
* `binding.maskHi`; the gate ORs both AND results so the high word is
|
|
53
|
+
* a no-op for ≤31-prefix components.
|
|
46
54
|
*/
|
|
47
|
-
export declare function _runPhase2(state: unknown, dirty: number, bindings: Binding[], bindingsBeforePhase1: number, componentName?: string, onBindingError?: (info: {
|
|
55
|
+
export declare function _runPhase2(state: unknown, dirty: number, dirtyHi: number, bindings: Binding[], bindingsBeforePhase1: number, componentName?: string, onBindingError?: (info: {
|
|
48
56
|
kind: string;
|
|
49
57
|
key?: string;
|
|
50
58
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-loop.d.ts","sourceRoot":"","sources":["../src/update-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAItD,OAAO,EAAE,KAAK,MAAM,EAAc,MAAM,cAAc,CAAA;AAoBtD,eAAO,MAAM,SAAS,QAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"update-loop.d.ts","sourceRoot":"","sources":["../src/update-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAItD,OAAO,EAAE,KAAK,MAAM,EAAc,MAAM,cAAc,CAAA;AAoBtD,eAAO,MAAM,SAAS,QAAiB,CAAA;AA6CvC,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACtE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC1B,KAAK,EAAE,CAAC,CAAA;IACR,cAAc,EAAE,CAAC,EAAE,CAAA;IACnB,YAAY,EAAE,QAAQ,CAAA;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,kBAAkB,EAAE,OAAO,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,CAAC,EAAE,CAAA;IAChB,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,eAAe,EAAE,eAAe,CAAA;CAkEjC;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,EACvD,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,IAAI,CAAC,EAAE,CAAC,EACR,cAAc,GAAE,QAAQ,GAAG,IAAW,EACtC,GAAG,CAAC,EAAE,MAAM,GACX,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAkE5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAI7E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAqExF;AA8ND;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,iBAAiB,EACvB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAU,GAClB,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAqEtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,oBAAoB,EAAE,MAAM,EAC5B,aAAa,CAAC,EAAE,MAAM,EAMtB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GAC/F,IAAI,CAiEN"}
|
package/dist/update-loop.js
CHANGED
|
@@ -13,12 +13,65 @@ import { applyBinding } from './binding.js';
|
|
|
13
13
|
import { setCurrentDirtyMask } from './primitives/memo.js';
|
|
14
14
|
import { enterAccessor, exitAccessor } from './render-context.js';
|
|
15
15
|
export const FULL_MASK = 0xffffffff | 0;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Path-keyed dirty mask computation. Walks the prefix table; each entry
|
|
18
|
+
* whose `prefix(prev) !== prefix(next)` contributes its bit to the dirty
|
|
19
|
+
* mask. Bit position = table position (single-word, ≤31 entries) — for
|
|
20
|
+
* overflow (>31 entries), bits 31..61 land in the high half of a
|
|
21
|
+
* `[number, number]` pair, matching `__dirty`'s overflow shape.
|
|
22
|
+
*
|
|
23
|
+
* The runtime is correct under structural-sharing reducers (immutable
|
|
24
|
+
* splice): a sub-tree that wasn't touched stays reference-equal across
|
|
25
|
+
* `prev`/`next`, so every accessor reading into that sub-tree skips.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function computeDirtyFromPrefixes(prefixes, prev, next) {
|
|
30
|
+
if (prefixes.length <= 31) {
|
|
31
|
+
let dirty = 0;
|
|
32
|
+
for (let i = 0; i < prefixes.length; i++) {
|
|
33
|
+
if (prefixes[i](prev) !== prefixes[i](next))
|
|
34
|
+
dirty |= 1 << i;
|
|
35
|
+
}
|
|
36
|
+
return dirty;
|
|
37
|
+
}
|
|
38
|
+
// Overflow: two words, 31 bits each (avoid sign bit for ergonomic
|
|
39
|
+
// bitwise ops). Bit i % 31 of word ⌊i / 31⌋.
|
|
40
|
+
let lo = 0;
|
|
41
|
+
let hi = 0;
|
|
42
|
+
const len = prefixes.length;
|
|
43
|
+
for (let i = 0; i < len && i < 31; i++) {
|
|
44
|
+
if (prefixes[i](prev) !== prefixes[i](next))
|
|
45
|
+
lo |= 1 << i;
|
|
46
|
+
}
|
|
47
|
+
for (let i = 31; i < len && i < 62; i++) {
|
|
48
|
+
if (prefixes[i](prev) !== prefixes[i](next))
|
|
49
|
+
hi |= 1 << (i - 31);
|
|
50
|
+
}
|
|
51
|
+
// Anything past 62 forces FULL_MASK in the high word — the unified
|
|
52
|
+
// model's compiler-emitted prefix counts shouldn't hit this in
|
|
53
|
+
// realistic apps, but degrade gracefully if they do.
|
|
54
|
+
if (len > 62)
|
|
55
|
+
hi = FULL_MASK;
|
|
56
|
+
return [lo, hi];
|
|
20
57
|
}
|
|
21
58
|
export function createComponentInstance(def, data, parentLifetime = null, dom) {
|
|
59
|
+
// Hand-authored `__dirty` is no longer supported — the path-keyed
|
|
60
|
+
// `__prefixes` emission supersedes it (62-prefix capacity, precise
|
|
61
|
+
// per-prefix gating). Compiler-emitted `__dirty` was removed in
|
|
62
|
+
// 2026-05 alongside this throw; any `__dirty` field at runtime must
|
|
63
|
+
// be user code, and silently ignoring it would hide a stale pattern.
|
|
64
|
+
// Migrate by deleting the `__dirty` field — the compiler emits
|
|
65
|
+
// `__prefixes` automatically from accessor analysis, and uncompiled
|
|
66
|
+
// components correctly fall back to FULL_MASK.
|
|
67
|
+
if (def.__dirty !== undefined) {
|
|
68
|
+
throw new Error(`[llui] Component "${def.name}" defines \`__dirty\` directly. ` +
|
|
69
|
+
`This field is no longer accepted — the compiler emits \`__prefixes\` ` +
|
|
70
|
+
`(path-keyed reactivity) automatically. Remove \`__dirty\` from the ` +
|
|
71
|
+
`ComponentDef; either the compiler will regenerate the correct ` +
|
|
72
|
+
`prefix table, or uncompiled components will fall back to FULL_MASK. ` +
|
|
73
|
+
`See docs/proposals/unified-composition-model.md.`);
|
|
74
|
+
}
|
|
22
75
|
const [initialState, initialEffects] = def.init(data);
|
|
23
76
|
const controller = new AbortController();
|
|
24
77
|
const inst = {
|
|
@@ -82,7 +135,7 @@ export function _forceState(inst, newState) {
|
|
|
82
135
|
const snapshot = inst.structuralBlocks.slice();
|
|
83
136
|
for (const block of snapshot) {
|
|
84
137
|
try {
|
|
85
|
-
block.reconcile(newState, FULL_MASK);
|
|
138
|
+
block.reconcile(newState, FULL_MASK, FULL_MASK);
|
|
86
139
|
}
|
|
87
140
|
catch (e) {
|
|
88
141
|
reportReconcileError(inst, e);
|
|
@@ -229,21 +282,40 @@ function processMessages(inst) {
|
|
|
229
282
|
return;
|
|
230
283
|
}
|
|
231
284
|
}
|
|
232
|
-
// Generic pipeline — drain queue, accumulate dirty bits
|
|
285
|
+
// Generic pipeline — drain queue, accumulate dirty bits (two words:
|
|
286
|
+
// bits 0..30 in `combinedDirty`, bits 31..61 in `combinedDirtyHi`).
|
|
233
287
|
let state = inst.state;
|
|
234
288
|
let combinedDirty = 0;
|
|
289
|
+
let combinedDirtyHi = 0;
|
|
235
290
|
const allEffects = [];
|
|
236
291
|
const defUpdate = inst.def.update;
|
|
237
|
-
|
|
292
|
+
// Path-keyed reactivity: when the compiler emits `__prefixes`, dirty
|
|
293
|
+
// bits correspond to entries in the prefix table (one bit per minimal
|
|
294
|
+
// reference-stable prefix read across the component's accessors). The
|
|
295
|
+
// runtime computes the dirty mask by reference-comparing `prefix(prev)
|
|
296
|
+
// !== prefix(next)` for each table entry — precise per-prefix and
|
|
297
|
+
// supports two-word emission for up to 62 prefixes. Components compiled
|
|
298
|
+
// without `__llui/vite-plugin` (or with no reactive accessors) have no
|
|
299
|
+
// `__prefixes` table; they fall back to `FULL_MASK` and pay the cost
|
|
300
|
+
// of re-evaluating every binding every cycle. User-authored `__dirty`
|
|
301
|
+
// is no longer accepted — see types.ts for the rationale.
|
|
302
|
+
const prefixes = inst.def.__prefixes;
|
|
238
303
|
for (let qi = 0; qi < queue.length; qi++) {
|
|
239
304
|
const msg = queue[qi];
|
|
240
305
|
const [newState, effects] = defUpdate(state, msg);
|
|
241
|
-
|
|
306
|
+
let dirty;
|
|
307
|
+
if (prefixes !== undefined) {
|
|
308
|
+
dirty = computeDirtyFromPrefixes(prefixes, state, newState);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
dirty = FULL_MASK;
|
|
312
|
+
}
|
|
242
313
|
if (typeof dirty === 'number') {
|
|
243
314
|
combinedDirty |= dirty;
|
|
244
315
|
}
|
|
245
316
|
else {
|
|
246
|
-
combinedDirty |= dirty[0]
|
|
317
|
+
combinedDirty |= dirty[0];
|
|
318
|
+
combinedDirtyHi |= dirty[1];
|
|
247
319
|
}
|
|
248
320
|
state = newState;
|
|
249
321
|
// Avoid spread — allocates an iterator per call. For typical effect
|
|
@@ -266,21 +338,26 @@ function processMessages(inst) {
|
|
|
266
338
|
const bindingsBeforePhase1 = bindings.length;
|
|
267
339
|
// Set current dirty mask BEFORE Phase 1 so memo() accessors used in
|
|
268
340
|
// structural primitives (e.g. each.items) can use the bitmask fast path.
|
|
269
|
-
setCurrentDirtyMask(combinedDirty);
|
|
341
|
+
setCurrentDirtyMask(combinedDirty, combinedDirtyHi);
|
|
270
342
|
if (inst.def.__update) {
|
|
271
|
-
// Compiler-generated fast path — replaces generic Phase 1 + Phase 2
|
|
272
|
-
|
|
343
|
+
// Compiler-generated fast path — replaces generic Phase 1 + Phase 2.
|
|
344
|
+
// `combinedDirtyHi` is passed as the trailing positional arg so
|
|
345
|
+
// stale 5-param compiled bundles continue to gate correctly: they
|
|
346
|
+
// ignore the extra arg, and for ≤31-prefix components `dirtyHi`
|
|
347
|
+
// is always 0 anyway. Fresh compiled bundles use the 6th param
|
|
348
|
+
// for precise two-word Phase 1 gating on 32..61-prefix components.
|
|
349
|
+
inst.def.__update(state, combinedDirty, bindings, inst.structuralBlocks, bindingsBeforePhase1, combinedDirtyHi);
|
|
273
350
|
}
|
|
274
351
|
else {
|
|
275
352
|
// Generic Phase 1 + Phase 2 fallback (uncompiled components)
|
|
276
|
-
genericUpdate(inst, state, combinedDirty, bindings, bindingsBeforePhase1);
|
|
353
|
+
genericUpdate(inst, state, combinedDirty, combinedDirtyHi, bindings, bindingsBeforePhase1);
|
|
277
354
|
}
|
|
278
355
|
// Dispatch effects after DOM updates
|
|
279
356
|
for (let i = 0; i < allEffects.length; i++) {
|
|
280
357
|
dispatchEffect(inst, allEffects[i]);
|
|
281
358
|
}
|
|
282
359
|
}
|
|
283
|
-
function genericUpdate(inst, state, combinedDirty, bindings, bindingsBeforePhase1) {
|
|
360
|
+
function genericUpdate(inst, state, combinedDirty, combinedDirtyHi, bindings, bindingsBeforePhase1) {
|
|
284
361
|
// Phase 1 — structural reconciliation. Structural primitives register
|
|
285
362
|
// their blocks BEFORE running builders, so parents precede their nested
|
|
286
363
|
// children in this array. That ordering matters: a parent's reconcile
|
|
@@ -291,17 +368,22 @@ function genericUpdate(inst, state, combinedDirty, bindings, bindingsBeforePhase
|
|
|
291
368
|
const blocks = inst.structuralBlocks;
|
|
292
369
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
293
370
|
const block = blocks[bi];
|
|
294
|
-
if (!block
|
|
371
|
+
if (!block)
|
|
372
|
+
continue;
|
|
373
|
+
// Two-word gate: skip when neither low nor high masks intersect.
|
|
374
|
+
// `block.maskHi` is 0 for ≤31-prefix blocks, so `0 & dirtyHi === 0`
|
|
375
|
+
// collapses the gate to the single-word check at runtime.
|
|
376
|
+
if (!((block.mask & combinedDirty) | (block.maskHi & combinedDirtyHi)))
|
|
295
377
|
continue;
|
|
296
378
|
try {
|
|
297
|
-
block.reconcile(state, combinedDirty);
|
|
379
|
+
block.reconcile(state, combinedDirty, combinedDirtyHi);
|
|
298
380
|
}
|
|
299
381
|
catch (e) {
|
|
300
382
|
reportReconcileError(inst, e);
|
|
301
383
|
}
|
|
302
384
|
}
|
|
303
385
|
// Phase 2 — compact + update bindings
|
|
304
|
-
_runPhase2(state, combinedDirty, bindings, bindingsBeforePhase1, inst.def.name, inst._onBindingError);
|
|
386
|
+
_runPhase2(state, combinedDirty, combinedDirtyHi, bindings, bindingsBeforePhase1, inst.def.name, inst._onBindingError);
|
|
305
387
|
}
|
|
306
388
|
/**
|
|
307
389
|
* Run a handler for a single message: call update(), reconcile blocks
|
|
@@ -310,8 +392,14 @@ function genericUpdate(inst, state, combinedDirty, bindings, bindingsBeforePhase
|
|
|
310
392
|
*
|
|
311
393
|
* @param method 0=reconcile, 1=reconcileItems, 2=reconcileClear, 3=reconcileRemove, -1=skip blocks
|
|
312
394
|
* @public — used by compiler-generated `__handlers`
|
|
395
|
+
*
|
|
396
|
+
* Backward-compat: pre-multi-word compiled bundles call this with 4
|
|
397
|
+
* args (no `dirtyHi`). The default `dirtyHi = 0` keeps those calls
|
|
398
|
+
* correct for ≤31-prefix components — the high gate always evaluates
|
|
399
|
+
* to 0 so the runtime falls back to the single-word check. Components
|
|
400
|
+
* with >31 prefixes need a fresh compile to start passing `dirtyHi`.
|
|
313
401
|
*/
|
|
314
|
-
export function _handleMsg(inst, msg, dirty, method) {
|
|
402
|
+
export function _handleMsg(inst, msg, dirty, method, dirtyHi = 0) {
|
|
315
403
|
const [s, e] = inst.def.update(inst.state, msg);
|
|
316
404
|
inst.state = s;
|
|
317
405
|
inst._onCommit?.(s);
|
|
@@ -322,12 +410,14 @@ export function _handleMsg(inst, msg, dirty, method) {
|
|
|
322
410
|
// memo short-circuits with the stale value left over from the
|
|
323
411
|
// previous cycle and structural blocks reconcile against a frozen
|
|
324
412
|
// input.
|
|
325
|
-
setCurrentDirtyMask(dirty);
|
|
413
|
+
setCurrentDirtyMask(dirty, dirtyHi);
|
|
326
414
|
if (method >= 0) {
|
|
327
415
|
const bl = inst.structuralBlocks;
|
|
328
416
|
for (let i = 0; i < bl.length; i++) {
|
|
329
417
|
const block = bl[i];
|
|
330
|
-
if (!block
|
|
418
|
+
if (!block)
|
|
419
|
+
continue;
|
|
420
|
+
if (!((block.mask & dirty) | (block.maskHi & dirtyHi)))
|
|
331
421
|
continue;
|
|
332
422
|
try {
|
|
333
423
|
// Specialized methods (`reconcileItems`, `reconcileClear`,
|
|
@@ -343,25 +433,25 @@ export function _handleMsg(inst, msg, dirty, method) {
|
|
|
343
433
|
// methods, so they keep their fast path.
|
|
344
434
|
switch (method) {
|
|
345
435
|
case 0:
|
|
346
|
-
block.reconcile(s, dirty);
|
|
436
|
+
block.reconcile(s, dirty, dirtyHi);
|
|
347
437
|
break;
|
|
348
438
|
case 1:
|
|
349
439
|
if (block.reconcileItems)
|
|
350
440
|
block.reconcileItems(s);
|
|
351
441
|
else
|
|
352
|
-
block.reconcile(s, dirty);
|
|
442
|
+
block.reconcile(s, dirty, dirtyHi);
|
|
353
443
|
break;
|
|
354
444
|
case 2:
|
|
355
445
|
if (block.reconcileClear)
|
|
356
446
|
block.reconcileClear();
|
|
357
447
|
else
|
|
358
|
-
block.reconcile(s, dirty);
|
|
448
|
+
block.reconcile(s, dirty, dirtyHi);
|
|
359
449
|
break;
|
|
360
450
|
case 3:
|
|
361
451
|
if (block.reconcileRemove)
|
|
362
452
|
block.reconcileRemove(s);
|
|
363
453
|
else
|
|
364
|
-
block.reconcile(s, dirty);
|
|
454
|
+
block.reconcile(s, dirty, dirtyHi);
|
|
365
455
|
break;
|
|
366
456
|
default:
|
|
367
457
|
// method >= 10: reconcileChanged with stride = method - 10
|
|
@@ -369,7 +459,7 @@ export function _handleMsg(inst, msg, dirty, method) {
|
|
|
369
459
|
if (block.reconcileChanged)
|
|
370
460
|
block.reconcileChanged(s, method - 10);
|
|
371
461
|
else
|
|
372
|
-
block.reconcile(s, dirty);
|
|
462
|
+
block.reconcile(s, dirty, dirtyHi);
|
|
373
463
|
}
|
|
374
464
|
break;
|
|
375
465
|
}
|
|
@@ -381,15 +471,21 @@ export function _handleMsg(inst, msg, dirty, method) {
|
|
|
381
471
|
}
|
|
382
472
|
}
|
|
383
473
|
const b = inst.allBindings;
|
|
384
|
-
_runPhase2(s, dirty, b, b.length, inst.def.name, inst._onBindingError);
|
|
474
|
+
_runPhase2(s, dirty, dirtyHi, b, b.length, inst.def.name, inst._onBindingError);
|
|
385
475
|
return [s, e];
|
|
386
476
|
}
|
|
387
477
|
/**
|
|
388
478
|
* Phase 2: compact dead bindings + update live bindings.
|
|
389
479
|
* Shared between genericUpdate and compiler-generated __update.
|
|
390
480
|
* @public — used by compiler-generated `__update` functions
|
|
481
|
+
*
|
|
482
|
+
* `dirtyHi` defaults to 0 for backward-compat with pre-multi-word
|
|
483
|
+
* compiled bundles that pass only the single-word `dirty`. Bindings
|
|
484
|
+
* read paths 0..30 in `binding.mask` and paths 31..61 in
|
|
485
|
+
* `binding.maskHi`; the gate ORs both AND results so the high word is
|
|
486
|
+
* a no-op for ≤31-prefix components.
|
|
391
487
|
*/
|
|
392
|
-
export function _runPhase2(state, dirty, bindings, bindingsBeforePhase1, componentName,
|
|
488
|
+
export function _runPhase2(state, dirty, dirtyHi, bindings, bindingsBeforePhase1, componentName,
|
|
393
489
|
// Optional `_onBindingError` hook. Type is duplicated here rather
|
|
394
490
|
// than referenced as `ComponentInstance['_onBindingError']` because
|
|
395
491
|
// the underlying field is `@internal` — stripped from the generated
|
|
@@ -406,7 +502,7 @@ onBindingError) {
|
|
|
406
502
|
bindings.length = w;
|
|
407
503
|
phase2Len = Math.min(w, bindingsBeforePhase1);
|
|
408
504
|
}
|
|
409
|
-
if (dirty !== 0) {
|
|
505
|
+
if (dirty !== 0 || dirtyHi !== 0) {
|
|
410
506
|
// Always catch+continue: a single accessor throw shouldn't abort
|
|
411
507
|
// the rest of the bindings on the same commit. The user-visible
|
|
412
508
|
// effect: a broken cell shows its previous value; sibling cells
|
|
@@ -427,7 +523,12 @@ onBindingError) {
|
|
|
427
523
|
try {
|
|
428
524
|
for (let i = 0, len = phase2Len; i < len; i++) {
|
|
429
525
|
const binding = bindings[i];
|
|
430
|
-
if (binding.dead
|
|
526
|
+
if (binding.dead)
|
|
527
|
+
continue;
|
|
528
|
+
// Two-word gate. `maskHi` is 0 for ≤31-prefix bindings; the
|
|
529
|
+
// `0 & dirtyHi === 0` branch collapses to a no-op under V8's
|
|
530
|
+
// inline cache.
|
|
531
|
+
if (!((binding.mask & dirty) | (binding.maskHi & dirtyHi)))
|
|
431
532
|
continue;
|
|
432
533
|
if (binding.kind === 'effect') {
|
|
433
534
|
try {
|
|
@@ -539,11 +640,6 @@ function enhanceBindingError(err, binding, componentName) {
|
|
|
539
640
|
}
|
|
540
641
|
function dispatchEffect(inst, effect) {
|
|
541
642
|
const eff = effect;
|
|
542
|
-
// Addressed effects — dispatch to target component
|
|
543
|
-
if (eff.__addressed === true && typeof eff.__targetKey !== 'undefined') {
|
|
544
|
-
addressedDispatcher?.(eff);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
643
|
// Built-in: delay
|
|
548
644
|
if (eff.type === 'delay') {
|
|
549
645
|
const ms = eff.ms;
|