table

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: 15 Imported by: 0

Documentation

Overview

Package table provides a cursor-driven, optionally filterable tabular view inside a bordered pane. Each Row is a []string of cell text aligned against a fixed-width Column slice. The header pins to the top of the pane (it does not scroll out of view as the cursor moves), but it scrolls horizontally with the body so columns stay aligned with their titles when the user scrolls a wide table left/right.

Cells are rendered ANSI-aware via x/ansi.Cut, so colored content (foreground-only escapes such as pkg/ansi.CellColor) survives column truncation without leaking color into adjacent cells. There is no "+8 budget for the ANSI escape" caveat — column Width is the visible width of the cell and that's what truncation respects. Lipgloss styles are also fine inside cells (header/selected styling applies on top of any inner styling), but inner full-reset SGR sequences will still clobber the selected-row background; prefer foreground-only color escapes for status-style cells when you need the row highlight to pass through unbroken.

Filter syntax (when Filterable=true): the input is split on whitespace into AND-ed terms. A bare term matches any cell as a case-insensitive substring. A term shaped "key:value" scopes the match to the column whose Title case-insensitively starts with key (e.g. "region:europe"); an ambiguous or unknown key falls through as a literal bare term, which is also how to search for a literal colon. A term whose value starts with "~" is compiled as a case-insensitive Go regex (e.g. "~^new", "region:~^euro"); compile errors fall back to a literal substring including the tilde, so the parser never refuses input. While the user is mid-typing a "key:val" term the filter pane's bottom-left slot lists the column's distinct values matching val, and tab completes val to the longest common prefix of the remaining candidates — regex terms skip the hint since enumerating regex matches isn't useful. The grammar itself lives in pkg/query, so a caller translating the same filter into a remote request gets the identical parse without importing this package.

Horizontal nav: ←/→ (or h/l) scroll by HScrollStep cells; 0/home jump to the leftmost edge; $/end jump to the rightmost edge; shift+←/shift+→ snap the viewport to the previous / next column boundary so a wide table can be stepped column-by-column instead of cell-by-cell.

Sort: set Column.Sortable to opt a column in. Keys are "[" / "]" to step the active sort column among Sortable columns and "s" to toggle direction; the active column gets a ▲ / ▼ marker after its title. Default comparator is case-insensitive on the ANSI-stripped cell text; override per-column with Column.Less for numeric, date, or unit-aware sort. SortColumn() / SortDescending() / SetSort(col, desc) carry sort state across SetTheme rebuilds the same way Cursor / Value do.

Remote sources: Options.FilterMode and Options.SortMode decide whether the table answers the filter and sort itself or reports them for someone else to answer. Under FilterRemote / SortRemote the table displays its rows exactly as given and emits QueryChangedMsg when the user commits a filter or requests a sort; the screen turns that into a request and pushes the result back through SetRows / SetKeyedRows. Filter hints come from SetDistinct rather than from the rows on screen, since a single page of a larger set completes to values that are wrong rather than merely incomplete. The two modes are independent — a table can sort remotely while filtering the page it holds, or the reverse.

SetWindow(rows, offset, total) is the other half: the table holds only the logical indices [offset, offset+len(rows)) of a set total rows long, while the cursor, the scrollbar and the counters all work against total. Indices the window doesn't hold render as Options.Placeholder and report ok=false from Selected, so scrolling past the loaded range shows filler rather than wrong data and a screen can't act on a row it never received. Pair it with ViewportChangedMsg, which reports the logical range now on screen — that is the signal to fetch the next window.

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 {
	Row   int
	Cells []string
	Token focus.Token
}

ActivatedMsg is emitted when the user opens the selected row with a double click — the mouse spelling of enter (rule 14). Row is the index into the post-filter visible set; Cells is that row's content.

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

type Borders

type Borders struct {
	// Vertical, when non-empty, replaces the single-space inter-column
	// separator with " <glyph> " (visible width 3). Typical values:
	// "│" (light), "┃" (heavy), "╎" (dashed). Pre-style the glyph.
	Vertical string
	// HeaderRule, when non-empty, draws a horizontal rule between the
	// header row and the first data row by repeating the field's first
	// visible rune to the table's full visible width. Typical values:
	// "─" (light), "═" (double), "·" (dotted). Pre-style the glyph; the
	// SGR escapes are extracted and re-applied around the repeated rune.
	HeaderRule string
}

Borders controls the two interior separators a table draws — the inter-column glyph and the horizontal rule below the header. Both fields are pre-styled glyph strings; pass them through pkg/ansi.CellColor (foreground-only) so the selected row's background passes through unbroken (rule 17). Set a field to "" to disable it.

type Column

type Column struct {
	Title string
	Width int
	// Align controls cell padding within the column. Use lipgloss.Left
	// (default), lipgloss.Right (good for numeric columns), or
	// lipgloss.Center.
	Align lipgloss.Position
	// Sortable allows the user to sort by this column with [/] (step
	// active sort column) and s (toggle direction). Non-sortable columns
	// are skipped during step. The active column gets a ▲/▼ marker
	// rendered after its title.
	Sortable bool
	// Less compares two cell strings for this column. If nil, sortable
	// columns use a case-insensitive comparison on the ANSI-stripped
	// text — fine for plain string columns. Set Less for numeric, date,
	// or unit-aware columns ("8.3M") that need custom parsing.
	Less func(a, b string) bool
	// Flex, when > 0, makes this column absorb a share of leftover
	// horizontal space after every column's base width is accounted for.
	// Multiple flex columns split the remainder proportionally
	// (Flex=1 + Flex=2 → 1:2 split). The base width acts as a minimum:
	// a flex column never shrinks below it, but it does grow when room
	// is available. When the table is narrower than the sum of base
	// widths, flex columns get no expansion (extra space goes to the
	// pane's horizontal scroll, not column reflow).
	Flex int
	// MaxWidth, when > 0, caps the column's effective width — flex
	// growth never pushes it above this value. When a flex column hits
	// its cap, the surplus redistributes to the remaining uncapped
	// flex columns by their weights (iteratively, so chains of caps
	// are handled). When every flex column is capped, leftover space
	// stays unused on the right edge of the row.
	MaxWidth int
	// Hidden, when true, keeps the column in the data model but omits it
	// from rendering + width computation entirely. Hidden columns still:
	//   - Participate in filter matching (bare terms scan every cell,
	//     key:value scoped terms resolve Title against hidden columns
	//     too — e.g. "namespace:default" filters a table that has a
	//     hidden Namespace column).
	//   - Appear in SelectedRow / RowFocusedMsg.Cells / Columns so
	//     parents can capture identity fields for drilldown (e.g. bind
	//     ${selection.Namespace}) without giving up screen real estate.
	// Rows must still have one cell per declared column — hidden columns
	// don't change the row shape, they just hide their slot from view.
	Hidden bool
}

Column declares one column's title, width (in visible cells), and cell alignment. Width sizing modes:

  • Width > 0, Flex == 0: fixed width.
  • Width == 0, Flex == 0: content-auto — sized to the widest of title and any cell value (ANSI-aware, floor of 4).
  • Flex > 0: column expands to absorb a share of leftover horizontal space, weighted by Flex. Width (or content-auto when Width==0) acts as a minimum; MaxWidth (when > 0) caps growth.

type FilterMode added in v0.20.0

type FilterMode int

FilterMode selects who applies the filter the user types.

const (
	// FilterLocal applies the filter to the rows the table holds. This is
	// the zero value, so existing tables keep filtering themselves.
	FilterLocal FilterMode = iota
	// FilterRemote stops the table filtering its own rows and makes it
	// report committed queries as QueryChangedMsg instead. The rows the
	// table holds are already the answer to the last query, so they are
	// displayed as given.
	FilterRemote
)

type KeyedRow

type KeyedRow struct {
	Key   string
	Cells []string
}

KeyedRow pairs a stable identity Key with the row's cells. Pass through SetKeyedRows when the row source is polled so the cursor can re-bind to the same Key after a refresh — when the row at the cursor's Key reappears in the new set the cursor follows it; otherwise it falls back to the clamped previous index. Use SelectedKey to read the current row's identity. KeyedRow is a separate path (not a swap of the Row type) so existing SetRows callers see no change.

type Keys

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

Keys is the table'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 table's stock keymap.

type Model

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

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

func New

func New(opts Options) Model

New constructs a table.

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) Columns

func (m Model) Columns() []Column

Columns returns the current column layout.

func (Model) Cursor

func (m Model) Cursor() int

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

func (Model) Filtering

func (m Model) Filtering() bool

Filtering reports whether the embedded filter currently has focus.

func (*Model) Focus added in v0.18.0

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

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

It deliberately does nothing when the filter already owns input: a click on the filter also asks the group for focus, and that grant arrives afterwards. Without this guard it 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 table'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 table responds to.

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 table's selection" — enter from the keyboard while the filter isn't taking input, or this table's own double-click activation. See list.Model.IsActivate; rule 14 makes the two inputs one verb, and this predicate is what keeps them that way.

func (Model) IsCapturingKeys added in v0.18.0

func (m Model) IsCapturingKeys() bool

IsCapturingKeys reports whether the table currently swallows printable keys — true while its filter is focused. Satisfies focus.Capturer.

func (Model) Loading

func (m Model) Loading() bool

Loading reports whether the table is 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 the filter hides.

func (Model) Markable added in v0.21.0

func (m Model) Markable() bool

Markable reports whether this table accepts marks.

func (Model) Marks added in v0.21.0

func (m Model) Marks() []string

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

func (Model) Query added in v0.20.0

func (m Model) Query() QueryChangedMsg

Query returns the query a remote source should currently be answering. Screens call it for the first fetch, before the user has touched anything, so the initial load runs through the same code path as every QueryChangedMsg that follows.

func (Model) Rows

func (m Model) Rows() []Row

Rows returns the full unfiltered row set.

func (Model) Selected

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

Selected returns the currently highlighted row. ok is false when the visible set (post-filter) is empty, and — under SetWindow — when the cursor sits on a logical index the resident window doesn't hold, so a screen never acts on a row it hasn't received.

func (Model) SelectedIndex

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

SelectedIndex returns the highlighted row's index into the original (pre-filter) Rows() slice. Use this when callers maintain a parallel source slice and need to identify which source row is selected.

func (Model) SelectedKey

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

SelectedKey returns the highlighted row's Key when the table was populated via SetKeyedRows. ok is false when no row is selected, or when the rows were set via SetRows (which carries no keys). Callers that drive a polled source should track the selection by Key, not by SelectedIndex, so re-fetches that reorder rows don't shift the selection out from under the user.

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 rows are anonymous or the table is windowed.

Reach for this rather than Marks: it removes the branch whose failure mode is a verb quietly acting on one row when the user 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".

func (*Model) SetActiveColor

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

SetActiveColor / SetInactiveColor update the body pane's border colors. Useful for theme swaps that don't rebuild the model.

func (*Model) SetCellStyle

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

func (*Model) SetColumns

func (m *Model) SetColumns(cols []Column)

SetColumns replaces the column layout. Cell text is preserved; effective widths recompute on the next refresh.

func (*Model) SetCursor

func (m *Model) SetCursor(n int)

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

func (*Model) SetDistinct added in v0.20.0

func (m *Model) SetDistinct(col int, values []string)

SetDistinct supplies the candidate values behind col's filter hint and tab completion. This is the FilterRemote counterpart to scraping them from resident rows: feed it a facet endpoint, an enum, or a schema, and completion suggests values the source actually has rather than the handful that happen to be on this page. Values are normalized on the way in, so pass them however the server spells them.

Under FilterLocal the candidates are recomputed from the rows on the next row or column change, which will overwrite anything set here.

func (*Model) SetHeaderStyle

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

SetHeaderStyle / SetSelectedStyle / SetCellStyle update row styling.

func (*Model) SetInactiveColor

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

func (*Model) SetKeyedRows

func (m *Model) SetKeyedRows(rows []KeyedRow)

SetKeyedRows replaces the row set with each row carrying a stable Key, then snaps the cursor to whichever row in the new set shares the previously-selected Key. When the previous Key has disappeared (row removed from the source), the cursor falls back to the clamped previous index so the user lands near where they were. The filter and sort state are preserved across the swap. This is the primitive pkg/poll uses to keep the user's place across periodic refreshes.

func (*Model) SetLoading

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

SetLoading toggles the loading state. Returns the spinner's first Tick when entering — propagate it back so the spinner animates.

func (*Model) SetLoadingLabel

func (m *Model) SetLoadingLabel(s string)

SetLoadingLabel updates the text rendered next to the spinner.

func (*Model) SetMarks added in v0.21.0

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

SetMarks replaces the marked set. 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 table in the given rect. When filterable, the internal filter pane takes the top 3 rows and the body pane gets the rest, offset below it. Each child receives its own absolute rect so a click resolves to the right one.

func (*Model) SetRows

func (m *Model) SetRows(rows []Row)

SetRows replaces the row set, re-applies the current filter, redraws. Cursor is preserved by visible index — fine for static datasets, but callers polling a live source should prefer SetKeyedRows so the cursor rebinds to the same logical row even when neighbours come and go.

func (*Model) SetSelectedStyle

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

func (*Model) SetSort

func (m *Model) SetSort(col int, desc bool)

SetSort sets the sort column and direction. col == -1 disables sort; otherwise col must reference a Sortable column. Use this on rebuild (theme swap) to carry SortColumn/SortDescending across the new model. Under SortRemote it adopts the sort silently, for the same reason SetValue does: restoring state is not the user asking for a new sort.

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)

SetTitle updates the title rendered on the body pane's top border.

func (*Model) SetValue

func (m *Model) SetValue(s string)

SetValue overwrites the filter text (no-op when not filterable). Under FilterRemote this adopts the new text as the committed baseline without emitting QueryChangedMsg — it is the setter a SetTheme rebuild uses, and a rebuild is not a new query. Call Query() yourself if you set a filter programmatically and want it fetched.

func (*Model) SetWindow added in v0.20.0

func (m *Model) SetWindow(rows []Row, offset, total int)

SetWindow installs a sparse window: rows are the logical indices [offset, offset+len(rows)) of a set total rows long. Pass total < 0 when the source can't say (cursor-paginated APIs); the table then treats the end of what has loaded as the end, which grows as more arrives.

The cursor is a logical index and does not move when a window lands, so scrolling to row 800 and having that window arrive leaves the cursor on row 800. Indices the window doesn't hold render as Placeholder and report ok=false from Selected, so a screen can't act on a row it hasn't actually received.

Windowing implies the rows are the source's answer: filter and sort are never applied locally to a window, whatever FilterMode / SortMode say, because filtering one page of a larger set is not filtering. Pair it with FilterRemote / SortRemote so the query the user builds actually reaches the source. Prefer fixed or Flex column widths too — content-auto sizes to the widest resident cell, so columns reflow every time the window swaps.

func (Model) SortColumn

func (m Model) SortColumn() int

SortColumn returns the active sort column index (-1 when no sort).

func (Model) SortDescending

func (m Model) SortDescending() bool

SortDescending reports the active sort direction.

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, the rows are anonymous, or the table is windowed.

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.

func (Model) Update

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

Update consumes cursor + filter keys; non-key messages flow 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.

func (Model) View

func (m Model) View() string

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

func (Model) Visible

func (m Model) Visible() []Row

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

func (Model) Window added in v0.20.0

func (m Model) Window() (offset, count, total int)

Window reports the resident window: the logical index of its first row, how many rows are resident, and the logical total (-1 when unknown). A coordinator turning ViewportChangedMsg into fetches reads this to decide whether the rows on screen are already in hand.

type Options

type Options struct {
	Width, Height int
	// Title sits on the pane's top-left border slot. Defaults to "Table".
	Title string
	// Columns declares the column layout. Required.
	Columns []Column
	// Rows is the full row set. The table copies this slice so the caller
	// can mutate their source independently.
	Rows []Row
	// Filterable embeds a filter.Model above the body pane (three rows).
	// See the package doc for the full filter syntax — bare substring,
	// "key:value" column scope, "~regex" prefix, and the distinct-value
	// hint + tab completion that fires while typing a "key:" term.
	Filterable bool

	// FilterMode selects who applies the filter. Defaults to FilterLocal.
	// FilterRemote requires Filterable — without a filter bar there is no
	// query to report.
	FilterMode FilterMode
	// SortMode selects who applies the sort. Defaults to SortLocal.
	SortMode SortMode
	// Placeholder is the cell text drawn for a row inside the logical
	// range that the current window doesn't hold — see SetWindow.
	// Defaults to "·". Pre-style it foreground-only (pkg/ansi.CellColor)
	// if you want it dimmed, the same way Borders glyphs are styled, so
	// the selected-row background passes through unbroken.
	Placeholder string

	// Pane pass-throughs.
	ActiveColor    lipgloss.TerminalColor
	InactiveColor  lipgloss.TerminalColor
	ActiveBorder   lipgloss.Border
	InactiveBorder lipgloss.Border
	SlotBrackets   pane.SlotBracketStyle

	// HScrollbar reserves a row at the bottom of the body pane and lets
	// ←/h and →/l scroll wide tables horizontally. theme.Table() enables
	// this by default — disable when columns are guaranteed to fit.
	HScrollbar bool

	// HeaderStyle is applied to the header row (typically bold). Header
	// cells are still padded/aligned by column before the style runs.
	HeaderStyle lipgloss.Style
	// SelectedStyle is applied to the highlighted row. theme.Table()
	// uses bold + Accent fg + Subtle bg.
	SelectedStyle lipgloss.Style

	// Markable adds a mark gutter and binds space / ctrl+a, so the user can
	// build a multi-selection the screen reads back with Selection().
	//
	// Off by default and free when off: the gutter takes no columns. Marking
	// requires keyed rows (SetKeyedRows) and is inert on a windowed table —
	// see mark.go.
	Markable bool

	// MarkStyle colors the ✓ on a marked row that isn't under the cursor.
	MarkStyle lipgloss.Style
	// CellStyle is applied to non-selected rows. Defaults to no style.
	CellStyle lipgloss.Style

	// SpinnerStyle styles the loading-state spinner glyph. Pass via
	// theme.Table() for a sensible default.
	SpinnerStyle lipgloss.Style
	// LoadingLabel is rendered next to the spinner while loading.
	LoadingLabel string

	// Filter configures the embedded filter.Model. Ignored when
	// Filterable=false. Theme.Table() pre-fills this from Theme.Filter().
	Filter filter.Options

	// Borders configures table-internal separators. Each field is emitted
	// verbatim, so pre-style with pkg/ansi.CellColor (foreground-only) so
	// the selected-row background passes through. Empty fields disable
	// the corresponding separator. Theme.Table() sets sensible defaults.
	Borders Borders

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

Options configures a new table. Theme.Table() returns this pre-styled — set Title/Columns/Rows/Filterable/Filter on the returned value.

type QueryChangedMsg added in v0.20.0

type QueryChangedMsg struct {
	// Raw is the committed filter text exactly as typed. Empty when the
	// filter is empty or FilterMode is FilterLocal.
	Raw string
	// Terms is Raw parsed against the column titles. Scoped terms carry
	// the resolved Title, so building "?region=europe" needs no lookup.
	// Nil when Raw is empty.
	Terms []query.Term
	// Sort is the active sort column's title, or "" when nothing is
	// sorted or SortMode is SortLocal.
	Sort string
	// SortColumn is that column's index, or -1.
	SortColumn int
	// Desc reverses the sort. False when SortColumn is -1.
	Desc bool
}

QueryChangedMsg is emitted when the query a remote source should answer changes — a filter the user committed, or a sort they requested. It only fires when FilterMode is FilterRemote or SortMode is SortRemote; a fully local table never emits it.

Filters are reported on commit, not per keystroke: enter, esc, or the filter losing focus. Typing is not a query, and a request per keystroke is a request storm. Tab completion is likewise silent — it edits the in-progress term without committing it.

The message does not move the cursor. A screen answering it typically resets cursor and offset to the top, since row 40 of the previous result set means nothing in the next one — but doing that here would jump the cursor through stale rows a frame before the new ones land, so it is the screen's call. Consecutive duplicate queries are elided, and the state-restoration setters (SetSort, SetValue) adopt their new state silently, so a SetTheme rebuild never reads as a user-driven change.

type Row

type Row []string

Row is one row of cell strings, positionally aligned to Options.Columns. Cells beyond len(Columns) are ignored; missing cells render as empty.

type RowFocusedMsg

type RowFocusedMsg struct {
	// Row is the cursor's index in the post-filter, post-sort visible
	// slice. Zero when Empty is true.
	Row int
	// Cells is the focused row's values in column order. Nil when Empty.
	Cells []string
	// Columns is the parallel column titles so subscribers can look up
	// cells by name without hardcoding index. Nil when Empty.
	Columns []string
	// Empty is true when no row is currently focused (empty visible set
	// or cursor out of range). Row / Cells / Columns are zero-valued.
	Empty bool
}

RowFocusedMsg is emitted by the table when the cursor lands on a different row than the last time we emitted — after cursor movement, after a filter/sort/SetRows swap that changes which row 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 row's cells. Dedup is on (row index, cells) so a SetRows 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 empty table never emits as its first message.

type SortMode added in v0.20.0

type SortMode int

SortMode selects who applies the sort.

const (
	// SortLocal reorders the rows the table holds. Zero value.
	SortLocal SortMode = iota
	// SortRemote leaves row order alone and reports the requested sort as
	// QueryChangedMsg. The ▲/▼ header marker still tracks the active
	// column, so the user sees what they asked for while it is in flight.
	SortRemote
)

type ViewportChangedMsg

type ViewportChangedMsg struct {
	// FirstVisible is the index of the topmost row currently on screen.
	FirstVisible int
	// LastVisible is the index of the bottommost row currently on screen
	// (inclusive). Equal to FirstVisible when only one row fits.
	LastVisible int
	// TotalRows is len(visible) at the time of emission — the size of the
	// filtered/sorted set, not the raw row count.
	TotalRows int
}

ViewportChangedMsg is emitted by the table whenever the visible slice of rows changes — scroll, resize, filter, sort, or a row-set swap. Parents can match this in their own Update to lazy-load off-screen data for the currently visible window (fetch details for FirstVisible..LastVisible, prefetch adjacent pages, etc). Indices are into the post-filter, post- sort visible slice, so they map directly to the rows the user sees. The message is only emitted once the viewport is real (dimensions applied, visible rows non-empty); a SetRows call that lands before the first WindowSizeMsg produces no msg until the next refresh sees a valid viewport. Consecutive duplicate viewports are elided.

Jump to

Keyboard shortcuts

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