Skip to content
D
Documentation

A11y — types

reference
2 min readUpdated

Types

Import these from @grafloria/renderer.

Types

ContainmentAction

Also has every member of String, listed on its own entry.

FOCUS CONTAINMENT — focus must never rest on geometry the user cannot see.

This is a real WCAG failure (2.4.11 Focus Not Obscured / 2.4.7 Focus Visible) and, before this wave, we failed it outright: keyboard focus walked the whole graph while the camera sat still, so tabbing past the edge of the viewport left the focus ring on a node that was scrolled off-screen — announced, but invisible. A sighted keyboard user simply lost the cursor.

The fix, in order of escalation:

  1. Already fully visible (with padding) → DO NOTHING. Not panning is the most important case: a stationary camera is what makes the diagram readable, and gratuitous re-centring on every focus step is nauseating.
  2. Off-screen but it FITS at the current zoom → pan the minimum distance that brings it inside the padded box. Minimum, not centring: the user keeps their mental map.
  3. Too big to fit at the current zoom → zoom OUT via fitToBounds, which is the existing camera maths. We do not fork it.

Motion honours prefers-reduced-motion: an animated pan becomes an instant jump. All camera writes go through {@link ViewportController} — this class owns policy, not geometry.

.

ts
type ContainmentAction = 'none' | 'pan' | 'zoom';

Politeness

Also has every member of String, listed on its own entry.

The managed aria-live REGION — the canvas's voice.

Two live regions, not one: an AT will not reliably re-announce a node whose politeness changed, and mixing an error into the polite queue means it waits behind whatever selection chatter is already in flight. So:

  • polite — selection, movement, focus, connect/disconnect;
    • assertive — validation failures and errors, which interrupt.

DE-DUPLICATION is the whole design problem. A naive live region wired to a render loop says "Process node, 2 connections" sixty times a second and is worse than useless — the user turns it off. Three defences, all here:

  1. IDENTICAL-MESSAGE SUPPRESSION. The same text in the same channel is dropped, unless force is set (a repeated action the user did repeat — nudging twice — is legitimately worth re-announcing).
  2. COALESCING. Announcements inside a short window replace, rather than queue behind, the previous one. Holding an arrow key produces the FINAL position, not fifty intermediate ones.
  3. MODEL-CHANGE DRIVEN. The controller is called on model change, never from the render loop. getSpeakCount() lets a test prove a quiet frame speaks zero times.

Framework-free: takes a container, owns two divs, no Angular anywhere.

.

ts
type Politeness = 'polite' | 'assertive';

Was this page helpful?

A11y — types — Grafloria