import type { Keymap } from "./keymap.js"; export interface KeymapEvent { name: string; ctrl: boolean; shift: boolean; meta: boolean; super?: boolean; hyper?: boolean; preventDefault(): void; stopPropagation(): void; readonly propagationStopped: boolean; } export type HostPlatform = "macos" | "windows" | "linux" | "unknown"; export type HostModifier = "ctrl" | "shift" | "meta" | "super" | "hyper"; export type HostCapability = "supported" | "unsupported" | "unknown"; export interface HostMetadata { platform: HostPlatform; primaryModifier: "ctrl" | "super" | "unknown"; modifiers: Record; } export interface KeymapHost { readonly metadata: HostMetadata; readonly rootTarget: TTarget; readonly isDestroyed: boolean; getFocusedTarget(): TTarget | null; getParentTarget(target: TTarget): TTarget | null; isTargetDestroyed(target: TTarget): boolean; onKeyPress(listener: (event: TEvent) => void): () => void; onKeyRelease(listener: (event: TEvent) => void): () => void; onFocusChange(listener: (target: TTarget | null) => void): () => void; /** Optional for hosts whose lifetime is managed by GC or root reachability. */ onDestroy?(listener: () => void): () => void; onTargetDestroy(target: TTarget, listener: () => void): () => void; onRawInput?(listener: (sequence: string) => boolean): () => void; createCommandEvent(): TEvent; } export type EventData = Record; export type Attributes = Record; export interface KeyStrokeInput { name: string; ctrl?: boolean; shift?: boolean; meta?: boolean; super?: boolean; hyper?: boolean; } export interface NormalizedKeyStroke extends KeyStrokeInput { ctrl: boolean; shift: boolean; meta: boolean; super: boolean; } export type KeyMatch = string; export interface EventMatchResolverContext { resolveKey(key: KeyLike): KeyMatch; } export type EventMatchResolver = (event: TEvent, ctx: EventMatchResolverContext) => readonly KeyMatch[] | undefined; export declare const KEY_DISAMBIGUATION_DECISION: unique symbol; export declare const KEY_DEFERRED_DISAMBIGUATION_DECISION: unique symbol; export interface KeyDisambiguationDecision { readonly [KEY_DISAMBIGUATION_DECISION]: true; } export interface KeyDeferredDisambiguationDecision { readonly [KEY_DEFERRED_DISAMBIGUATION_DECISION]: true; } export interface KeyDisambiguationContext { readonly event: Readonly>; readonly focused: TTarget | null; readonly sequence: readonly KeySequencePart[]; readonly stroke: KeySequencePart; readonly exact: readonly ActiveBinding[]; readonly continuations: readonly ActiveKey[]; getData(name: string): unknown; setData(name: string, value: unknown): void; runExact(): KeyDisambiguationDecision; continueSequence(): KeyDisambiguationDecision; clear(): KeyDisambiguationDecision; defer(run: KeyDeferredDisambiguationHandler): KeyDisambiguationDecision; } export interface KeyDeferredDisambiguationContext { readonly signal: AbortSignal; readonly sequence: readonly KeySequencePart[]; readonly focused: TTarget | null; sleep(ms: number): Promise; runExact(): KeyDeferredDisambiguationDecision; continueSequence(): KeyDeferredDisambiguationDecision; clear(): KeyDeferredDisambiguationDecision; } export type KeyDeferredDisambiguationHandler = (ctx: KeyDeferredDisambiguationContext) => KeyDeferredDisambiguationDecision | void | Promise; export type KeyDisambiguationResolver = (ctx: KeyDisambiguationContext) => KeyDisambiguationDecision | undefined; export interface ResolvedKeyToken { stroke: NormalizedKeyStroke; match: KeyMatch; } export interface KeySequencePart { stroke: NormalizedKeyStroke; display: string; match: KeyMatch; tokenName?: string; patternName?: string; payloadKey?: string; } export interface StringifyOptions { preferDisplay?: boolean; separator?: string; } export type KeyStringifyInput = KeyStrokeInput | NormalizedKeyStroke | KeySequencePart | { stroke: NormalizedKeyStroke; display?: string; }; export type KeyLike = string | KeyStrokeInput; /** * Public command shape used for layer registration, command queries, command * contexts, and command resolver results. Custom command fields are top-level * properties so registration stays as simple as `{ name, run, desc }`. */ export interface Command { name: string; run(ctx: CommandContext): CommandResult; [key: string]: unknown; } export type CommandQueryValue = unknown | readonly unknown[] | ((value: unknown, command: Command) => boolean); export type CommandFilter = Readonly>> | ((command: Command) => boolean); export interface CommandQuery { visibility?: "reachable" | "active" | "registered"; focused?: TTarget | null; namespace?: string | readonly string[]; search?: string; searchIn?: readonly string[]; filter?: CommandFilter; limit?: number; } export interface CommandBindingsQuery { visibility?: "reachable" | "active" | "registered"; focused?: TTarget | null; commands: readonly string[]; } export interface RunCommandOptions { event?: TEvent; focused?: TTarget | null; target?: TTarget | null; includeCommand?: boolean; payload?: unknown; } export type RunCommandResult = { ok: true; command?: Command; } | { ok: false; reason: "not-found"; } | { ok: false; reason: "inactive" | "disabled" | "invalid-args" | "rejected" | "error"; command?: Command; }; export interface CommandContext { keymap: Keymap; event: TEvent; focused: TTarget | null; target: TTarget | null; data: Readonly; command?: Command; input: string; payload: TPayload; } export type CommandResult = boolean | void | RunCommandResult | Promise>; export type CommandResolutionStatus = "resolved" | "unresolved" | "error"; export type CommandHandler = (ctx: CommandContext) => CommandResult; export type BindingCommand = string | CommandHandler; export type BindingEvent = "press" | "release"; export interface Binding { key: KeyLike; cmd?: BindingCommand; event?: BindingEvent; /** * Default `true`. Calls `event.preventDefault()` and * `event.stopPropagation()` so the matched key does not reach the focused * target or later host listeners. Independent of `fallthrough`, which only * controls dispatch inside the keymap. Set `preventDefault: false` if you * want a fallthrough binding to keep matching inside the keymap and still let * the key escape to later handlers. */ preventDefault?: boolean; /** * Default `false`. Continues to later matching bindings in the same * dispatch chain after this command runs. Independent of `preventDefault`, * which controls whether the key event leaves the keymap. */ fallthrough?: boolean; [key: string]: unknown; } export type Bindings = readonly Binding[]; export type TargetMode = "focus" | "focus-within"; export interface Layer { target?: TTarget; priority?: number; bindings?: Bindings; commands?: readonly Command[]; targetMode?: TargetMode; /** * Extra layer fields feed layer-field compilers and binding compilation via * `BindingParserContext.layer` / `BindingTransformerContext.layer`. Unlike * binding and command fields, layer fields do not compile into public attrs. */ [key: string]: unknown; } export interface ParsedCommand { input: string; name: string; args: string[]; } export interface CommandResolverContext { readonly input: string; readonly payload: unknown; setInput(input: string): void; setPayload(payload: unknown): void; getCommand(name: string): Command | undefined; } export type CommandResolver = (command: string, ctx: CommandResolverContext) => Command | undefined; export interface KeyToken { name: string; key: KeyLike; } export interface SequencePatternMatch { value?: unknown; display?: string; } export interface SequencePattern { name: string; display?: string; payloadKey?: string; min?: number; max?: number; match(event: TEvent): SequencePatternMatch | undefined; finalize?(values: readonly unknown[]): unknown; } export interface ResolvedSequencePattern { name: string; display?: string; payloadKey: string; match: KeyMatch; min: number; max: number; matcher(event: TEvent): SequencePatternMatch | undefined; finalize?: (values: readonly unknown[]) => unknown; } export interface ActiveBinding { sequence: KeySequencePart[]; command?: BindingCommand; commandAttrs?: Readonly; attrs?: Readonly; event: BindingEvent; preventDefault: boolean; fallthrough: boolean; } export interface DispatchLayer { order: number; priority: number; target?: TTarget; targetMode?: TargetMode; } export interface DispatchBinding extends ActiveBinding { sourceLayerOrder: number; bindingIndex: number; } export type DispatchPhase = "sequence-start" | "sequence-advance" | "sequence-clear" | "binding-execute" | "binding-reject"; export interface DispatchEvent { phase: DispatchPhase; event: BindingEvent; focused: TTarget | null; layer?: DispatchLayer; binding?: DispatchBinding; sequence: readonly KeySequencePart[]; command?: BindingCommand; } export type KeyAfterReason = "intercept-consumed" | "binding-handled" | "binding-rejected" | "no-match" | "sequence-pending" | "sequence-miss" | "sequence-cleared"; export interface KeyAfterInputContext { event: TEvent; eventType: BindingEvent; focused: TTarget | null; handled: boolean; reason: KeyAfterReason; sequence: readonly KeySequencePart[]; pendingSequence: readonly KeySequencePart[]; setData: (name: string, value: unknown) => void; getData: (name: string) => unknown; consume: (options?: { preventDefault?: boolean; stopPropagation?: boolean; }) => void; } /** * Command metadata together with the bindings that invoke it in a given query * projection. */ export interface CommandEntry { command: Command; bindings: readonly ActiveBinding[]; } export interface ActiveKeyOptions { includeBindings?: boolean; includeMetadata?: boolean; } export interface ActiveKey { stroke: NormalizedKeyStroke; display: string; tokenName?: string; bindings?: ActiveBinding[]; bindingAttrs?: Readonly; commandAttrs?: Readonly; command?: BindingCommand; continues: boolean; } /** * Boolean source with subscription-based invalidation. `ctx.activeWhen(...)` * subscribes at registration time and unsubscribes when the owning * layer or binding is removed. */ export interface ReactiveMatcher { get(): boolean; subscribe(onChange: () => void): () => void; } export interface BindingFieldContext { require(name: string, value: unknown): void; attr(name: string, value: unknown): void; /** * Registers a runtime matcher. Matchers are evaluated when the owning * record is queried or dispatched; reactive matchers also notify state * subscribers when their external source changes. */ activeWhen(matcher: (() => boolean) | ReactiveMatcher): void; } export type BindingFieldCompiler = (value: unknown, ctx: BindingFieldContext) => void; export interface LayerFieldContext { require(name: string, value: unknown): void; attr(name: string, value: unknown): void; /** * Registers a runtime matcher. Matchers are evaluated when the owning * record is queried or dispatched; reactive matchers also notify state * subscribers when their external source changes. */ activeWhen(matcher: (() => boolean) | ReactiveMatcher): void; } export type LayerFieldCompiler = (value: unknown, ctx: LayerFieldContext) => void; export interface LayerAnalysisContext { target?: TTarget; order: number; sourceBindings: readonly Binding[]; bindings: readonly LayerBindingAnalysis[]; hasTokenBindings: boolean; checkCommandResolution(command: string): CommandResolutionStatus; warn(code: string, warning: unknown, message: string): void; warnOnce(key: string, code: string, warning: unknown, message: string): void; error(code: string, error: unknown, message: string): void; } export interface LayerBindingAnalysis { sequence: KeySequencePart[]; command?: BindingCommand; attrs?: Readonly; event: BindingEvent; preventDefault: boolean; fallthrough: boolean; parsedBinding: ParsedBinding; sourceTarget?: TTarget; sourceLayerOrder: number; bindingIndex: number; hasCommandAtSequence: boolean; hasContinuations: boolean; } export type LayerAnalyzer = (ctx: LayerAnalysisContext) => void; export interface BindingParserContext { input: string; index: number; layer: Readonly>; tokens: ReadonlyMap; patterns: ReadonlyMap; normalizeTokenName(token: string): string; createMatch(id: string): KeyMatch; parseObjectKey(key: KeyStrokeInput, options?: { display?: string; match?: KeyMatch; tokenName?: string; }): KeySequencePart; } export interface BindingExpanderContext { input: string; displays?: readonly string[]; layer: Readonly>; } export interface BindingExpansion { key: string; displays?: readonly string[]; } export interface BindingParserResult { parts: KeySequencePart[]; nextIndex: number; usedTokens?: readonly string[]; unknownTokens?: readonly string[]; } export type BindingParser = (ctx: BindingParserContext) => BindingParserResult | undefined; export type BindingExpander = (ctx: BindingExpanderContext) => readonly BindingExpansion[] | undefined; export interface ParsedBinding { key: KeyLike; sequence: KeySequencePart[]; cmd?: BindingCommand; event?: BindingEvent; preventDefault?: boolean; fallthrough?: boolean; [key: string]: unknown; } export interface BindingTransformerContext { layer: Readonly>; parseKey(key: KeyLike): KeySequencePart; add(binding: ParsedBinding): void; skipOriginal(): void; } export type BindingTransformer = (binding: ParsedBinding, ctx: BindingTransformerContext) => void; export type BindingsValidationResult = { ok: true; } | { ok: false; reason: string; }; export interface LayerBindingsTransformerContext { layer: Readonly>; validateBindings(bindings: unknown): BindingsValidationResult; } export type LayerBindingsTransformer = (bindings: readonly Binding[], ctx: LayerBindingsTransformerContext) => readonly Binding[] | void; export interface CommandTransformerContext { layer: Readonly>; add(command: Command): void; skipOriginal(): void; } export type CommandTransformer = (command: Command, ctx: CommandTransformerContext) => void; export interface CommandFieldContext { require(name: string, value: unknown): void; attr(name: string, value: unknown): void; /** * Registers a runtime matcher. Matchers are evaluated when the owning * record is queried or dispatched; reactive matchers also notify state * subscribers when their external source changes. */ activeWhen(matcher: (() => boolean) | ReactiveMatcher): void; } export type CommandFieldCompiler = (value: unknown, ctx: CommandFieldContext) => void; export interface KeyInputContext { event: TEvent; setData: (name: string, value: unknown) => void; getData: (name: string) => unknown; consume: (options?: { preventDefault?: boolean; stopPropagation?: boolean; }) => void; } export interface RawInputContext { sequence: string; stop: () => void; } export type Hooks = { /** * Batched "derived state may have changed" signal. Re-read through getters; * framework adapters should use this event. */ state: void; /** * Synchronous pending-sequence updates, including clear. Payload is the * current sequence. */ pendingSequence: readonly KeySequencePart[]; /** Dispatch trace events for sequence continuation and binding execution. */ dispatch: DispatchEvent; }; export type HookName = keyof Hooks; export type Listener = [TValue] extends [void] ? () => void : (value: TValue) => void; export interface WarningEvent { code: string; message: string; warning: unknown; } export interface ErrorEvent { code: string; message: string; error: unknown; } /** Events exposed by `keymap.on(...)`. `state` is batched and `pendingSequence` is synchronous. */ export type Events = Hooks & { warning: WarningEvent; error: ErrorEvent; }; export type EventName = keyof Events; export type Intercepts = { key: KeyInputContext; "key:after": KeyAfterInputContext; raw: RawInputContext; }; export type InterceptName = keyof Intercepts; export interface KeyInterceptOptions { priority?: number; release?: boolean; } export interface RawInterceptOptions { priority?: number; } export type { Keymap }; export interface RuntimeMatcher { source: string; match: () => boolean; /** * Present for reactive matchers; wired during registration and torn down via * `dispose`. */ subscribe?: (onChange: () => void) => () => void; dispose?: () => void; } export interface RuntimeMatchable { requires: readonly [name: string, value: unknown][]; matchers: readonly RuntimeMatcher[]; } export interface BindingState extends ActiveBinding, RuntimeMatchable { binding: Binding; run?: CommandHandler; parsedBinding: ParsedBinding; sourceTarget?: TTarget; sourceLayerOrder: number; bindingIndex: number; } export interface ActiveKeySelection { display: string; tokenName?: string; continues: boolean; firstBinding?: BindingState; commandBinding?: BindingState; bindings?: readonly BindingState[]; stop: boolean; } export interface ActiveKeyState { stroke: NormalizedKeyStroke; display: string; tokenName?: string; continues: boolean; firstBinding?: BindingState; commandBinding?: BindingState; bindings?: BindingState[]; } export interface CommandState extends RuntimeMatchable { command: Command; fields: Readonly>; attrs?: Readonly; } export interface BindingCompilationResult { bindings: readonly BindingState[]; hasTokenBindings: boolean; } export interface SequenceNode { parent: SequenceNode | null; depth: number; stroke: NormalizedKeyStroke | null; match: KeyMatch | null; pattern?: ResolvedSequencePattern; children: Map>; patternChildren: SequenceNode[]; bindings: BindingState[]; reachableBindings: BindingState[]; } export interface RegisteredLayer { order: number; target?: TTarget; targetMode?: TargetMode; priority: number; requires: readonly [name: string, value: unknown][]; matchers: readonly RuntimeMatcher[]; fields?: Readonly>; attrs?: Readonly; commands: readonly CommandState[]; sourceBindings: readonly Binding[]; bindings: readonly BindingState[]; root: SequenceNode; hasTokenBindings: boolean; activeKeyCacheBlocked: boolean; activeCommandViewCacheBlocked: boolean; offTargetDestroy?: () => void; } export interface PendingSequenceCapture { layer: RegisteredLayer; binding: BindingState; index: number; parts: readonly KeySequencePart[]; patterns?: readonly PendingSequencePatternCapture[]; } export interface PendingSequencePatternCapture { name: string; payloadKey: string; values: readonly unknown[]; parts: readonly KeySequencePart[]; } export interface PendingSequenceState { captures: readonly PendingSequenceCapture[]; }