Skip to content

Commit

Permalink
Update TypeScript interface (#22)
Browse files Browse the repository at this point in the history
* Update the interface. It changed!

* update after interface changes
  • Loading branch information
koddsson committed May 19, 2024
1 parent b93882e commit 545112b
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 40 deletions.
34 changes: 14 additions & 20 deletions context-protocol.ts
Original file line number Diff line number Diff line change
@@ -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<T> = {
name: string;
initialValue?: T;
};
export type Context<KeyType, ValueType> = KeyType & { __context__: ValueType };

/**
* An unknown context type
*/
export type UnknownContext = Context<unknown>;
export type UnknownContext = Context<unknown, unknown>;

/**
* A helper type which can extract a Context value type from a Context type
*/
export type ContextType<T extends UnknownContext> =
T extends Context<infer Y> ? Y : never;
T extends Context<infer _, infer V> ? V : never;

/**
* A function which creates a Context value object
*/
export function createContext<T>(
name: string,
initialValue?: T,
): Readonly<Context<T>> {
return {
name,
initialValue,
};
}
export const createContext = <ValueType>(key: unknown) =>
key as Context<typeof key, ValueType>;

/**
* A callback which is provided by a context requester and is called with the value satisfying the request.
Expand All @@ -51,7 +45,7 @@ export type ContextCallback<ValueType> = (
* 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<T extends UnknownContext> extends Event {
export class ContextRequestEvent<T extends UnknownContext> extends Event {
public constructor(
public readonly context: T,
public readonly callback: ContextCallback<ContextType<T>>,
Expand All @@ -67,12 +61,12 @@ export class ContextEvent<T extends UnknownContext> extends Event {
*/
declare global {
interface WindowEventMap {
"context-request": ContextEvent<UnknownContext>;
"context-request": ContextRequestEvent<UnknownContext>;
}
interface ElementEventMap {
"context-request": ContextEvent<UnknownContext>;
"context-request": ContextRequestEvent<UnknownContext>;
}
interface HTMLElementEventMap {
"context-request": ContextEvent<UnknownContext>;
"context-request": ContextRequestEvent<UnknownContext>;
}
}
25 changes: 11 additions & 14 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ObservableMap } from "./observable-map.js";
import {
createContext,
ContextEvent,
Context,
ContextRequestEvent,
UnknownContext,
} from "./context-protocol.js";

Expand All @@ -27,7 +28,7 @@ export declare type Constructor<T> = new (...args: any[]) => T;

type ProviderElement = CustomElement & {
contexts?: Record<PropertyKey, () => unknown>;
updateContext?(name: string, value: unknown): void;
updateContext?(name: PropertyKey, value: unknown): void;
};

type ConsumerElement = CustomElement & {
Expand Down Expand Up @@ -57,18 +58,14 @@ export function ProviderMixin<T extends Constructor<ProviderElement>>(
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<UnknownContext>) {
const { name, initialValue } = event.context;
#handleContextRequest(event: ContextRequestEvent<UnknownContext>) {
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();

Expand All @@ -93,11 +90,11 @@ export function ConsumerMixin<T extends Constructor<ConsumerElement>>(
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;
}),
);
Expand All @@ -116,7 +113,7 @@ export function ConsumerMixin<T extends Constructor<ConsumerElement>>(
// 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);
Expand Down
15 changes: 9 additions & 6 deletions observable-map.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
type Subscriber<T> = (value: T) => void;

export class ObservableMap {
#store = new Map<string, {value: unknown, subscribers: Set<Subscriber<unknown>>}>
export class ObservableMap<K, V> {
#store = new Map<K, { value: V; subscribers: Set<Subscriber<V>> }>();

set(key: string, value: unknown, subscribers = new Set<Subscriber<unknown>>()) {
set(key: K, value: V, subscribers = new Set<Subscriber<V>>()) {
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);
}
}

0 comments on commit 545112b

Please sign in to comment.