create-plume-react-project
Version:
CLI to create Plume React Project
151 lines (126 loc) • 3.56 kB
text/typescript
import { Dayjs } from 'dayjs';
export type SortingFunction<TData> = (rowA: TData, rowB: TData) => number;
export const reSplitAlphaNumeric: RegExp = /(\d+)/gm;
const alphanumeric: SortingFunction<unknown> = (rowA: unknown, rowB: unknown) => compareAlphanumeric(
toString(rowA)
.toLowerCase(),
toString(rowB)
.toLowerCase(),
);
const alphanumericCaseSensitive: SortingFunction<unknown> = (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<unknown> = (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<unknown> = (rowA: unknown, rowB: unknown) => compareBasic(
toString(rowA),
toString(rowB),
);
const datetime: SortingFunction<Dayjs> = (rowA: Dayjs, rowB: Dayjs) => {
if (rowA.isAfter(rowB)) {
return 1;
}
if (rowB.isAfter(rowA)) {
return -1;
}
return 0;
};
const basic: SortingFunction<string> = (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<T>(
sortFn: SortingFunction<T>,
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<unknown>,
alphanumericCaseSensitive: SortingFunction<unknown>,
text: SortingFunction<unknown>,
textCaseSensitive: SortingFunction<unknown>,
datetime: SortingFunction<Dayjs>,
basic: SortingFunction<string>,
withSortDirection: <T>(sortFn: SortingFunction<T>, isDesc: boolean) => SortingFunction<T>,
} = {
alphanumeric,
alphanumericCaseSensitive,
text,
textCaseSensitive,
datetime,
basic,
withSortDirection,
};
export default sortingFunctions;