Keyboard shortcuts (KeyboardControl)
Overview
KeyboardControl registers shortcuts in the page, listens for keys and chords, and can register global shortcuts in the suite environment.
Import
import { KeyboardControl } from '@ugreen-nas/core/plugins'DefineKeyAction fields
Each shortcut is an object. Besides the table below you may add custom fields (e.g. propname) for your own use.
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | Shortcut string; use + for chords, e.g. ctrl+a; no spaces around + |
| action | string | No | Action id in the callback; either this or command |
| command | string | No | Alias of action |
| disabled | boolean | No | If true, this entry is not registered |
| hidden | boolean | No | If true, this entry is not registered |
| longPress | number | No | Long-press threshold in seconds |
| immediately | boolean | No | If true, repeated chord triggers (e.g. hold Ctrl and press c/v); throttling prevents default automatically |
| global | boolean | No | If true, register as cross-iframe global shortcut; only effective in app tray/card environments (see “Global shortcuts”) |
Constructor
KeyboardControl()
Creates an instance and attaches keydown / keyup listeners.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| defineAction | DefineKeyAction[] | Yes | — | Shortcut definitions |
| enableInFormElements | boolean | No | true | If true, shortcuts also fire inside input, textarea, etc.; if false, they do not, to avoid breaking typing |
| triggerInterval | number | No | 300 | Minimum interval (ms) between triggers for immediately throttling |
Returns: KeyboardControl instance.
Static methods
haveGlobalKey()
Whether a normalized chord string already exists in the global shortcut table.
| Name | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | Normalized chord (lowercase, ctrl/meta rules, etc.) |
Returns: boolean
Instance methods
destroy()
Removes listeners and clears global entries registered by this instance.
Returns: void
updateKeyMap()
Replaces definitions: clears prior global registrations, then rebuilds from the new array.
| Name | Type | Required | Description |
|---|---|---|---|
| defineAction | DefineKeyAction[] | No | New array; if omitted, nothing is updated |
Returns: void
setTriggerInterval()
Sets throttle interval (ms) for immediately and similar.
| Name | Type | Required | Description |
|---|---|---|---|
| interval | number | Yes | Used when ≥ 0 |
Returns: void
syncRemoteGlobalKeys()
Syncs the global shortcut list from the host.
| Name | Type | Required | Description |
|---|---|---|---|
| keys | string[] | Yes | Normalized chord list |
Returns: void
Getter / setter
beforeHandle (setter)
Interceptor before handling; if it returns true, the shortcut is not handled.
Assign a function or undefined.
| Name | Type | Required | Description |
|---|---|---|---|
| func | (() => boolean) | undefined | No | Return true to block |
Events
action
Fired when a registered shortcut is recognized (in-iframe or after global/host forwarding).
| Name | Type | Description |
|---|---|---|
| action | string | action / command from config (or internal placeholder) |
| event | KeyboardEvent | For long-press edge cases, type may be keyup, longKeydown, keydown, etc. (see notes) |
keyPress
Fired when a key participates in the current logic (including keyCache branches).
| Name | Type | Description |
|---|---|---|
| event | KeyboardEvent | Keyboard event |
keyEvent
Fired on any key down or up.
| Name | Type | Description |
|---|---|---|
| event | KeyboardEvent | Keyboard event |
| type | 'up' | 'down' | Down or up |
debug
Debug: logs the declared chord string during handling.
| Name | Type | Description |
|---|---|---|
| press | string | Normalized chord string |
Global shortcuts (cross iframe)
In multi-iframe suite setups, some shortcuts can be global: if another iframe does not define the same key, the registrant handles it.
Requirements
- Set
global: trueon the entry - Environment must be app tray/card
Behavior
- Priority: If the current iframe defines the same key, local wins; global is not used
- Cross-origin children: host syncs the shortcut table to child windows
- Lifecycle:
destroy()/updateKeyMap()clear global entries; calldestroy()when the suite closes to avoid leaks
Example
import { KeyboardControl } from '@ugreen-nas/core/plugins'
const kb = new KeyboardControl([
{ key: 'Ctrl+S', action: 'save' },
{ key: 'ArrowLeft', action: 'globalLeft', global: true },
])
kb.on('action', (action, event) => {
if (action === 'globalLeft') {
/* ... */
}
})
kb.destroy()Key definitions
Rules
- Key names are case-insensitive; normalized to lowercase internally
- Chords use
+, e.g.ctrl+a - Multiple modifiers allowed, e.g.
ctrl+shift+a - No spaces around
+
Modifiers
| Name | Meaning | Platform notes |
|---|---|---|
ctrl | Control | On Mac, mapped to Command (meta) |
alt | Alt | On Mac, Option |
option | Option | Normalized to alt; Mac only |
shift | Shift | Universal |
meta | Meta | Windows: Win; Mac: Command; non-Mac may map to ctrl |
command | Command | Maps to meta on Mac or ctrl elsewhere |
Special keys
| Name | Key | Notes |
|---|---|---|
space | Space | Do not use a literal space character |
enter | Enter | |
tab | Tab | |
esc | Esc | Normalized to escape |
escape | Esc | Full form |
backspace | Backspace | |
delete | Delete |
Arrows
| Name | Key |
|---|---|
arrowup | Up |
arrowdown | Down |
arrowleft | Left |
arrowright | Right |
Function keys
| Name | Key |
|---|---|
f1 - f12 | F1 - F12 |
Example: define and listen
import { KeyboardControl } from '@ugreen-nas/core/plugins'
const keyboardControl = new KeyboardControl([
{ key: 'Ctrl+c', action: 'copy', disabled: true },
{ key: 'Shift+c', action: 'custom', hidden: true },
{ key: 'Ctrl+v', action: 'paste' },
])
keyboardControl.on('action', (action, event) => {
// ...
})
keyboardControl.destroy()immediately mode
Note
With the snippet below you can keep Ctrl held and press C / V repeatedly. Throttling prevents default automatically.
import { KeyboardControl } from '@ugreen-nas/core/plugins'
const keyboardControl = new KeyboardControl([
{ key: 'Ctrl+c', action: 'copy', immediately: true },
{ key: 'Ctrl+v', action: 'custom', immediately: true },
])
keyboardControl.on('action', (action, event) => {
// ...
})Notes
- On macOS,
Ctrlis converted per the modifier rules above (typically to Command). - With
longPress: a satisfied long press firesactiononce with a possiblelongKeydowntype; a short press may still fire onkeyupwithevent.type === 'keyup'; while held without satisfying long press, behavior matches normalkeydown. - With
global: true, the environment must be tray/card; otherwise it is not added to the global table.