import { Dayjs } from 'dayjs'; export type SortingFunction = (rowA: TData, rowB: TData) => number; export const reSplitAlphaNumeric: RegExp = /(\d+)/gm; const alphanumeric: SortingFunction = (rowA: unknown, rowB: unknown) => compareAlphanumeric( toString(rowA) .toLowerCase(), toString(rowB) .toLowerCase(), ); const alphanumericCaseSensitive: SortingFunction = (rowA: unknown, rowB: unknown) => compareAlphanumeric( toString(rowA), toString(rowB), ); // The text filter is more basic (less numeric support) // but is much faster const text: SortingFunction = (rowA: unknown, rowB: unknown) => compareBasic( toString(rowA) .toLowerCase(), toString(rowB) .toLowerCase(), ); // The text filter is more basic (less numeric support) // but is much faster const textCaseSensitive: SortingFunction = (rowA: unknown, rowB: unknown) => compareBasic( toString(rowA), toString(rowB), ); const datetime: SortingFunction = (rowA: Dayjs, rowB: Dayjs) => { if (rowA.isAfter(rowB)) { return 1; } if (rowB.isAfter(rowA)) { return -1; } return 0; }; const basic: SortingFunction = (rowA: string, rowB: string) => compareBasic(rowA, rowB); // Utils function compareBasic(a: string, b: string) { if (a === b) { return 0; } return a > b ? 1 : -1; } function toString(a: unknown) { if (typeof a === 'number') { if (Number.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: string[] = aStr.split(reSplitAlphaNumeric) .filter(Boolean); const b: string[] = bStr.split(reSplitAlphaNumeric) .filter(Boolean); // While while (a.length && b.length) { const aa: string = a.shift()!; const bb: string = b.shift()!; const an: number = parseInt(aa, 10); const bn: number = parseInt(bb, 10); const combo: number[] = [an, bn].sort(); // Both are string if (Number.isNaN(combo[0])) { if (aa > bb) { return 1; } if (bb > aa) { return -1; } // eslint-disable-next-line no-continue continue; } // One is a string, one is a number if (Number.isNaN(combo[1])) { return Number.isNaN(an) ? -1 : 1; } // Both are numbers if (an > bn) { return 1; } if (bn > an) { return -1; } } return a.length - b.length; } function withSortDirection( sortFn: SortingFunction, isDesc: boolean, ): (a: T, b: T) => number { return (a: T, b: T) => { const result: number = sortFn(a, b); return isDesc ? -result : result; }; } // Exports const sortingFunctions: { alphanumeric: SortingFunction, alphanumericCaseSensitive: SortingFunction, text: SortingFunction, textCaseSensitive: SortingFunction, datetime: SortingFunction, basic: SortingFunction, withSortDirection: (sortFn: SortingFunction, isDesc: boolean) => SortingFunction, } = { alphanumeric, alphanumericCaseSensitive, text, textCaseSensitive, datetime, basic, withSortDirection, }; export default sortingFunctions;