Keyboard Shortcuts

Any IC component can listen for keyboard shortcuts. You register a key combination, provide a callback, and the framework takes care of matching keystrokes and routing them to your MATLAB code. Bindings are scoped to the component: they fire when the component (or any element inside it) has focus.

Registering shortcuts

The onKey method registers a keyboard shortcut on a component. It takes a shortcut string, a callback function, and optional settings. The callback receives the component as the first argument and a data struct as the second.

Click on the input field below and press Ctrl+S or Escape to see the events fire:

Events
No events fired yet
k = editor.onKey("Ctrl+S", @(~, evt) save(evt));
k = editor.onKey("Escape", @(~, ~) cancel());

Shortcut format

A shortcut string is one or more modifiers joined with +, followed by the key name. Modifier order does not matter: Ctrl+Shift+S and Shift+Ctrl+S are equivalent.

The available modifiers are:

ModifierDescription
CtrlControl key (Windows/Linux/macOS)
ShiftShift key
AltAlt / Option key
MetaCmd key on macOS, Windows key on Windows

The key part is any standard KeyboardEvent.key value: Enter, Escape, Tab, ArrowUp, ArrowDown, F1 through F12, or a single character like s, d, z. Single character keys are matched case-insensitively.

Cross-platform shortcuts

On macOS, users expect Cmd-based shortcuts (Cmd+S, Cmd+Enter), while Windows and Linux use Ctrl. Since Ctrl and Meta are separate modifiers, register both if you want a shortcut that works on all platforms:

% register both Ctrl (Windows/Linux) and Cmd (macOS)
editor.onKey("Ctrl+Enter", @(~,~) compile(), "PreventDefault", true);
editor.onKey("Meta+Enter", @(~,~) compile(), "PreventDefault", true);

Containers and event bubbling

Keyboard events bubble up through the DOM tree. When a user presses a key while a child component has focus, the event propagates upward through all ancestor elements. This means you can register a shortcut on a container (a panel, a grid, a tab) and it will fire whenever any component inside that container has focus:

panel = ic.Panel();
editor = ic.CodeEditor();
panel.addChild(editor);

% fires when any child of the panel has focus
panel.onKey("Ctrl+S", @(~,~) saveAll());

This is especially useful for grouping related shortcuts or capturing key events for the whole ic.Frame. Instead of registering the same shortcut on every child component, register it once on the parent container and handle it in a single place.

To avoid that a parent can capture a child event, use onKey on the child with the StopPropagation option, explained in the next section.

Preventing default behavior

Some key combinations trigger default browser actions: Enter inserts a newline in a text area, Ctrl+S opens the browser’s save dialog, Tab moves focus. The PreventDefault option stops the browser from performing its default action when the shortcut matches. There is also a StopPropagation option that prevents the event from bubbling to parent elements.

editor.onKey("Ctrl+Enter", @(~,~) compile(), ...
    "PreventDefault", true);

Both options can be combined in a single call:

editor.onKey("Ctrl+Enter", @(~,~) compile(), ...
    "PreventDefault", true, ...
    "StopPropagation", true);

In the demo below, Enter is intercepted on a text area. Normally it would insert a newline, but PreventDefault suppresses that, and the keystroke shows up in the event log instead:

Events
No events fired yet

Callback data

The callback receives two arguments: the component that owns the binding, and a struct with three fields:

FieldDescriptionExample
shortcutThe matched shortcut string"Ctrl+S"
keyThe key value from the browser"s"
codeThe code value (physical key)"KeyS"
comp.onKey("Ctrl+S", @(src, data) handleKey(src, data));

function handleKey(comp, data)
    disp(data.shortcut);  % "Ctrl+S"
    disp(data.key);       % "s"
    disp(data.code);      % "KeyS"
end

In most cases you only need the shortcut name to know which action to take. The key and code fields are useful when you need to distinguish physical keys regardless of keyboard layout.

Multiple callbacks

You can register multiple callbacks for the same shortcut. Each one fires independently when the shortcut matches. Removing one does not affect the others:

% two independent callbacks on the same shortcut
k1 = comp.onKey("Ctrl+S", @(~,~) save());
k2 = comp.onKey("Ctrl+S", @(~,~) showNotification("Saved"));

% removing one does not affect the other
k1.remove();

When multiple bindings exist for the same shortcut with different options, the framework merges them with OR logic: if any binding requests PreventDefault, the browser default is suppressed for all of them.

Removing bindings

The onKey method returns a ic.key.KeyBinding handle. Call remove() on it to unregister that specific callback. When the handle goes out of scope or is deleted, the binding is removed automatically.

k = comp.onKey("Ctrl+S", @(~,~) save());

% later, remove just this binding
k.remove();

To remove all bindings from a component at once, use clearKeys():

% remove all keyboard bindings from a component
comp.clearKeys();