alexbruf/elm-table/1.0.0

Table

Headless table state and row-model pipeline: a port of TanStack Table core as pure functions.

There is no table instance. You own the State, you own the data, and every function here takes a Config row and a State and gives you a value back.

Columns and headers

Column and header types, the builders that assemble a Config row's column list, and the readers that walk it back.

Types

type alias Column row =
Column row

A column definition. Build one with column, group, or display.

type alias Config row =
Config row

Everything about a table that is not state: the columns, how to find row ids and sub-rows, and every feature flag.

It is a plain record, so { cfg | manualSorting = True } works for any flag that has no builder. Later versions of this package may add fields, which is a breaking change for code that pattern matches on the record but not for record update.

Fields, with the defaults config sets:

  • columns : List (Column row)
  • getRowId : Maybe (row -> Int -> Maybe String -> String), Nothing (index paths)
  • getSubRows : row -> List row, none
  • manualSorting, manualFiltering, manualGrouping, manualExpanding, manualPagination : Bool, all False; True makes that stage return its input
  • enableSorting, enableMultiSort, enableSortingRemoval, enableMultiRemove : Bool, all True; maxMultiSortColCount : Int, unlimited; sortDescFirst : Maybe Bool, Nothing (automatic per column)
  • enableFilters, enableColumnFilters, enableGlobalFilter : Bool, all True; getColumnCanGlobalFilter : Maybe (Column row -> Bool), Nothing (strings and numbers); filterFromLeafRows : Bool, False; maxLeafRowFilterDepth : Int, 100; globalFilterFn : Maybe FilterFn, Nothing (automatic)
  • enableGrouping : Bool, True; groupedColumnMode : GroupedColumnMode, groupedColumnsReorder
  • enableExpanding : Bool, True; getRowCanExpand, getIsRowExpanded : Maybe (Row row -> Bool), Nothing; paginateExpandedRows : Bool, True
  • pageCount, rowCount : Maybe Int, Nothing (manual pagination only)
  • enableRowSelection, enableMultiRowSelection, enableSubRowSelection, enableRowPinning : Row row -> Bool, all always True; keepPinnedRows : Bool, True
  • enableColumnPinning, enableHiding : Bool, True; defaultColumn : SizeDefaults, size 150, min 20, max unlimited
  • enableCellSpanning, enableCellSelection, enableCellRangeSelection, enableMultiCellRangeSelection : Bool, all True; cellSelectionFilter : Maybe (Cell -> Bool), Nothing
  • enableColumnResizing : Bool, True; columnResizeMode : ColumnResizeMode, resizeOnEnd (TanStack's default); columnResizeDirection : ColumnResizeDirection, resizeLtr
  • manualAggregation : Bool, False; autoResetAll, autoResetPageIndex, autoResetExpanded, autoResetSorting, autoResetCellSelection : Maybe Bool, all Nothing (TanStack's automatic rule: reset unless the matching manual* flag is set)
type alias State =
State

Every state slice the pipeline reads. Start from initialState and update it yourself, directly or through the transition functions in this module.

  • sorting : List SortColumn, in priority order
  • columnFilters : List ColumnFilter
  • globalFilter : Value, Null for none
  • grouping : List String, column ids in grouping order
  • expanded : Expanded
  • rowSelection : Set String, selected row ids
  • pagination : Pagination
  • columnOrder : List String, empty for definition order
  • columnVisibility : Dict String Bool, missing means visible
  • columnPinning : ColumnPinning
  • columnSizing : Dict String Float, missing means the column's own size
  • rowPinning : RowPinning
  • cellSelection : List CellSelectionRange
type alias Row row =
Row row

One row of a row model.

type alias RowModel row =
RowModel row

The output of a pipeline stage: the row tree, the same rows flattened depth first, and a lookup by row id.

type alias HeaderGroup row =
HeaderGroup row

One header row.

type alias Cell =
Cell

One cell of one row, computed on demand.

type alias Expanded =
Expanded

Which rows are expanded. ExpandAll is TanStack's expanded: true.

type alias SortUndefined =
SortUndefined

Where Null values land when a column is sorted.

type alias GroupedColumnMode =
GroupedColumnMode

What the leaf column list does with grouped columns.

type alias SortColumn =
SortColumn

One entry of State.sorting.

type alias ColumnFilter =
ColumnFilter

One entry of State.columnFilters.

type alias ColumnPinning =
ColumnPinning

Column ids pinned to either edge.

type alias RowPinning =
RowPinning

Row ids pinned to the top or the bottom.

type alias SizeDefaults =
SizeDefaults

The default sizing of a column, in pixels.

Elm cannot re-export the variants of a type that is declared in another module, so the three unions above are abstract here and come with one function per variant.

expandAll : Expanded

Every row is expanded, whatever State.rowSelection holds. TanStack's expanded: true.

expandedIds : Set String -> Expanded

Only the rows with these ids are expanded.

expandedIdsOf : Expanded -> Maybe (Set String)

The expanded row ids, or Nothing when every row is expanded.

sortNullsFirst : SortUndefined

Sort Null cell values before every other value.

sortNullsLast : SortUndefined

Sort Null cell values after every other value. This is the default.

sortNullsAsMinusOne : SortUndefined

Sort Null cell values as if they compared -1 against anything else, TanStack's sortUndefined: -1.

sortNullsAsPlusOne : SortUndefined

Sort Null cell values as if they compared 1 against anything else, TanStack's sortUndefined: 1.

groupedColumnsReorder : GroupedColumnMode

Move grouped columns to the front of the leaf column list.

groupedColumnsRemove : GroupedColumnMode

Drop grouped columns from the leaf column list.

groupedColumnsIgnore : GroupedColumnMode

Leave grouped columns where they are.

Configuration

config : List (Column row) -> Config row

A configuration for a list of columns, with TanStack's defaults for every flag.

config [ Table.column "firstName" (.firstName >> Value.String) ]
initialState : State

The state a table starts in: nothing sorted, nothing filtered, nothing grouped, page 0 of size 10.

withGetRowId : (row -> Int -> Maybe String -> String) -> Config row -> Config row

Give rows stable ids. The function receives the datum, its index among its siblings, and its parent's row id.

Without it, root rows are "0", "1", and children are "0.1", "0.2".

withSubRows : (row -> List row) -> Config row -> Config row

Tell the core row model how to reach a row's children.

withDefaultColumn : SizeDefaults -> Config row -> Config row

Override the default column sizing (150, 20, 9007199254740991).

withGlobalFilterFn : FilterFn -> Config row -> Config row

Set the filter function the global filter uses.

withRowSelection : (Row row -> Bool) -> Config row -> Config row

Decide per row whether it can be selected.

Building columns

column : String -> (row -> Value) -> Column row

An accessor column: an id and a way to read a cell value.

Table.column "age" (.age >> toFloat >> Value.Number)
group : String -> List (Column row) -> Column row

A group column: an id and the columns nested under it. Group columns have no accessor and no cells, they only produce header rows.

display : String -> Column row

A display column: an id, no accessor, no children.

withHeader : String -> Column row -> Column row

Set the header text.

withFooter : String -> Column row -> Column row

Set the footer text.

withSortFn : SortFn -> Column row -> Column row

Sort this column with a built-in sort function.

withCustomSort : (Row row -> Row row -> Order) -> Column row -> Column row

Sort this column with a comparison on whole rows.

withSortDescFirst : Bool -> Column row -> Column row

Make the first click on this column sort descending.

withInvertSorting : Bool -> Column row -> Column row

Invert the sort direction of this column.

withSortUndefined : SortUndefined -> Column row -> Column row

Decide where Null values land when this column is sorted.

withEnableSorting : Bool -> Column row -> Column row

Allow or forbid sorting on this column.

withEnableMultiSort : Bool -> Column row -> Column row

Allow or forbid this column in a multi-sort.

withFilterFn : FilterFn -> Column row -> Column row

Filter this column with a built-in filter function.

withCustomFilter : (Row row -> Value -> Bool) -> Column row -> Column row

Filter this column with a predicate on whole rows.

withEnableColumnFilter : Bool -> Column row -> Column row

Allow or forbid a column filter on this column.

withEnableGlobalFilter : Bool -> Column row -> Column row

Include or exclude this column from the global filter.

withAggregationFn : AggregationFn -> Column row -> Column row

Aggregate this column's values on group rows.

withGetGroupingValue : (row -> Int -> Value) -> Column row -> Column row

Read the value this column groups by, when it differs from the accessor. The second argument is the row's index, mirroring TanStack's getGroupingValue(originalRow, index, row).

withGetUniqueValues : (row -> List Value) -> Column row -> Column row

Read the faceting values of a row, when one cell holds several.

withEnableGrouping : Bool -> Column row -> Column row

Allow or forbid grouping by this column.

withEnableHiding : Bool -> Column row -> Column row

Allow or forbid hiding this column.

withEnablePinning : Bool -> Column row -> Column row

Allow or forbid pinning this column.

withSize : Float -> Column row -> Column row

Set this column's size in pixels.

withMinSize : Float -> Column row -> Column row

Set this column's minimum size in pixels.

withMaxSize : Float -> Column row -> Column row

Set this column's maximum size in pixels.

Reading columns

columnId : Column row -> String

The column id.

columnHeader : Column row -> Maybe String

The header text, when one was set.

columnFooter : Column row -> Maybe String

The footer text, when one was set.

columnDepth : Column row -> Int

How deep the column sits in the column tree. Top level is 0.

columnColumns : Column row -> List (Column row)

The columns nested under a group column.

columnParentId : Column row -> Maybe String

The id of the group column this column sits under.

columnAccessor : Column row -> Maybe (row -> Value)

The accessor, when the column has one. Group and display columns have none.

columnSize : Config row -> Column row -> Float

This column's size in pixels, clamped to its minimum and maximum.

columnMinSize : Config row -> Column row -> Float

This column's minimum size in pixels.

columnMaxSize : Config row -> Column row -> Float

This column's maximum size in pixels.

allColumns : Config row -> List (Column row)

Every column, group columns included, each group before its children.

leafColumns : Config row -> List (Column row)

Every leaf column, in definition order.

visibleLeafColumns : Config row -> State -> List (Column row)

The leaf columns a table renders: State.columnOrder applied, hidden columns dropped.

findColumn : Config row -> String -> Maybe (Column row)

Find a column by id. Group columns are found too.

columnFlatColumns : Column row -> List (Column row)

One column and every column below it, the column itself first.

columnLeafColumns : Column row -> List (Column row)

The leaf columns below one column. A leaf column returns itself.

Headers

headerGroups : Config row -> State -> List (HeaderGroup row)

The header rows of a table, top row first.

footerGroups : Config row -> State -> List (HeaderGroup row)

The footer rows: the header rows bottom row first.

flatHeaders : Config row -> State -> List (Header row)

Every header of every header row.

leafHeaders : Config row -> State -> List (Header row)

The leaf headers reachable from the top header row.

getLeafHeaders : Header row -> List (Header row)

The descendants of a header, deepest first, with the header itself last.

headerId : Header row -> String

The header id. Placeholder headers get a compound id.

headerColumnId : Header row -> String

The id of the column this header renders.

headerColSpan : Header row -> Int

How many leaf columns this header spans.

headerRowSpan : Header row -> Int

How many header rows this header spans. 0 means a header above already covers this cell.

headerDepth : Header row -> Int

Which header row this header belongs to, counted from 1 at the top.

headerIndex : Header row -> Int

The header's position in its header row.

headerIsPlaceholder : Header row -> Bool

Is this a filler header standing in for a column that has no group at this level?

headerPlaceholderId : Header row -> Maybe String

How many placeholders for the same column came before this one.

headerSubHeaders : Header row -> List (Header row)

The headers nested under this one.

Row models

The pipeline that turns your data into a row tree, the readers that walk one row, and the faceted values later stages sample from it.

Reading rows

rowId : Row row -> String

The row id.

rowIndex : Row row -> Int

The row's index among its siblings.

rowDepth : Row row -> Int

How deep the row sits in the row tree. Root rows are 0.

rowOriginal : Row row -> row

The original datum this row was built from.

rowSubRows : Row row -> List (Row row)

The row's children.

rowParentId : Row row -> Maybe String

The id of the row's parent, when it has one.

rowOriginalSubRows : Row row -> List row

The raw children Config.getSubRows returned for this row.

rowGroupingColumnId : Row row -> Maybe String

The column a group row groups by, when the row is a group row.

rowGroupingValue : Row row -> Value

The value a group row groups by.

rowLeafRows : Row row -> List (Row row)

The leaf rows a group row was built from. Empty for ordinary rows.

rowAggregatedValues : Row row -> Dict String Value

The aggregated values of a group row, keyed by column id.

getValue : Config row -> Row row -> String -> Value

Read one cell value. Unknown columns and columns without an accessor give Null.

getUniqueValues : Config row -> Row row -> String -> List Value

The values faceting and grouping use for one cell. A column with withGetUniqueValues decides them; otherwise the cell value is wrapped in a one-item list.

getLeafRows : Row row -> List (Row row)

Every descendant of a row, depth first. The row itself is not included.

getParentRow : RowModel row -> Row row -> Maybe (Row row)

The direct parent of a row, looked up in a row model.

getParentRows : RowModel row -> Row row -> List (Row row)

The ancestors of a row, from the root down to its direct parent.

getAllCells : Config row -> State -> Row row -> List Cell

One cell per leaf column, in leaf column order. Hidden columns are included.

findRow : RowModel row -> String -> Maybe (Row row)

Look a row up by id.

maxSubRowDepth : RowModel row -> Int

The deepest row depth in a row model, counting sub-rows and group rows. A flat model is 0; one level of sub-rows makes it 1. Useful for sizing indentation or the header checkbox of an expanding table.

The pipeline

rows : Config row -> State -> Array row -> RowModel row

The whole pipeline: core, filtered, grouped, sorted, expanded, paginated, in that order.

A manual flag on the config skips its stage and passes the row model through unchanged.

rowsFromList : Config row -> State -> List row -> RowModel row

The List form of rows.

coreRowModel : Config row -> State -> Array row -> RowModel row

The untouched row model: one row per datum, sub-rows resolved, ids assigned.

coreRowModelFromList : Config row -> State -> List row -> RowModel row

The List form of coreRowModel.

filteredRowModel : Config row -> State -> RowModel row -> RowModel row

Drop the rows that fail the column filters and the global filter. Config.manualFiltering skips this stage.

groupedRowModel : Config row -> State -> RowModel row -> RowModel row

Replace the rows with group rows. Config.manualGrouping skips this stage.

sortedRowModel : Config row -> State -> RowModel row -> RowModel row

Sort every level of the row tree. Config.manualSorting skips this stage.

expandedRowModel : Config row -> State -> RowModel row -> RowModel row

Flatten the expanded branches into the row list. Config.manualExpanding skips this stage.

paginatedRowModel : Config row -> State -> RowModel row -> RowModel row

Keep only the rows of the current page. Config.manualPagination skips this stage.

Faceting

facetedUniqueValues : Config row -> State -> RowModel row -> String -> List ( Value, Int )

Every distinct value of one column with the number of rows that carry it, in first-seen order. Pass the pre-filtered row model (usually the core row model); the count is taken over facetedRowModel, which applies every filter except this column's own. List cells contribute each item.

facetedMinMax : Config row -> State -> RowModel row -> String -> Maybe ( Float, Float )

The smallest and largest Number value of one column, or Nothing when it has none. Pass the pre-filtered row model; like facetedUniqueValues it looks through facetedRowModel.

facetedRowModel : Config row -> State -> RowModel row -> String -> RowModel row

The rows a column's facets are computed from: the pre-filtered rows with every active filter applied except that column's own, so a filter UI keeps showing the values the user could switch to.

Pass the row model you handed to filteredRowModel. Passing globalFacetKey as the column id excludes the global filter instead of a column filter.

The column id that stands for the global filter's own facet context, "__global__". Passing it to facetedRowModel, facetedUniqueValues or facetedMinMax aggregates across every globally filterable column.

Filtering

Column filters narrow a row model down to the rows that match; the global filter runs the same comparison across every column that allows it. Every reader that has to guess something from the data (an 'auto' filter function, for instance) takes a RowModel row to sample, exactly like TanStack, which samples the core or filtered row model for the same job.

Column filter state

getCanFilter : Config row -> String -> Bool

Can this column carry a column filter? It needs an accessor, and neither the column nor Config.enableColumnFilters nor Config.enableFilters may have switched filtering off.

The filtered row model does not consult this: a State.columnFilters entry for a column that answers False is still applied, matching TanStack.

getIsFiltered : State -> String -> Bool

Does State.columnFilters hold an entry for this column?

getFilterValue : State -> String -> Maybe Value

This column's current filter value, when it has one.

getFilterIndex : State -> String -> Int

This column's position in State.columnFilters, or -1.

getFilterFn : Config row -> RowModel row -> String -> Maybe FilterFn

The filter function a column filters with: the one set with withFilterFn, or the automatic choice. Nothing when the column does not exist.

Pass the core row model; the automatic choice samples it.

getAutoFilterFn : Config row -> RowModel row -> String -> FilterFn

The filter function 'auto' picks for a column, from the type of its first non-null value: includesString for strings, inNumberRange for numbers, equals for booleans, arrIncludes for lists, inDateRange for dates, and weakEquals when every value is Null.

Should a filter value be dropped from state instead of stored? A filter function's own rule wins; without one, Null and the empty string are dropped.

setColumnFilter : Config row -> RowModel row -> String -> Value -> State -> State

Set one column's filter value: replaced in place when the column already has one, appended otherwise, and removed when shouldAutoRemoveFilter says the value is blank.

Pass the core row model: a column without an explicit filter function picks its automatic one from the first values, and that choice decides the auto-remove rule.

setColumnFilters : Config row -> RowModel row -> List ColumnFilter -> State -> State

Replace State.columnFilters wholesale, dropping the entries of known columns whose value should auto-remove. Pass the core row model, as for setColumnFilter.

resetColumnFilters : State -> State

Clear every column filter.

Global filter state

getCanGlobalFilter : Config row -> RowModel row -> String -> Bool

Does the global filter run against this column? It needs an accessor, Config.enableGlobalFilter and Config.enableFilters have to be on, the column must not opt out, and Config.getColumnCanGlobalFilter (whose default keeps a column only when its first non-null value is a string or a number) has to agree.

getGlobalFilterFn : Config row -> FilterFn

The filter function the global filter uses: Config.globalFilterFn, or globalAutoFilterFn.

The global filter's automatic function: Table.FilterFn.includesString.

setGlobalFilter : Value -> State -> State

Set the global filter value.

resetGlobalFilter : State -> State

Clear the global filter.

Sorting

One or more columns order the rows; withSortDescFirst, withInvertSorting, and withSortUndefined tune how a single column compares.

Sort direction

type alias SortDir =
SortDir

A sort direction.

sortAsc : SortDir

Ascending.

sortDesc : SortDir

Descending.

Sorting state

getCanSort : Config row -> String -> Bool

Can this column be sorted? It needs an accessor and both the column and Config.enableSorting have to allow it.

getCanMultiSort : Config row -> String -> Bool

Can this column join a multi-sort? The column's own setting wins over Config.enableMultiSort.

getIsSorted : State -> String -> Maybe SortDir

This column's sort direction, or Nothing when it is not sorted.

getSortIndex : State -> String -> Int

This column's position in State.sorting, or -1.

getAutoSortFn : Config row -> RowModel row -> String -> SortFn

The sort function 'auto' picks for a column. The first ten rows of the row model are sampled: a date gives datetime, a string holding digits gives alphanumeric, any other string gives text, and anything else gives basic.

Pass the filtered row model, which is what TanStack samples.

getSortFn : Config row -> RowModel row -> String -> SortFn

The sort function a column sorts with: the one set with withSortFn, or the automatic choice. A column set up with withCustomSort compares whole rows and has no SortFn, so this reports its automatic choice while the row model uses the custom comparison.

getAutoSortDir : Config row -> RowModel row -> String -> SortDir

The direction a column starts sorting in when nothing says otherwise: the first non-null value among the first ten rows decides, strings ascending and everything else descending.

getFirstSortDir : Config row -> RowModel row -> String -> SortDir

The direction the first click on a column sorts in: the column's withSortDescFirst wins, then Config.sortDescFirst, then getAutoSortDir.

getNextSortingOrder : Config row -> RowModel row -> State -> String -> Bool -> Maybe SortDir

The next step of a column's sort cycle. Nothing means the next step removes the sort, which Config.enableSortingRemoval and (in a multi-sort) Config.enableMultiRemove can forbid.

Config row
-> RowModel row
-> String
-> { desc : Maybe Bool
, multi : Bool
}
-> State
-> State

Step a column's sort: add it, replace the sort with it, flip its direction, or remove it.

desc = Just d sets the direction outright instead of stepping the cycle. multi = True asks to add to the existing sort rather than replace it, which happens only when getCanMultiSort allows it; Config.maxMultiSortColCount caps how many columns a multi-sort keeps.

The row model is the pre-sorted one (the grouped row model, or the core row model when nothing is grouped or filtered): the first sort direction of a column without sortDescFirst depends on its values.

setSorting : List SortColumn -> State -> State

Replace State.sorting.

clearSorting : String -> State -> State

Remove one column from State.sorting, leaving the others in order.

resetSorting : State -> State

Clear every sort.

Pagination

Slices the pre-pagination row model into pages; Config.pageCount and Config.rowCount stand in for a server-side count when the table is not paginating in memory.

Pagination state

prePaginationRowModel : Config row -> State -> RowModel row -> RowModel row

The row model pagination slices, which is the expanded row model. The row counts and page counts below all read it.

rowsInDisplayOrder : Config row -> State -> RowModel row -> List (Row row)

The rows a caller renders, in order. With Config.paginateExpandedRows = False the expanded descendants that the pre-pagination row model does not carry are inserted here.

displayIndex : Config row -> State -> RowModel row -> Row row -> Int

A row's zero-based position in rowsInDisplayOrder, or -1 when it is not there.

setPage : Config row -> Int -> State -> State

Go to a page, clamped to [0, Config.pageCount - 1] when Config.pageCount is set. A Config.pageCount of Just -1 means the count is unknown and clamps nothing.

setPageSize : Int -> State -> State

Change the page size, at least 1. The page index moves so the row that was at the top of the page stays in view.

setPagination : Pagination -> State -> State

Replace State.pagination.

resetPageIndex : Config row -> State -> State

Back to page 0.

resetPageSize : State -> State

Back to a page size of 10.

resetPagination : State -> State

Back to page 0 with a page size of 10.

getPageCount : Config row -> State -> RowModel row -> Int

How many pages there are: Config.pageCount when it is set, otherwise getRowCount divided by the page size, rounded up.

getPageOptions : Config row -> State -> RowModel row -> List Int

Every page index, [0, 1, ...].

getRowCount : Config row -> RowModel row -> Int

How many rows pagination is slicing: Config.rowCount when it is set, otherwise the rows of the pre-pagination row model.

getCanPreviousPage : State -> Bool

Is there a page before this one?

getCanNextPage : Config row -> State -> RowModel row -> Bool

Is there a page after this one? An unknown page count always says yes.

getCanLastPage : Config row -> State -> RowModel row -> Bool

Is there a known last page after this one?

previousPage : Config row -> State -> State

Go back one page, clamped at 0.

nextPage : Config row -> State -> State

Go forward one page.

firstPage : Config row -> State -> State

Go to page 0.

lastPage : Config row -> State -> RowModel row -> State

Go to the last page. A no-op when the page count is unknown or empty.

The page size that puts every row on one page. Elm has no Infinity for Int, so this is Number.MAX_SAFE_INTEGER where TanStack writes Infinity.

Grouping and aggregation

The grouped row model replaces the rows with one group row per distinct value of every column in State.grouping, recursively, and rolls up every other column with an AggregationFn.

Group rows carry rowGroupingColumnId, rowGroupingValue, rowLeafRows, and rowAggregatedValues, and their ids are "columnId:groupingValue" joined to the parent group's id with >.

Grouping and expanding configuration

withRowCanExpand : (Row row -> Bool) -> Config row -> Config row

Set a per-row override for "can this row expand?", TanStack's getRowCanExpand. It wins over Config.enableExpanding and over the "has sub-rows" rule.

withIsRowExpanded : (Row row -> Bool) -> Config row -> Config row

Set a per-row override for "is this row expanded?", TanStack's getIsRowExpanded. It wins over State.expanded outright.

withMaxAggregationDepth : Int -> Column row -> Column row

How far below an aggregated row its aggregation looks for values. 0, the default, aggregates the rows themselves; 1 aggregates their children. TanStack's maxAggregationDepth.

Grouping state

getCanGroup : Config row -> String -> Bool

Can this column be grouped? Grouping has to be enabled on the table and on the column, and the column needs either an accessor or a withGetGroupingValue.

getIsGrouped : State -> String -> Bool

Is this column in State.grouping?

getGroupedIndex : State -> String -> Int

Where this column sits in State.grouping, or -1.

toggleGrouping : String -> State -> State

Add this column to State.grouping, or drop it and keep the rest in order. TanStack's column_toggleGrouping does not check getCanGroup either; its click handler does.

setGrouping : List String -> State -> State

Replace State.grouping.

resetGrouping : State -> State

Empty State.grouping.

rowIsGrouped : Row row -> Bool

Was this row built by the grouped row model?

rowGroupingValueFor : Config row -> Row row -> String -> Value

The value this row groups by for one column: withGetGroupingValue when the column has one, the cell value otherwise.

cellIsGrouped : State -> Row row -> String -> Bool

Is this the cell of the column its group row groups by? Cell carries its row and column ids, so this takes the row and the column id.

cellIsPlaceholder : State -> Row row -> String -> Bool

Is this the cell of a grouped column that is not this row's own grouping column? Those cells render as placeholders.

preGroupedRowModel : Config row -> State -> RowModel row -> RowModel row

The row model grouping runs on: the filtered one.

Aggregation

getAutoAggregationFn : Config row -> RowModel row -> String -> Maybe AggregationFn

The aggregation function a column with no withAggregationFn gets: sum for a numeric column, extent for a date column, none for anything else. The kind is read off the first flat row of the row model handed in.

getAggregationFn : Config row -> RowModel row -> String -> Maybe AggregationFn

The aggregation function of a column: its own, or the automatic one.

aggregationValue : Config row -> RowModel row -> String -> Value

Aggregate one column over the rows of a row model, at the column's own withMaxAggregationDepth. TanStack's column.getAggregationValue().

Config row
-> RowModel row
-> String
-> { maxDepth : Int
, rows : List (Row row)
}
-> Value

Aggregate one column over a chosen row list and depth, TanStack's column.getAggregationValue({ rows, maxDepth }). Use it for footers and summaries that the grouped row model does not produce, for example the total of a column over every filtered row. The row model is only there to resolve an automatic aggregation function from the column's values; pass the core or filtered model. maxDepth stops the descent into sub-rows; Nothing means the column's own maxAggregationDepth.

cellIsAggregated : Config row -> RowModel row -> State -> Row row -> String -> Bool

Is this cell an aggregated one? True on a group row for a column that is neither the row's own grouping column nor itself grouped, and that has an aggregation function.

Expanding

Splices the sub-rows of expanded rows back into the row list, according to State.expanded.

Expanded state

preExpandedRowModel : Config row -> State -> RowModel row -> RowModel row

The row model expansion runs on: the sorted one.

getCanExpand : Config row -> Row row -> Bool

Can this row expand? withRowCanExpand wins, otherwise Config.enableExpanding has to be on and the row needs sub-rows.

getIsExpanded : Config row -> State -> Row row -> Bool

Is this row expanded? withIsRowExpanded wins, otherwise State.expanded decides.

getIsAllParentsExpanded : Config row -> State -> RowModel row -> Row row -> Bool

Is every ancestor of this row expanded? The row itself is not considered.

getCanSomeRowsExpand : Config row -> RowModel row -> Bool

Can any row of this row model expand? TanStack reads the pre-pagination row model here, so controls can reflect rows that are not on this page.

getIsSomeRowsExpanded : State -> Bool

Is any row expanded? True for expandAll and for a non-empty expandedIds set; it does not check that the ids still exist in the data, which is what TanStack's getIsSomeRowsExpanded does too.

getIsAllRowsExpanded : Config row -> State -> RowModel row -> Bool

Is every expandable row of this row model expanded? An empty State.expanded is False, and so is one whose ids match no expandable row.

getExpandedDepth : Config row -> State -> RowModel row -> Int

The deepest expanded row id, counted in .-separated segments.

toggleExpanded : Config row -> RowModel row -> Row row -> Maybe Bool -> State -> State

Expand or collapse one row. Nothing toggles it. Expanding a row that cannot expand and any request that matches the current state are no-ops; collapsing always applies, so a stale id can be cleaned up.

The row model materialises expandAll into the ids of the rows that can expand before the change lands. Pass the pre-expanded row model (the sorted row model, or whatever preExpandedRowModel gives you), so group rows are included when grouping is on.

toggleAllRowsExpanded : Config row -> RowModel row -> Maybe Bool -> State -> State

Expand or collapse every row. Nothing toggles on getIsAllRowsExpanded.

setExpanded : Expanded -> State -> State

Replace State.expanded.

resetExpanded : State -> State

Collapse everything: State.expanded back to no ids.

Row selection

Row selection propagates to sub-rows and reports isSomeSelected and isAllSelected per parent. Every transition is ... -> State -> State so it pipes, and every query takes the Config and the State first, then the row model or column it is about.

Selection types

type alias SubRowSelection =
SubRowSelection

How much of a parent row's sub-tree is selected.

type alias SelectOptions =
SelectOptions

The options of a selection toggle. selectChildren also writes the row's sub-tree; deselectParents drops the ancestors of a row that is being deselected.

noSubRowsSelected : SubRowSelection

No selectable descendant of this row is selected.

someSubRowsSelected : SubRowSelection

Some, but not all, selectable descendants are selected.

allSubRowsSelected : SubRowSelection

Every selectable descendant is selected.

defaultSelectOptions : SelectOptions

selectChildren on, deselectParents off: TanStack's defaults.

These are abstract for the same reason as the types above, so they come with one function per variant.

Selection state

toggleRowSelected : Config row -> RowModel row -> Row row -> Maybe Bool -> State -> State

Select or deselect one row; Nothing flips it. Sub-rows follow along. The row model is only read for the parent chain, so the core row model is the usual argument.

Config row
-> SelectOptions
-> RowModel row
-> Row row
-> Maybe Bool
-> State
-> State
toggleAllRowsSelected : Config row -> RowModel row -> Maybe Bool -> State -> State

Select or deselect every row of the given model; Nothing flips on the current all-selected state. Pass the filtered row model, which is what TanStack's pre-grouped model is.

toggleAllPageRowsSelected : Config row -> RowModel row -> Maybe Bool -> State -> State

Select or deselect every row of the current page. Pass the paginated row model.

deselectAllRows : State -> State

Clear the selection, ids of rows that cannot be selected included. This is TanStack's deselectAll option.

setRowSelection : Set String -> State -> State

Replace the selection.

resetRowSelection : State -> State

Clear the selection.

selectRange : Config row -> RowModel row -> String -> Row row -> Bool -> State -> State

Select or deselect every row between an anchor id and this row, in display order. Pass the pre-pagination row model (the expanded row model): like TanStack's getRowsInDisplayOrder, the range ignores the current page and honours Config.paginateExpandedRows, so a shift-click can span pages. Falls back to an ordinary toggle when the range is not usable, which is what the shift-click handler does.

Config row
-> SelectOptions
-> RowModel row
-> String
-> Row row
-> Bool
-> State
-> State

selectRange with explicit SelectOptions.

canSelectRange : Config row -> State -> RowModel row -> String -> Row row -> Bool

Would a range from this anchor to this row be selected as a range? Both endpoints have to be in the display order of the pre-pagination row model and allow multi-selection.

getIsRowSelected : State -> Row row -> Bool

Is this row selected?

getIsSomeRowsSelected : State -> Bool

Is anything selected at all?

getIsAllRowsSelected : Config row -> State -> RowModel row -> Bool

Is every selectable row of the given model selected? Pass the filtered row model.

getIsAllPageRowsSelected : Config row -> State -> RowModel row -> Bool

Is every selectable row of the current page selected?

getIsSomePageRowsSelected : Config row -> State -> RowModel row -> Bool

Is any row of the current page selected, or partly selected?

getCanSelect : Config row -> Row row -> Bool

Can this row be selected?

getCanSelectSubRows : Config row -> Row row -> Bool

Can selecting this row select its sub-rows?

getCanMultiSelect : Config row -> Row row -> Bool

Can this row take part in a multi-row selection?

getIsSomeSelected : Config row -> State -> Row row -> Bool

Is part, but not all, of this row's sub-tree selected?

getIsAllSubRowsSelected : Config row -> State -> Row row -> Bool

Is this row's whole sub-tree selected?

subRowSelection : Config row -> State -> Row row -> SubRowSelection

How much of this row's sub-tree is selected.

selectedRowIds : State -> List String

The selected row ids.

selectedRowModel : State -> RowModel row -> RowModel row

Keep only the selected rows of a row model. Selected descendants of unselected parents stay in flatRows and rowsById but not in rows, exactly like TanStack's selectRowsFn. TanStack's three selected row models are this function over the core, the filtered, and the sorted row model.

Pinning

Column pinning returns left, center, and right leaf column lists; row pinning does the same for rows. Neither one touches the DOM.

Pinning types

type alias ColumnPinPosition =
ColumnPinPosition

Where a column is pinned: pinnedLeft, pinnedRight, or columnUnpinned. TanStack calls these 'start', 'end', and false.

type alias ColumnRegion =
ColumnRegion

Which slice of the visible leaf columns a query is about. allColumnsRegion is TanStack's absent position argument and means the whole visible list in table order.

type alias RowPinPosition =
RowPinPosition

Where a row is pinned: pinnedTop, pinnedBottom, or rowUnpinned.

type alias PinRowOptions =
PinRowOptions

The options of pinRowWith: pin the row's leaf rows and its ancestors along with it.

type alias PinnedRowsSource row =
PinnedRowsSource row

The two row models the pinned row lists read from. With Config.keepPinnedRows on, a pinned row is taken from prePaginated even when it is off the current page; with it off, only current is searched.

type alias PinnedColumns row =
PinnedColumns row

The three visible column slices of a pinned table.

pinnedLeft : ColumnPinPosition

Pinned to the left edge. TanStack's 'start'.

pinnedRight : ColumnPinPosition

Pinned to the right edge. TanStack's 'end'.

columnUnpinned : ColumnPinPosition

Not pinned. Passing this to pinColumn unpins the column.

allColumnsRegion : ColumnRegion

Every visible leaf column, in table order, with no pin partitioning.

leftColumnsRegion : ColumnRegion

The columns pinned to the left edge.

centerColumnsRegion : ColumnRegion

The columns that are not pinned.

rightColumnsRegion : ColumnRegion

The columns pinned to the right edge.

pinnedTop : RowPinPosition

Pinned to the top of the table.

pinnedBottom : RowPinPosition

Pinned to the bottom of the table.

rowUnpinned : RowPinPosition

Not pinned. Passing this to pinRow unpins the row.

defaultPinRowOptions : PinRowOptions

Pin the row alone, without its leaf rows or its ancestors.

These are abstract for the same reason as the types above, so they come with one function per variant.

Column pinning

pinColumn : ColumnPinPosition -> Column row -> State -> State

Pin one column to an edge, or unpin it with columnUnpinned. A group column pins every leaf below it.

setColumnPinning : ColumnPinning -> State -> State

Replace the column pinning state.

resetColumnPinning : State -> State

Unpin every column.

columnCanPin : Config row -> Column row -> Bool

Can this column be pinned? At least one leaf below it has to allow it and Config.enableColumnPinning has to be on.

columnIsPinned : State -> Column row -> ColumnPinPosition

Where is this column pinned? A group column reports the region of its first pinned leaf, left before right.

columnPinnedIndex : State -> Column row -> Int

The column's position inside its pinned region. Unpinned columns give 0, matching TanStack.

isSomeColumnsPinned : State -> Bool

Is any column pinned to either edge?

isSomeColumnsPinnedLeft : State -> Bool

Is any column pinned to the left edge?

isSomeColumnsPinnedRight : State -> Bool

Is any column pinned to the right edge?

leftLeafColumns : Config row -> State -> List (Column row)

The leaf columns pinned left, in pinning-state order.

centerLeafColumns : Config row -> State -> List (Column row)

The leaf columns that are not pinned, in table order.

rightLeafColumns : Config row -> State -> List (Column row)

The leaf columns pinned right, in pinning-state order.

pinnedLeafColumns : Config row -> State -> ColumnRegion -> List (Column row)

The leaf columns of one region, hidden columns included.

leftVisibleLeafColumns : Config row -> State -> List (Column row)

The visible leaf columns pinned left.

centerVisibleLeafColumns : Config row -> State -> List (Column row)

The visible leaf columns that are not pinned.

rightVisibleLeafColumns : Config row -> State -> List (Column row)

The visible leaf columns pinned right.

pinnedVisibleLeafColumns : Config row -> State -> ColumnRegion -> List (Column row)

The visible leaf columns of one region. allColumnsRegion gives visibleLeafColumns unchanged.

pinnedColumns : Config row -> State -> PinnedColumns row

The three visible column slices at once, in render order.

leftHeaderGroups : Config row -> State -> List (HeaderGroup row)

The header rows of the left-pinned columns.

centerHeaderGroups : Config row -> State -> List (HeaderGroup row)

The header rows of the unpinned columns.

rightHeaderGroups : Config row -> State -> List (HeaderGroup row)

The header rows of the right-pinned columns.

leftFooterGroups : Config row -> State -> List (HeaderGroup row)

The footer rows of the left-pinned columns.

centerFooterGroups : Config row -> State -> List (HeaderGroup row)

The footer rows of the unpinned columns.

rightFooterGroups : Config row -> State -> List (HeaderGroup row)

The footer rows of the right-pinned columns.

leftFlatHeaders : Config row -> State -> List (Header row)

Every header of the left-pinned header rows.

centerFlatHeaders : Config row -> State -> List (Header row)

Every header of the center header rows.

rightFlatHeaders : Config row -> State -> List (Header row)

Every header of the right-pinned header rows.

leftLeafHeaders : Config row -> State -> List (Header row)

The left-pinned headers that have no sub-headers.

centerLeafHeaders : Config row -> State -> List (Header row)

The center headers that have no sub-headers.

rightLeafHeaders : Config row -> State -> List (Header row)

The right-pinned headers that have no sub-headers.

leftVisibleCells : Config row -> State -> Row row -> List Cell

The visible cells of one row pinned left, in pinning-state order.

centerVisibleCells : Config row -> State -> Row row -> List Cell

The visible cells of one row whose column is not pinned.

rightVisibleCells : Config row -> State -> Row row -> List Cell

The visible cells of one row pinned right, in pinning-state order.

Row pinning

pinRow : RowPinPosition -> Row row -> State -> State

Pin one row to an edge, or unpin it with rowUnpinned. Pinning removes the row id from the other edge first, so a row is never in both lists. Whether pinned rows are drawn from the whole data set or only the current page is Config.keepPinnedRows, read by topRows and bottomRows. Use pinRowWith to pin a row's parents or children along with it.

RowPinPosition
-> PinRowOptions
-> RowModel row
-> Row row
-> State
-> State

pinRow with the leaf rows or the ancestors of the row pinned along with it. The row model is where the ancestors are looked up.

setRowPinning : RowPinning -> State -> State

Replace the row pinning state.

resetRowPinning : State -> State

Unpin every row.

getIsRowPinned : State -> Row row -> RowPinPosition

Where is this row pinned?

getRowPinnedIndex : Config row -> State -> PinnedRowsSource row -> Row row -> Int

The row's position among the pinned rows that are actually shown, or -1 when it is not pinned.

getCanPinRow : Config row -> Row row -> Bool

Can this row be pinned?

isSomeRowsPinned : State -> Bool

Is any row pinned at either edge?

isSomeRowsPinnedTop : State -> Bool

Is any row pinned to the top?

isSomeRowsPinnedBottom : State -> Bool

Is any row pinned to the bottom?

topRows : Config row -> State -> PinnedRowsSource row -> List (Row row)

The rows pinned to the top, in pinning-state order.

bottomRows : Config row -> State -> PinnedRowsSource row -> List (Row row)

The rows pinned to the bottom, in pinning-state order.

centerRows : State -> RowModel row -> List (Row row)

The rows of the current page that are not pinned.

Column ordering, visibility and sizing

Which columns render, in what order, and how wide each one is.

Column visibility

columnIsVisible : State -> Column row -> Bool

Is this column visible? A group column is visible when any leaf below it is.

columnCanHide : Config row -> Column row -> Bool

Can this column be hidden? Both the column flag and Config.enableHiding have to allow it.

toggleColumnVisibility : Config row -> Column row -> Maybe Bool -> State -> State

Show or hide one column; Nothing flips it. A group column writes every hideable leaf below it, because visibility is keyed by leaf column id.

setColumnVisibility : Dict String Bool -> State -> State

Replace the whole visibility map.

resetColumnVisibility : State -> State

Clear the visibility map, which shows every column again.

toggleAllColumnsVisible : Config row -> Maybe Bool -> State -> State

Show or hide every leaf column; Nothing flips the current state. Columns that cannot hide stay visible.

isAllColumnsVisible : Config row -> State -> Bool

Is every leaf column visible?

isSomeColumnsVisible : Config row -> State -> Bool

Is at least one leaf column visible?

visibleFlatColumns : Config row -> State -> List (Column row)

Every column of the table, group columns included, minus the hidden ones.

visibleCells : Config row -> State -> Row row -> List Cell

The cells of one row whose column is visible: left-pinned first, then the unpinned cells in table order, then the right-pinned ones.

visibleCellsByColumnId : Config row -> State -> Row row -> Dict String Cell

The visible cells of one row keyed by column id.

Column order

setColumnOrder : List String -> State -> State

Replace State.columnOrder.

resetColumnOrder : State -> State

Drop State.columnOrder, restoring definition order.

orderColumns : Config row -> State -> List (Column row) -> List (Column row)

Put a column list in table order: State.columnOrder first, unlisted columns behind the listed ones, then the grouped-column rules.

orderGroupedColumns : Config row -> State -> List (Column row) -> List (Column row)

Apply Config.groupedColumnMode to a leaf column list: move the grouped columns to the front, remove them, or leave the list alone.

columnIndex : Config row -> State -> ColumnRegion -> Column row -> Int

Where this column sits in one region of the visible leaf columns, or -1 when it is not in that region.

columnIsFirst : Config row -> State -> ColumnRegion -> Column row -> Bool

Is this the first visible column of the region?

columnIsLast : Config row -> State -> ColumnRegion -> Column row -> Bool

Is this the last visible column of the region?

Column sizing

getColumnSize : Config row -> State -> Column row -> Float

The rendered width of a column: the committed size from State.columnSizing when there is one, otherwise the column's own size and then the configured default, clamped between minSize and maxSize.

getColumnStart : Config row -> State -> ColumnRegion -> Column row -> Float

How far from the start of its region a column begins.

getColumnAfter : Config row -> State -> ColumnRegion -> Column row -> Float

How far from the end of its region a column ends.

setColumnSize : String -> Float -> State -> State

Commit one column's size.

setColumnSizing : Dict String Float -> State -> State

Replace the whole sizing map.

resetColumnSize : String -> State -> State

Drop one column's committed size, leaving the other columns alone.

resetColumnSizing : State -> State

Drop every committed size.

getHeaderSize : Config row -> State -> Header row -> Float

The width of a header: its column's size for a leaf header, the sum of the sub-header widths for a parent header.

getHeaderStart : Config row -> State -> List (Header row) -> Header row -> Float

How far from the start of its header row a header begins. Pass the headers of the row the header belongs to.

totalSize : Config row -> State -> Float

The width of the whole table: the sum of the top header row.

leftTotalSize : Config row -> State -> Float

The width of the left-pinned region.

centerTotalSize : Config row -> State -> Float

The width of the unpinned region.

rightTotalSize : Config row -> State -> Float

The width of the right-pinned region.

Column resizing

Dragging a column edge to a new width. The package holds the transient state of the drag and does the arithmetic; the pointer events, the handle, and the subscription are yours.

type alias ColumnResizingState =
ColumnResizingState

The transient state of one resize drag, State.columnResizing. It is empty between drags and is written by the three transitions below.

{ columnSizingStart = List ( String, Float )
, deltaOffset = Maybe Float
, deltaPercentage = Maybe Float
, isResizingColumn = Maybe String
, startOffset = Maybe Float
, startSize = Maybe Float
}

columnSizingStart is the width every leaf header under the dragged header had when the drag began. TanStack's isResizingColumn: false | string is a Maybe String here.

type alias ColumnResizeMode =
ColumnResizeMode

When a drag commits the widths it computes: resizeOnChange on every pointer move, resizeOnEnd only when the drag ends.

ColumnResizeDirection

Which way a drag counts as growth: resizeLtr or resizeRtl.

resizeOnChange : ColumnResizeMode

Commit the new widths on every pointer move.

resizeOnEnd : ColumnResizeMode

Commit the new widths only when the drag ends. Until then the drag shows in State.columnResizing.deltaOffset, which is what a preview indicator renders from.

resizeLtr : ColumnResizeDirection

A left-to-right layout: dragging right widens the column. TanStack's 'ltr'.

resizeRtl : ColumnResizeDirection

A right-to-left layout: dragging left widens the column. TanStack's 'rtl'.

The two unions are abstract here for the same reason as the ones above, so they come with one function per variant.

withEnableResizing : Bool -> Column row -> Column row

Allow or forbid resizing one column. Ports the column-level enableResizing.

columnCanResize : Config row -> Column row -> Bool

Can this column be resized? Both the column's withEnableResizing and the table's Config.enableColumnResizing default to True.

columnIsResizing : State -> Column row -> Bool

Is this column the one being dragged?

headerCanResize : Config row -> Header row -> Bool

Can this header's column be resized? This is the one to ask before rendering a resize handle on a header.

headerIsResizing : State -> Header row -> Bool

Is this header's column the one being dragged?

startColumnResize : Config row -> State -> Header row -> Float -> State

Begin a drag on a header at the pointer's clientX. Records where the pointer started, how wide the header was, and how wide every leaf header under it was. A column that cannot be resized leaves the state alone.

This is the mousedown or touchstart half of TanStack's header.getResizeHandler().

updateColumnResize : Config row -> Float -> State -> State

Move the drag to a new clientX. Writes deltaOffset and deltaPercentage, and in resizeOnChange mode commits the new widths to State.columnSizing. With no drag in progress nothing happens.

This is the mousemove or touchmove half of the handler.

endColumnResize : Config row -> State -> State

End the drag: commit the widths the last updateColumnResize computed, then clear State.columnResizing. Call updateColumnResize first with the position of the event that ended the drag, when it carries one.

This is the mouseup, touchend, or touchcancel half of the handler.

setColumnResizing : ColumnResizingState -> State -> State

Replace the whole transient slice. Ports table.setColumnResizing.

resetColumnResizing : State -> State

Drop the transient slice back to "no drag in progress". Ports table.resetHeaderSizeInfo(true).

Auto reset, filter meta, aggregation options

The three corners of TanStack that need a caller to stand in for the table instance: the autoReset* scheduler, the per-row filter bookkeeping the filtered row model writes, and the aggregation options that hand a column's value to the caller.

Auto reset

TanStack schedules a reset of one state slice whenever a row-model stage recomputes: the page index goes back to 0 when the data, the filters, the sorting or the grouping changed, the expanded rows collapse when the data, the filters or the grouping changed, and a data change also clears the sorting and the cell selection when those are switched on.

There is no scheduler here, so autoReset is the whole thing as one pure step: produce your next State in update, then hand it and the previous one over.

Config row
-> { previous : State
, next : State
, dataChanged : Bool
}
-> State

Apply every reset TanStack's autoReset* scheduler would schedule for one change. Call it in update once you have the next State:

newState =
    Table.autoReset config
        { previous = model.tableState
        , next = nextState
        , dataChanged = False
        }

The page index goes back to 0 when the data, the column filters, the global filter, the sorting or the grouping changed; the expanded rows collapse when the data, the filters or the grouping changed (the grouped stage sits downstream of filtering, so a filter change reaches it even with no grouping active); and a data change clears the sorting and the cell selection.

Each slice consults autoResetAll, then its own flag, then its default: withAutoResetPageIndex defaults to on unless Config.manualPagination is set, withAutoResetExpanded to on unless Config.manualExpanding is set, withAutoResetSorting to off, and withAutoResetCellSelection to on.

Every reset goes to the feature default, never to a remembered initial state: there is no table.initialState here.

withAutoResetAll : Bool -> Config row -> Config row

Force every auto-reset on or off, overriding each individual flag. Ports autoResetAll.

withAutoResetPageIndex : Bool -> Config row -> Config row

Override whether autoReset returns to page 0. The default is on unless Config.manualPagination is set. Ports autoResetPageIndex.

withAutoResetExpanded : Bool -> Config row -> Config row

Override whether autoReset collapses the expanded rows. The default is on unless Config.manualExpanding is set. Ports autoResetExpanded.

withAutoResetSorting : Bool -> Config row -> Config row

Override whether autoReset clears the sorting on a data change. The default is off, as in TanStack. Ports autoResetSorting.

withAutoResetCellSelection : Bool -> Config row -> Config row

Override whether autoReset clears the cell selection on a data change. The default is on. Ports autoResetCellSelection.

Filter meta

_createFilteredRowModel writes a pass/fail flag and an optional meta value onto every row it tests, for every filter it evaluates, and the fuzzy-filter example then sorts by the rank its filter fn stored. The flags live on the rows filteredRowModel returns; to read them for the rows it dropped, run taggedRowModel, which tags the same rows and drops nothing.

The global filter's verdict is stored under globalFacetKey, TanStack's __global__, and its meta under the id of the column that produced it.

taggedRowModel : Config row -> State -> RowModel row -> RowModel row

The pre-filtered row model with the filter flags and the filter meta written onto every row, dropping nothing.

TanStack writes both maps onto the rows of getPreFilteredRowModel() in place while getFilteredRowModel() runs, so they can be read there for rows that did not survive. Rows are immutable here, so this is the function that hands those rows back. Config.manualFiltering skips it, exactly as it skips filteredRowModel.

rowColumnFilters : Row row -> Dict String Bool

Whether the row passed each filter filteredRowModel evaluated, keyed by column id, plus globalFacetKey for the global filter. Empty on every row model but a filtered one.

rowColumnFiltersMeta : Row row -> Dict String Value

The meta each filter recorded for the row, keyed by the column id it was evaluated for. A filter fn records meta when it was built with Table.FilterFn.withMeta, a column when it was built with withCustomFilterMeta.

rowFilterMeta : Row row -> String -> Maybe Value

The meta one column's filter recorded for the row. This is the rank a fuzzy filter stores and a custom sort then orders by.

withCustomFilterMeta : (Row row -> Value -> ( Bool, Maybe Value )) -> Column row -> Column row

Filter this column with a predicate on whole rows that also produces the meta the filtered row model records for the row, TanStack's addMeta callback turned into a return value.

withCustomFilter is unchanged and records no meta; when a column carries both, this one wins.

Aggregation options

A column can aggregate with several functions at once, each under its own key, with a function of the whole AggregationContext rather than of the values alone, or not at all, with the caller supplying the value instead.

type alias AggregationContext row =
AggregationContext row

What one aggregation sees while it runs. Ports AggregationContext, minus its column and table members: there is no table instance and the column is fixed by the call site.

rows is the depth-selected frontier and values are that frontier's values for columnId. subRows and subRowValues are the immediate sub-rows of a group row and the values they already carry, and are empty for root or caller-supplied-row aggregation, where groupingRow is Nothing too.

type alias ContextAggregationFn row =
ContextAggregationFn row

An aggregation that reads the whole AggregationContext instead of only the values, which is TanStack's AggregationFnDef.aggregate. Build one with aggregationFnWithContext.

It lives here rather than in Table.AggregationFn because that module is imported by the one that declares Row and so cannot mention it.

withAggregationFns : List ( String, AggregationFn ) -> Column row -> Column row

Aggregate this column with several aggregation functions at once, each under its own key: TanStack's aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }].

The result is a keyed Dict, not one Value, so it is read back with aggregationResults and rowAggregationResults rather than with getValue, which gives Null for such a column. A duplicated key keeps its entry and gives Null, which is TanStack warning and keeping the key with undefined.

withContextAggregationFn : ContextAggregationFn row -> Column row -> Column row

Aggregate this column with a function of the whole AggregationContext. It takes precedence over withAggregationFn.

withGetAggregationValue : (AggregationContext row -> Value) -> Column row -> Column row

Supply this column's aggregation value instead of computing it, TanStack's getAggregationValue column option. It short-circuits aggregationValue and aggregationValueOf, before withManualAggregation and before any aggregation function, and like TanStack it does not touch the grouped row model, which aggregates through the column's aggregation functions.

TanStack lets the provider decline by returning undefined and fall back to the local aggregation; there is no counterpart for that here, since a provider that returns Null is TanStack's handled { value: undefined }.

aggregationFnWithContext : (AggregationContext row -> Value) -> ContextAggregationFn row

The AggregationFn.custom of a fold that needs the context: the rows and values it is folding, the immediate sub-rows and their results, and the group row the result is for.

It is here rather than in Table.AggregationFn because that module is imported by the one that declares Row and so cannot mention it.

withManualAggregation : Bool -> Config row -> Config row

Hand every column's aggregation value to the caller: aggregationValue and aggregationValueOf give Null for a column without withGetAggregationValue. Ports the manualAggregation table option; like TanStack's it leaves the grouped row model alone.

aggregationResults : Config row -> RowModel row -> String -> Dict String Value

The keyed counterpart of aggregationValue for a column built with withAggregationFns: one result per key. A column without the list option gives an empty Dict.

rowAggregationResults : Row row -> String -> Dict String Value

The keyed aggregation results a group row carries for one column, the counterpart of rowAggregatedValues for a column built with withAggregationFns.

aggregationValueById : Row row -> String -> String -> Maybe Value

One keyed aggregation result of a group row: the row, the column id, then the aggregation key.

Cell spanning and cell selection

Spans merge adjacent cells; cell selection tracks rectangular ranges, focus, and keyboard movement over the visible grid. Optional; ignore this section if your table does not need either.

Span index types

type alias CellSpanIndex =
CellSpanIndex

The cell span index of the rows a caller renders. Build it with cellSpanIndex and read it with cellRowSpan, cellColSpan, and cellIsCovered.

type alias RowSpanContext row =
RowSpanContext row

What a withSpanRowsWhen predicate is given for each candidate row. The run is anchored: anchorRow is the row whose cell renders the merged content, and every later row of the run is tested against it.

Cell spanning

withCellSpanning : Bool -> Config row -> Config row

Allow or forbid cell spanning for the whole table. False makes every cell report a span of 1 and builds no span index.

withEnableCellSpanning : Bool -> Column row -> Column row

Turn one column off for cell spanning even when the table allows it.

withSpanRows : Column row -> Column row

Merge adjacent rows whose value for this column is equal into one vertically spanning cell. Null never merges under this comparison; use withSpanRowsWhen to opt in.

withSpanRowsWhen : (RowSpanContext row -> Bool) -> Column row -> Column row

Decide per candidate row whether it joins the vertical run anchored at anchorRow.

withSpanColumns : (Row row -> Int) -> Column row -> Column row

Make this column's cell span that many columns in the given row, counted in render order. A span is clamped to the end of the cell's pinned region, so it never crosses the left, center, or right boundary.

The stand-in for Infinity in a withSpanColumns callback: "the rest of my region".

columnCanSpan : Config row -> Column row -> Bool

Does this column take part in cell spanning? A column opting out wins over the table option.

cellSpanIndex : Config row -> State -> RowModel row -> CellSpanIndex

Build the span index of the rows a caller renders. Pass the row model you render, which is normally paginatedRowModel; row pinning is read off the state.

cellSpanIndexRowIds : CellSpanIndex -> List String

The row ids the index was built from, in render order.

cellSpanIndexRowSpans : CellSpanIndex -> Dict String (List Int)

The vertical runs per column id, indexed by render-order row position. Only columns with at least one run longer than one row appear; a missing column means every cell in it spans exactly one row.

cellRowSpan : CellSpanIndex -> Cell -> Int

How many rows this cell spans: 1 when it does not span, and 0 when a spanning cell above covers it. Never render a 0; skip the cell instead.

cellColSpan : CellSpanIndex -> Cell -> Int

How many columns this cell spans: 1 when it does not span, and 0 when another cell's column span covers it.

cellIsCovered : CellSpanIndex -> Cell -> Bool

Does another cell's span cover this cell? Covered cells carry no content of their own and must not be rendered.

Cell selection types

type alias CellSelectionRange =
CellSelectionRange

One rectangular cell selection, stored as its two defining corners. The anchor stays put while the focus corner moves during a shift-extend or a drag, so the pair carries more than a normalized rectangle would. Build one with cellRange.

CellSelectionOperation

How a range changes the selection the ranges before it produced.

type alias CellSelectionMode =
CellSelectionMode

Whether a write replaces the selection, adds a rectangle, or subtracts one.

type alias CellSelectionBounds =
CellSelectionBounds

A range resolved into inclusive display-order indexes. Rows are positions in rowsInDisplayOrder; columns are positions in the visible leaf columns in render order.

type alias CellSelectionEdges =
CellSelectionEdges

Which sides of a selected cell sit on the outer boundary of the selection, for drawing a spreadsheet-style outline.

type alias CellDirection =
CellDirection

One step of keyboard navigation.

type alias SelectionRows row =
SelectionRows row

The two row models cell selection reads: prePaginated fixes the display-order indexes a range resolves against, so a range spans pages, and current is the page a caller renders, which bounds keyboard navigation and cell spanning. Without pagination both are the same model.

includeCells : CellSelectionOperation

A range that adds its rectangle to the selection.

excludeCells : CellSelectionOperation

A range that subtracts its rectangle from the selection.

replaceSelection : CellSelectionMode

Replace the whole selection with this rectangle.

includeSelection : CellSelectionMode

Add this rectangle alongside the existing ranges.

excludeSelection : CellSelectionMode

Subtract this rectangle from the existing ranges.

cellUp : CellDirection

Move or extend one row up.

cellDown : CellDirection

Move or extend one row down.

cellLeft : CellDirection

Move or extend one column left.

cellRight : CellDirection

Move or extend one column right.

Cell selection options

withCellSelection : Bool -> Config row -> Config row

Allow or forbid cell selection for the whole table.

withCellSelectionWhen : (Cell -> Bool) -> Config row -> Config row

Decide per cell whether it can be selected. The predicate replaces the boolean, exactly like TanStack's enableCellSelection in its function form.

withCellRangeSelection : Bool -> Config row -> Config row

Allow or forbid extending a cell selection into a range, which is what shift-click and drag do.

withMultiCellRangeSelection : Bool -> Config row -> Config row

Allow or forbid adding and subtracting further rectangles, which is what ctrl-click and meta-click do.

withEnableCellSelection : Bool -> Column row -> Column row

Allow or forbid selecting the cells of one column.

Cell selection transitions

cellRange : String -> String -> String -> String -> CellSelectionRange

A range from its two corners, taken as an inclusion: cellRange anchorRowId anchorColumnId focusRowId focusColumnId.

setCellSelection : List CellSelectionRange -> State -> State

Replace the whole cellSelection slice.

clearCellSelection : State -> State

Drop every range. This is TanStack's resetCellSelection(table, true); there is no separate resetCellSelection here because the feature default is the empty list.

selectCellRange : CellSelectionRange -> State -> State

Select a rectangle, replacing the selection.

selectCellRangeWith : CellSelectionMode -> CellSelectionRange -> State -> State

Select a rectangle with replace, include, or exclude semantics.

selectAllCells : Config row -> SelectionRows row -> State -> State

Select every selectable cell as one range.

setFocusedCell : String -> String -> State -> State

Collapse the selection to a single cell at the given coordinates.

selectCell : Config row -> Cell -> State -> State

Start a selection at one cell, replacing whatever was selected. This is the state half of the mousedown handler with no modifier key.

extendCellSelectionTo : Config row -> Cell -> State -> State

Move the active range's focus corner to this cell, keeping its anchor and its operation. This is the state half of a shift-mousedown and of a drag's mouseenter. With no active range, or with withCellRangeSelection off, it selects the cell instead.

toggleCellSelection : Config row -> SelectionRows row -> Cell -> State -> State

Add a rectangle at this cell alongside the existing ranges, subtracting instead when the cell is already selected. This is the state half of a ctrl- or meta-mousedown. With withMultiCellRangeSelection off, it selects the cell instead.

moveCellSelection : Config row -> SelectionRows row -> CellDirection -> State -> State

Move the selection one step, collapsing it to a single cell. Columns that cannot be selected are skipped over, and a merged cell is one stop. With nothing selected this selects the first selectable cell.

extendCellSelection : Config row -> SelectionRows row -> CellDirection -> State -> State

Extend the active range one step, keeping its anchor fixed.

Cell selection queries

cellCanSelect : Config row -> Cell -> Bool

Can this cell currently be selected? A column opting out wins over the table option.

cellIsSelected : Config row -> State -> SelectionRows row -> Cell -> Bool

Does this cell fall inside the final positive selection?

cellIsFocused : State -> Cell -> Bool

Is this cell the active cell, the anchor of the most recent range? An exclusion's active cell is focused even though it is not selected.

cellTabIndex : State -> Cell -> Int

0 for the focused cell and -1 otherwise, for a roving tabindex.

cellSelectionEdges : Config row -> State -> SelectionRows row -> Cell -> CellSelectionEdges

Which sides of this cell sit on the outer boundary of the selection. All four are False when the cell is not selected.

focusedCell : Config row -> State -> SelectionRows row -> Maybe Cell

The active cell: the anchor of the most recent range.

cellSelectionBounds : Config row -> State -> SelectionRows row -> List CellSelectionBounds

The final positive selection as disjoint, inclusive display-order index rectangles, after every include and exclude is applied. A range whose corners no longer resolve is omitted rather than clamped, so it contributes nothing while staying in state.

cellSelectionMergeBounds : Config row -> State -> SelectionRows row -> List CellSelectionBounds

The merged-cell rectangles of the rendered rows, in the same index space. Selection rectangles grow to enclose these, so a merged cell is always entirely selected or entirely unselected.

cellSelectionColumnIndexes : Config row -> State -> Dict String Int

The render-order index of every visible column id.

selectedCellIds : Config row -> State -> SelectionRows row -> List String

The unique ids of all selected cells, in row-major order. Cells another cell's span covers are skipped, so the ids match what renders.

selectedCellCount : Config row -> State -> SelectionRows row -> Int

How many cells are selected. A merged cell counts once.

selectedCellRangesData : Config row -> State -> SelectionRows row -> List (List (List Value))

Each final positive region's values as a row-major grid, indexed as region, then row, then column. Covered cells keep their values so the grid stays rectangular; serializing it is the caller's job.

cellSelectionRowIds : Config row -> State -> SelectionRows row -> List String

The ids of all rows the selection intersects.

cellSelectionColumnIds : Config row -> State -> SelectionRows row -> List String

The ids of all columns the selection intersects.

Cell selection geometry

CellSelectionBounds
-> CellSelectionBounds
-> Maybe CellSelectionBounds

The overlap of two rectangles, or Nothing when they are disjoint.

CellSelectionBounds
-> CellSelectionBounds
-> List CellSelectionBounds

The parts of the first rectangle the second does not cover, as up to four disjoint rectangles.

List CellSelectionBounds
-> CellSelectionBounds
-> List CellSelectionBounds

Add a rectangle to a disjoint set, keeping the set disjoint.

mergeAdjacentCellSelectionBounds : List CellSelectionBounds -> List CellSelectionBounds

Fuse rectangles that share a full side into one, to a fixed point.

CellSelectionBounds
-> List CellSelectionBounds
-> CellSelectionBounds

Grow a rectangle until it fully contains every merged-cell rectangle it touches.

List ( CellSelectionOperation, CellSelectionBounds )
-> List CellSelectionBounds

Run ordered include and exclude operations, giving the final positive selection as disjoint rectangles.