npm.io
0.9.0 • Published 11h ago

keybuddy

Licence
MIT
Version
0.9.0
Deps
0
Size
179 kB
Vulns
0
Weekly
0
Stars
5

keybuddy

npm version

Keyboard shortcuts for the browser, built around physical keys, chords, sequences, and scopes.

Install

pnpm add keybuddy

Usage

import { createKeybuddy } from 'keybuddy';

const kb = createKeybuddy(document);

kb.bind('cmd+s', save);
kb.bind('a+b', toggleOverlay);
kb.bind('g i', goToInbox);
kb.bind('cmd+k cmd+c', copyCode);
Binding Action
cmd+s hold Cmd and press S
a+b hold A and B together
g i press G, then I
cmd+k cmd+c press Cmd+K, then Cmd+C
ctrl k tap Ctrl, release it, then press K

The first step in a sequence acts as its leader key. ctrl k and ctrl+k are different: the former is a sequence, the latter is a chord.

Physical keys

Keybuddy uses KeyboardEvent.code, so bindings follow key positions, not printed characters. a means the position of A on a QWERTY keyboard even when another layout prints something else on that key. If the typed character matters, use input or beforeinput instead.

Scopes

A new instance starts in DEFAULT_SCOPE, whose value is 'default'. Exactly one scope is active at a time; default is an ordinary scope, not a global layer.

The same shortcut can do different things in different parts of an application:

import { createKeybuddy, DEFAULT_SCOPE } from 'keybuddy';

const kb = createKeybuddy(document);

kb.bind('escape', closeHome, { scope: DEFAULT_SCOPE });
kb.bind('escape', closeEditor, { scope: 'editor' });
kb.bind('escape', closePalette, { scope: 'palette' });

kb.setScope('editor');

Only closeEditor can run while editor is active. Bindings from default do not also run, and bindings in different scopes do not conflict. Within one scope, Keybuddy rejects an ambiguous pair such as standalone g and sequence g i.

When scope is omitted, bind, unbind, unbindKeys, isBound, and canBind use the active scope.

Instances

Each instance owns its bindings, listeners, scope, and held-key state. There is no global singleton, so separate documents and iframes can use separate instances.

const mainKeys = createKeybuddy(document);
const iframeKeys = createKeybuddy(iframe.contentDocument as Document);

The default filter ignores shortcuts from input, select, textarea, and contenteditable elements. Replace it when the application needs different behavior:

const kb = createKeybuddy(document, {
  filter: (event) => shouldHandleShortcut(event),
  scope: 'editor',
  sequenceTimeout: 1500,
});

Bindings

Multiple handlers can share a binding. Set exclusive: true when one handler should win over other bindings matched by the same event:

kb.bind('escape', closeModal, { exclusive: true });

Remove one handler, every handler for a shortcut, or a whole scope:

kb.unbind('ctrl+s', save);
kb.unbindKeys('ctrl+s');
kb.unbindScope('editor');

The unbind methods return the number of removed handlers.

For configurable shortcuts, canBind reports invalid input and conflicts without throwing:

const result = kb.canBind('g i');

if (!result.ok) {
  showShortcutError(result.message);
}

getBindings returns registered bindings with their scope, handler, exclusivity, and parsed steps. It can be used to build a shortcut settings screen or cheat sheet.

kb.getBindings();
kb.getBindings({ scope: 'editor' });
kb.getBindings({ keys: 'ctrl+s' });

API

createKeybuddy(document, options?)
Option Default Purpose
filter ignores editable targets decides whether an event should be handled
scope default sets the initial scope
sequenceTimeout 1000 sets the sequence timeout in milliseconds
Instance methods
Method Purpose
bind(keys, handler, options?) register a handler
unbind(keys, handler, options?) remove an exact handler
unbindKeys(keys, options?) remove every handler for a binding
unbindScope(scope) remove every handler in a scope
unbindAll() remove every handler
isBound(keys, options?) check whether a binding exists
canBind(keys, options?) validate a binding and report conflicts
getBindings(options?) inspect registered bindings
setScope(scope) change the active scope
getScope() read the active scope
reset() clear held keys and pending sequences
destroy() remove all bindings and event listeners

Invalid or conflicting bindings passed to bind throw BindError.

Supported keys

Group Keys
Letters and digits az, 09
Modifiers shift, alt/option, control/ctrl, meta/cmd/command
Function f1f24
Navigation arrows, home, end, pageup, pagedown
Editing enter, escape, backspace, tab, space, delete, insert
Locks capslock, numlock, scrolllock
Other punctuation, numpad, system, and international keys

Tokens are case-insensitive. , , , and are modifier aliases.

Not supported
  • layout-aware character bindings — Keybuddy always uses physical key positions;
  • comma-separated alternatives such as cmd+r, ctrl+r — register each binding separately;
  • media and browser keys outside the groups above;
  • unknown key names — bind throws BindError instead of registering them.

Keywords