@llui/dom 0.0.17 → 0.0.18

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.
@@ -80,12 +80,36 @@ export declare function provideValue<T>(ctx: Context<T>, value: T, children: ()
80
80
  * three-step "look up the accessor, ignore the state arg, get the
81
81
  * value" dance.
82
82
  *
83
- * Use this only with values provided via `provideValue` (or via
84
- * `provide` with an accessor that ignores its state argument). Calling
85
- * `useContextValue` against a context whose accessor reads from state
86
- * will resolve the accessor against `undefined` and likely throw or
87
- * return wrong data there's no way for the runtime to detect the
88
- * mismatch, so honor the contract at the API surface.
83
+ * ## Value capture contract
84
+ *
85
+ * **The returned value is captured once, at view-construction time.**
86
+ * Any reference you store from `useContextValue(ctx)` into a closure
87
+ * for example, by assigning it to a local `const` inside `view(...)`
88
+ * and reading it from an event handler — sees the value as it was
89
+ * when the view ran. The closure does NOT re-read the context on each
90
+ * event dispatch.
91
+ *
92
+ * That's fine, and usually what you want, for stable dispatcher bags:
93
+ * the bag's methods close over the layout's `send`, and `send` itself
94
+ * is stable across the layout's lifetime, so the methods work
95
+ * correctly regardless of when the handler fires. Pages can stash
96
+ * `const toast = useContextValue(ToastContext)` at the top of their
97
+ * `view()` and call `toast.show(...)` from any event handler below.
98
+ *
99
+ * **Do NOT use `useContextValue` when the consumer needs to see
100
+ * updates to the context value.** If a parent re-`provideValue`s the
101
+ * context with a different object later, existing consumers already
102
+ * holding the captured value will still see the old one. For
103
+ * reactive consumption, use `useContext(ctx)` — that returns an
104
+ * accessor that re-reads the provider on each binding evaluation, so
105
+ * reactive bindings (`class`, `text`, etc.) pick up updates
106
+ * automatically.
107
+ *
108
+ * **Do NOT use `useContextValue` against a provider whose accessor
109
+ * reads from state.** The accessor is invoked with `undefined` here,
110
+ * so any `(s) => s.something` provider will throw or return garbage.
111
+ * Match `provideValue` on the producer side with `useContextValue` on
112
+ * the consumer side, and `provide` with `useContext`.
89
113
  */
90
114
  export declare function useContextValue<T>(ctx: Context<T>): T;
91
115
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/primitives/context.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,SAAS,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAE5E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAC1B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,QAAQ,EAAE,MAAM,IAAI,EAAE,GACrB,IAAI,EAAE,CAoBR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CA0B7D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,GAAG,IAAI,EAAE,CAEzF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAKrD"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/primitives/context.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,SAAS,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAE5E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAC1B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,QAAQ,EAAE,MAAM,IAAI,EAAE,GACrB,IAAI,EAAE,CAoBR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CA0B7D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,GAAG,IAAI,EAAE,CAEzF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAKrD"}
@@ -132,12 +132,36 @@ export function provideValue(ctx, value, children) {
132
132
  * three-step "look up the accessor, ignore the state arg, get the
133
133
  * value" dance.
134
134
  *
135
- * Use this only with values provided via `provideValue` (or via
136
- * `provide` with an accessor that ignores its state argument). Calling
137
- * `useContextValue` against a context whose accessor reads from state
138
- * will resolve the accessor against `undefined` and likely throw or
139
- * return wrong data there's no way for the runtime to detect the
140
- * mismatch, so honor the contract at the API surface.
135
+ * ## Value capture contract
136
+ *
137
+ * **The returned value is captured once, at view-construction time.**
138
+ * Any reference you store from `useContextValue(ctx)` into a closure
139
+ * for example, by assigning it to a local `const` inside `view(...)`
140
+ * and reading it from an event handler — sees the value as it was
141
+ * when the view ran. The closure does NOT re-read the context on each
142
+ * event dispatch.
143
+ *
144
+ * That's fine, and usually what you want, for stable dispatcher bags:
145
+ * the bag's methods close over the layout's `send`, and `send` itself
146
+ * is stable across the layout's lifetime, so the methods work
147
+ * correctly regardless of when the handler fires. Pages can stash
148
+ * `const toast = useContextValue(ToastContext)` at the top of their
149
+ * `view()` and call `toast.show(...)` from any event handler below.
150
+ *
151
+ * **Do NOT use `useContextValue` when the consumer needs to see
152
+ * updates to the context value.** If a parent re-`provideValue`s the
153
+ * context with a different object later, existing consumers already
154
+ * holding the captured value will still see the old one. For
155
+ * reactive consumption, use `useContext(ctx)` — that returns an
156
+ * accessor that re-reads the provider on each binding evaluation, so
157
+ * reactive bindings (`class`, `text`, etc.) pick up updates
158
+ * automatically.
159
+ *
160
+ * **Do NOT use `useContextValue` against a provider whose accessor
161
+ * reads from state.** The accessor is invoked with `undefined` here,
162
+ * so any `(s) => s.something` provider will throw or return garbage.
163
+ * Match `provideValue` on the producer side with `useContextValue` on
164
+ * the consumer side, and `provide` with `useContext`.
141
165
  */
142
166
  export function useContextValue(ctx) {
143
167
  const accessor = useContext(ctx);
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/primitives/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC;;;GAGG;AACH,MAAM,UAAU,GAAG,IAAI,OAAO,EAA+C,CAAA;AAQ7E;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAI,YAAgB,EAAE,IAAa;IAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACjF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,OAAO,CACrB,GAAe,EACf,QAAqB,EACrB,QAAsB;IAEtB,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAA;IAC7C,MAAM,WAAW,GAAG,SAAS,CAAC,SAAS,CAAA;IACvC,wEAAwE;IACxE,wEAAwE;IACxE,qEAAqE;IACrE,uEAAuE;IACvE,MAAM,aAAa,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmC,CAAA;IACtD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,QAAmC,CAAC,CAAA;IACrD,UAAU,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,CAAA;IAClC,sEAAsE;IACtE,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,GAAG,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAA;IAC5D,IAAI,CAAC;QACH,OAAO,QAAQ,EAAE,CAAA;IACnB,CAAC;YAAS,CAAC;QACT,kBAAkB,EAAE,CAAA;QACpB,gBAAgB,CAAC,SAAS,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAO,GAAe;IAC9C,IAAI,KAAK,GAAiB,IAAI,CAAA;IAC9B,IAAI,CAAC;QACH,KAAK,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC,SAAS,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;QACjE,4CAA4C;IAC9C,CAAC;IACD,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAE,CAAA;YAClC,OAAO,QAAuB,CAAA;QAChC,CAAC;QACD,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;IACtB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAA;QACtB,OAAO,GAAG,EAAE,CAAC,CAAC,CAAA;IAChB,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,SAAS,CAAA;IAC3D,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,6CAA6C;QACrE,sCAAsC,KAAK,2BAA2B;QACtE,uCAAuC,CAC1C,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAI,GAAe,EAAE,KAAQ,EAAE,QAAsB;IAC/E,OAAO,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe,CAAI,GAAe;IAChD,MAAM,QAAQ,GAAG,UAAU,CAAa,GAAG,CAAC,CAAA;IAC5C,+DAA+D;IAC/D,8CAA8C;IAC9C,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC","sourcesContent":["import type { Scope } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope } from '../scope.js'\n\n/**\n * Per-scope storage: scope → (context-id → accessor).\n * WeakMap so disposed scopes are GC'd.\n */\nconst contextMap = new WeakMap<Scope, Map<symbol, (s: unknown) => unknown>>()\n\nexport interface Context<T> {\n readonly _id: symbol\n readonly _default: T | undefined\n readonly _name: string | undefined\n}\n\n/**\n * Create a typed context key. Pass a default value to make consumers without a\n * provider resolve to it; omit to make unprovided consumption throw.\n *\n * ```ts\n * const ThemeContext = createContext<'light' | 'dark'>('light')\n * ```\n */\nexport function createContext<T>(defaultValue?: T, name?: string): Context<T> {\n return { _id: Symbol(name ?? 'llui-ctx'), _default: defaultValue, _name: name }\n}\n\n/**\n * Provide a reactive value for `ctx` to every descendant rendered inside `children`.\n * The accessor `(s: S) => T` is evaluated lazily at binding read time, so providers\n * can thread state slices down without prop drilling.\n *\n * ```ts\n * view: ({ send }) => [\n * provide(ThemeContext, (s: State) => s.theme, () => [\n * header(send),\n * main(send),\n * ]),\n * ]\n * ```\n *\n * Nested providers shadow outer ones within their subtree. The outer value is\n * restored after `children()` returns, so sibling subtrees aren't affected.\n */\nexport function provide<S, T>(\n ctx: Context<T>,\n accessor: (s: S) => T,\n children: () => Node[],\n): Node[] {\n const renderCtx = getRenderContext('provide')\n const parentScope = renderCtx.rootScope\n // Create a sub-scope so the context is attached to THIS provider alone.\n // Descendants (including those mounted later via show/branch/each) walk\n // up to this scope via their own parent chain and find the accessor.\n // Nested providers create their own sub-scope, shadowing outer values.\n const providerScope = createScope(parentScope)\n const map = new Map<symbol, (s: unknown) => unknown>()\n map.set(ctx._id, accessor as (s: unknown) => unknown)\n contextMap.set(providerScope, map)\n // Render children with the provider scope as the new rootScope so any\n // primitives (bindings, structural blocks, nested providers) attach here.\n setRenderContext({ ...renderCtx, rootScope: providerScope })\n try {\n return children()\n } finally {\n clearRenderContext()\n setRenderContext(renderCtx)\n }\n}\n\n/**\n * Read a context accessor within a view or view-function. Walks the scope chain\n * from the current render point to find the nearest provider. Returns an\n * `(s: S) => T` accessor that can be passed to bindings (text/class/etc.).\n *\n * ```ts\n * export function themedCard(): Node[] {\n * const theme = useContext(ThemeContext)\n * return div({ class: (s) => `card theme-${theme(s)}` }, [...])\n * }\n * ```\n */\nexport function useContext<S, T>(ctx: Context<T>): (s: S) => T {\n let scope: Scope | null = null\n try {\n scope = getRenderContext('useContext').rootScope\n } catch {\n // No render context (e.g. called from connect() in a unit test).\n // Fall through to default resolution below.\n }\n while (scope) {\n const map = contextMap.get(scope)\n if (map?.has(ctx._id)) {\n const accessor = map.get(ctx._id)!\n return accessor as (s: S) => T\n }\n scope = scope.parent\n }\n if (ctx._default !== undefined) {\n const d = ctx._default\n return () => d\n }\n const label = ctx._name ?? ctx._id.description ?? 'unknown'\n throw new Error(\n `[LLui] useContext(${label}): no provider found and no default value. ` +\n `Wrap a parent element with provide(${label}, accessor, () => [...]) ` +\n `or pass a default to createContext().`,\n )\n}\n\n/**\n * Provide a state-independent value to every descendant. Companion to\n * `provide()` for the common case of publishing a stable dispatcher\n * bag, callback record, or DI container — anything that doesn't depend\n * on the parent's state.\n *\n * ```ts\n * provideValue(ToastContext, { show: (m) => send({ type: 'toast', m }) }, () => [\n * main([pageSlot()]),\n * ])\n * ```\n *\n * Equivalent to `provide(ctx, () => value, children)`, but exists so\n * the call site reads as \"provide this value\" rather than \"provide an\n * accessor that ignores its state argument and returns a value.\" Pair\n * with `useContextValue` for symmetric ergonomics on the consumer side.\n *\n * Internally still uses the accessor mechanism: the value is wrapped\n * in a constant lambda. Consumers that read via the reactive\n * `useContext` form will get an `(s) => T` whose accessor ignores `s`.\n */\nexport function provideValue<T>(ctx: Context<T>, value: T, children: () => Node[]): Node[] {\n return provide(ctx, () => value, children)\n}\n\n/**\n * Read a state-independent value from the nearest provider. Companion\n * to `useContext()` for the common case of consuming a stable\n * dispatcher bag, callback record, or DI container.\n *\n * ```ts\n * const toast = useContextValue(ToastContext)\n * button({ onClick: () => toast.show('Saved') }, [text('Save')])\n * ```\n *\n * Equivalent to calling the accessor returned by `useContext` with\n * `undefined`, but reads as a single function call instead of a\n * three-step \"look up the accessor, ignore the state arg, get the\n * value\" dance.\n *\n * Use this only with values provided via `provideValue` (or via\n * `provide` with an accessor that ignores its state argument). Calling\n * `useContextValue` against a context whose accessor reads from state\n * will resolve the accessor against `undefined` and likely throw or\n * return wrong data there's no way for the runtime to detect the\n * mismatch, so honor the contract at the API surface.\n */\nexport function useContextValue<T>(ctx: Context<T>): T {\n const accessor = useContext<unknown, T>(ctx)\n // The contract above: the producer side promised this accessor\n // doesn't read its state arg. Pass undefined.\n return accessor(undefined)\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/primitives/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createScope } from '../scope.js'\n\n/**\n * Per-scope storage: scope → (context-id → accessor).\n * WeakMap so disposed scopes are GC'd.\n */\nconst contextMap = new WeakMap<Scope, Map<symbol, (s: unknown) => unknown>>()\n\nexport interface Context<T> {\n readonly _id: symbol\n readonly _default: T | undefined\n readonly _name: string | undefined\n}\n\n/**\n * Create a typed context key. Pass a default value to make consumers without a\n * provider resolve to it; omit to make unprovided consumption throw.\n *\n * ```ts\n * const ThemeContext = createContext<'light' | 'dark'>('light')\n * ```\n */\nexport function createContext<T>(defaultValue?: T, name?: string): Context<T> {\n return { _id: Symbol(name ?? 'llui-ctx'), _default: defaultValue, _name: name }\n}\n\n/**\n * Provide a reactive value for `ctx` to every descendant rendered inside `children`.\n * The accessor `(s: S) => T` is evaluated lazily at binding read time, so providers\n * can thread state slices down without prop drilling.\n *\n * ```ts\n * view: ({ send }) => [\n * provide(ThemeContext, (s: State) => s.theme, () => [\n * header(send),\n * main(send),\n * ]),\n * ]\n * ```\n *\n * Nested providers shadow outer ones within their subtree. The outer value is\n * restored after `children()` returns, so sibling subtrees aren't affected.\n */\nexport function provide<S, T>(\n ctx: Context<T>,\n accessor: (s: S) => T,\n children: () => Node[],\n): Node[] {\n const renderCtx = getRenderContext('provide')\n const parentScope = renderCtx.rootScope\n // Create a sub-scope so the context is attached to THIS provider alone.\n // Descendants (including those mounted later via show/branch/each) walk\n // up to this scope via their own parent chain and find the accessor.\n // Nested providers create their own sub-scope, shadowing outer values.\n const providerScope = createScope(parentScope)\n const map = new Map<symbol, (s: unknown) => unknown>()\n map.set(ctx._id, accessor as (s: unknown) => unknown)\n contextMap.set(providerScope, map)\n // Render children with the provider scope as the new rootScope so any\n // primitives (bindings, structural blocks, nested providers) attach here.\n setRenderContext({ ...renderCtx, rootScope: providerScope })\n try {\n return children()\n } finally {\n clearRenderContext()\n setRenderContext(renderCtx)\n }\n}\n\n/**\n * Read a context accessor within a view or view-function. Walks the scope chain\n * from the current render point to find the nearest provider. Returns an\n * `(s: S) => T` accessor that can be passed to bindings (text/class/etc.).\n *\n * ```ts\n * export function themedCard(): Node[] {\n * const theme = useContext(ThemeContext)\n * return div({ class: (s) => `card theme-${theme(s)}` }, [...])\n * }\n * ```\n */\nexport function useContext<S, T>(ctx: Context<T>): (s: S) => T {\n let scope: Scope | null = null\n try {\n scope = getRenderContext('useContext').rootScope\n } catch {\n // No render context (e.g. called from connect() in a unit test).\n // Fall through to default resolution below.\n }\n while (scope) {\n const map = contextMap.get(scope)\n if (map?.has(ctx._id)) {\n const accessor = map.get(ctx._id)!\n return accessor as (s: S) => T\n }\n scope = scope.parent\n }\n if (ctx._default !== undefined) {\n const d = ctx._default\n return () => d\n }\n const label = ctx._name ?? ctx._id.description ?? 'unknown'\n throw new Error(\n `[LLui] useContext(${label}): no provider found and no default value. ` +\n `Wrap a parent element with provide(${label}, accessor, () => [...]) ` +\n `or pass a default to createContext().`,\n )\n}\n\n/**\n * Provide a state-independent value to every descendant. Companion to\n * `provide()` for the common case of publishing a stable dispatcher\n * bag, callback record, or DI container — anything that doesn't depend\n * on the parent's state.\n *\n * ```ts\n * provideValue(ToastContext, { show: (m) => send({ type: 'toast', m }) }, () => [\n * main([pageSlot()]),\n * ])\n * ```\n *\n * Equivalent to `provide(ctx, () => value, children)`, but exists so\n * the call site reads as \"provide this value\" rather than \"provide an\n * accessor that ignores its state argument and returns a value.\" Pair\n * with `useContextValue` for symmetric ergonomics on the consumer side.\n *\n * Internally still uses the accessor mechanism: the value is wrapped\n * in a constant lambda. Consumers that read via the reactive\n * `useContext` form will get an `(s) => T` whose accessor ignores `s`.\n */\nexport function provideValue<T>(ctx: Context<T>, value: T, children: () => Node[]): Node[] {\n return provide(ctx, () => value, children)\n}\n\n/**\n * Read a state-independent value from the nearest provider. Companion\n * to `useContext()` for the common case of consuming a stable\n * dispatcher bag, callback record, or DI container.\n *\n * ```ts\n * const toast = useContextValue(ToastContext)\n * button({ onClick: () => toast.show('Saved') }, [text('Save')])\n * ```\n *\n * Equivalent to calling the accessor returned by `useContext` with\n * `undefined`, but reads as a single function call instead of a\n * three-step \"look up the accessor, ignore the state arg, get the\n * value\" dance.\n *\n * ## Value capture contract\n *\n * **The returned value is captured once, at view-construction time.**\n * Any reference you store from `useContextValue(ctx)` into a closure\n * — for example, by assigning it to a local `const` inside `view(...)`\n * and reading it from an event handler sees the value as it was\n * when the view ran. The closure does NOT re-read the context on each\n * event dispatch.\n *\n * That's fine, and usually what you want, for stable dispatcher bags:\n * the bag's methods close over the layout's `send`, and `send` itself\n * is stable across the layout's lifetime, so the methods work\n * correctly regardless of when the handler fires. Pages can stash\n * `const toast = useContextValue(ToastContext)` at the top of their\n * `view()` and call `toast.show(...)` from any event handler below.\n *\n * **Do NOT use `useContextValue` when the consumer needs to see\n * updates to the context value.** If a parent re-`provideValue`s the\n * context with a different object later, existing consumers already\n * holding the captured value will still see the old one. For\n * reactive consumption, use `useContext(ctx)` — that returns an\n * accessor that re-reads the provider on each binding evaluation, so\n * reactive bindings (`class`, `text`, etc.) pick up updates\n * automatically.\n *\n * **Do NOT use `useContextValue` against a provider whose accessor\n * reads from state.** The accessor is invoked with `undefined` here,\n * so any `(s) => s.something` provider will throw or return garbage.\n * Match `provideValue` on the producer side with `useContextValue` on\n * the consumer side, and `provide` with `useContext`.\n */\nexport function useContextValue<T>(ctx: Context<T>): T {\n const accessor = useContext<unknown, T>(ctx)\n // The contract above: the producer side promised this accessor\n // doesn't read its state arg. Pass undefined.\n return accessor(undefined)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/dom",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {