Selkie--Widget--Table

NAME

Selkie::Widget::Table - Scrollable tabular data with columns, header, and sorting

SYNOPSIS

use Selkie::Widget::Table;
use Selkie::Sizing;

my $table = Selkie::Widget::Table.new(sizing => Sizing.flex);

$table.add-column(name => 'id',    label => 'ID',    sizing => Sizing.fixed(6));
$table.add-column(name => 'name',  label => 'Name',  sizing => Sizing.flex,     :sortable);
$table.add-column(name => 'size',  label => 'Size',  sizing => Sizing.fixed(10), :sortable);

$table.set-rows([
    { id => 1, name => 'alpha', size => 42_000 },
    { id => 2, name => 'beta',  size => 1_200_000 },
    { id => 3, name => 'gamma', size => 873 },
]);

$table.on-select.tap:   -> UInt $idx { show-detail($idx) };
$table.on-activate.tap: -> UInt $idx { open-row($idx) };

DESCRIPTION

A two-section widget: a header row (column labels, plus sort indicators on sortable columns) and a scrollable body of data rows. Rows are supplied as hashes keyed by column name. Each cell is rendered as the string form of the hash value unless the column has a custom render callback.

Column widths follow the same sizing model as Selkie::Layout::HBox: fixed, percent, or flex, allocated in three passes.

Navigation

Up / Down / PageUp / PageDown / Home / End move the row cursor; the cursor is always fully visible (the body auto-scrolls). Enter fires on-activate. Mouse-wheel scrolls without changing the cursor.

Sorting

Set :sortable on a column to enable sorting. Call sort-by($name) to cycle the given column through ascending → descending → unsorted. The UI shows ā–² / ā–¼ next to the active sort column's label.

By default, sort comparison uses cmp on the raw hash value. Pass &sort-key on the column for a custom comparator key (e.g. to sort by length, by a derived property, etc).

Per-row styling

Pass row-style at construction (or call set-row-style later) to color rows by their data — status columns, error highlighting, dimmed archive entries. The callback receives the row hash and returns a Selkie::Style to overlay, or Nil for the theme default. The override is merged over the row's base style with Selkie::Style.merge: override colors win, style flags OR. The cursor row keeps its highlight flags, so a red error row under the cursor stays red and gains the highlight's bold.

EXAMPLES

Styling rows by state

Render failed jobs red and completed ones dimmed, leaving everything else on the theme default:

my $err = Selkie::Style.new(fg => 0xF38BA8, bold => True);
my $dim = Selkie::Style.new(fg => 0x6C7086);

$table.set-row-style(-> %row {
    given %row<state> {
        when 'error' { $err }
        when 'done'  { $dim }
        default      { Nil }
    }
});

Custom cell rendering

Render a size column as a human-readable string while still sorting on the raw bytes:

sub human(Int $bytes) {
    given $bytes {
        when * < 1024          { "{$bytes} B" }
        when * < 1024 ** 2     { sprintf '%.1f KB', $bytes / 1024 }
        when * < 1024 ** 3     { sprintf '%.1f MB', $bytes / (1024 ** 2) }
        default                { sprintf '%.1f GB', $bytes / (1024 ** 3) }
    }
}

$table.add-column(
    name     => 'size',
    label    => 'Size',
    sizing   => Sizing.fixed(10),
    sortable => True,
    render   => -> $raw { human($raw) },    # display
    sort-key => -> $raw { $raw.Int },       # sort by number, not string
);

Store-driven rows

Bind the table to a store path so filter/sort changes reflect automatically:

$app.store.subscribe-with-callback(
    'files-table',
    -> $s { ($s.get-in('files') // []).List },
    -> @rows { $table.set-rows(@rows) },
    $table,
);

$table.on-activate.tap: -> UInt $idx {
    my $row = $table.row-at($idx);
    $app.store.dispatch('file/open', id => $row<id>);
};

Keybind-driven sorting

$table.on-key('s', -> $ {
    # Cycle sort column through the sortable ones
    my @sortable = $table.columns.grep(*<sortable>);
    my $current = $table.sort-column;
    my $idx = @sortable.first(*.<name> eq $current, :k) // -1;
    my $next = @sortable[($idx + 1) mod @sortable.elems];
    $table.sort-by($next<name>);
});

SEE ALSO

has Callable &.row-style

Optional per-row style callback: - %row { Selkie::Style or Nil }>. Called once per visible row at render time with the row hash; a defined result is merged over the row's base style (cursor highlight or theme text) via Selkie::Style.merge — override colors win, style flags OR. Return Nil for theme defaults.

method columns

method columns() returns List

The list of registered columns in order. Read-only.

method rows

method rows() returns List

The underlying row data. Read-only.

method cursor

method cursor() returns UInt

Current cursor position within the (possibly sorted) view. 0-based.

method sort-column

method sort-column() returns Str

Name of the column currently sorted, or Nil if the rows are in insertion order.

method sort-direction

method sort-direction() returns Str

'asc' or 'desc'.

method on-select

method on-select() returns Supply

Supply emitting the cursor row index whenever the cursor moves.

method on-activate

method on-activate() returns Supply

Supply emitting the cursor row index when the user hits Enter.

method set-row-style

method set-row-style(
    &cb
) returns Nil

Set or replace the per-row style callback (see row-style). Pass a Callable taking the row hash and returning a Selkie::Style to overlay, or Nil for theme defaults.

method row-at

method row-at(
    Int $idx where { ... }
) returns Mu

Fetch the row (hash) at a position in the current view. Honors the current sort. Returns Nil if out of range.

method selected-row

method selected-row() returns Mu

The row currently under the cursor, or Nil if the table is empty.

method add-column

method add-column(
    Str:D :$name!,
    Str:D :$label!,
    Selkie::Sizing::Sizing :$sizing = Code.new,
    Bool :$sortable = Bool::False,
    :&render,
    :&sort-key
) returns Mu

Register a column. See the main pod for parameter meanings. name keys into row hashes. label is what appears in the header. sizing controls column width (Sizing.fixed, .percent, .flex). sortable enables sort-by for this column. render optional ($raw-value -- Str)> callback — without it the raw value is stringified. sort-key optional ($raw-value -- Cool)> callback — used to derive a sort key from the raw value.

method clear-columns

method clear-columns() returns Mu

Remove every column.

method set-rows

method set-rows(
    @new-rows
) returns Mu

Replace the row set. Preserves the current sort (re-applies it to the new rows). Cursor is clamped to bounds. Emits on-select if the table is non-empty.

method sort-by

method sort-by(
    Str:D $column-name,
    Str :$direction
) returns Mu

Sort the view by the given column name. Cycles through ascending → descending → unsorted on repeated calls for the same column. Set :direction explicitly to skip the cycle. No-op if the column doesn't exist or isn't sortable.

method clear-sort

method clear-sort() returns Mu

Clear any active sort and restore insertion order.

method select-index

method select-index(
    Int $idx where { ... }
) returns Mu

Move the cursor to row $idx (clamped to the last row in the current sort view). Emits on on-row-selected when the cursor actually moves; idempotent on no-ops. No-op when the table is empty.

method effective-row-style

method effective-row-style(
    %row,
    Bool :$is-cursor = Bool::False
) returns Selkie::Style

Resolve the final style for a row: the base (cursor highlight for the cursor row, theme text otherwise, both over the theme base background) merged with the row-style callback's result when one is set and returns a defined Selkie::Style. Override colors win; style flags OR — an error-red row under the cursor keeps its red foreground and additionally gains the highlight's flags. Public so style resolution is testable without a live notcurses plane.

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.