import { ContextMap } from './context-map'; /** * Options for {@link ContextRegistry.scope}. * * Two knobs, both optional: * * - `host`, start a DOM walk from this element. Typical for components. * - `with`, hand in fixed values for some context keys. Typical for * tests or headless code that has no DOM host. * * They compose: pass both and `with` overrides whatever the DOM * provides for the named keys. Pass neither and `get` returns * `undefined` for everything. * * @beta * @group Context */ export interface ContextScopeOptions { /** * Element to start looking from when resolving context keys that * come from the DOM. */ host?: HTMLElement; /** * Fixed values for specific context keys. Wins over anything the * DOM or a session-wide default would provide. */ with?: Partial; } /** * A host-bound, typed view over {@link ContextMap}. Resolves each key * its own way: by walking the DOM, by consulting a session-wide * default, or by reading from a fixed value bag. The caller picks * which strategy when they build the scope with * {@link ContextRegistry.scope}. * * One method, `get(key)`. A consumer that needs more than one key * calls `get` multiple times. * * Each key resolves in order from most specific to least: a per-call * override on {@link ContextRegistry.scope} wins first, then the * closest DOM ancestor that registered a value through * {@link ContextRegistry.provide | ContextRegistry.provide}, then a * session-wide default registered through * {@link ContextRegistry.register | ContextRegistry.register}. If none * of those answer, `get` returns `undefined`. * * Resolution is pull-based, not reactive: a `ContextScope` answers * the current value on each `get` call but does not notify when a * value changes. Re-evaluating against a scope when its inputs change * is the caller's responsibility. For reactive consumption of context * values, use {@link ContextRegistry.subscribe | ContextRegistry.subscribe} * directly. * * Rules are one consumer of this contract, but not the only one. * Command guards, filter predicates, and any other code that wants a * "bind a host plus overrides once, then read keys repeatedly" helper * builds a `ContextScope` and reads from it. * * @example Reading and extending a scope * ```ts * import { Element } from '@stencil/core'; * import { * Action, * Command, * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * @Command({ id: 'archive-record' }) * class ArchiveRecordCommand {} * * const logger = createLogger('my-component'); * * class MyListComponent { * @Element() * private host: HTMLElement; * * private actions: Action[] = [ * { * label: 'Archive', * icon: 'archive', * command: new ArchiveRecordCommand(), * }, * ]; * * public connectedCallback() { * const registry = platform.get(PlatformServiceName.ContextRegistry); * const scope = registry.scope({ host: this.host }); * const user = scope.get('user'); * * for (const action of this.actions) { * // Same host, one extra pinned key per row. * const rowScope = scope.extend({ action }); * * logger.debug('Row scope', { * username: user?.username, * action: rowScope.get('action')?.label, * }); * } * } * } * ``` * * @see {@link ContextRegistry.scope}, the only supported way to construct a `ContextScope`. * @see {@link ContextMap}, what keys exist and what they return. * * @beta * @group Context */ export interface ContextScope { /** * Look up the current value of a context key. * * @param key - The context id. Must be one of the keys the scope's * consumer declared upfront (e.g. a primitive's `reads`), or any * {@link ContextMap} key when the scope is used through the wide * default. * @returns The value if any provider answered, otherwise `undefined`. */ get(key: K): ContextMap[K] | undefined; /** * Derive a new scope that layers fixed `overrides` on top of this * one. The child wins for the keys it names; every other key falls * through to this scope's own resolution (its `with`, then its DOM * walk from `host`, then the session-wide fallback). * * Use it when a host-bound scope is built once and handed down, but a * per-item consumer needs to pin a few more values without re-stating * the host. A list view builds `scope({ host })` once; each row action * evaluates against `scope.extend({ action })`. * * Overrides are **pinned**, exactly like the `with` passed to * {@link ContextRegistry.scope}: they are fixed values, not reactive * providers. A value that must change over time has to come from a * provider resolved through the scope's `host`, not from `extend`. See * {@link RuleRegistry.subscribe}, which treats pinned keys as constant. * * `extend` does not change the host; the derived scope walks the same * DOM anchor as its parent. To anchor elsewhere, build a fresh scope * with {@link ContextRegistry.scope}. * * Chainable: the result is itself a `ContextScope`, so it can be * extended again. Later overrides win over earlier ones for the same * key. * * The pinned keys join this scope's read set: the returned scope's * `get` accepts everything this scope could read plus every key in * `overrides`. Narrowing from the parent is preserved, not discarded. * * @param overrides - Fixed values to pin on top of this scope. Wins * over anything this scope would otherwise resolve for those keys. * @returns A new scope reading `TReads` plus the pinned keys; this * scope is left unchanged. */ extend(overrides: Pick): ContextScope; }