# KeyboardNavigationController

Import it from `@grafloria/renderer`.

```ts
class KeyboardNavigationController
```

**Methods**

- `constructor(config: Partial<KeyboardNavConfig> = {})`
- `getConfig(): KeyboardNavConfig`
- `updateConfig(patch: Partial<KeyboardNavConfig>): void`
- `getFocusOrder(engine: DiagramEngine): FocusTarget[]` — Tab order: nodes in reading order (top-to-bottom, then left-to-right), then
links ordered by their source node's place in that same order — so tabbing
walks the structure the way a sighted user reads it.
- `getFocused(): FocusTarget | null`
- `setFocus(target: FocusTarget | null, engine?: DiagramEngine): FocusTarget | null` — Set focus (null clears it) and announce the new target.
- `focusNext(engine: DiagramEngine): FocusTarget | null` — Tab.
- `focusPrevious(engine: DiagramEngine): FocusTarget | null` — Shift+Tab.
- `focusDirection(engine: DiagramEngine, direction: NavDirection): FocusTarget | null` — Spatial arrow-key focus movement across NODES: pick the nearest node whose
centre lies in the requested half-plane, preferring small perpendicular
offset (the standard "directional focus" heuristic).
- `getFocusRing(engine: DiagramEngine): FocusRing | null` — The visible focus ring for the current target (null when nothing is focused).
- `selectFocused(engine: DiagramEngine, additive = false): boolean` — Make the focused entity the selection (Space, or Enter on a link). Selection lives on the model, which is what the renderer draws and what the
clipboard/delete commands read once the host syncs it to the store.
- `incidentEdgesOfFocus(engine: DiagramEngine): Incidence[]` — The edges incident on the focused node, in a stable traversal order.
- `followEdge(engine: DiagramEngine, index = 0): FocusTarget | null` — FOLLOW-EDGE NAVIGATION. From a focused node, step along its Nth incident
edge to the node at the far end. From a focused EDGE, step to its endpoints.

This is the move that makes a diagram traversable without sight: you are on
"Is order valid?", you are told it has 2 outgoing edges, and you walk one.
- `followOutgoing(engine: DiagramEngine, index = 0): FocusTarget | null` — Walk to the Nth node this one points AT.
- `followIncoming(engine: DiagramEngine, index = 0): FocusTarget | null` — Walk back to the Nth node that points at this one.
- `focusIncidentEdge(engine: DiagramEngine, index = 0): FocusTarget | null` — Focus the EDGE itself (rather than jumping over it) — so it can be deleted.
- `focusEntryPoint(engine: DiagramEngine, index = 0): FocusTarget | null` — Jump to the first entry point — "take me back to the start of the flow".
- `positionContextOfFocus(engine: DiagramEngine): string` — The POSITION CONTEXT for the focused node — "node 3 of 12, 2 incoming,
1 outgoing". The orientation a sighted user gets for free from the picture.
- `announcePosition(engine: DiagramEngine): Announcement | null` — Announce where we are in the graph. Bound to a key in the host.
- `announceSummary(engine: DiagramEngine): Announcement | null` — The whole-diagram natural-language summary, announced on entry.
- `deleteCommand(engine: DiagramEngine): Command | null` — Delete the selection (or, with nothing selected, the focused entity), as one
undoable macro. Deleting a node takes its incident edges with it — leaving
dangling links is how a keyboard user silently corrupts a diagram.
- `duplicateCommand(engine: DiagramEngine, offset: Point = { x: 24, y: 24 }): Command | null` — Duplicate the focused/selected nodes at an offset, carrying any edge that
runs BETWEEN two duplicated nodes (an edge to a node you did not copy has
no meaningful counterpart, so it is dropped — the same rule the pointer
duplicate uses).
- `reparentCommand(engine: DiagramEngine, parentId: string | null): Command | null` — REPARENT the focused node into (or out of) a container — the last thing that
was pointer-only. `null` unparents.

`SetParentCommand` already rejects a cycle; we catch it EARLY so the user
gets an assertive "cannot" instead of an exception thrown into the void.
- `reparentCandidates(engine: DiagramEngine): NodeModel[]` — The containers the focused node could legally be reparented into — what a
host puts in a "move to…" list. Excludes itself and its own descendants.
- `nudgeDelta(key: string, coarse = false): Point | null` — World delta for an arrow key (null when the key is not an arrow).
- `nudgeCommand( engine: DiagramEngine, dx: number, dy: number, options: { mergeable?: boolean } = {} ): Command | null` — Move the selection (or, with nothing selected, the focused node) by a world
delta, as ONE undoable command per key press. Locked / undraggable nodes are
skipped; returns null when there is nothing to move.

`mergeable` defaults FALSE — the a11y contract is "⌘Z undoes each nudge to
the exact pixel". An editor that wants held-key auto-repeat to collapse
into one undo entry (Visio's feel) passes true and the CommandManager's
merge window does the rest.
- `getConnectState(): KeyboardConnectState | null`
- `isConnecting(): boolean`
- `beginConnect(engine: DiagramEngine): boolean` — Returns false when the focused
node has no connectable port.
- `cyclePort(engine: DiagramEngine, delta: number): boolean` — Cycle the port being picked in the current phase.
- `cycleTargetNode(engine: DiagramEngine, delta: number): boolean` — Cycle the TARGET node.
- `confirmConnect(engine: DiagramEngine): Command | null`
- `cancelConnect(): void` — Escape.
- `onAnnounce(listener: AnnouncementListener): AnnouncementUnsubscribe`
- `getLastAnnouncement(): Announcement | null`
- `announce(message: string, politeness: 'polite' | 'assertive' = 'polite'): Announcement`
- `announceSelection(engine: DiagramEngine): Announcement | null` — "2 nodes and 1 link selected" — announced whenever the selection changes.
- `announceStructure(engine: DiagramEngine, change: string): Announcement | null` — "Node Start added. 4 nodes, 3 links." — for structure changes.
- `describe(engine: DiagramEngine, target: FocusTarget): string` — The accessible name of a target: what a screen reader reads on focus, and
what the host puts in `aria-label` on the rendered element.
- `nodeName(node: NodeModel): string` — Human name of a node: its label, else its type + short id.
- `dispose(): void`
