Skip to content

Hotkey, HotkeyData, and globalHotkeyRegistry

Hotkey (from scenerystack/scenery) is the low-level object that represents a single keyboard shortcut — one main key plus optional modifier keys, with press/release/fire callbacks. KeyboardListener is built on top of Hotkey (its keys option internally creates one Hotkey per entry, exposed via keyboardListener.hotkeys), and most application code should reach for KeyboardListener first. Use Hotkey directly when you need a shortcut that lives outside the usual "attach to a focusable Node" model — either globally regardless of focus (via globalHotkeyRegistry), or as a light-weight TInputListener entry without the rest of KeyboardListener's API.

ts
import { Hotkey, globalHotkeyRegistry } from 'scenerystack/scenery';
import { Property } from 'scenerystack/axon';

// Available anywhere in the document, regardless of what has focus.
globalHotkeyRegistry.add( new Hotkey( {
  keyStringProperty: new Property( 'alt+shift+d' ),
  fire: () => console.log( 'debug overlay toggled' )
} ) );

// Or, scoped to a Node's own focus, added directly as an inputListeners entry:
someNode.addInputListener( {
  hotkeys: [
    new Hotkey( {
      keyStringProperty: new Property( 'escape' ),
      fire: () => console.log( 'closed' )
    } )
  ]
} );

Hotkey options

OptionDefaultEffect
keyStringProperty(required)A TReadOnlyProperty<OneKeyStroke> describing the key + modifiers, e.g. 'shift+tab' — a Property (not a plain string) so hotkeys can support i18n/remapping later
fireno-op( event: KeyboardEvent | null ) => void — the event is null when fired via fire-on-hold repetition
press / releaseno-opCalled on press/release; press is not called for fire-on-hold repeats
fireOnDowntrueFire when the combination is first pressed; false fires on release instead
fireOnHoldfalseWhether holding the combination repeats fire
fireOnHoldTiming'browser''browser' uses the OS/browser's own key-repeat timing; 'custom' uses the two options below
fireOnHoldCustomDelay / fireOnHoldCustomInterval400 / 100Timing in ms when fireOnHoldTiming: 'custom'
overlapBehavior'handle'How this hotkey behaves when another active hotkey shares one of its keys — see below

isPressedProperty reports whether the hotkey is currently active, interrupted records whether the most recent release was due to an interruption rather than a key-up, and interrupt() cancels an in-progress press.

overlapBehavior

ValueMeaning
'handle'Default for node-scoped hotkeys — if two active hotkeys share keys, only the one closest to the focused Node in the scene graph fires
'prevent'Default for globalHotkeyRegistry entries — overlapping global hotkeys are treated as a programming error and assert
'allow'Both overlapping hotkeys fire; takes precedence over 'prevent' if the two disagree

HotkeyData

HotkeyData bundles a set of keystrokes with the documentation metadata scenery needs for the keyboard-help dialog and PhET's internal "binder" documentation generator — it doesn't itself listen for anything. It's the shape KeyboardDragListener and other built-in listeners use internally to describe which keys they respond to, separately from the listener that acts on them.

MemberMeaning
keys (constructor option)Array<OneKeyStroke | TReadOnlyProperty<OneKeyStroke>> — the keystrokes this data describes
keyStringPropertiesNormalized array of TReadOnlyProperty<OneKeyStroke>, one per key in keys
hasKeyStroke( keyStroke )Whether any of this data's keys match the given stroke
HotkeyData.combineKeyStringProperties( array )Static — flattens several HotkeyData's key Properties into one array, handy for feeding a single KeyboardListener
HotkeyData.anyHaveKeyStroke( array, keyStroke )Static — checks a keystroke against a whole array of HotkeyData at once

globalHotkeyRegistry

A module-level singleton (not a class you construct) with one member: hotkeysProperty, a TProperty<Set<Hotkey>> of every currently-registered global hotkey. Call globalHotkeyRegistry.add( hotkey ) / .remove( hotkey ) to make a Hotkey active regardless of what has document focus — this is what KeyboardListener.createGlobal() uses internally.

Modifier keys must be listed explicitly, exactly as with KeyboardListener

Hotkey (and therefore KeyboardListener) only fires when exactly the described keys are down. A hotkey for 'tab' will not fire while shift is also held — add 'shift+tab' as a second Hotkey, or mark the modifier ignorable in the key string with 'shift?+tab', exactly as documented on KeyboardListener.