Context Protocol

HTMLGameKit implements the Context Community Protocol for DOM-based data sharing between components that don't share a common shell ancestor.

Signals vs Context

For almost all game data, use signals. The shell exposes all game state as Signal.State instances accessible via this.shell in any GameComponent. This covers scene, score, round, difficulty, stats, sprite sheet URL, and everything else the shell manages.

effectCallback({ scene, score, spriteSheet }) {
  // reactive access to any shell state — no context needed
}

Context is the right choice only when the data provider is not the shell — that is, when you have a custom element that needs to distribute its own data to its DOM descendants, and those descendants don't have another way to reach it.

The canonical example is <game-word-source>: it fetches a word and needs to share it with <word-game> below it. Neither is the shell. Context is the natural fit.

// Provider (game-word-source)
this.#provider = new ContextProvider(this, gameWordContext, "");
this.#provider.setValue("crane");

// Consumer (word-game, inside connectedCallback)
this.subscribe(gameWordContext, (word) => {
  this.#target = word;
});

If you find yourself using context to share something that the shell already knows about, use signals instead.


Import

import {
  createContext,
  ContextProvider,
  subscribe,
  ContextRequestEvent,
} from "htmlgamekit";

createContext(key)

createContext(key)
Returns a context identifier. Two calls with the same key return equal contexts, so they can be compared across modules.

Parameters:

  • key -- string -- A unique string identifier for the context.

Returns: A context object suitable for use with ContextProvider and subscribe.

export const gameWordContext = createContext("game-word");

Export the context object from the module that defines it so consumers can import it.


ContextProvider

Provides a context value to any descendant that requests it.

new ContextProvider(host, context, initialValue)
Creates a new provider attached to host. Immediately begins listening for context-request events from descendants.

Parameters:

  • host -- HTMLElement -- The element that owns this provider.
  • context -- A context object from createContext.
  • initialValue -- The initial value to provide.
connectedCallback() {
  this.#provider = new ContextProvider(this, gameWordContext, "");
  super.connectedCallback();
}
.setValue(value)
Update the provided value. All active subscribers are notified synchronously. Skips notification if the new value is strictly equal to the current one.
this.#provider.setValue("crane");
.value
any -- The current provided value (read-only).

this.subscribe(context, callback) on GameComponent

The GameComponent base class provides a subscribe method that handles cleanup automatically — the subscription is released when the component disconnects.

connectedCallback() {
  this.subscribe(gameWordContext, (word) => {
    this.#target = word;
  });
  super.connectedCallback();
}

This is the recommended way to consume context inside a GameComponent. The subscription fires immediately with the current value and again on every update.


subscribe(host, context, callback, options?) (standalone)

For consuming context outside of a GameComponent:

subscribe(host, context, callback, options?)
Dispatches a context-request event on host. When a provider above responds, the callback fires immediately with the current value and again on every subsequent update.

Parameters:

  • host -- HTMLElement -- The subscribing element.
  • context -- A context object.
  • callback -- (value) => void -- Called with each value update.
  • options -- { signal?: AbortSignal } -- Pass an AbortSignal to automatically unsubscribe when it aborts.

When the signal aborts, the subscription is removed and the provider releases its reference to the callback.


GC Safety

ContextProvider holds subscriber callbacks via WeakRef. If a subscribing element is garbage-collected without explicitly unsubscribing, the provider won't hold it alive. To prevent premature collection (before the element is actually removed), HTMLGameKit stores a strong reference to each callback keyed by its AbortSignal, releasing it when the signal aborts. This is transparent — you don't need to interact with it directly.