Keyboard
VisuallyJs ships with a KeyboardShortcutsPlugin which offers common keyboard event handling such as undo/redo, delete, select all and vertex positioning via arrow keys. You can also provide your own custom key event bindings and instruct VisuallyJs to switch off certain defaults if you wish to.
To switch on the plugin, with the default bindings:
<script setup>
import { PLUGIN_TYPE_KEYBOARD_SHORTCUTS } from "@visuallyjs/browser-ui"
const renderOptions = {
plugins: [
PLUGIN_TYPE_KEYBOARD_SHORTCUTS
]
}
</script>
<template>
<SurfaceComponent :renderOptions="renderOptions" />
</template>
This canvas has the keyboard plugin activated, with all default actions available - click a node to select it. Use Delete or Backspace to delete the selected node(s). Use Ctrl+z or Cmd+z to undo, Ctrl+y or Cmd+y to redo. Ctrl+a/Cmd+a will select every node. Escape clears the current selection.
Default shortcuts
| Key | Action |
|---|---|
| Delete | Remove all objects in the current selection |
| Escape | Clear the current selection |
| Ctrl/Cmd+Z | Undo. Undo and redo require the model's undo/redo support, which is enabled by default. If you disable undo/redo, the keyboard shortcuts will not work. |
| Ctrl/Cmd+Shift+Z, Ctrl/Cmd+Y | Redo |
| Ctrl/Cmd+A | Select all objects in this canvas's data source |
| Ctrl/Cmd+C | Copy the selection into the internal clipboard. |
| Ctrl/Cmd+V | Paste the internal clipboard. Note that, when pasting via the keyboard, the paste will occur directly on top of the copied content's original location, since there is no way of specifying via the keyboard where the paste origin should be. |
| ArrowLeft, ArrowUp, ArrowRight, ArrowDown | Move selected draggable nodes and groups. Arrow keys move by one model unit, or by one grid cell when snapping is enabled. Holding an arrow key repeats movement; the plugin groups the movement into a transaction until the keys are released or focus is lost. |
Disabling a default binding
To disable a default binding, set it to false in the shortcuts object:
const keyboardOptions = {
shortcuts: {
Delete: false
}
};
Managing Focus
For keyboard events to be captured in a browser it is necessary that the element on which you wish to capture the events has a tabindex attribute set. The KeyboardBindingsPlugin sets this up for you, and also attaches a couple of pointer listeners to ensure that focus is set on the VisuallyJs canvas whenever the user is interacting with it:
- Whenever a node/group is tapped, the canvas is given the focus. This excludes tap events on input controls inside a node/group, because when a user clicks on an input control they wish to give it focus in order to interact with it.
- Whenever the canvas whitespace is tapped, the canvas is given the focus.
Normally when an element is focused in a browser, the browser applies a default CSS style to communicate to the user that it is focused. Given that it is unlikely most people will want to see this focus outline on their VisuallyJs canvas, we have added a rule to the default stylesheet to hide the outline:
.vjs-surface:focus, .vjs-paper:focus {
outline:none;
}
Excluding elements
Ff you want to mark some element, or part of some element, excluded from processing keyboard events, you can add a data-vjs-keyboard-ignore attribute:
<div data-vjs-keyboard-ignore>
...
</div>
This attribute will exclude the element and its descendants from both keyboard management and automatic canvas focus.
Custom bindings
The plugin's shortcuts option extends its defaults. Each property is a shortcut string, and its value can be:
- A function, called on
keydown. - An object with
keydownand/orkeyuphandlers. false, to remove that binding from the plugin's defaults.
Binding syntax
You can bind single keys:
const keyboardOptions = {
shortcuts: {
K: ({event, consume, ui, model}) => {
console.log("You Pressed K");
}
}
};
or you can combine them with modifiers:
const keyboardOptions = {
shortcuts: {
'Cmd+K': ({event, consume, ui, model}) => {
console.log("You pressed Cmd+K");
},
'Cmd+Shift+O': ({event, consume, ui, model}) => {
console.log("You pressed Cmd+Shift+O");
}
}
};
Modifiers
Available modifiers are:
| Modifier | Description |
|---|---|
ctrl | the Control key on Windows/Linux |
cmd | the Command key on Mac |
meta | the meta key on Windows/Mac (on Mac this maps to the Command key) |
shift | the Shift key |
Constants
There are also a few placeholders constants mapping to various keys:
| Modifier | Description |
|---|---|
Space | the spacebar |
Escape | the escape key |
Delete | the delete key (Mac) |
Backspace | tthe backspace key (aliased by delete on a Mac) |
Case sensitivity
Modifiers and constants are not case sensitive: ctrl+k is the same as Ctrl+k. However, keychars are case sensitive: ctrl+k is not the same as ctrl+K.
If you wish to specify both ctrl+k and ctrl+K, you can do that with the /i suffix:
const keyboardOptions = {
shortcuts: {
'Cmd+k/i': ({event, consume, ui, model}) => {
console.log("You pressed Cmd+K or maybe you pressed Cmd+k");
},
'S/i': ({event, consume, ui, model}) => {
console.log("You pressed 'S' or maybe you pressed 's'");
}
}
};
Providing a handler for an existing shortcut replaces that binding. Ctrl and Cmd bindings are separate entries: disable or replace both if you want to change both platform variants. A false entry removes only this plugin's binding; it does not block other registrations or the browser's own behavior.
Keyboard events are dispatched to a stack of handlers. Every handler receives a KeyboardContext containing four values:
event- theKeyboardEventthat was fired. Custom handlers also receive repeated keydown events; inspectevent.repeatif a command should run only once per press.consume- Handlers can invoke this function to stop the event from being propagated to subsequent handlers in the manager's stack. It does not callevent.preventDefault()or stop DOM propagation - call those event methods separately when your command needs them.ui- theBrowserUIin which the keyboard event was firedmodel- the UI's model. Provided for convenience - you can access this viaui.model.
K:{event, consume, ui, model}:{event:KeyboardEvent, consume:()=> any, ui:BrowserUI, model:VisuallyJsModel}) => {
console.log('Custom command');
event.preventDefault();
consume();
}
Example
<script setup>
import { PLUGIN_TYPE_KEYBOARD_SHORTCUTS } from "@visuallyjs/browser-ui"
const renderOptions = {
plugins: [
{
type: PLUGIN_TYPE_KEYBOARD_SHORTCUTS,
options: {
shortcuts: {
Delete: false,
Backspace: false,
K: ({event, consume, ui, model}) => {
alert('Custom command');
event.preventDefault();
consume();
}
}
}
}
]
}
</script>
<template>
<SurfaceComponent :renderOptions="renderOptions" />
</template>
In this example the Delete and Backspace bindings are disabled (Backspace is how the Mac's delete key is presented in keyboard events), and the K key pops up an alert. Note that the key is case sensitive - you must either type Shift+k or K (with caps lock down).
Plugin options
| Name | Type | Description |
|---|---|---|
| readOnly? | boolean | Disable built-in commands that modify the model (Delete, cut, paste, nudging and undo/redo). Copy and selection commands remain available. Defaults to false. Diagram enforces this when editable is false. Explicit application callbacks remain the application's responsibility. |
| shortcuts? | KeyboardShortcuts<KeyboardEvent,BrowserKeyboardContext> | Map of shortcuts to support. Optional. If you do not provide this, the default shortcuts will be used. |