list

package
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package list provides a cursor-driven, optionally filterable list inside a bordered pane. It bundles item storage, cursor tracking, viewport auto- scroll, and a filter.Model together so parents can drop it in with one New + Update + View.

Items are plain []string — callers format their data before passing it in. For filtering, the match is a case-insensitive substring across the item text. Anything richer (fuzzy match, per-field search, struct items) is out of scope and should be composed via pane + filter directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActivatedMsg added in v0.18.0

type ActivatedMsg struct {
	Index int
	Item  string
	Token focus.Token
}

ActivatedMsg is emitted when the user opens the selected item with a double click — the mouse spelling of enter (rule 14). Index and Item match the selection at the moment of the second click.

Token identifies which list sent it, so a screen holding several lists can tell them apart. Prefer IsActivate over matching this type directly unless you need the payload.

type KeyedItem

type KeyedItem struct {
	Key     string
	Display string
}

KeyedItem is a list entry with a stable identity. Pass to SetKeyedItems to preserve the cursor across data swaps even when items are reordered or partially replaced — e.g. polled refreshes of a live data set.

type Keys

type Keys struct {
	Up, Down         key.Binding
	Top, Bottom      key.Binding
	HalfUp, HalfDown key.Binding
	Filter           key.Binding
	Mark, MarkAll    key.Binding
	Pane             pane.Keys
}

Keys is the list's keymap. Each binding carries both its dispatch keys (WithKeys) and its help label (WithHelp) — Update and Help() read from the same struct, so a custom binding propagates everywhere. The embedded pane.Keys covers horizontal scroll; mutate fields on Pane to override h-scroll without touching the rest.

func DefaultKeys

func DefaultKeys() Keys

DefaultKeys returns the list's stock keymap. Mutate the returned value to override individual actions.

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is the list widget. Embed as a value; mutate via the setters.

func New

func New(opts Options) Model

New constructs a list. Call Update/View from the parent model.

func (*Model) Blur added in v0.18.0

func (m *Model) Blur()

Blur releases the keyboard, clearing *both* regions. Leaving a filter focused on a blurred component is what lets a second filterable pane end up invisibly eating keys.

func (*Model) BlurFilter added in v0.18.1

func (m *Model) BlurFilter()

BlurFilter returns input from the filter to the body.

func (*Model) ClearMarks added in v0.21.0

func (m *Model) ClearMarks()

ClearMarks drops every mark.

func (Model) Cursor

func (m Model) Cursor() int

Cursor returns the current cursor index into the visible (post-filter) set.

func (*Model) Deselect added in v0.18.1

func (m *Model) Deselect()

Deselect moves the cursor off every row, so nothing is highlighted and Selected reports ok=false. Use it for a list that must start with no choice made — a form select that demands a deliberate pick.

SetCursor deliberately does not accept -1: it clamps, so a stray negative index cannot silently blank a list. Deselecting is a distinct intent and gets a distinct call.

func (Model) Filtering

func (m Model) Filtering() bool

Filtering reports whether the embedded filter currently has focus — callers use this to decide whether to intercept global keys like "q".

func (*Model) Focus added in v0.18.0

func (m *Model) Focus() tea.Cmd

Focus gives the component the keyboard, highlighting the body pane.

A filterable list has two focusable regions behind one Focusable, so this deliberately does nothing when the filter already owns input: a click on the filter also asks the group for focus, and the grant arrives afterwards. Without this guard that grant would snatch the highlight back to the body while the filter kept the keystrokes.

func (*Model) FocusFilter added in v0.18.1

func (m *Model) FocusFilter() tea.Cmd

FocusFilter moves input to the filter and takes the highlight off the body, so exactly one region ever reads as active.

func (Model) FocusToken added in v0.18.0

func (m Model) FocusToken() focus.Token

FocusToken returns the list's stable focus identity. See focus.Identified.

func (Model) Focused added in v0.18.0

func (m Model) Focused() bool

Focused reports whether either of the component's regions owns input.

func (Model) Help

func (m Model) Help() []key.Binding

Help returns the keys this list responds to. While the embedded filter is focused it returns the filter's keys; otherwise the configured nav/scroll/filter bindings from m.keys.

func (Model) Init

func (m Model) Init() tea.Cmd

Init satisfies tea.Model — nothing to kick off.

func (Model) IsActivate added in v0.18.0

func (m Model) IsActivate(msg tea.Msg) bool

IsActivate reports whether msg means "open this list's selection" — enter from the keyboard while the filter isn't taking input, or this list's own double-click activation.

Rule 14 makes those the same verb, and routing them through one predicate is what keeps them that way: a screen writes the open branch once and both inputs reach it.

if s.menu.IsActivate(msg) {
    return s, screen.Push(detailFor(s.menu.Cursor()))
}

func (Model) IsCapturingKeys added in v0.18.0

func (m Model) IsCapturingKeys() bool

IsCapturingKeys reports whether the list currently swallows printable keys — true while its filter is focused. Satisfies focus.Capturer, so a focus.Group can answer the app shell's global-key gating (rule 5) without the screen restating it.

func (Model) Items

func (m Model) Items() []string

Items returns the full unfiltered item set.

func (Model) Loading

func (m Model) Loading() bool

Loading reports whether the list is currently in its loading state.

func (Model) MarkCount added in v0.21.0

func (m Model) MarkCount() int

MarkCount is how many keys are marked, including any whose rows the current filter hides.

func (Model) Markable added in v0.21.0

func (m Model) Markable() bool

Markable reports whether this list accepts marks.

func (Model) Marks added in v0.21.0

func (m Model) Marks() []string

Marks returns the marked keys in item order — not in the order they were marked, so the result is stable across equivalent selections.

func (Model) Rect added in v0.21.0

func (m Model) Rect() geom.Rect

Rect is the area the list occupies, for a caller that needs to test a position against it before acting — deciding whether a right-click landed on this list, say. Matches the accessor input, toggle, form and confirm already expose.

func (Model) Selected

func (m Model) Selected() (string, bool)

Selected returns the currently highlighted item. ok is false when the visible set (post-filter) is empty.

func (Model) SelectedIndex

func (m Model) SelectedIndex() (int, bool)

SelectedIndex returns the highlighted row's index into the original (pre-filter) Items() slice. ok is false when the visible set is empty.

Use this when list items are formatted display strings rendered from a richer source slice — SelectedIndex identifies which source row is selected, regardless of whether a filter is currently applied. For the row's text, use Selected; for the cursor's position within the filtered view, use Cursor.

func (Model) SelectedKey

func (m Model) SelectedKey() (string, bool)

SelectedKey returns the Key of the currently highlighted item when the list was populated via SetKeyedItems. ok is false when the visible set is empty or when items are anonymous (no keys set).

func (Model) Selection added in v0.21.0

func (m Model) Selection() []string

Selection is the marked keys, or the cursor row's key when nothing is marked. Empty when the items are anonymous.

This is the accessor a screen should reach for, and the reason it exists is the branch it removes. Without it every caller writes

if ks := l.Marks(); len(ks) > 0 { … } else { … }

which is easy to write once and easy to forget, and whose failure mode is a verb quietly acting on one row when the user had marked six.

func (Model) SelectionLabel added in v0.21.0

func (m Model) SelectionLabel() string

SelectionLabel names the selection for a confirm string or a menu title: the single key, or "N items".

It returns the key rather than the display text because the key is the identity — display text is usually a formatted row with columns in it, which reads badly in a sentence. A caller wanting something else formats Selection() itself.

func (*Model) SetActiveColor

func (m *Model) SetActiveColor(c lipgloss.TerminalColor)

SetActiveColor updates the body pane's active border color. Useful when reacting to a theme swap without rebuilding the model.

func (*Model) SetCursor

func (m *Model) SetCursor(n int)

SetCursor moves the cursor (clamped to the visible range) and scrolls to keep it on screen.

func (*Model) SetInactiveColor

func (m *Model) SetInactiveColor(c lipgloss.TerminalColor)

SetInactiveColor updates the body pane's inactive border color.

func (*Model) SetItems

func (m *Model) SetItems(items []string)

SetItems replaces the item set, re-applies the current filter, and redraws. Clears any per-item keys previously set via SetKeyedItems.

func (*Model) SetKeyedItems

func (m *Model) SetKeyedItems(items []KeyedItem)

SetKeyedItems replaces the item set with keyed entries and snaps the cursor to the previously-selected Key after the swap (falling back to the clamped previous cursor index when the key is gone). Use this for polled refreshes of a live data set so the user's selection survives reordering or partial replacement.

func (*Model) SetLoading

func (m *Model) SetLoading(b bool) tea.Cmd

SetLoading toggles the loading state. When entering, returns the spinner's initial Tick command — propagate it back from your screen's Update so the spinner animates. The list's Update already forwards every msg to the body pane, so subsequent ticks chain automatically.

func (*Model) SetLoadingLabel

func (m *Model) SetLoadingLabel(s string)

SetLoadingLabel updates the text rendered next to the spinner while loading.

func (*Model) SetMarks added in v0.21.0

func (m *Model) SetMarks(keys []string)

SetMarks replaces the marked set. Keys that match no item are kept: an item set swapped out and back should not silently lose the user's selection. Carries marks across a SetTheme rebuild (rule 4).

func (*Model) SetRect added in v0.18.0

func (m *Model) SetRect(r geom.Rect)

SetRect places the list in the given rect. When filterable, the internal filter pane consumes the top 3 rows and the body pane gets the rest, offset below it; otherwise the body pane takes the whole rect. Each child receives its own absolute rect so a click resolves to the right one.

func (*Model) SetSelectedColor

func (m *Model) SetSelectedColor(c lipgloss.TerminalColor)

SetSelectedColor updates the foreground color of the highlighted row.

func (*Model) SetSpinnerStyle

func (m *Model) SetSpinnerStyle(s lipgloss.Style)

SetSpinnerStyle updates the lipgloss style applied to the spinner glyph.

func (*Model) SetTitle

func (m *Model) SetTitle(s string)

func (*Model) SetTopRight added in v0.18.1

func (m *Model) SetTopRight(s string)

SetTitle updates the title rendered on the body pane's top border. Useful when the list represents a slice that can change identity at runtime (e.g. "detail · <selection>"). SetTopRight writes into the pane's top-right border slot. Hosts use it for a short annotation that belongs to the component as a whole — pkg/form paints validation errors there. Pass "" to clear.

func (*Model) SetValue

func (m *Model) SetValue(s string)

SetValue overwrites the filter text (no-op when not filterable). Useful when rebuilding the list on theme swap / resize — carry the old Value().

func (*Model) ToggleMark added in v0.21.0

func (m *Model) ToggleMark()

ToggleMark flips the mark on the cursor row. No-op when marking is off or the items are anonymous.

func (*Model) ToggleMarkAll added in v0.21.0

func (m *Model) ToggleMarkAll()

ToggleMarkAll marks every currently visible row, or clears them when they are all already marked.

Visible means post-filter, which is the useful reading: filter to a subset, mark it wholesale, act. Under a windowed table the same rule applies to the rows actually held — see table.ToggleMarkAll.

func (Model) Update

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd)

Update consumes up/down/j/k and "/" (when filterable); while the filter is focused, every key is forwarded to it. Mouse events inside the list's rect move the cursor and request focus. Non-key messages are forwarded to the body pane so spinner ticks reach the loading-state animation.

func (Model) Value

func (m Model) Value() string

Value returns the current filter text ("" when not filterable or empty).

func (Model) View

func (m Model) View() string

View stacks filter (if filterable) and the body pane.

func (Model) Visible

func (m Model) Visible() []string

Visible returns the post-filter items, in display order.

type Options

type Options struct {
	Width, Height int
	// Title sits on the pane's top-left border slot. Defaults to "List".
	Title string
	// Items is the full item set. The list copies this slice so the caller
	// can mutate their source independently.
	Items []string
	// Filterable embeds a filter.Model above the body pane (three rows). If
	// false, "/" is ignored and the full height is used for items.
	Filterable bool

	// Pane pass-throughs. See pkg/pane.Options for defaults.
	ActiveColor    lipgloss.TerminalColor
	InactiveColor  lipgloss.TerminalColor
	ActiveBorder   lipgloss.Border
	InactiveBorder lipgloss.Border
	SlotBrackets   pane.SlotBracketStyle

	// HScrollbar reserves a row at the bottom of the list pane for a
	// horizontal scrollbar and lets ←/h and →/l scroll long rows
	// horizontally. theme.List() enables this by default — disable when
	// items are guaranteed short and the extra row is unwanted.
	HScrollbar bool

	// SelectedColor foregrounds the highlighted row (bold).
	SelectedColor lipgloss.TerminalColor

	// Markable adds a mark column and binds space / ctrl+a, so the user can
	// build a multi-selection the screen reads back with Selection().
	//
	// Off by default, and it costs a row nothing when off: the gutter stays
	// two cells wide. Marking requires keyed items (SetKeyedItems) — see
	// mark.go for why holding marks by index is not an option.
	Markable bool

	// MarkStyle colors the ✓ on a marked row that isn't under the cursor.
	// The cursor row is drawn as one styled run instead, so its highlight
	// cannot be broken mid-row (rule 19).
	MarkStyle lipgloss.Style

	// SpinnerStyle is applied to the spinner glyph rendered while the list
	// is in its loading state (see SetLoading). Pass via theme.List() for
	// a sensible default.
	SpinnerStyle lipgloss.Style
	// LoadingLabel is rendered next to the spinner while loading — e.g.
	// "loading cities…".
	LoadingLabel string

	// Filter configures the embedded filter. Ignored when Filterable=false.
	Filter filter.Options

	// Keys is the list's keymap. Leave zero to use DefaultKeys; set
	// individual bindings to override (others fall back to defaults via
	// fillDefaults). theme.List() pre-populates this.
	Keys Keys
}

Options configures a new list. Zero-value fields fall back to sane defaults where that's meaningful; otherwise the pane/filter defaults apply.

type SelectedChangedMsg added in v0.17.0

type SelectedChangedMsg struct {
	// Index is the cursor's position in the post-filter visible slice.
	// Zero when Empty is true.
	Index int
	// Item is the currently focused item's string value. Empty when
	// Empty is true.
	Item string
	// Empty is true when no item is focused (empty visible set or
	// cursor out of range). Index / Item are zero-valued.
	Empty bool
}

SelectedChangedMsg is emitted by the list when the cursor lands on a different item than the last time we emitted — after cursor movement, after a SetItems / filter operation that changes which item is under the cursor, or on the initial view. Parents subscribe to it to drive "detail on hover" patterns: refetch a parameterized detail source keyed on the focused item. Dedup is on (index, item) so a SetItems swap that lands the same content under the cursor doesn't re-emit; a swap that changes the content does. Empty=true fires only as a transition (had focus → no focus) — an initially-empty list never emits.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL