nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
102 lines (101 loc) • 3.93 kB
JavaScript
import { isArrayOfType, isObject, isValidArray } from '../guards/non-primitives.js';
import { isBoolean, isNumber, isString } from '../guards/primitives.js';
export function naturalSort(a, b, options) {
const { caseInsensitive = true, localeAware = false } = options || {};
const _createChunks = (str) => {
const chunks = [];
let current = '';
let isNumeric = false;
for (const char of str) {
const charIsNum = !Number.isNaN(Number(char));
if (current?.length === 0) {
current = char;
isNumeric = charIsNum;
continue;
}
if (charIsNum === isNumeric) {
current += char;
}
else {
chunks?.push(isNumeric ? Number(current) : current);
current = char;
isNumeric = charIsNum;
}
}
if (current?.length > 0) {
chunks?.push(isNumeric ? Number(current) : current);
}
return chunks;
};
const aChunks = _createChunks(a);
const bChunks = _createChunks(b);
for (let i = 0; i < Math.min(aChunks?.length, bChunks?.length); i++) {
let aChunk = aChunks[i];
let bChunk = bChunks[i];
if (caseInsensitive && isString(aChunk) && isString(bChunk)) {
aChunk = aChunk?.toLowerCase();
bChunk = bChunk?.toLowerCase();
}
if (typeof aChunk !== typeof bChunk) {
return isString(aChunk) ? 1 : -1;
}
if (aChunk !== bChunk) {
if (isNumber(aChunk) && isNumber(bChunk)) {
return aChunk - bChunk;
}
if (isString(aChunk) && isString(bChunk)) {
if (localeAware) {
const cmp = aChunk.localeCompare(bChunk, undefined, {
sensitivity: caseInsensitive ? 'accent' : 'variant',
});
if (cmp !== 0)
return cmp;
}
return aChunk < bChunk ? -1 : 1;
}
}
}
return aChunks?.length - bChunks?.length;
}
export function sortAnArray(array, options) {
if (!isValidArray(array))
return array;
if (isArrayOfType(array, isString)) {
return [...array].sort((a, b) => options?.sortOrder === 'desc' ? naturalSort(b, a) : naturalSort(a, b));
}
if (isArrayOfType(array, isNumber)) {
return [...array].sort((a, b) => (options?.sortOrder === 'desc' ? b - a : a - b));
}
if (isArrayOfType(array, isBoolean)) {
return [...array].sort((a, b) => options?.sortOrder === 'desc' ? Number(b) - Number(a) : Number(a) - Number(b));
}
if (isArrayOfType(array, isObject) && options && 'sortByField' in options) {
return [...array].sort((a, b) => {
const _getKeyValue = (obj, path) => {
return path
.split('.')
.reduce((acc, key) => acc?.[key], obj);
};
const keyA = _getKeyValue(a, options?.sortByField);
const keyB = _getKeyValue(b, options?.sortByField);
if (keyA == null || keyB == null) {
return keyA == null ? 1 : -1;
}
if (isString(keyA) && isString(keyB)) {
return options?.sortOrder === 'desc'
? naturalSort(keyB, keyA)
: naturalSort(keyA, keyB);
}
if (isNumber(keyA) && isNumber(keyB)) {
return options?.sortOrder === 'desc' ? keyB - keyA : keyA - keyB;
}
if (isBoolean(keyA) && isBoolean(keyB)) {
return options?.sortOrder === 'desc'
? Number(keyB) - Number(keyA)
: Number(keyA) - Number(keyB);
}
return 0;
});
}
return [...array];
}