If you haven't already, please read about ARIA and WCAG accessibility guidelines, as introduced at Accessibility Information (MDN).
By default each GoJS Diagram provides the ability for users who cannot or do not want to use a mouse but who can use a keyboard to use most tools that diagrams provide for manipulation. The focus navigation and virtual pointer capabilities must be enabled in each diagram, either programmatically or interactively.
By default each GoJS Diagram provides generic support for screen readers when using focus navigation. However, because the GoJS library cannot know what is important to say and when, it is important for each diagram to customize exactly what is said for each state that the diagram might be in.
With version 3.1 the CommandHandler supports a "keyboard control" mode in which the user can use the keyboard to interact with a diagram without using the mouse. Nearly any kind of diagram can be controlled this way, except for commands or tools that are very time-dependent or that require specific pointing precision such as freehand drawing or cursive writing.
This keyboard control mechanism supports a way to use the keyboard instead of the mouse to focus on a node or a link or on a button or editable TextBlock or port within a node or a link, and to act on it or to change focus to a nearby or related node or link or button or port. This kind of GoJS focus depends on but is in addition to the regular HTML DOM focus mechanism, because GraphObjects are not HTML elements and cannot by themselves receive DOM keyboard events.
This mechanism only works as long as the diagram has HTML keyboard focus.
If another HTML element gains HTML focus, including when the user Tab
s out of the diagram,
the commands described in this page no longer apply.
The GoJS focus navigation mechanism is independent of the diagram's selection mechanism, although it does make it easy to select or deselect a focused node or link. Focus is shown using a single Adornment that is the CommandHandler.focusBox. It is added to the GraphObject that is the CommandHandler.focus, the GraphObject that has GoJS focus.
The keyboard control mechanism also supports a virtual pointer to allow keyboard events to produce
mouse InputEvents for invoking and controlling tools.
The virtual pointer is shown as a single Part, the CommandHandler.virtualPointerBox
in the "Tool" Layer, when a Shift
key is held down.
Shift
-arrow or numpad keys move it around, and other Shift
modified keys
perform actions to generate mouse InputEvents or modify future generated mouse InputEvents.
Enable the GoJS keyboard control functionality in any Diagram by Ctrl-Alt-Enter
.
That command actually toggles whether or not the focus navigation and virtual pointer system are enabled.
When CommandHandler.isFocusEnabled and CommandHandler.isVirtualPointerEnabled are false (the default)
there is no difference in behavior from earlier version libraries,
except for the addition of this Ctrl-Alt-Enter
command to toggle those two properties on and off.
For all of these commands, you can freely interchange Meta
(the Command key on macOS) with Ctrl
.
Activating a GraphObject (including a Part) means calling its click event handler,
or editing a TextBlock if it is TextBlock.editable, or showing its toolTip.
Use the ContextMenu
key to show a GraphObject.contextMenu.
Keys | Focus and Virtual Pointer Effect |
---|---|
Ctrl-Alt-Enter or Ctrl-Alt-NumpadEnter |
toggle keyboard control mode |
ArrowUp and ArrowDown and ArrowLeft and ArrowRight |
change focus to nearby object within scope (top-level Parts, Group members, or GraphObjects within a Part) |
Ctrl-ArrowUp and Ctrl-ArrowDown and Ctrl-ArrowLeft and Ctrl-ArrowRight |
change focus from Node to a connected Link or to the next or the previous connected Link or Node |
Enter or NumpadEnter |
focus enters Group subgraph or Part's GraphObjects or activates focused GraphObject within a Part |
Escape |
focus leaves for containing Part or containing Group |
Ctrl-Enter or Ctrl-NumpadEnter |
activates focused GraphObject, even if the focus is a Part |
Space |
select focused Part or show tooltip for focused GraphObject |
Ctrl-Space |
toggle focused Part selection or show tooltip for focused GraphObject |
Shift-Space |
add focused Part to selection or show tooltip for focused GraphObject |
ShiftLeft or ShiftRight |
show virtual pointer when a Shift key is down and not when the key is up |
Shift-ArrowUp and Shift-ArrowDown and Shift-ArrowLeft and Shift-ArrowRight |
move virtual pointer 10 units in given direction |
Ctrl-Shift-ArrowUp and Ctrl-Shift-ArrowDown and Ctrl-Shift-ArrowLeft and Ctrl-Shift-ArrowRight |
move virtual pointer 1 unit in given direction |
Shift-Numpad1 through Shift-Numpad9 , except for Shift-Numpad5 |
move virtual pointer 10 units in given direction; 2, 4, 6, 8 down, left, right, up; 1, 3, 7, and 9 on the diagonal |
Ctrl-Shift-Numpad1 through Ctrl-Shift-Numpad9 , except for Ctrl-Shift-Numpad5 |
move virtual pointer 1 unit in given direction; 2, 4, 6, 8 down, left, right, up; 1, 3, 7, and 9 on the diagonal |
Shift-Enter or Shift-NumpadEnter or Shift-Numpad5 |
mouse button down or up |
Shift-A |
toggle alt modifier for next mouse button event |
Shift-C |
toggle control modifier for next mouse button event |
Shift-D or Shift-NumpadDecimal |
toggles the click count between 1 and 2 for the next mouse button up event, which auto-resets to 1 |
Shift-M |
toggle meta modifier for next mouse button event |
Shift-S |
toggle shift modifier for next mouse button event |
Shift-1 |
use left button modifier on next mouse button event |
Shift-2 |
use middle button modifier on next mouse button event |
Shift-3 |
use right button modifier on next mouse button event |
Shift-Numpad0 |
toggles between the left and right button modifiers for the next mouse button event |
All other commands operate in the standard manner, as listed in Standard Keyboard Commands. They mostly operate on the current Diagram.selection; none of them operate on the current CommandHandler.focus.
The focus navigation mode handles arrow keys and the Enter
, Escape
, Space
,
and ContextMenu
keys to allow the user to change
which GraphObject has focus and to perform operations on what has focus. The focus is completely independent of
the Diagram.selection and Diagram.highlighteds collections and mechanisms.
All other keyboard commands should operate normally.
For example, the Delete
command will always operate on the selection, not on the focus.
As focus changes, either a built-in element or a programmer supplied one can provide a screen reader some descriptive text to read aloud. Read more about this at Customizing for Screen Readers.
Here is a simulation, using the Robot extension, of using Arrow keys and Enter, Space, and Escape as if the user is controlling the focus in a diagram. As the Robot simulates keyboard commands, they are recorded in a list that you can see.
The first keyboard command is Ctrl-Alt-Enter
in order to enable CommandHandler.isFocusEnabled.
Focus is given to a root Node, in this case the "Alpha" Node.
Ctrl
-arrow keys follow connected Links from a focused Node, so
the first Ctrl-ArrowDown
key changes focus from the "Alpha" Node to the Link going to the "Beta" Node.
A Ctrl-ArrowRight
would change focus to the second Link coming out of the "Alpha" Node.
The next command, Ctrl-ArrowDown
, changes focus to that "Beta" Node.
Then the Enter
key changes focus to be on editable TextBlocks, buttons, and ports of the Node.
Now the arrow keys focus on the next GraphObject within the Node in the direction determined by the arrow key.
The ArrowRight
key focuses on the "TreeExpanderButton", which is then clicked twice with the Enter
key.
The Escape
key leaves the context inside the Node back to the Node itself.
Then the ArrowRight
key chooses the first Part to the right of the current focus, which is the "Gamma" Node.
It is selected with the Space
key, and deleted by the Delete
key.
(Remember that CommandHandler commands operate on the selection, not on the focus.)
Then it undoes the deletion, and programmatically focuses on the "Epsilon" Node.
A couple arrow key commands changes focus to the "Delta" Node and then the "Beta" Node.
The ContextMenu
key brings up the context menu.
There are two "ContextMenuButtons", with focus on the first one, which is invoked by the Enter
key.
The context menu command puts out a string to the Robot Log.
Focus is automatically returned to the Node, and finally an ArrowUp
key changes focus back to the "Alpha" node.
There are no Groups in this example, but if there had been, once a Group gets focus, the Enter
key acts
just as it does on any Node -- it changes focus to a GraphObject in the Group that is a button or port or editable TextBlock.
Hitting Escape
, if the Group has any member Parts, would change focus to one of those member Nodes.
Once a member Part has focus, the Escape
key changes focus back up to the Group itself.
You can try using focus navigation in any of our samples, or in your own app.
For example you can try it in the Incremental Tree sample.
Click in the diagram and type Ctrl-Alt-Enter
to enable focus changing behavior.
When a Node (or a Link) has focus you need to type Enter
to change focus to any buttons
(clickable GraphObjects) or editable TextBlocks or ports or GraphObjects with context menus or tool tips
that are within the Node.
When there is more than one, use the arrow keys to get to the GraphObject that you want.
In the case of the Incremental Tree sample, the only eligible GraphObject within the Node is a "TreeExpanderButton".
When it has focus you can type Enter
to click on the button,
expanding or collapsing its subtree.
When you are navigating amongst the GraphObjects of a Node or a Link, the arrow keys stay within the Part.
They only change focus to other eligible GraphObjects within that Part, if there are any.
Remember to type Escape
to change focus back to the containing Node,
so that you can use the arrow keys to navigate to a different Node.
If you are navigating in a diagram with Groups, you need to Enter
a Group in order to focus on
any of its member Parts.
The arrow keys are limited to navigating amongst the member Parts of that one Group.
You need to Escape
to leave the subgraph and focus on the Group as a whole again,
so that you can navigate to other Nodes in the diagram.
When the user holds down a Shift
key, it goes into virtual pointer mode, where the virtualPointerBox shows where the virtual pointer is,
and Shift
-arrow keys move that virtualPointerBox around. Moving the virtualPointerBox results in mouse-move InputEvents.
Also holding down the Ctrl
modifier when typing arrow keys offers finer positioning at one document unit per keystroke.
Shift-Enter
toggles whether the virtual pointer is 'down' or 'up', also shown in the virtualPointerBox with a thick dark-cyan circle
indicating 'down', and results in mouse-down or mouse-up InputEvents.
Basically, to drag a node, type Shift-Enter
to do the mouse-down,
type Shift
-arrow keys to move the virtual pointer around,
and finally type Shift-Enter
again to do the mouse-up and stop the drag.
Note that Shift
does not need to be held down the whole time during what would be a mouse-down/moves/up sequence.
This allows almost any mouse Tool to run, except ones that involve timing which can be hard to control.
Changing the current focus object automatically cancels any Tool running due to the virtual pointer mechanism,
which helps with the times that the user forgets to do the second Shift-Enter
for the mouse-up effect.
Shift-Space
changes the focus to be the Part at the current virtualPointerLocation.
Because Shift
and Ctrl
are used to control the virtual pointer, there is no natural way to specify modifiers
during virtual mouse events, such as control-drag-and-drop in order to copy the selection.
Nor is there a reasonable way to specify which button to use for mouse-down or mouse-up events,
nor to control whether it's a single click or a double click. So there are additional commands to control those
aspects of future simulated mouse input events:
Shift-A
, Shift-S
, Shift-C
, Shift-M
to toggle each of
InputEvent.alt, InputEvent.shift, InputEvent.control, InputEvent.meta
Shift-1
, Shift-2
, Shift-3
for one of
InputEvent.left, InputEvent.middle, InputEvent.right.
Or type Shift-Numpad0
to toggle between left and right mouse buttons.
Shift-D
(or Shift-NumpadDecimal
) to specify a InputEvent.clickCount of 2
for the next Shift-Enter
event that performs a mouse-up.
After a simulated mouse up event the InputEvent.clickCount is automatically reset to 1.
The current modifiers/button/clickCount state is reflected in the standard virtualPointerBox, as shown in the following simulation controlled by another instance of Robot. The Diagram.contextClick event handler has been set to a function that logs "context clicked!".
This demonstration involves both focus navigation and use of the virtual pointer to draw a new Link and to resize a Node.
First the user types Ctrl-Alt-Enter
in order to enable CommandHandler.isVirtualPointerEnabled.
The user then uses the arrow keys to navigate to the "Delta" Node.
An Enter
changes focus to the GraphObjects within the Node.
Arrow keys change focus to the bottom left port.
Now the user starts the virtual pointer mode by holding down the Shift
key.
The virtual pointer starts at the center of the current CommandHandler.focus.
An Enter
key produces a mouse down event.
Note that the virtual pointer has a darker circle, showing that the mouse is down during the mouse move.
Then the following Shift
-arrow keys start the LinkingTool to draw a new link.
Then an Enter
key produces a mouse up event, finishing drawing the new link.
The focus has remained on the starting port in the "Delta" Node, so an Escape
key changes focus to that Node.
An ArrowDown
and a Space
key cause the "Epsilon" Node to be selected, showing its resize handles.
The virtual pointer starts with a Shift
, and right arrow keys move it to the resize handle on the right side.
Note that one can use the Ctrl
modifier with the Shift
-arrow keys in order to move it just one document unit at a time,
instead of the default 10 document units.
The ResizingTool is started by a mouse down caused by a Shift-Enter
.
Following Shift
-arrow keys drag that resize handle, and another Shift-Enter
produces a mouse up event,
finishing the resizing of the node.
The Ctrl
and Meta
modifier keys are interchangeable for all built-in command keyboard shortcuts.
Use whatever works well on your keyboard. But note that not all Tools are that way.
For example, the Ctrl
modifier when using the DraggingTool is replaced by the Alt
modifier on macOS, following the convention there.
The Caps Lock
, Insert
, and Scroll Lock
keys are not used for GoJS keyboard control, and thus can be used by screen readers.
Tab
normally changes the HTML element that has DOM keyboard focus,
and that behavior is unmodified while the diagram has keyboard focus.
If the diagram is showing scrollbars, Tab
will focus on the diagram's HTML element holding the scrollbars,
allowing for the arrow keys to scroll normally.
Note that nearly all of the regular keyboard-shortcut commands are unaffected by focus navigation.
This includes the common commands:
delete, cut, copy, paste, selectAll, undo, redo, *Zoom, group, ungroup, editTextBlock.
However the arrow keys do not scroll, and the ContextMenu
key works on the focus, not on the selection,
unless there is no focus object.
Events from an actual mouse input device, perhaps caused by accidentally moving it, may interfere with the intended behaviors using the virtual pointer.
You will want to provide your own function as the CommandHandler.focusChanged event handler in order to customize exactly what is said and shown as the user changes the CommandHandler.focus to a new Node or Link, or to a button or editable TextBlock or port within a Node, or to null.
Your page will probably want to have an element
that is updated as your CommandHandler.focusChanged function is called.
The CommandHandler needs to know about that element, so you also need to set CommandHandler.liveElementId
to refer to that element.
It will set the aria-labelledby
attribute to refer to your element.
Do not set the aria-live
attribute on that element.
Exactly what information you put there should be very specific to your particular diagram. Only you the programmer can know exactly what your users will want or need to know as they traverse your diagram, and what language and terminology they will be expecting.
You can call CommandHandler.describe in order to generate the default description for a focused GraphObject, given the previous focus.
In Windows Narrator, when focus gets to the diagram,
turn off scan mode (CapsLock-Space
) so that the diagram is not considered a simple image,
thereby allowing GoJS focus navigation.
This action is not needed when using NVDA on Windows or Voice Over on macOS.
Although you can programmatically turn on focus navigation mode in the diagram by setting the
CommandHandler.isFocusEnabled and CommandHandler.isVirtualPointerEnabled properties,
you could document to your users that they can invoke the Ctrl-Alt-Enter
keyboard command
in order to enable all of this functionality.
For examples, examine the Accessibility sample, which provides some customizations for the Org Chart Editor sample, or the IVR Tree sample.
If you compare the implementations of the Accessibility
and Org Chart Editor samples,
you will see that a function named describe
has been added to better describe
each of the Nodes, Links, and GraphObjects within Nodes.