UNPKG

@tanstack/table-core

Version:

Headless UI for building powerful tables & datagrids for TS/JS.

324 lines (322 loc) 10.7 kB
import { cloneState, isFunction } from "../../utils.js"; import { reSplitAlphaNumeric, sortFn_basic } from "./sortFns.js"; //#region src/features/row-sorting/rowSortingFeature.utils.ts /** * Creates the default sorting state. * * The feature default is an empty array, meaning no columns are sorted. Reset * APIs use this value when `defaultState` is `true`. * * @example * ```ts * const sorting = getDefaultSortingState() * ``` */ function getDefaultSortingState() { return []; } /** * Routes a sorting updater through the table's sorting change handler. * * The updater may be a next `SortingState` array or a function of the previous * sorting state, matching the instance `table.setSorting` behavior. * * @example * ```ts * table_setSorting(table, (old) => [...old, { id: 'age', desc: true }]) * ``` */ function table_setSorting(table, updater) { table.options.onSortingChange?.(updater); } /** * Resets `sorting` to the configured initial state or feature default. * * With no argument, the reset clones `table.initialState.sorting` when it * exists. Passing `true` ignores initial state and resets to `[]`. * * @example * ```ts * table_resetSorting(table) * table_resetSorting(table, true) * ``` */ function table_resetSorting(table, defaultState) { table_setSorting(table, defaultState ? [] : cloneState(table.initialState.sorting ?? [])); } /** * Resets sorting after the table data changes when explicitly enabled. * * Unlike other auto-reset behaviors, sorting is preserved by default. An * explicit `autoResetAll` value takes precedence over `autoResetSorting`. * * @example * ```ts * table_autoResetSorting(table) * ``` */ function table_autoResetSorting(table) { if (!table.atoms.sorting) return; if (table.options.autoResetAll ?? table.options.autoResetSorting ?? false) table_resetSorting(table); } /** * Chooses a built-in sorting function from sampled filtered row values. * * Date-like values use `datetime`, mixed text/numeric strings use * `alphanumeric`, plain strings use `text`, and unknown values fall back to * `basic`. * * @example * ```ts * const sortFn = column_getAutoSortFn(column) * ``` */ function column_getAutoSortFn(column) { const sortFns = column.table._rowModelFns.sortFns; const firstRows = column.table.getFilteredRowModel().flatRows.slice(0, 10); let sortFnName; let isString = false; for (let i = 0; i < firstRows.length; i++) { const value = firstRows[i].getValue(column.id); if (Object.prototype.toString.call(value) === "[object Date]") { sortFnName = "datetime"; break; } if (typeof value === "string") { isString = true; if (value.split(reSplitAlphaNumeric).length > 1) { sortFnName = "alphanumeric"; break; } } } if (!sortFnName && isString) sortFnName = "text"; if (sortFnName) { let sortFn = sortFns?.[sortFnName]; if (!sortFn) { if (process.env.NODE_ENV === "development") console.warn(`sortFn '${sortFnName}' (auto) for column '${column.id}' is not registered`); if (sortFnName === "alphanumeric") sortFn = sortFns?.text; } if (sortFn) return sortFn; } return sortFn_basic; } /** * Chooses the default first sort direction from sampled filtered row values. * * The first non-nullish value among the sampled rows decides: string columns * start ascending so alphabetical order is natural; other value types (or * columns with no non-nullish sample) start descending. Sampling past leading * nullish values keeps the toggle cycle stable when sorting or a data swap * moves an empty value into the first row. * * @example * ```ts * const direction = column_getAutoSortDir(column) * ``` */ function column_getAutoSortDir(column) { const firstRows = column.table.getFilteredRowModel().flatRows.slice(0, 10); for (let i = 0; i < firstRows.length; i++) { const value = firstRows[i].getValue(column.id); if (value == null) continue; return typeof value === "string" ? "asc" : "desc"; } return "desc"; } /** * Resolves the sorting function configured for a column. * * Function-valued `columnDef.sortFn` is returned directly, `'auto'` delegates * to `column_getAutoSortFn`, and string values are looked up in the table's * sorting function registry before falling back to `basic`. * * @example * ```ts * const sortFn = column_getSortFn(column) * ``` */ function column_getSortFn(column) { const sortFns = column.table._rowModelFns.sortFns; if (isFunction(column.columnDef.sortFn)) return column.columnDef.sortFn; if (column.columnDef.sortFn === "auto") return column_getAutoSortFn(column); const sortFn = sortFns?.[column.columnDef.sortFn]; if (process.env.NODE_ENV === "development" && !sortFn) console.warn(`sortFn '${String(column.columnDef.sortFn)}' for column '${column.id}' is not registered`); return sortFn ?? sortFn_basic; } /** * Applies the next sorting state for this column. * * The toggle can add, replace, flip, or remove this column's sort entry. Multi * sorting respects `enableMultiSort`, `enableMultiRemove`, * `maxMultiSortColCount`, and the `multi` argument. * * @example * ```ts * column_toggleSorting(column, undefined, true) * ``` */ function column_toggleSorting(column, desc, multi) { const nextSortingOrder = column_getNextSortingOrder(column, multi && column_getCanMultiSort(column)); const hasManualValue = typeof desc !== "undefined"; table_setSorting(column.table, (old) => { const existingIndex = old.findIndex((d) => d.id === column.id); const existingSorting = existingIndex === -1 ? void 0 : old[existingIndex]; let newSorting = []; let sortAction; const nextDesc = hasManualValue ? desc : nextSortingOrder === "desc"; const isMultiMode = !!(old.length && column_getCanMultiSort(column) && multi); if (isMultiMode) if (existingSorting) sortAction = "toggle"; else sortAction = "add"; else if (existingSorting) sortAction = "toggle"; else sortAction = "replace"; if (sortAction === "toggle") { if (!hasManualValue) { if (!nextSortingOrder) sortAction = "remove"; } } if (sortAction === "add") { newSorting = [...old, { id: column.id, desc: nextDesc }]; newSorting.splice(0, newSorting.length - (column.table.options.maxMultiSortColCount ?? Number.MAX_SAFE_INTEGER)); } else if (sortAction === "toggle") newSorting = isMultiMode ? old.map((d) => { if (d.id === column.id) return { ...d, desc: nextDesc }; return d; }) : [{ id: column.id, desc: nextDesc }]; else if (sortAction === "remove") newSorting = isMultiMode ? old.filter((d) => d.id !== column.id) : []; else newSorting = [{ id: column.id, desc: nextDesc }]; return newSorting; }); } /** * Resolves the first direction used when this column begins sorting. * * Column-level `sortDescFirst` wins, then table-level `sortDescFirst`, then the * auto direction inferred from sampled values. * * @example * ```ts * const firstDirection = column_getFirstSortDir(column) * ``` */ function column_getFirstSortDir(column) { return column.columnDef.sortDescFirst ?? column.table.options.sortDescFirst ?? column_getAutoSortDir(column) === "desc" ? "desc" : "asc"; } /** * Resolves the next sort order for this column's toggle cycle. * * The cycle starts with the first sort direction, flips between `asc` and * `desc`, and can return `false` when sorting removal is enabled. * * @example * ```ts * const nextOrder = column_getNextSortingOrder(column) * ``` */ function column_getNextSortingOrder(column, multi) { const firstSortDirection = column_getFirstSortDir(column); const isSorted = column_getIsSorted(column); if (!isSorted) return firstSortDirection; if (isSorted !== firstSortDirection && (column.table.options.enableSortingRemoval ?? true) && (multi ? column.table.options.enableMultiRemove ?? true : true)) return false; return isSorted === "desc" ? "asc" : "desc"; } /** * Checks whether this accessor column can participate in sorting. * * The column must have an accessor and sorting must be enabled by both the * column definition and table options. * * @example * ```ts * const canSort = column_getCanSort(column) * ``` */ function column_getCanSort(column) { return (column.columnDef.enableSorting ?? true) && (column.table.options.enableSorting ?? true) && !!column.accessorFn; } /** * Checks whether this column can be added to a multi-sort state. * * Column-level `enableMultiSort` wins over table-level `enableMultiSort`; if * neither is set, accessor columns can multi-sort by default. * * @example * ```ts * const canMultiSort = column_getCanMultiSort(column) * ``` */ function column_getCanMultiSort(column) { return column.columnDef.enableMultiSort ?? column.table.options.enableMultiSort ?? !!column.accessorFn; } /** * Reads this column's current sort direction. * * The result is `false` when the column is not sorted, otherwise `'asc'` or * `'desc'` based on the column's entry in `state.sorting`. * * @example * ```ts * const direction = column_getIsSorted(column) * ``` */ function column_getIsSorted(column) { const columnSort = column.table.atoms.sorting?.get()?.find((d) => d.id === column.id); return !columnSort ? false : columnSort.desc ? "desc" : "asc"; } /** * Finds this column's position in the ordered `state.sorting` array. * * The result is `-1` when the column is not sorted. * * @example * ```ts * const index = column_getSortIndex(column) * ``` */ function column_getSortIndex(column) { return column.table.atoms.sorting?.get()?.findIndex((d) => d.id === column.id) ?? -1; } /** * Removes this column from the sorting state. * * Other sorted columns are preserved, including their relative order. * * @example * ```ts * column_clearSorting(column) * ``` */ function column_clearSorting(column) { table_setSorting(column.table, (old) => old.length ? old.filter((d) => d.id !== column.id) : []); } /** * Creates a header event handler that toggles this column's sorting. * * The handler ignores events when the column cannot sort, and asks * `options.isMultiSortEvent` whether the event should add to a multi-sort. * * @example * ```ts * const onClick = column_getToggleSortingHandler(column) * ``` */ function column_getToggleSortingHandler(column) { const canSort = column_getCanSort(column); return (e) => { if (!canSort) return; column_toggleSorting(column, void 0, column_getCanMultiSort(column) ? column.table.options.isMultiSortEvent?.(e) : false); }; } //#endregion export { column_clearSorting, column_getAutoSortDir, column_getAutoSortFn, column_getCanMultiSort, column_getCanSort, column_getFirstSortDir, column_getIsSorted, column_getNextSortingOrder, column_getSortFn, column_getSortIndex, column_getToggleSortingHandler, column_toggleSorting, getDefaultSortingState, table_autoResetSorting, table_resetSorting, table_setSorting };