@webjsdev/core 0.8.0 → 0.9.1

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.
Files changed (85) hide show
  1. package/README.md +5 -0
  2. package/dist/webjs-core-browser.js +6 -5
  3. package/dist/webjs-core-browser.js.map +3 -3
  4. package/dist/webjs-core-testing.js +20 -19
  5. package/dist/webjs-core-testing.js.map +3 -3
  6. package/dist/webjs-core.js +7 -6
  7. package/dist/webjs-core.js.map +3 -3
  8. package/index-browser.js +14 -9
  9. package/index.d.ts +53 -10
  10. package/index.js +17 -9
  11. package/package.json +15 -2
  12. package/src/action-effects-client.d.ts +5 -0
  13. package/src/action-effects-client.js +84 -0
  14. package/src/action-seed-client.js +5 -5
  15. package/src/asset-url.d.ts +18 -0
  16. package/src/asset-url.js +12 -13
  17. package/src/async-directive.d.ts +18 -0
  18. package/src/async-directive.js +88 -0
  19. package/src/attribute-reader.js +197 -23
  20. package/src/binding-prefixes.js +0 -12
  21. package/src/component/lifecycle.js +760 -209
  22. package/src/component/reactive.js +2 -10
  23. package/src/component/server-element.js +23 -1
  24. package/src/component.d.ts +151 -41
  25. package/src/context.d.ts +94 -18
  26. package/src/context.js +341 -217
  27. package/src/converters.d.ts +7 -0
  28. package/src/converters.js +38 -0
  29. package/src/csp-nonce.d.ts +18 -0
  30. package/src/css.d.ts +32 -6
  31. package/src/css.js +178 -14
  32. package/src/directive.d.ts +58 -0
  33. package/src/directive.js +119 -0
  34. package/src/directives.d.ts +38 -1
  35. package/src/directives.js +158 -1
  36. package/src/form-action.d.ts +13 -0
  37. package/src/form-action.js +139 -4
  38. package/src/hot.js +23 -0
  39. package/src/html.d.ts +4 -0
  40. package/src/html.js +35 -3
  41. package/src/is-server.d.ts +2 -0
  42. package/src/is-server.js +18 -0
  43. package/src/jsx-guard.js +130 -0
  44. package/src/metadata.d.ts +22 -3
  45. package/src/nav.d.ts +4 -1
  46. package/src/nav.js +44 -11
  47. package/src/optimistic.js +2 -2
  48. package/src/refresh-header.js +52 -0
  49. package/src/registry.js +10 -1
  50. package/src/render-client/adopt-slots.js +8 -3
  51. package/src/render-client/parts.js +745 -31
  52. package/src/render-client/reconciler.js +56 -11
  53. package/src/render-client/template-compiler.js +139 -44
  54. package/src/render-client.d.ts +5 -1
  55. package/src/render-server/attrs.js +2 -2
  56. package/src/render-server/compile.js +34 -0
  57. package/src/render-server/errors.js +37 -0
  58. package/src/render-server/expand.js +61 -8
  59. package/src/render-server/served-attrs.js +1 -2
  60. package/src/render-server/walk.js +135 -3
  61. package/src/render-server.d.ts +3 -0
  62. package/src/render-server.js +2 -0
  63. package/src/repeat.d.ts +7 -2
  64. package/src/repeat.js +20 -6
  65. package/src/router-client/dom-differ.js +17 -0
  66. package/src/router-client/fetch-apply.js +86 -5
  67. package/src/router-client/navigator.js +45 -9
  68. package/src/router-client/prefetch.js +3 -4
  69. package/src/router-client/root-attrs.js +43 -0
  70. package/src/router-client/stream.js +7 -0
  71. package/src/router-client/swap.js +4 -1
  72. package/src/routes.d.ts +75 -18
  73. package/src/serialize.d.ts +2 -0
  74. package/src/serialize.js +35 -4
  75. package/src/signal.d.ts +7 -0
  76. package/src/signal.js +26 -6
  77. package/src/slot/polyfills.js +3 -3
  78. package/src/slot.js +4 -8
  79. package/src/static-html.d.ts +22 -0
  80. package/src/static-html.js +144 -0
  81. package/src/task.d.ts +63 -20
  82. package/src/task.js +326 -293
  83. package/src/testing.d.ts +42 -0
  84. package/src/webjs-config.d.ts +1 -8
  85. package/src/websocket-client.d.ts +24 -0
@@ -59,7 +59,7 @@ export function warnFunctionReflection(host, propName, attrName) {
59
59
  if (typeof console === 'undefined' || !console.warn) return;
60
60
  const tag = tagOf(/** @type any */ (host.constructor)) || host.tagName?.toLowerCase() || 'unknown';
61
61
  console.warn(
62
- `[webjs] reflect:true property "${propName}" on <${tag}> holds a function `
62
+ `[webjs] reflect:true property "${String(propName)}" on <${tag}> holds a function `
63
63
  + `(or an array carrying one), which has no HTML attribute representation. `
64
64
  + `Removing "${attrName}" instead of stringifying it (a stringified function `
65
65
  + `writes its source into the page, so a server action's body would ship to `
@@ -96,7 +96,7 @@ export function warnUnserializableReflection(host, propName, attrName, detail) {
96
96
  if (typeof console === 'undefined' || !console.warn) return;
97
97
  const tag = tagOf(/** @type any */ (host.constructor)) || host.tagName?.toLowerCase() || 'unknown';
98
98
  console.warn(
99
- `[webjs] reflect:true property "${propName}" on <${tag}> holds a value `
99
+ `[webjs] reflect:true property "${String(propName)}" on <${tag}> holds a value `
100
100
  + `JSON.stringify cannot serialize (a cycle, a BigInt, or a throwing `
101
101
  + `toJSON), so it has no HTML attribute representation. Removing `
102
102
  + `"${attrName}" instead. Detail: ${detail}`
@@ -133,14 +133,6 @@ export function warnUnserializableReflection(host, propName, attrName, detail) {
133
133
  * `changedProperties` is a `Map<string, unknown>` where each entry maps
134
134
  * a property name to its previous value.
135
135
  *
136
- * MAINTAINER NOTE. Adding a new overridable lifecycle hook here means
137
- * the display-only component elision analyser must learn about it too,
138
- * or it will wrongly elide a component that now does client work. Add
139
- * the hook name to `CLIENT_LIFECYCLE_HOOKS` in
140
- * `packages/server/src/component-elision.js`. The guard test at
141
- * `packages/server/test/elision/lifecycle-coverage.test.js` introspects
142
- * this prototype and fails until you do.
143
- *
144
136
  * Usage:
145
137
  * ```js
146
138
  * import { signal } from '@webjsdev/core';
@@ -44,7 +44,8 @@ export function makeServerInternals() {
44
44
  * reflected/added attributes in the output. Event methods are no-ops (no
45
45
  * server event loop), and `attachInternals()` returns the inert object
46
46
  * above. The genuinely browser-only surface (`querySelector`, layout reads,
47
- * `attachShadow`, `focus`) is deliberately absent and still throws at SSR,
47
+ * `focus`) is deliberately absent and still throws at SSR (`attachShadow`
48
+ * throws with the `static shadow = true` fix),
48
49
  * which the `no-browser-globals-in-render` rule and the SSR crash hint flag.
49
50
  *
50
51
  * Deliberate deviation from lit: this shim lowercases attribute names so
@@ -209,6 +210,27 @@ export class ServerElement {
209
210
  removeEventListener() {}
210
211
  dispatchEvent() { return true; }
211
212
 
213
+ /**
214
+ * There is no shadow root at SSR: the server writes a shadow component's
215
+ * root as Declarative Shadow DOM from `static shadow = true`, and the
216
+ * browser attaches it while parsing.
217
+ * @returns {null}
218
+ */
219
+ get shadowRoot() { return null; }
220
+
221
+ /**
222
+ * `attachShadow()` cannot run at SSR, which has no DOM. Called from a
223
+ * constructor or `render()` (Lit's habit of attaching in the constructor),
224
+ * it names the WebJs spelling instead of failing as an unknown method.
225
+ */
226
+ attachShadow() {
227
+ throw new Error(
228
+ '[webjs] attachShadow() was called during SSR, which has no DOM. Declare `static shadow = true` ' +
229
+ '(and `static shadowRootOptions` for the attachShadow options) instead: the server writes the shadow root ' +
230
+ 'as Declarative Shadow DOM and the browser attaches it.',
231
+ );
232
+ }
233
+
212
234
  /** @returns {any} */
213
235
  attachInternals() {
214
236
  // Match the browser (and lit's shim): a second attach is an error.
@@ -9,7 +9,7 @@
9
9
  * cost: nothing in this file ships to the browser.
10
10
  */
11
11
 
12
- import type { CSSResult } from './css.js';
12
+ import type { CSSResultGroup, CSSResultOrNative } from './css.js';
13
13
  import type { TemplateResult } from './html.js';
14
14
 
15
15
  /** Any constructor the framework accepts as the `type:` field of a property. */
@@ -22,23 +22,41 @@ export type PropertyConstructor<T = unknown> =
22
22
  | (new (...args: any[]) => T)
23
23
  | ((v: any) => T);
24
24
 
25
- /** Runtime-level property declaration: one entry in `WebComponent({ … })`. */
25
+ /** Runtime-level property declaration: one entry in `WebComponent({ … })` or in Lit's `static properties`. */
26
26
  export interface PropertyDeclaration<T = unknown> {
27
27
  /** Constructor used for string → value coercion when the attribute changes. */
28
28
  type?: PropertyConstructor<T>;
29
- /** Write property changes back to the HTML attribute (kebab-cased). */
29
+ /** Write property changes back to the HTML attribute (the lowercased property name unless `attribute` names one). */
30
30
  reflect?: boolean;
31
31
  /** Internal-only: no attribute, no reflection, but still reactive. */
32
32
  state?: boolean;
33
- /** Rename the attribute, or pass `false` to suppress it entirely. */
34
- attribute?: string | false;
35
- /** Custom attribute ⇄ property serialisation. Takes precedence over `type`. */
36
- converter?: {
37
- fromAttribute?: (value: string | null, type?: PropertyConstructor<T>) => T;
38
- toAttribute?: (value: T, type?: PropertyConstructor<T>) => string | null;
39
- };
33
+ /** Rename the attribute (a string), keep the default name (`true`), or suppress it (`false`). */
34
+ attribute?: string | boolean;
35
+ /**
36
+ * Custom attribute ⇄ property serialisation. Takes precedence over `type`.
37
+ * A bare function is the `fromAttribute` half (reflection then uses the
38
+ * default serialisation), as in Lit.
39
+ */
40
+ converter?:
41
+ | ((value: string | null, type?: PropertyConstructor<T>) => T)
42
+ | {
43
+ fromAttribute?: (value: string | null, type?: PropertyConstructor<T>) => T;
44
+ toAttribute?: (value: T, type?: PropertyConstructor<T>) => string | null;
45
+ };
46
+ /**
47
+ * Install no accessor: the class's own get/set stays and must call
48
+ * `this.requestUpdate(name, oldValue)` itself (Lit's `noAccessor`).
49
+ */
50
+ noAccessor?: boolean;
51
+ /**
52
+ * Lit's `useDefault`: the first value is the default. It is not reflected,
53
+ * and removing the attribute restores it instead of `null`.
54
+ */
55
+ useDefault?: boolean;
40
56
  /** Custom dirty check. Return `true` to schedule an update. */
41
57
  hasChanged?: (newValue: T, oldValue: T) => boolean;
58
+ /** Initial value, or a function called per instance for a fresh object / array. */
59
+ default?: T | (() => T);
42
60
  }
43
61
 
44
62
  /** Reactive controller protocol (Lit-compatible). */
@@ -49,12 +67,52 @@ export interface ReactiveController {
49
67
  hostUpdated?(): void;
50
68
  }
51
69
 
70
+ /**
71
+ * Lit's `ReactiveControllerHost`: what a controller may rely on from its host.
72
+ * Every `WebComponent` satisfies it, so a controller typed against it works
73
+ * with WebJs and Lit alike.
74
+ */
75
+ export interface ReactiveControllerHost {
76
+ addController(controller: ReactiveController): void;
77
+ removeController(controller: ReactiveController): void;
78
+ requestUpdate(): void;
79
+ readonly updateComplete: Promise<boolean>;
80
+ }
81
+
82
+ /** Lit's `ComplexAttributeConverter`: the object form of a property `converter`. */
83
+ export interface ComplexAttributeConverter<Type = unknown, TypeHint = unknown> {
84
+ fromAttribute?(value: string | null, type?: TypeHint): Type;
85
+ toAttribute?(value: Type, type?: TypeHint): unknown;
86
+ }
87
+
88
+ /** Lit's `PropertyDeclarations`: the shape of a `static properties` block. */
89
+ export interface PropertyDeclarations {
90
+ readonly [key: string]: PropertyDeclaration | PropertyConstructor;
91
+ }
92
+
93
+ /**
94
+ * Lit's `PropertyValueMap<T>`: a `changedProperties` Map whose keys and old
95
+ * values are typed against the component (`PropertyValues<this>`).
96
+ */
97
+ export interface PropertyValueMap<T> extends Map<PropertyKey, unknown> {
98
+ get<K extends keyof T>(k: K): T[K] | undefined;
99
+ set<K extends keyof T>(key: K, value: T[K]): this;
100
+ has<K extends keyof T>(k: K): boolean;
101
+ delete<K extends keyof T>(k: K): boolean;
102
+ }
103
+
104
+ /**
105
+ * Lit's `PropertyValues`: the `changedProperties` argument of the lifecycle
106
+ * hooks. `PropertyValues<this>` types its keys against the component.
107
+ */
108
+ export type PropertyValues<T = any> = T extends object ? PropertyValueMap<T> : Map<PropertyKey, unknown>;
109
+
52
110
  /**
53
111
  * Base class for interactive web components.
54
112
  *
55
- * Declare reactive properties via the `extends WebComponent({ … })`
56
- * factory. It installs the reactive accessor AND types each field for
57
- * you (no `declare`, no `static properties`):
113
+ * Declare reactive properties in either of two equivalent ways. The
114
+ * `extends WebComponent({ … })` factory installs the reactive accessor AND
115
+ * types each field for you (no `declare` needed):
58
116
  *
59
117
  * class StudentCard extends WebComponent({ student: prop<Student>(Object) }) {
60
118
  * render() { return html`<p>${this.student.name}</p>`; }
@@ -64,10 +122,18 @@ export interface ReactiveController {
64
122
  * A bare constructor type covers the common case (`WebComponent({ count:
65
123
  * Number })` gives `this.count: number`); use the `prop()` helper for
66
124
  * options (`prop(Number, { reflect: true })`) or to narrow the type
67
- * (`prop<Student>(Object)`). Set defaults via the `default` option or in
68
- * the constructor, never a class-field initializer (it runs after super()
69
- * and clobbers the reactive accessor). A hand-written `static properties`
70
- * in a class body is no longer supported and throws at construction.
125
+ * (`prop<Student>(Object)`). Lit's own spelling works too, with an erasable
126
+ * `declare` for the type:
127
+ *
128
+ * class Counter extends WebComponent {
129
+ * static properties = { count: { type: Number, reflect: true } };
130
+ * declare count: number;
131
+ * }
132
+ *
133
+ * Props merge down the class chain as in Lit, so a subclass's own
134
+ * `static properties` adds to its parent's. Set defaults via the `default`
135
+ * option or in the constructor, never a class-field initializer (it runs
136
+ * after super() and clobbers the reactive accessor).
71
137
  *
72
138
  * See the Editor Setup doc for tooling that gives attribute/tag
73
139
  * intelligence inside `html\`…\`` templates.
@@ -91,7 +157,7 @@ interface WebComponentBase extends HTMLElement {
91
157
  * wires every signal the update cycle reads to a re-render automatically;
92
158
  * explicit `requestUpdate()` is rarely needed.
93
159
  */
94
- requestUpdate(name?: string, oldValue?: unknown): void;
160
+ requestUpdate(name?: PropertyKey, oldValue?: unknown, options?: PropertyDeclaration): void;
95
161
  /** A Promise that resolves after the next render commit. */
96
162
  readonly updateComplete: Promise<boolean>;
97
163
  /** Override point for `updateComplete`. Default returns the internal promise. */
@@ -100,18 +166,40 @@ interface WebComponentBase extends HTMLElement {
100
166
  addController(controller: ReactiveController): void;
101
167
  /** Detach a reactive controller. */
102
168
  removeController(controller: ReactiveController): void;
103
- /** Returns the template for this render. May be async. */
104
- render(): TemplateResult | Promise<TemplateResult> | void;
169
+ /** Returns the template for this render. May be async. The default returns `noChange`. */
170
+ render(): unknown;
171
+ /**
172
+ * The node `render()` commits into: the element itself for light DOM, its
173
+ * shadow root for `static shadow = true`. Undefined until the element
174
+ * connects, and at SSR.
175
+ */
176
+ renderRoot: HTMLElement | ShadowRoot | DocumentFragment | undefined;
177
+ /** True once the first update has completed. */
178
+ readonly hasUpdated: boolean;
179
+ /** True while an update is queued or running. */
180
+ readonly isUpdatePending: boolean;
181
+ /**
182
+ * Create the render root, once per element. The default returns the element
183
+ * for light DOM, and for `static shadow = true` attaches the shadow root
184
+ * with `static shadowRootOptions` and adopts `static styles`.
185
+ */
186
+ createRenderRoot(): HTMLElement | ShadowRoot | DocumentFragment;
187
+ /** Called from the update microtask; override to change when an update runs. */
188
+ scheduleUpdate(): void | Promise<unknown>;
189
+ /** Run the pending update now, synchronously. No-op when nothing is pending. */
190
+ performUpdate(): void;
191
+ /** Called on connect, right before the first update is queued. */
192
+ enableUpdating(requestedUpdate: boolean): void;
105
193
  /** Decide whether to update. Default returns `true`. */
106
- shouldUpdate(changedProperties: Map<string, unknown>): boolean;
194
+ shouldUpdate(changedProperties: PropertyValues): boolean;
107
195
  /** Pre-render hook. Safe to set properties; folds into current cycle. */
108
- willUpdate(changedProperties: Map<string, unknown>): void;
196
+ willUpdate(changedProperties: PropertyValues): void;
109
197
  /** Render-and-commit step. Default calls `render()` and commits. */
110
- update(changedProperties: Map<string, unknown>): void;
198
+ update(changedProperties: PropertyValues): void;
111
199
  /** Post-render hook. Runs after every commit. */
112
- updated(changedProperties: Map<string, unknown>): void;
200
+ updated(changedProperties: PropertyValues): void;
113
201
  /** One-shot hook after the first render lands in the DOM. */
114
- firstUpdated?(changedProperties: Map<string, unknown>): void;
202
+ firstUpdated?(changedProperties: PropertyValues): void;
115
203
  /** Optional render-error boundary inside the component. */
116
204
  renderError?(error: Error): TemplateResult | void;
117
205
  /**
@@ -137,6 +225,11 @@ export type Infer<C> =
137
225
  C extends BooleanConstructor ? boolean :
138
226
  C extends NumberConstructor ? number :
139
227
  C extends StringConstructor ? string :
228
+ // A bare Array / Object says nothing about the element type, so it is the
229
+ // open type an undeclared Lit property has, not unknown: a typed callback
230
+ // (`items.filter((m: Message) => ...)`) has to compile, as it would in Lit.
231
+ C extends ArrayConstructor ? any[] :
232
+ C extends ObjectConstructor ? any :
140
233
  C extends new (...a: any[]) => infer T ? T :
141
234
  C extends (v: any) => infer T ? T : unknown;
142
235
 
@@ -153,24 +246,34 @@ export type InferProps<S> = {
153
246
  */
154
247
  interface WebComponentStatics {
155
248
  shadow: boolean;
249
+ /** The `attachShadow()` options, also written onto the server's Declarative Shadow DOM template. */
250
+ shadowRootOptions: ShadowRootInit;
156
251
  hydrate: 'visible' | undefined;
157
- properties: Record<string, PropertyDeclaration>;
158
- styles: CSSResult | CSSResult[] | null;
252
+ properties: Record<string, PropertyDeclaration | PropertyConstructor>;
253
+ styles: CSSResultGroup | null | undefined;
254
+ /** `static styles` flattened by `finalizeStyles()`: what the shadow root adopts. */
255
+ readonly elementStyles: CSSResultOrNative[];
256
+ /** Flatten and dedupe `static styles`; override to post-process. */
257
+ finalizeStyles(styles?: CSSResultGroup | null): CSSResultOrNative[];
159
258
  lazy?: boolean;
160
- /**
161
- * Force this component to ship (hydrate) even when the elision analyser would
162
- * drop it as display-only. The escape hatch for interactivity the analyser
163
- * cannot see statically: an OBSERVER that computes the tag it waits for, a
164
- * `:defined` rule in an external stylesheet outside the module graph, or a
165
- * consumer reaching the element through a string selector. It
166
- * cannot rescue a component whose OWN registration tag is computed, since
167
- * the scanner requires a literal tag (invariant 3) and never sees that
168
- * component at all. Leave unset for the automatic verdict; inspect it with
169
- * `webjs elision`.
170
- */
171
- interactive?: boolean;
172
259
  register(tag: string): void;
173
260
  readonly observedAttributes: string[];
261
+ /** Every declared reactive property, own and inherited, as name to declaration. */
262
+ readonly elementProperties: Map<PropertyKey, PropertyDeclaration>;
263
+ /** The declaration for `name`, or the default `{ type: String }`. */
264
+ getPropertyOptions(name: PropertyKey): PropertyDeclaration;
265
+ /** Declare a reactive property from code. Must run before the first instance. */
266
+ createProperty(name: PropertyKey, options?: PropertyDeclaration): void;
267
+ /**
268
+ * The accessor installed on the prototype for one reactive property.
269
+ * Override to wrap `super.getPropertyDescriptor(...)`; return `undefined`
270
+ * to install none.
271
+ */
272
+ getPropertyDescriptor(name: PropertyKey, key: string | symbol, options: PropertyDeclaration): PropertyDescriptor | undefined;
273
+ /** Prepare the class metadata once. Called for you; idempotent. */
274
+ finalize(): void;
275
+ /** Run `initializer(element)` in the constructor of every instance of this class and its subclasses. */
276
+ addInitializer(initializer: (element: WebComponentBase) => void): void;
174
277
  }
175
278
 
176
279
  export interface WebComponentConstructor extends WebComponentStatics {
@@ -211,10 +314,17 @@ export declare function prop<T>(
211
314
  ): PropertyDeclaration<T>;
212
315
 
213
316
  // No type constructor and no attribute: nothing says what the value is, so it
214
- // is `unknown` until narrowed (`prop<boolean>({ state: true })`).
317
+ // is `unknown` until narrowed (`prop<boolean>({ state: true })`). With a
318
+ // `default` it holds a `T` from the start; without one nothing fills it until
319
+ // the component sets it, so it is `T | undefined`, Lit's `@state() error?:
320
+ // string` (#1686: an agent clearing such a prop with `undefined` failed
321
+ // typecheck against a type that said it was never undefined).
215
322
  export declare function prop<T = unknown>(
216
- opts: PropertyDeclaration<T> & ({ state: true } | { attribute: false })
323
+ opts: PropertyDeclaration<T> & ({ state: true } | { attribute: false }) & { default: T | (() => T) }
217
324
  ): PropertyDeclaration<T>;
325
+ export declare function prop<T = unknown>(
326
+ opts: PropertyDeclaration<T> & ({ state: true } | { attribute: false })
327
+ ): PropertyDeclaration<T | undefined>;
218
328
 
219
329
  export declare function prop<T = string>(
220
330
  opts?: PropertyDeclaration<T>
package/src/context.d.ts CHANGED
@@ -1,34 +1,110 @@
1
- import type { ReactiveController, WebComponent } from './component.js';
1
+ /**
2
+ * @license
3
+ * Portions derived from Lit (`@lit/context`), Copyright Google LLC, BSD-3-Clause.
4
+ */
5
+ import type { ReactiveController } from './component.js';
2
6
 
3
- export interface Context<T = unknown> {
4
- __context__: symbol;
5
- name: string;
7
+ /** Any element that hosts reactive controllers (a WebComponent). */
8
+ interface ContextHost extends HTMLElement {
9
+ addController(controller: ReactiveController): void;
10
+ requestUpdate(): void;
6
11
  }
7
12
 
8
- export function createContext<T = unknown>(name: string): Context<T>;
13
+ /**
14
+ * A context key carrying its value type. At runtime it is the key itself (a
15
+ * string, a Symbol, an object), and the key IS the identity.
16
+ */
17
+ export type Context<KeyType, ValueType> = KeyType & { __context__: ValueType };
9
18
 
10
- export class ContextRequestEvent<T = unknown> extends Event {
11
- context: Context<T>;
12
- callback: (value: T, unsubscribe?: () => void) => void;
13
- subscribe: boolean;
19
+ /** The value type a context carries. */
20
+ export type ContextType<Key extends Context<unknown, unknown>> =
21
+ Key extends Context<unknown, infer ValueType> ? ValueType : never;
22
+
23
+ /** Returns `key`, typed as a context of `ValueType`. */
24
+ export function createContext<ValueType, K = unknown>(key: K): Context<K, ValueType>;
25
+
26
+ export type ContextCallback<ValueType> = (value: ValueType, unsubscribe?: () => void) => void;
27
+
28
+ export class ContextRequestEvent<C extends Context<unknown, unknown>> extends Event {
29
+ readonly context: C;
30
+ readonly contextTarget: Element;
31
+ readonly callback: ContextCallback<ContextType<C>>;
32
+ readonly subscribe: boolean;
14
33
  constructor(
15
- context: Context<T>,
16
- callback: (value: T, unsubscribe?: () => void) => void,
34
+ context: C,
35
+ contextTarget: Element,
36
+ callback: ContextCallback<ContextType<C>>,
17
37
  subscribe?: boolean,
18
38
  );
19
39
  }
20
40
 
21
- export class ContextProvider<T = unknown> implements ReactiveController {
22
- constructor(host: WebComponent, options: { context: Context<T>; initialValue?: T });
41
+ export class ContextProviderEvent<C extends Context<unknown, unknown>> extends Event {
42
+ readonly context: C;
43
+ readonly contextTarget: Element;
44
+ constructor(context: C, contextTarget: Element);
45
+ }
46
+
47
+ declare global {
48
+ interface HTMLElementEventMap {
49
+ 'context-request': ContextRequestEvent<Context<unknown, unknown>>;
50
+ 'context-provider': ContextProviderEvent<Context<unknown, unknown>>;
51
+ }
52
+ }
53
+
54
+ export class ValueNotifier<T> {
55
+ protected readonly subscriptions: Map<ContextCallback<T>, { disposer: () => void; consumerHost: Element }>;
56
+ constructor(defaultValue?: T);
23
57
  get value(): T;
24
- setValue(newValue: T): void;
58
+ set value(v: T);
59
+ setValue(v: T, force?: boolean): void;
60
+ updateObservers: () => void;
61
+ addCallback(callback: ContextCallback<T>, consumerHost: Element, subscribe?: boolean): void;
62
+ clearCallbacks(): void;
63
+ }
64
+
65
+ export interface ContextProviderOptions<C extends Context<unknown, unknown>> {
66
+ context: C;
67
+ initialValue?: ContextType<C>;
68
+ }
69
+
70
+ export class ContextProvider<
71
+ T extends Context<unknown, unknown>,
72
+ HostElement extends ContextHost = ContextHost,
73
+ > extends ValueNotifier<ContextType<T>> implements ReactiveController {
74
+ protected readonly host: HostElement;
75
+ constructor(host: HostElement, options: ContextProviderOptions<T>);
76
+ constructor(host: HostElement, context: T, initialValue?: ContextType<T>);
77
+ onContextRequest: (ev: ContextRequestEvent<Context<unknown, unknown>>) => void;
78
+ onProviderRequest: (ev: ContextProviderEvent<Context<unknown, unknown>>) => void;
25
79
  hostConnected(): void;
26
- hostDisconnected(): void;
27
80
  }
28
81
 
29
- export class ContextConsumer<T = unknown> implements ReactiveController {
30
- constructor(host: WebComponent, options: { context: Context<T>; subscribe?: boolean });
31
- get value(): T | undefined;
82
+ export interface ContextConsumerOptions<C extends Context<unknown, unknown>> {
83
+ context: C;
84
+ callback?: (value: ContextType<C>, dispose?: () => void) => void;
85
+ /** Keep receiving changes. Default `false`, a one-time read per connection. */
86
+ subscribe?: boolean;
87
+ }
88
+
89
+ export class ContextConsumer<
90
+ C extends Context<unknown, unknown>,
91
+ HostElement extends ContextHost = ContextHost,
92
+ > implements ReactiveController {
93
+ protected host: HostElement;
94
+ value?: ContextType<C>;
95
+ constructor(host: HostElement, options: ContextConsumerOptions<C>);
96
+ constructor(
97
+ host: HostElement,
98
+ context: C,
99
+ callback?: (value: ContextType<C>, dispose?: () => void) => void,
100
+ subscribe?: boolean,
101
+ );
32
102
  hostConnected(): void;
33
103
  hostDisconnected(): void;
34
104
  }
105
+
106
+ /** Buffers unanswered subscribing requests and replays them when a provider connects. */
107
+ export class ContextRoot {
108
+ attach(element: HTMLElement): void;
109
+ detach(element: HTMLElement): void;
110
+ }