Theme
On this page (5)

§The shared listener

bindGlobalKeys(handler) adds a handler to a single document-level keydown listener that core.js installs on the first registration and removes again after the last unbind. Instead of every component hand-rolling its own listener and guard, they all share this one, which means:

how handlers run
RuleWhat it means
Typing is never a shortcut (by default)Keys aimed at an input, textarea, select or contenteditable element are not dispatched - unless the handler was bound with the editable: true option; it then decides itself (isEditableTarget(event))
Registration orderHandlers run in the order they were bound
return true stops the restA handler that took the key returns true - later handlers skip that event
UnbindbindGlobalKeys returns its own unbind function; the last unbind removes the document listener
Safe before a document existsBinding outside a browser is a no-op that still returns a working unbind
const { bindGlobalKeys } = df$.shadcn.shared; // installed by core.js / all.js
const unbind = bindGlobalKeys((event) => {
  if (event.key !== '?') return;      // not ours - let later handlers see it
  event.preventDefault();
  df$('#help').get(0).showModal();    // df$ selects; showModal() stays native
  return true;                        // ours - stop here
});
// later, e.g. when the view that owns the shortcut goes away
unbind();

§Your own shortcuts

By default the shared listener is typing-safe: a key pressed while an <input>, <textarea>, <select> or contenteditable element has focus is never handed to any handler. That is deliberate - otherwise a ? or / typed into a search box would open help or jump focus instead of being typed. The catch: with the default, ? and / simply do nothing while the field has focus.

When a shortcut should work inside a field, the handler opts in per registration with { editable: true } - and then owns the decision itself: df$.shadcn.shared.isEditableTarget(event) tells it the key came from a field, and it lets ordinary typing through. The example below does exactly that:

const { bindGlobalKeys, isEditableTarget } = df$.shadcn.shared;
bindGlobalKeys((event) => {
  const inField = isEditableTarget(event);
  if (inField && event.target.value !== '' && event.key !== 'Escape') return; // typing - hands off
  if (event.key === '?') { event.preventDefault(); openHelp(); return true; }
  if (event.key === '/' && !inField) { event.preventDefault(); search.focus(); return true; }
  if (event.key === 'Escape' && inField) { event.target.blur(); return true; }
}, { editable: true }); // ← without this, keys typed into fields never arrive here

§Help on ?, search on /

One bindGlobalKeys handler registered with { editable: true }: ? opens help everywhere except inside a field that already holds text, / jumps into the search, Esc leaves the field. Click into the example first - keyboard focus has to be inside it.

§Shortcuts the components ship

built-in global keys
ComponentKeysListener
Sidebar⌘B / Ctrl+B - dock / undock the first .app-sidebarshared (bindGlobalKeys)
Presentation→ PageDown Space next · ← PageUp previous · Home End first / last · N notes · F fullscreenshared (bindGlobalKeys)
Animation Canvas← → ↑ ↓ to the neighbouring slide · O / Esc overviewshared (bindGlobalKeys)
Command⌘K / Ctrl+K - open / close the command paletteits own document listener - on purpose: ⌘K must work while typing in a field

Components only take a key when it is theirs - the sidebar only claims ⌘B when the page has a sidebar, a deck only claims arrow keys when it has a slide to go to - so the browser's own shortcuts keep working everywhere else.

§Good shortcuts

Comments, ideas or improvements? Edit this page's source on GitHub