diff --git a/context-protocol.ts b/context-protocol.ts index 6b8873e..69c501b 100644 --- a/context-protocol.ts +++ b/context-protocol.ts @@ -1,36 +1,30 @@ // From: https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md#definitions /** - * A Context object defines an optional initial value for a Context, as well as a name identifier for debugging purposes. + * A context key. + * + * A context key can be any type of object, including strings and symbols. The + * Context type brands the key type with the `__context__` property that + * carries the type of the value the context references. */ -export type Context = { - name: string; - initialValue?: T; -}; +export type Context = KeyType & { __context__: ValueType }; /** * An unknown context type */ -export type UnknownContext = Context; +export type UnknownContext = Context; /** * A helper type which can extract a Context value type from a Context type */ export type ContextType = - T extends Context ? Y : never; + T extends Context ? V : never; /** * A function which creates a Context value object */ -export function createContext( - name: string, - initialValue?: T, -): Readonly> { - return { - name, - initialValue, - }; -} +export const createContext = (key: unknown) => + key as Context; /** * A callback which is provided by a context requester and is called with the value satisfying the request. @@ -51,7 +45,7 @@ export type ContextCallback = ( * multiple times if the value is changed, if this is the case the provider should pass an `unsubscribe` * function to the callback which requesters can invoke to indicate they no longer wish to receive these updates. */ -export class ContextEvent extends Event { +export class ContextRequestEvent extends Event { public constructor( public readonly context: T, public readonly callback: ContextCallback>, @@ -67,12 +61,12 @@ export class ContextEvent extends Event { */ declare global { interface WindowEventMap { - "context-request": ContextEvent; + "context-request": ContextRequestEvent; } interface ElementEventMap { - "context-request": ContextEvent; + "context-request": ContextRequestEvent; } interface HTMLElementEventMap { - "context-request": ContextEvent; + "context-request": ContextRequestEvent; } } diff --git a/index.ts b/index.ts index 84ae654..9866fc0 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,8 @@ import { ObservableMap } from "./observable-map.js"; import { createContext, - ContextEvent, + Context, + ContextRequestEvent, UnknownContext, } from "./context-protocol.js"; @@ -27,7 +28,7 @@ export declare type Constructor = new (...args: any[]) => T; type ProviderElement = CustomElement & { contexts?: Record unknown>; - updateContext?(name: string, value: unknown): void; + updateContext?(name: PropertyKey, value: unknown): void; }; type ConsumerElement = CustomElement & { @@ -57,18 +58,14 @@ export function ProviderMixin>( this.removeEventListener("context-request", this.#handleContextRequest); } - updateContext(name: string, value: unknown) { - this.#dataStore.set(name, value); + updateContext(name: PropertyKey, value: unknown) { + this.#dataStore.set(createContext(name), value); } // We listen for a bubbled context request event and provide the event with the context requested. - #handleContextRequest(event: ContextEvent) { - const { name, initialValue } = event.context; + #handleContextRequest(event: ContextRequestEvent) { const subscribe = event.subscribe; - if (initialValue) { - this.#dataStore.set(name, initialValue); - } - const data = this.#dataStore.get(name); + const data = this.#dataStore.get(event.context); if (data) { event.stopPropagation(); @@ -93,11 +90,11 @@ export function ConsumerMixin>( return class extends Class { #unsubscribes: Array<() => void> = []; - getContext(contextName: string) { - let result; + getContext(contextName: PropertyKey) { + let result: unknown; this.dispatchEvent( - new ContextEvent(createContext(contextName), (data) => { + new ContextRequestEvent(createContext(contextName), (data) => { result = data; }), ); @@ -116,7 +113,7 @@ export function ConsumerMixin>( // reaches a component that is able to provide that value to us. // The event has a callback for the the value. this.dispatchEvent( - new ContextEvent( + new ContextRequestEvent( context, (data, unsubscribe) => { callback(data); diff --git a/observable-map.ts b/observable-map.ts index e3bf3e3..1b7e710 100644 --- a/observable-map.ts +++ b/observable-map.ts @@ -1,19 +1,22 @@ type Subscriber = (value: T) => void; -export class ObservableMap { - #store = new Map>}> +export class ObservableMap { + #store = new Map> }>(); - set(key: string, value: unknown, subscribers = new Set>()) { + set(key: K, value: V, subscribers = new Set>()) { const data = this.#store.get(key); - subscribers = new Set([...subscribers, ...(data?.subscribers || new Set())]); + subscribers = new Set([ + ...subscribers, + ...(data?.subscribers || new Set()), + ]); - this.#store.set(key, {value, subscribers}); + this.#store.set(key, { value, subscribers }); for (const subscriber of subscribers) { subscriber(value); } } - get(key: string) { + get(key: K) { return this.#store.get(key); } }