Selkie--Widget--CommandPalette
NAME
Selkie::Widget::CommandPalette - VS-Code-style fuzzy-filtered action launcher
SYNOPSIS
use Selkie::Widget::CommandPalette;
my $palette = Selkie::Widget::CommandPalette.new;
$palette.add-command(label => 'New note', -> { create-note });
$palette.add-command(label => 'Quit', -> { $app.quit });
$palette.add-command(label => 'Toggle theme', -> { toggle-theme });
my $modal = $palette.build;
# Close the modal and run the command when the user activates one.
$palette.on-command.tap: -> $cmd {
$app.close-modal;
$cmd.action.();
};
# Bind Ctrl+P to open the palette
$app.on-key('ctrl+p', -> $ {
$palette.reset;
$app.show-modal($modal);
$app.focus($palette.focusable-widget);
});DESCRIPTION
A modal that slides in a search box over a scrollable action list. Type to filter, arrows to navigate, Enter to run, Esc to cancel. Commands are registered once ā each carries a label and an action callback.
Filtering is a simple case-insensitive substring match. Matches are ranked with earlier-position-in-label winning over later-position matches; ties fall back to insertion order. Good enough for action palettes with hundreds of commands, which covers most real use.
The modal closes itself on Enter before invoking the callback ā so the callback runs with focus restored to whatever had it before the palette opened. If you need the palette to stay open (e.g. for chained commands), call $app.show-modal again from inside the callback.
EXAMPLES
App-wide command palette
my $palette = Selkie::Widget::CommandPalette.new;
# Register at startup, wherever your commands live:
$palette.add-command(label => 'Save document',
-> { $app.store.dispatch('doc/save') });
$palette.add-command(label => 'New document',
-> { $app.store.dispatch('doc/new') });
$palette.add-command(label => 'Close document',
-> { $app.store.dispatch('doc/close') });
$palette.add-command(label => 'Toggle dark mode',
-> { $app.store.dispatch('theme/toggle') });
my $modal = $palette.build;
$app.on-key('ctrl+p', -> $ {
$palette.reset; # clear filter + reset selection
$app.show-modal($modal);
$app.focus($palette.focusable-widget);
});Contextual palette for a specific screen
Build separate palettes for different contexts ā e.g. an editor palette distinct from an inbox palette. Register each on a screen-scoped keybind:
$app.on-key('ctrl+p', :screen('editor'), -> $ {
$app.show-modal($editor-palette.build);
$app.focus($editor-palette.focusable-widget);
});
$app.on-key('ctrl+p', :screen('inbox'), -> $ {
$app.show-modal($inbox-palette.build);
$app.focus($inbox-palette.focusable-widget);
});SEE ALSO
Selkie::Widget::Modal ā underlying modal
Selkie::Widget::FileBrowser ā similar wrapper pattern for file picking
method on-command
method on-command() returns SupplySupply emitting the activated Command when the user hits Enter on a filtered row. Tap this to close the modal and run the action.
method add-command
method add-command(
&action,
Str:D :$label!
) returns MuRegister a command. label is shown in the list and matched against the user's filter query; the positional &action is called with no arguments when the user activates the row. Typical usage puts the action block at the call-site tail: $palette.add-command(label => 'Save', -> { save-document });
method clear-commands
method clear-commands() returns MuRemove every registered command. Useful if commands are context-dependent and the palette is rebuilt on open.
method reset
method reset() returns MuReset the filter and cursor to fresh state. Call before re-opening the palette so the user starts with the full list.
method build
method build(
Rat :$width-ratio = 0.5,
Rat :$height-ratio = 0.5
) returns Selkie::Widget::ModalBuild the modal and wire its widgets. Call once at setup; cache the returned Modal and pass it to $app.show-modal whenever the palette should open. Safe to call multiple times ā subsequent calls return the same modal.
method focusable-widget
method focusable-widget() returns MuMouse: clicking a list row positions the cursor (single-click) or activates the command (double-click) ā these are ListView's standard semantics, inherited because CommandPalette delegates list rendering and dispatch to its inner Selkie::Widget::ListView. Scroll-wheel over the list moves the cursor. Clicking the input row places the caret as TextInput would. Which widget should receive initial focus when the modal opens. The TextInput ā typing immediately filters without pressing Tab.
method modal
method modal() returns MuThe underlying Modal, available after build has been called. Pass to $app.show-modal when binding the palette to a keybind.