Sorting & Filtering

Both features follow the same shape: pass the row model factory, then drive state through column or table methods. Every state change flows through onStateChange, bumps tableVersion, and the UI re-reads the instance.

Sorting

Pass getSortedRowModel() and toggle from a header click:

import { createTable, getCoreRowModel, getSortedRowModel } from "@domphy/table"

const table = createTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    // ...bridge options from the overview
})
thead: table.getHeaderGroups().map((headerGroup) => ({
    tr: headerGroup.headers.map((header) => ({
        th: [
            String(header.column.columnDef.header),
            { asc: " ▲", desc: " ▼", false: "" }[String(header.column.getIsSorted())],
        ],
        onclick: () => header.column.toggleSorting(),
        _key: header.id,
    })),
    _key: headerGroup.id,
})),

The per-column API:

  • column.toggleSorting(desc?, multi?) — cycle ascdesc → unsorted (or force a direction)
  • column.getIsSorted()"asc" | "desc" | false
  • column.getCanSort(), column.clearSorting(), column.getToggleSortingHandler()

Or set the whole state imperatively:

table.setSorting([{ id: "age", desc: true }])
table.resetSorting()

Multi-Sort

Sorting state is an array, so multi-sort works out of the box — hold a modifier and pass multi: true (the default getToggleSortingHandler() reads event.shiftKey for you):

onclick: (e: MouseEvent) => header.column.toggleSorting(undefined, e.shiftKey),

Control it with the enableMultiSort, maxMultiSortColCount, and isMultiSortEvent table options.

Built-In Sorting Functions

Pick per column with sortingFn, or rely on auto-detection:

sortingFnBehavior
alphanumericMixed strings/numbers, case-insensitive (default for mixed values).
alphanumericCaseSensitiveSame, case-sensitive.
textPlain string compare, case-insensitive.
textCaseSensitivePlain string compare, case-sensitive.
datetimeDate values.
basicFast a > b compare (default for numbers).
helper.accessor("createdAt", { sortingFn: "datetime" })

A custom sortingFn is (rowA, rowB, columnId) => number. All built-ins are also exported as the sortingFns object.

Column Filters

Pass getFilteredRowModel() and set values per column:

import { getFilteredRowModel } from "@domphy/table"

// in createTable options:
getFilteredRowModel: getFilteredRowModel(),
const nameColumn = table.getColumn("firstName")!

const FilterInput: DomphyElement<"input"> = {
    input: null,
    placeholder: "Filter names...",
    oninput: (e, node) => nameColumn.setFilterValue(node.element.value),
    $: [inputText()],
}

The per-column API: column.setFilterValue(value), column.getFilterValue(), column.getIsFiltered(), column.getCanFilter(). Setting a filter value to undefined (or an empty string for string filters) removes it automatically.

Built-In Filter Functions

Pick per column with filterFn:

filterFnMatches when
includesStringValue contains the filter string, case-insensitive (default for strings).
includesStringSensitiveSame, case-sensitive.
equalsStringValue equals the filter string, case-insensitive.
equalsStrict ===.
weakEqualsLoose ==.
arrIncludesArray value includes the filter value.
arrIncludesAllArray value includes all filter values.
arrIncludesSomeArray value includes at least one filter value.
inNumberRangeValue is within [min, max].
helper.accessor("age", { filterFn: "inNumberRange" })

table.getColumn("age")!.setFilterValue([18, 65])

All built-ins are exported as the filterFns object.

Custom Filter Functions

A filter function is (row, columnId, filterValue) => boolean:

helper.accessor("status", {
    filterFn: (row, columnId, filterValue: string[]) =>
        filterValue.length === 0 || filterValue.includes(row.getValue(columnId)),
})

Optional statics refine behavior: myFilterFn.autoRemove = (value) => ... removes the filter when the value is "empty", and myFilterFn.resolveFilterValue pre-transforms the value once before filtering.

Global Filtering

One filter value applied across all filterable columns — same getFilteredRowModel() powers it:

const SearchInput: DomphyElement<"input"> = {
    input: null,
    placeholder: "Search all columns...",
    oninput: (e, node) => table.setGlobalFilter(node.element.value),
    $: [inputText()],
}
  • table.setGlobalFilter(value) / table.resetGlobalFilter() — state lives in state.globalFilter
  • globalFilterFn table option picks the function (auto-detected otherwise; inspect with table.getGlobalFilterFn())
  • the same getFilteredRowModel() applies it — table.getFilteredRowModel() returns rows after both column and global filters, table.getPreFilteredRowModel() returns rows before either

Column filters and the global filter compose — a row must pass both to appear in table.getRowModel().