Skip to content
D
Documentation

KeyboardNavigationController

reference
3 min readUpdated

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

Was this page helpful?

KeyboardNavigationController — Grafloria