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

method on-command

method on-command() returns Supply

Supply 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 Mu

Register 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 Mu

Remove every registered command. Useful if commands are context-dependent and the palette is rebuilt on open.

method reset

method reset() returns Mu

Reset 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::Modal

Build 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 Mu

Mouse: 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 Mu

The underlying Modal, available after build has been called. Pass to $app.show-modal when binding the palette to a keybind.

Selkie v0.10.0

High-level TUI framework built on Notcurses

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Notcurses::Native:ver<0.4.0+>:auth<zef:apogee>

Test Dependencies

Provides

  • Selkie
  • Selkie::App
  • Selkie::App::Internal::Dispatch
  • Selkie::App::Internal::ErrorLog
  • Selkie::App::Internal::FocusTree
  • Selkie::App::Internal::HitTest
  • Selkie::App::Internal::IdleBudget
  • Selkie::App::Internal::OverlayTree
  • Selkie::App::Internal::RenderLoop
  • Selkie::App::Internal::ScreenModalLifecycle
  • Selkie::App::Internal::Terminal
  • Selkie::App::Internal::TerminalSequences
  • Selkie::Container
  • Selkie::EffectiveBounds
  • Selkie::Event
  • Selkie::Layout::Allocate
  • Selkie::Layout::HBox
  • Selkie::Layout::Split
  • Selkie::Layout::VBox
  • Selkie::Plot::Palette
  • Selkie::Plot::Scaler
  • Selkie::Plot::Ticks
  • Selkie::ScreenManager
  • Selkie::Sizing
  • Selkie::Store
  • Selkie::Style
  • Selkie::Test::Focus
  • Selkie::Test::Keys
  • Selkie::Test::Snapshot
  • Selkie::Test::Snapshot::Harness
  • Selkie::Test::Store
  • Selkie::Test::Supply
  • Selkie::Test::Tree
  • Selkie::Theme
  • Selkie::Trace
  • Selkie::Tree
  • Selkie::Widget
  • Selkie::Widget::Axis
  • Selkie::Widget::BarChart
  • Selkie::Widget::Border
  • Selkie::Widget::Button
  • Selkie::Widget::CardList
  • Selkie::Widget::Checkbox
  • Selkie::Widget::CommandPalette
  • Selkie::Widget::ConfirmModal
  • Selkie::Widget::FileBrowser
  • Selkie::Widget::FocusableByDefault
  • Selkie::Widget::Heatmap
  • Selkie::Widget::HelpOverlay
  • Selkie::Widget::Histogram
  • Selkie::Widget::Image
  • Selkie::Widget::Legend
  • Selkie::Widget::LineChart
  • Selkie::Widget::ListView
  • Selkie::Widget::Modal
  • Selkie::Widget::MultiLineInput
  • Selkie::Widget::PasswordStrength
  • Selkie::Widget::Plot
  • Selkie::Widget::ProgressBar
  • Selkie::Widget::RadioGroup
  • Selkie::Widget::RichText
  • Selkie::Widget::RichText::Span
  • Selkie::Widget::ScatterPlot
  • Selkie::Widget::ScrollView
  • Selkie::Widget::Select
  • Selkie::Widget::Sparkline
  • Selkie::Widget::Spinner
  • Selkie::Widget::TabBar
  • Selkie::Widget::Table
  • Selkie::Widget::Text
  • Selkie::Widget::TextInput
  • Selkie::Widget::TextInput::HighlightSpan
  • Selkie::Widget::TextStream
  • Selkie::Widget::Toast
  • Selkie::Widget::ViewportedCardList

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.