421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
/**
|
||
* natural-orderby v5.0.0
|
||
*
|
||
* Copyright (c) Olaf Ennen
|
||
*
|
||
* This source code is licensed under the MIT license found in the
|
||
* LICENSE.md file in the root directory of this source tree.
|
||
*
|
||
* @license MIT
|
||
*/
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.naturalOrderBy = {}));
|
||
})(this, (function (exports) { 'use strict';
|
||
|
||
var compareNumbers = function compareNumbers(numberA, numberB) {
|
||
if (numberA < numberB) {
|
||
return -1;
|
||
}
|
||
if (numberA > numberB) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
var compareUnicode = function compareUnicode(stringA, stringB, locale) {
|
||
var result = stringA.localeCompare(stringB, locale);
|
||
return result ? result / Math.abs(result) : 0;
|
||
};
|
||
|
||
var RE_NUMBERS = /(^0x[\da-fA-F]+$|^([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?(?!\.\d+)(?=\D|\s|$))|\d+)/g;
|
||
var RE_LEADING_OR_TRAILING_WHITESPACES = /^\s+|\s+$/g; // trim pre-post whitespace
|
||
var RE_WHITESPACES = /\s+/g; // normalize all whitespace to single ' ' character
|
||
var RE_INT_OR_FLOAT = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/; // identify integers and floats
|
||
var RE_DATE = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[/-]\d{1,4}[/-]\d{1,4}|^\w+, \w+ \d+, \d{4})/; // identify date strings
|
||
var RE_LEADING_ZERO = /^0+[1-9]{1}[0-9]*$/;
|
||
// eslint-disable-next-line no-control-regex
|
||
var RE_UNICODE_CHARACTERS = /[^\x00-\x80]/;
|
||
|
||
var stringCompare = function stringCompare(stringA, stringB) {
|
||
if (stringA < stringB) {
|
||
return -1;
|
||
}
|
||
if (stringA > stringB) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
var compareChunks = function compareChunks(chunksA, chunksB, locale) {
|
||
var lengthA = chunksA.length;
|
||
var lengthB = chunksB.length;
|
||
var size = Math.min(lengthA, lengthB);
|
||
for (var i = 0; i < size; i++) {
|
||
var chunkA = chunksA[i];
|
||
var chunkB = chunksB[i];
|
||
if (chunkA.normalizedString !== chunkB.normalizedString) {
|
||
if (chunkA.normalizedString === '' !== (chunkB.normalizedString === '')) {
|
||
// empty strings have lowest value
|
||
return chunkA.normalizedString === '' ? -1 : 1;
|
||
}
|
||
if (chunkA.parsedNumber !== undefined && chunkB.parsedNumber !== undefined) {
|
||
// compare numbers
|
||
var result = compareNumbers(chunkA.parsedNumber, chunkB.parsedNumber);
|
||
if (result === 0) {
|
||
// compare string value, if parsed numbers are equal
|
||
// Example:
|
||
// chunkA = { parsedNumber: 1, normalizedString: "001" }
|
||
// chunkB = { parsedNumber: 1, normalizedString: "01" }
|
||
// chunkA.parsedNumber === chunkB.parsedNumber
|
||
// chunkA.normalizedString < chunkB.normalizedString
|
||
return stringCompare(chunkA.normalizedString, chunkB.normalizedString);
|
||
}
|
||
return result;
|
||
} else if (chunkA.parsedNumber !== undefined || chunkB.parsedNumber !== undefined) {
|
||
// number < string
|
||
return chunkA.parsedNumber !== undefined ? -1 : 1;
|
||
} else if (RE_UNICODE_CHARACTERS.test(chunkA.normalizedString + chunkB.normalizedString)) {
|
||
// use locale comparison only if one of the chunks contains unicode characters
|
||
return compareUnicode(chunkA.normalizedString, chunkB.normalizedString, locale);
|
||
} else {
|
||
// use common string comparison for performance reason
|
||
return stringCompare(chunkA.normalizedString, chunkB.normalizedString);
|
||
}
|
||
}
|
||
}
|
||
// if the chunks are equal so far, the one which has more chunks is greater than the other one
|
||
if (lengthA > size || lengthB > size) {
|
||
return lengthA <= size ? -1 : 1;
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
var compareOtherTypes = function compareOtherTypes(valueA, valueB) {
|
||
if (!valueA.chunks ? valueB.chunks : !valueB.chunks) {
|
||
return !valueA.chunks ? 1 : -1;
|
||
}
|
||
if (valueA.isNaN ? !valueB.isNaN : valueB.isNaN) {
|
||
return valueA.isNaN ? -1 : 1;
|
||
}
|
||
if (valueA.isSymbol ? !valueB.isSymbol : valueB.isSymbol) {
|
||
return valueA.isSymbol ? -1 : 1;
|
||
}
|
||
if (valueA.isObject ? !valueB.isObject : valueB.isObject) {
|
||
return valueA.isObject ? -1 : 1;
|
||
}
|
||
if (valueA.isArray ? !valueB.isArray : valueB.isArray) {
|
||
return valueA.isArray ? -1 : 1;
|
||
}
|
||
if (valueA.isFunction ? !valueB.isFunction : valueB.isFunction) {
|
||
return valueA.isFunction ? -1 : 1;
|
||
}
|
||
if (valueA.isNull ? !valueB.isNull : valueB.isNull) {
|
||
return valueA.isNull ? -1 : 1;
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
var compareValues = function compareValues(valueA, valueB, locale) {
|
||
if (valueA.value === valueB.value) {
|
||
return 0;
|
||
}
|
||
if (valueA.parsedNumber !== undefined && valueB.parsedNumber !== undefined) {
|
||
return compareNumbers(valueA.parsedNumber, valueB.parsedNumber);
|
||
}
|
||
if (valueA.chunks && valueB.chunks) {
|
||
return compareChunks(valueA.chunks, valueB.chunks, locale);
|
||
}
|
||
return compareOtherTypes(valueA, valueB);
|
||
};
|
||
|
||
var normalizeAlphaChunk = function normalizeAlphaChunk(chunk) {
|
||
return chunk.replace(RE_WHITESPACES, ' ').replace(RE_LEADING_OR_TRAILING_WHITESPACES, '');
|
||
};
|
||
|
||
var parseNumber = function parseNumber(value) {
|
||
if (value.length !== 0) {
|
||
var parsedNumber = Number(value.replace(/_/g, ''));
|
||
if (!Number.isNaN(parsedNumber)) {
|
||
return parsedNumber;
|
||
}
|
||
}
|
||
return undefined;
|
||
};
|
||
|
||
var normalizeNumericChunk = function normalizeNumericChunk(chunk, index, chunks) {
|
||
if (RE_INT_OR_FLOAT.test(chunk)) {
|
||
// don´t parse a number, if there´s a preceding decimal point
|
||
// to keep significance
|
||
// e.g. 1.0020, 1.020
|
||
if (!RE_LEADING_ZERO.test(chunk) || index === 0 || chunks[index - 1] !== '.') {
|
||
return parseNumber(chunk) || 0;
|
||
}
|
||
}
|
||
return undefined;
|
||
};
|
||
|
||
var createChunkMap = function createChunkMap(chunk, index, chunks) {
|
||
return {
|
||
parsedNumber: normalizeNumericChunk(chunk, index, chunks),
|
||
normalizedString: normalizeAlphaChunk(chunk)
|
||
};
|
||
};
|
||
|
||
var createChunks = function createChunks(value) {
|
||
return value.replace(RE_NUMBERS, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
|
||
};
|
||
|
||
var createChunkMaps = function createChunkMaps(value) {
|
||
var chunksMaps = createChunks(value).map(createChunkMap);
|
||
return chunksMaps;
|
||
};
|
||
|
||
var isFunction = function isFunction(value) {
|
||
return typeof value === 'function';
|
||
};
|
||
|
||
var isNaN = function isNaN(value) {
|
||
return Number.isNaN(value) || value instanceof Number && Number.isNaN(value.valueOf());
|
||
};
|
||
|
||
var isNull = function isNull(value) {
|
||
return value === null;
|
||
};
|
||
|
||
var isObject = function isObject(value) {
|
||
return value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Number) && !(value instanceof String) && !(value instanceof Boolean) && !(value instanceof Date);
|
||
};
|
||
|
||
var isSymbol = function isSymbol(value) {
|
||
return typeof value === 'symbol';
|
||
};
|
||
|
||
var isUndefined = function isUndefined(value) {
|
||
return value === undefined;
|
||
};
|
||
|
||
var parseDate = function parseDate(value) {
|
||
try {
|
||
var parsedDate = Date.parse(value);
|
||
if (!Number.isNaN(parsedDate)) {
|
||
if (RE_DATE.test(value)) {
|
||
return parsedDate;
|
||
}
|
||
}
|
||
return undefined;
|
||
} catch (_unused) {
|
||
return undefined;
|
||
}
|
||
};
|
||
|
||
var numberify = function numberify(value) {
|
||
var parsedNumber = parseNumber(value);
|
||
if (parsedNumber !== undefined) {
|
||
return parsedNumber;
|
||
}
|
||
return parseDate(value);
|
||
};
|
||
|
||
var stringify = function stringify(value) {
|
||
if (typeof value === 'boolean' || value instanceof Boolean) {
|
||
return Number(value).toString();
|
||
}
|
||
if (typeof value === 'number' || value instanceof Number) {
|
||
return value.toString();
|
||
}
|
||
if (value instanceof Date) {
|
||
return value.getTime().toString();
|
||
}
|
||
if (typeof value === 'string' || value instanceof String) {
|
||
return value.toLowerCase().replace(RE_LEADING_OR_TRAILING_WHITESPACES, '');
|
||
}
|
||
return '';
|
||
};
|
||
|
||
var getMappedValueRecord = function getMappedValueRecord(value) {
|
||
if (typeof value === 'string' || value instanceof String || (typeof value === 'number' || value instanceof Number) && !isNaN(value) || typeof value === 'boolean' || value instanceof Boolean || value instanceof Date) {
|
||
var stringValue = stringify(value);
|
||
var parsedNumber = numberify(stringValue);
|
||
var chunks = createChunkMaps(parsedNumber ? "" + parsedNumber : stringValue);
|
||
return {
|
||
parsedNumber: parsedNumber,
|
||
chunks: chunks,
|
||
value: value
|
||
};
|
||
}
|
||
return {
|
||
isArray: Array.isArray(value),
|
||
isFunction: isFunction(value),
|
||
isNaN: isNaN(value),
|
||
isNull: isNull(value),
|
||
isObject: isObject(value),
|
||
isSymbol: isSymbol(value),
|
||
isUndefined: isUndefined(value),
|
||
value: value
|
||
};
|
||
};
|
||
|
||
var baseCompare = function baseCompare(options) {
|
||
return function (valueA, valueB) {
|
||
var a = getMappedValueRecord(valueA);
|
||
var b = getMappedValueRecord(valueB);
|
||
var result = compareValues(a, b, options.locale);
|
||
return result * (options.order === 'desc' ? -1 : 1);
|
||
};
|
||
};
|
||
|
||
var isValidOrder = function isValidOrder(value) {
|
||
return typeof value === 'string' && (value === 'asc' || value === 'desc');
|
||
};
|
||
var getOptions = function getOptions(customOptions) {
|
||
var order = 'asc';
|
||
var locale; // = 'en';
|
||
if (typeof customOptions === 'string' && isValidOrder(customOptions)) {
|
||
order = customOptions;
|
||
} else if (customOptions && typeof customOptions === 'object') {
|
||
if (customOptions.order && isValidOrder(customOptions.order)) {
|
||
order = customOptions.order;
|
||
}
|
||
if (customOptions.locale && customOptions.locale.length > 0) {
|
||
locale = customOptions.locale;
|
||
}
|
||
}
|
||
return {
|
||
order: order,
|
||
locale: locale
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Creates a compare function that defines the natural sort order considering
|
||
* the given `options` which may be passed to [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
|
||
*/
|
||
function compare(options) {
|
||
var validatedOptions = getOptions(options);
|
||
return baseCompare(validatedOptions);
|
||
}
|
||
|
||
var compareMultiple = function compareMultiple(recordA, recordB, orders, locale) {
|
||
var indexA = recordA.index,
|
||
valuesA = recordA.values;
|
||
var indexB = recordB.index,
|
||
valuesB = recordB.values;
|
||
var length = valuesA.length;
|
||
var ordersLength = orders.length;
|
||
for (var i = 0; i < length; i++) {
|
||
var order = i < ordersLength ? orders[i] : null;
|
||
if (order && typeof order === 'function') {
|
||
var result = order(valuesA[i].value, valuesB[i].value);
|
||
if (result) {
|
||
return result;
|
||
}
|
||
} else {
|
||
var _result = compareValues(valuesA[i], valuesB[i], locale);
|
||
if (_result) {
|
||
return _result * (order === 'desc' ? -1 : 1);
|
||
}
|
||
}
|
||
}
|
||
return indexA - indexB;
|
||
};
|
||
|
||
var createIdentifierFn = function createIdentifierFn(identifier) {
|
||
if (typeof identifier === 'function') {
|
||
// identifier is already a lookup function
|
||
return identifier;
|
||
}
|
||
return function (value) {
|
||
if (Array.isArray(value)) {
|
||
var index = Number(identifier);
|
||
if (Number.isInteger(index)) {
|
||
return value[index];
|
||
}
|
||
} else if (value && typeof value === 'object') {
|
||
var result = Object.getOwnPropertyDescriptor(value, identifier);
|
||
return result == null ? void 0 : result.value;
|
||
}
|
||
return value;
|
||
};
|
||
};
|
||
|
||
var getElementByIndex = function getElementByIndex(collection, index) {
|
||
return collection[index];
|
||
};
|
||
|
||
var getValueByIdentifier = function getValueByIdentifier(value, getValue) {
|
||
return getValue(value);
|
||
};
|
||
|
||
var baseOrderBy = function baseOrderBy(collection, identifiers, orders, locale) {
|
||
var identifierFns = identifiers.length ? identifiers.map(createIdentifierFn) : [function (value) {
|
||
return value;
|
||
}];
|
||
|
||
// temporary array holds elements with position and sort-values
|
||
var mappedCollection = collection.map(function (element, index) {
|
||
var values = identifierFns.map(function (identifier) {
|
||
return getValueByIdentifier(element, identifier);
|
||
}).map(getMappedValueRecord);
|
||
return {
|
||
index: index,
|
||
values: values
|
||
};
|
||
});
|
||
|
||
// iterate over values and compare values until a != b or last value reached
|
||
mappedCollection.sort(function (recordA, recordB) {
|
||
return compareMultiple(recordA, recordB, orders, locale);
|
||
});
|
||
return mappedCollection.map(function (element) {
|
||
return getElementByIndex(collection, element.index);
|
||
});
|
||
};
|
||
|
||
var getIdentifiers = function getIdentifiers(identifiers) {
|
||
if (!identifiers) {
|
||
return [];
|
||
}
|
||
var identifierList = !Array.isArray(identifiers) ? [identifiers] : [].concat(identifiers);
|
||
if (identifierList.some(function (identifier) {
|
||
return typeof identifier !== 'string' && typeof identifier !== 'number' && typeof identifier !== 'function';
|
||
})) {
|
||
return [];
|
||
}
|
||
return identifierList;
|
||
};
|
||
|
||
var getOrders = function getOrders(orders) {
|
||
if (!orders) {
|
||
return [];
|
||
}
|
||
var orderList = !Array.isArray(orders) ? [orders] : [].concat(orders);
|
||
if (orderList.some(function (order) {
|
||
return order !== 'asc' && order !== 'desc' && typeof order !== 'function';
|
||
})) {
|
||
return [];
|
||
}
|
||
return orderList;
|
||
};
|
||
|
||
/**
|
||
* Creates an array of elements, natural sorted by specified identifiers and
|
||
* the corresponding sort orders. This method implements a stable sort
|
||
* algorithm, which means the original sort order of equal elements is
|
||
* preserved.
|
||
*/
|
||
function orderBy(collection, identifiers, orders, locale) {
|
||
if (!collection || !Array.isArray(collection)) {
|
||
return [];
|
||
}
|
||
var validatedIdentifiers = getIdentifiers(identifiers);
|
||
var validatedOrders = getOrders(orders);
|
||
return baseOrderBy(collection, validatedIdentifiers, validatedOrders, locale);
|
||
}
|
||
|
||
exports.compare = compare;
|
||
exports.orderBy = orderBy;
|
||
|
||
}));
|