@tanstack/table-core
Version:
Headless UI for building powerful tables & datagrids for TS/JS.
129 lines (106 loc) • 3.04 kB
text/typescript
import { SortingFn } from './features/RowSorting'
export const reSplitAlphaNumeric = /([0-9]+)/gm
const alphanumeric: SortingFn<any> = (rowA, rowB, columnId) => {
return compareAlphanumeric(
toString(rowA.getValue(columnId)).toLowerCase(),
toString(rowB.getValue(columnId)).toLowerCase()
)
}
const alphanumericCaseSensitive: SortingFn<any> = (rowA, rowB, columnId) => {
return compareAlphanumeric(
toString(rowA.getValue(columnId)),
toString(rowB.getValue(columnId))
)
}
// The text filter is more basic (less numeric support)
// but is much faster
const text: SortingFn<any> = (rowA, rowB, columnId) => {
return compareBasic(
toString(rowA.getValue(columnId)).toLowerCase(),
toString(rowB.getValue(columnId)).toLowerCase()
)
}
// The text filter is more basic (less numeric support)
// but is much faster
const textCaseSensitive: SortingFn<any> = (rowA, rowB, columnId) => {
return compareBasic(
toString(rowA.getValue(columnId)),
toString(rowB.getValue(columnId))
)
}
const datetime: SortingFn<any> = (rowA, rowB, columnId) => {
const a = rowA.getValue<Date>(columnId)
const b = rowB.getValue<Date>(columnId)
// Can handle nullish values
// Use > and < because == (and ===) doesn't work with
// Date objects (would require calling getTime()).
return a > b ? 1 : a < b ? -1 : 0
}
const basic: SortingFn<any> = (rowA, rowB, columnId) => {
return compareBasic(rowA.getValue(columnId), rowB.getValue(columnId))
}
// Utils
function compareBasic(a: any, b: any) {
return a === b ? 0 : a > b ? 1 : -1
}
function toString(a: any) {
if (typeof a === 'number') {
if (isNaN(a) || a === Infinity || a === -Infinity) {
return ''
}
return String(a)
}
if (typeof a === 'string') {
return a
}
return ''
}
// Mixed sorting is slow, but very inclusive of many edge cases.
// It handles numbers, mixed alphanumeric combinations, and even
// null, undefined, and Infinity
function compareAlphanumeric(aStr: string, bStr: string) {
// Split on number groups, but keep the delimiter
// Then remove falsey split values
const a = aStr.split(reSplitAlphaNumeric).filter(Boolean)
const b = bStr.split(reSplitAlphaNumeric).filter(Boolean)
// While
while (a.length && b.length) {
const aa = a.shift()!
const bb = b.shift()!
const an = parseInt(aa, 10)
const bn = parseInt(bb, 10)
const combo = [an, bn].sort()
// Both are string
if (isNaN(combo[0]!)) {
if (aa > bb) {
return 1
}
if (bb > aa) {
return -1
}
continue
}
// One is a string, one is a number
if (isNaN(combo[1]!)) {
return isNaN(an) ? -1 : 1
}
// Both are numbers
if (an > bn) {
return 1
}
if (bn > an) {
return -1
}
}
return a.length - b.length
}
// Exports
export const sortingFns = {
alphanumeric,
alphanumericCaseSensitive,
text,
textCaseSensitive,
datetime,
basic,
}
export type BuiltInSortingFn = keyof typeof sortingFns