/** * The global keyboard map (weave-workspace §11 P4). * * > Global keys: `⌘K` search, `⌘1/2/3` focus column, `/` filter tree, `g` fit * > graph, `?` help, `Esc` clear selection. Vim-ish `j/k` in the tree. * > *Exit: the whole workspace is drivable without a mouse.* * * One pure function, {@link shellKey}, maps an **event descriptor** and a * **context** to an action or to `null`. Nothing here listens, nothing here * calls `preventDefault`, and nothing here knows what a `KeyboardEvent` is — * {@link KeyDescriptor} is five booleans and a string. `keys.ts` does the * subscribing and `Shell.tsx` does the dispatching, which is what makes the * whole keymap coverable without a DOM (§10). * * ## `null` means "the browser keeps it", and it is the important case * * This is `treeKey`'s `handled: false` contract, promoted to the document. * The stakes are higher here: `treeKey` sits on one column, while this * handler sees **every** keystroke in the workspace. A global handler that * swallows Tab traps keyboard users; one that swallows ⌘R breaks reload; one * that fires `g` while a user is typing "graph" into the filter box makes * text entry impossible. So the default is `null` and every claim is narrow: * * | | Claimed when | * | --- | --- | * | `⌘K` / `Ctrl K` | always — a modifier shortcut is unambiguous even mid-word | * | `⌘1` `⌘2` `⌘3` | always | * | `Esc` | an overlay is open, or something is selected. Never otherwise | * | `/` `g` `?` `t` | **only** when focus is not in a text field and no overlay is open | * * `j` / `k` are not here at all: they are tree-scoped, so they live in * `tree.model.ts`'s `normalizeTreeKey` where the tree's own handler applies * them — a global `j` would move the tree selection while the user is looking * at the graph. * * ## While an overlay is open, the global map is one key wide * * The palette contains a text input and its own listbox navigation, and the * help sheet is a trap the user must be able to leave. Anything beyond * `Escape` reaching the document from inside a modal is a bug — most sharply * `⌘K`, which would otherwise re-open the palette on top of itself. */ import { COLUMNS, type ColumnId, type OverlayId } from "./shell.model"; // --- the event, without the DOM --------------------------------------------------- /** * A keystroke, reduced to what a decision needs. * * `typing` rather than a target element: whether focus sits in a text field * is the only property of the target this map cares about, and passing the * element would drag `HTMLElement` into a module the root `tsconfig.json` * project compiles. */ export interface KeyDescriptor { /** `KeyboardEvent.key` — `"k"`, `"Escape"`, `"/"`, `"?"`. */ readonly key: string; readonly ctrl: boolean; readonly meta: boolean; readonly shift: boolean; readonly alt: boolean; /** Focus is in an ``, `