routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
# natural-orderby
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- feat: support numeric separators
|
||||
- feat: add ability to define locale for sorting unicode
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- fix: update `Identifier<T>` to be `keyof T` instead of any `string`
|
||||
|
||||
## 3.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix: performance issue when parsing date strings
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix umd imports
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Migrate codebase to TypeScript
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 - present Olaf Ennen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
# Welcome to 🌲 natural-orderby
|
||||
|
||||
Lightweight (< 1.6kB gzipped) and performant natural sorting of arrays and collections by differentiating between unicode characters, numbers, dates, etc.
|
||||
|
||||
[](https://www.npmjs.com/package/natural-orderby)
|
||||
[](https://www.npmjs.com/package/natural-orderby)
|
||||
[](https://github.com/yobacca/natural-orderby/actions)
|
||||
[](https://codecov.io/gh/yobacca/natural-orderby)
|
||||
|
||||
People sort strings containing numbers differently than most sorting algorithms, which sort values by comparing strings in Unicode code point order. This produces an ordering that is inconsistent with human logic.
|
||||
|
||||
`natural-orderby` sorts the primitive values of [`Boolean`](https://developer.mozilla.org/en-US/docs/Glossary/Boolean), [`Null`](https://developer.mozilla.org/en-US/docs/Glossary/Null), [`Undefined`](https://developer.mozilla.org/en-US/docs/Glossary/Undefined), [`Number`](https://developer.mozilla.org/en-US/docs/Glossary/Number) or [`String`](https://developer.mozilla.org/en-US/docs/Glossary/String) type as well as [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) objects. When comparing strings it differentiates between unicode characters, integer, floating as well as hexadecimal numbers, various date formats, etc. You may sort flat or nested arrays or arrays of objects in a natural sorting order using `natural-orderby`.
|
||||
|
||||
In addition to the efficient and fast `orderBy()` method `natural-orderby` also provides the method `compare()`, which may be passed to [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
|
||||
|
||||
## Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Usage](#usage)
|
||||
- [API Reference](#api-reference)
|
||||
- [TypeScript Declarations](#typescript-declarations)
|
||||
- [Credits](#credits)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install natural-orderby --save
|
||||
|
||||
# yarn
|
||||
yarn add natural-orderby
|
||||
```
|
||||
|
||||
If you´re not using a module bundler or package manager there´s also a global ("IIFE") build hosted on the unpkg CDN. Simply add the following `<script>` tag to the bottom of your HTML file:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/natural-orderby/dist/umd/natural-orderby.production.min.js"></script>
|
||||
```
|
||||
|
||||
Once you've added `natural-orderby` you will have access to the global `window.naturalOrderBy` variable.
|
||||
|
||||
## Usage
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```javascript
|
||||
// Using ES modules
|
||||
import { orderBy } from 'natural-orderby';
|
||||
|
||||
// Using CommonJS modules
|
||||
// const { orderBy } = require('natural-orderby');
|
||||
|
||||
const users = [
|
||||
{
|
||||
username: 'Bamm-Bamm',
|
||||
ip: '192.168.5.2',
|
||||
datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)'
|
||||
},
|
||||
{
|
||||
username: 'Wilma',
|
||||
ip: '192.168.10.1',
|
||||
datetime: '14 Jun 2018 00:00:00 PDT'
|
||||
},
|
||||
{
|
||||
username: 'Dino',
|
||||
ip: '192.168.0.2',
|
||||
datetime: 'June 15, 2018 14:48:00'
|
||||
},
|
||||
{
|
||||
username: 'Barney',
|
||||
ip: '192.168.1.1',
|
||||
datetime: 'Thu, 14 Jun 2018 07:00:00 GMT'
|
||||
},
|
||||
{
|
||||
username: 'Pebbles',
|
||||
ip: '192.168.1.21',
|
||||
datetime: '15 June 2018 14:48 UTC'
|
||||
},
|
||||
{
|
||||
username: 'Hoppy',
|
||||
ip: '192.168.5.10',
|
||||
datetime: '2018-06-15T14:48:00.000Z'
|
||||
},
|
||||
];
|
||||
|
||||
const sortedUsers = orderBy(
|
||||
users,
|
||||
[v => v.datetime, v => v.ip],
|
||||
['desc', 'asc']
|
||||
);
|
||||
```
|
||||
|
||||
This is the return value of `orderBy()`:
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
username: 'Dino',
|
||||
ip: '192.168.0.2',
|
||||
datetime: 'June 15, 2018 14:48:00',
|
||||
},
|
||||
{
|
||||
username: 'Pebbles',
|
||||
ip: '192.168.1.21',
|
||||
datetime: '15 June 2018 14:48 UTC',
|
||||
},
|
||||
{
|
||||
username: 'Bamm-Bamm',
|
||||
ip: '192.168.5.2',
|
||||
datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)',
|
||||
},
|
||||
{
|
||||
username: 'Hoppy',
|
||||
ip: '192.168.5.10',
|
||||
datetime: '2018-06-15T14:48:00.000Z',
|
||||
},
|
||||
{
|
||||
username: 'Barney',
|
||||
ip: '192.168.1.1',
|
||||
datetime: 'Thu, 14 Jun 2018 07:00:00 GMT',
|
||||
},
|
||||
{
|
||||
username: 'Wilma',
|
||||
ip: '192.168.10.1',
|
||||
datetime: '14 Jun 2018 00:00:00 PDT',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `orderBy()`
|
||||
|
||||
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.
|
||||
It also avoids the high overhead caused by [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) invoking a compare function multiple times per element within the array.
|
||||
|
||||
#### Syntax
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```typescript
|
||||
orderBy<T>(
|
||||
collection: ReadonlyArray<T>,
|
||||
identifiers?: ReadonlyArray<Identifier<T>> | Identifier<T> | null,
|
||||
orders?: ReadonlyArray<Order> | Order | null
|
||||
locale?: string
|
||||
): Array<T>
|
||||
```
|
||||
|
||||
| Type | Value |
|
||||
| :-------------- | :----------------------------------------------------------------------------------- |
|
||||
| `Identifier<T>` | <code>keyof T | number | (value: T) => unknown</code> |
|
||||
| `Order` | <code>'asc' | 'desc' | (valueA: unknown, valueB: unknown) => number</code> |
|
||||
|
||||
#### Description
|
||||
|
||||
`orderBy()` sorts the elements of an array by specified identifiers and the corresponding sort orders in a natural order and returns a new array containing the sorted elements.
|
||||
|
||||
If `collection` is an array of primitives, `identifiers` may be unspecified. Otherwise, you should specify `identifiers` to sort by or `collection` will be returned unsorted. An identifier can be expressed by:
|
||||
|
||||
- an index position, if `collection` is a nested array,
|
||||
- a property name, if `collection` is an array of objects,
|
||||
- a function which returns a particular value from an element of a nested array or an array of objects. This function will be invoked by passing one element of `collection`.
|
||||
|
||||
If `orders` is unspecified, all values are sorted in ascending order. Otherwise, specify an order of `'desc'` for descending or `'asc'` for ascending sort order of corresponding values. You may also specify a compare function for an order, which will be invoked by two arguments: `(valueA, valueB)`. It must return a number representing the sort order.
|
||||
|
||||
If you want to sort unicode strings according to a specific locale, please provide a string value for `locale` with a BCP 47 language tag. If the argument is unspecified, the host locale setting will be used.
|
||||
|
||||
> Note: `orderBy()` always returns a new array, even if the original was already sorted.
|
||||
|
||||
#### Examples
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```javascript
|
||||
import { orderBy } from 'natural-orderby';
|
||||
|
||||
// Simple numerics
|
||||
|
||||
orderBy(['10', 9, 2, '1', '4']);
|
||||
// => ['1', 2, '4', 9, '10']
|
||||
|
||||
|
||||
// Floats
|
||||
|
||||
orderBy(['10.0401', 10.022, 10.042, '10.021999']);
|
||||
// => ['10.021999', 10.022, '10.0401', 10.042]
|
||||
|
||||
|
||||
// Float & decimal notation
|
||||
|
||||
orderBy(['10.04f', '10.039F', '10.038d', '10.037D']);
|
||||
// => ['10.037D', '10.038d', '10.039F', '10.04f']
|
||||
|
||||
|
||||
// Scientific notation
|
||||
|
||||
orderBy(['1.528535047e5', '1.528535047e7', '1.528535047e3']);
|
||||
// => ['1.528535047e3', '1.528535047e5', '1.528535047e7']
|
||||
|
||||
|
||||
// IP addresses
|
||||
|
||||
orderBy(['192.168.201.100', '192.168.201.12', '192.168.21.1']);
|
||||
// => ['192.168.21.1', '192.168.201.12', '192.168.21.100']
|
||||
|
||||
|
||||
// Filenames
|
||||
|
||||
orderBy(['01asset_0815.png', 'asset_47103.jpg', 'asset_151.jpg', '001asset_4711.jpg', 'asset_342.mp4']);
|
||||
// => ['001asset_4711.jpg', '01asset_0815.png', 'asset_151.jpg', 'asset_342.mp4', 'asset_47103.jpg']
|
||||
|
||||
// Filenames - ordered by extension and filename
|
||||
|
||||
orderBy(
|
||||
['01asset_0815.png', 'asset_47103.jpg', 'asset_151.jpg', '001asset_4711.jpg', 'asset_342.mp4'],[v => v.split('.').pop(), v => v]
|
||||
);
|
||||
// => ['001asset_4711.jpg', 'asset_151.jpg', 'asset_47103.jpg', 'asset_342.mp4', '01asset_0815.png']
|
||||
|
||||
|
||||
// Dates
|
||||
|
||||
orderBy(['10/12/2018', '10/11/2018', '10/11/2017', '10/12/2017']);
|
||||
// => ['10/11/2017', '10/12/2017', '10/11/2018', '10/12/2018']
|
||||
|
||||
orderBy(['Thu, 15 Jun 2017 20:45:30 GMT', 'Thu, 3 May 2018 17:45:30 GMT', 'Thu, 15 Jun 2017 17:45:30 GMT']);
|
||||
// => ['Thu, 15 Jun 2017 17:45:30 GMT', 'Thu, 15 Jun 2018 20:45:30 GMT', 'Thu, 3 May 2018 17:45:30 GMT']
|
||||
|
||||
|
||||
// Money
|
||||
|
||||
orderBy(['$102.00', '$21.10', '$101.02', '$101.01']);
|
||||
// => ['$21.10', '$101.01', '$101.02', '$102.00']
|
||||
|
||||
|
||||
// Case-insensitive sort order
|
||||
|
||||
orderBy(['A', 'C', 'E', 'b', 'd', 'f']);
|
||||
// => ['A', 'b', 'C', 'd', 'E', 'f']
|
||||
|
||||
|
||||
// Default ascending sort order
|
||||
|
||||
orderBy(['a', 'c', 'f', 'd', 'e', 'b']);
|
||||
// => ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
|
||||
|
||||
// Descending sort order
|
||||
|
||||
orderBy(['a', 'c', 'f', 'd', 'e', 'b'], null, ['desc']);
|
||||
// => ['f', 'e', 'd', 'c', 'b', 'a']
|
||||
|
||||
|
||||
// Custom compare function
|
||||
|
||||
orderBy([2, 1, 5, 8, 6, 9], null, [(valueA, valueB) => valueA - valueB]);
|
||||
// => [1, 2, 5, 6, 8, 9]
|
||||
|
||||
|
||||
// collections
|
||||
|
||||
const users = [
|
||||
{
|
||||
username: 'Bamm-Bamm',
|
||||
ip: '192.168.5.2',
|
||||
datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)'
|
||||
},
|
||||
{
|
||||
username: 'Wilma',
|
||||
ip: '192.168.10.1',
|
||||
datetime: '14 Jun 2018 00:00:00 PDT'
|
||||
},
|
||||
{
|
||||
username: 'Dino',
|
||||
ip: '192.168.0.2',
|
||||
datetime: 'June 15, 2018 14:48:00'
|
||||
},
|
||||
{
|
||||
username: 'Barney',
|
||||
ip: '192.168.1.1',
|
||||
datetime: 'Thu, 14 Jun 2018 07:00:00 GMT'
|
||||
},
|
||||
{
|
||||
username: 'Pebbles',
|
||||
ip: '192.168.1.21',
|
||||
datetime: '15 June 2018 14:48 UTC'
|
||||
},
|
||||
{
|
||||
username: 'Hoppy',
|
||||
ip: '192.168.5.10',
|
||||
datetime: '2018-06-15T14:48:00.000Z'
|
||||
},
|
||||
];
|
||||
|
||||
orderBy(
|
||||
users,
|
||||
[v => v.datetime, v => v.ip],
|
||||
['desc', 'asc']
|
||||
);
|
||||
// => [
|
||||
// {
|
||||
// username: 'Dino',
|
||||
// ip: '192.168.0.2',
|
||||
// datetime: 'June 15, 2018 14:48:00',
|
||||
// },
|
||||
// {
|
||||
// username: 'Pebbles',
|
||||
// ip: '192.168.1.21',
|
||||
// datetime: '15 June 2018 14:48 UTC',
|
||||
// },
|
||||
// {
|
||||
// username: 'Bamm-Bamm',
|
||||
// ip: '192.168.5.2',
|
||||
// datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)',
|
||||
// },
|
||||
// {
|
||||
// username: 'Hoppy',
|
||||
// ip: '192.168.5.10',
|
||||
// datetime: '2018-06-15T14:48:00.000Z',
|
||||
// },
|
||||
// {
|
||||
// username: 'Barney',
|
||||
// ip: '192.168.1.1',
|
||||
// datetime: 'Thu, 14 Jun 2018 07:00:00 GMT',
|
||||
// },
|
||||
// {
|
||||
// username: 'Wilma',
|
||||
// ip: '192.168.10.1',
|
||||
// datetime: '14 Jun 2018 00:00:00 PDT',
|
||||
// },
|
||||
// ]
|
||||
```
|
||||
|
||||
### `compare()`
|
||||
|
||||
Creates a compare function that defines the natural sort order and which may be passed to [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
|
||||
|
||||
#### Syntax
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```javascript
|
||||
compare(options?: CompareOptions): CompareFn
|
||||
```
|
||||
|
||||
| Type | Value |
|
||||
| :--------------- | :------------------------------------------------------------ |
|
||||
| `CompareOptions` | <code>{ order?: 'asc' | 'desc', locale?: string }</code> |
|
||||
| `CompareFn` | <code>(valueA: unknown, valueB: unknown) => number</code> |
|
||||
|
||||
#### Description
|
||||
|
||||
`compare()` returns a compare function that defines the natural sort order and which may be passed to [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
|
||||
|
||||
If `options` or its property `order` is unspecified, values are sorted in ascending sort order. Otherwise, specify an order of `'desc'` for descending or `'asc'` for ascending sort order of values.
|
||||
If unicode strings should be ordered corresponding to a specific locale setting, specify the according value for the options property `locale`. It must be a string with a BCP 47 language tag. If the argument is unspecified, the host locale setting will be used.
|
||||
|
||||
#### Examples
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```javascript
|
||||
import { compare } from 'natural-orderby';
|
||||
|
||||
// Simple numerics
|
||||
|
||||
['10', 9, 2, '1', '4'].sort(compare());
|
||||
// => ['1', 2, '4', 9, '10']
|
||||
|
||||
|
||||
// Floats
|
||||
|
||||
['10.0401', 10.022, 10.042, '10.021999'].sort(compare());
|
||||
// => ['10.021999', 10.022, '10.0401', 10.042]
|
||||
|
||||
|
||||
// Float & decimal notation
|
||||
|
||||
['10.04f', '10.039F', '10.038d', '10.037D'].sort(compare());
|
||||
// => ['10.037D', '10.038d', '10.039F', '10.04f']
|
||||
|
||||
|
||||
// Scientific notation
|
||||
|
||||
['1.528535047e5', '1.528535047e7', '1.528535047e3'].sort(compare());
|
||||
// => ['1.528535047e3', '1.528535047e5', '1.528535047e7']
|
||||
|
||||
|
||||
// IP addresses
|
||||
|
||||
['192.168.201.100', '192.168.201.12', '192.168.21.1'].sort(compare());
|
||||
// => ['192.168.21.1', '192.168.201.12', '192.168.21.100']
|
||||
|
||||
|
||||
// Filenames
|
||||
|
||||
['01asset_0815.jpg', 'asset_47103.jpg', 'asset_151.jpg', '001asset_4711.jpg', 'asset_342.mp4'].sort(compare());
|
||||
// => ['001asset_4711.jpg', '01asset_0815.jpg', 'asset_151.jpg', 'asset_342.mp4', 'asset_47103.jpg']
|
||||
|
||||
|
||||
// Dates
|
||||
|
||||
['10/12/2018', '10/11/2018', '10/11/2017', '10/12/2017'].sort(compare());
|
||||
// => ['10/11/2017', '10/12/2017', '10/11/2018', '10/12/2018']
|
||||
|
||||
['Thu, 15 Jun 2017 20:45:30 GMT', 'Thu, 3 May 2018 17:45:30 GMT', 'Thu, 15 Jun 2017 17:45:30 GMT'].sort(compare());
|
||||
// => ['Thu, 15 Jun 2017 17:45:30 GMT', 'Thu, 15 Jun 2018 20:45:30 GMT', 'Thu, 3 May 2018 17:45:30 GMT']
|
||||
|
||||
|
||||
// Money
|
||||
|
||||
['$102.00', '$21.10', '$101.02', '$101.01'].sort(compare());
|
||||
// => ['$21.10', '$101.01', '$101.02', '$102.00']
|
||||
|
||||
|
||||
// Case-insensitive sort order
|
||||
|
||||
['A', 'C', 'E', 'b', 'd', 'f'].sort(compare());
|
||||
// => ['A', 'b', 'C', 'd', 'E', 'f']
|
||||
|
||||
|
||||
// Default ascending sort order
|
||||
|
||||
['a', 'c', 'f', 'd', 'e', 'b'].sort(compare());
|
||||
// => ['a', 'b', 'c', 'd', 'e', 'f']
|
||||
|
||||
|
||||
// Descending sort order
|
||||
|
||||
['a', 'c', 'f', 'd', 'e', 'b'].sort(compare({ order: 'desc' }));
|
||||
// => ['f', 'e', 'd', 'c', 'b', 'a']
|
||||
|
||||
|
||||
// collections
|
||||
|
||||
const users = [
|
||||
{
|
||||
username: 'Bamm-Bamm',
|
||||
lastLogin: {
|
||||
ip: '192.168.5.2',
|
||||
datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)'
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'Wilma',
|
||||
lastLogin: {
|
||||
ip: '192.168.10.1',
|
||||
datetime: '14 Jun 2018 00:00:00 PDT'
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'Dino',
|
||||
lastLogin: {
|
||||
ip: '192.168.0.2',
|
||||
datetime: 'June 15, 2018 14:48:00'
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'Barney',
|
||||
lastLogin: {
|
||||
ip: '192.168.1.1',
|
||||
datetime: 'Thu, 14 Jun 2018 07:00:00 GMT'
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'Pebbles',
|
||||
lastLogin: {
|
||||
ip: '192.168.1.21',
|
||||
datetime: '15 June 2018 14:48 UTC'
|
||||
},
|
||||
},
|
||||
{
|
||||
username: 'Hoppy',
|
||||
lastLogin: {
|
||||
ip: '192.168.5.10',
|
||||
datetime: '2018-06-15T14:48:00.000Z'
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
users.sort((a, b) => compare()(a.lastLogin.ip, b.lastLogin.ip));
|
||||
// => [
|
||||
// {
|
||||
// username: 'Dino',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.0.2',
|
||||
// datetime: 'June 15, 2018 14:48:00'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// username: 'Barney',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.1.1',
|
||||
// datetime: 'Thu, 14 Jun 2018 07:00:00 GMT'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// username: 'Pebbles',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.1.21',
|
||||
// datetime: '15 June 2018 14:48 UTC'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// username: 'Bamm-Bamm',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.5.2',
|
||||
// datetime: 'Fri Jun 15 2018 16:48:00 GMT+0200 (CEST)'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// username: 'Hoppy',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.5.10',
|
||||
// datetime: '2018-06-15T14:48:00.000Z'
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// username: 'Wilma',
|
||||
// lastLogin: {
|
||||
// ip: '192.168.10.1',
|
||||
// datetime: '14 Jun 2018 00:00:00 PDT'
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
```
|
||||
|
||||
## TypeScript Declarations
|
||||
|
||||
`natural-orderby` is completely written in [TypeScript](https://www.typescriptlang.org/) and provides TypeScript declarations.
|
||||
|
||||
## Credits
|
||||
|
||||
Inspired by [The Alphanum Algorithm](http://www.davekoelle.com/alphanum.html) from Dave Koelle.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the MIT License, Copyright © 2018 - present Olaf Ennen.
|
||||
|
||||
See [LICENSE](./LICENSE.md) for more information.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
type CompareFn = (valueA: unknown, valueB: unknown) => number;
|
||||
type OrderEnum = 'asc' | 'desc';
|
||||
type Order = OrderEnum | CompareFn;
|
||||
type CompareOptions = {
|
||||
order?: OrderEnum;
|
||||
locale?: Locale;
|
||||
} | OrderEnum | undefined;
|
||||
type Locale = string;
|
||||
type IdentifierFn<T> = (value: T) => unknown;
|
||||
type Identifier<T> = IdentifierFn<T> | keyof T | number;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
declare function compare(options?: CompareOptions): CompareFn;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
declare function orderBy<T>(collection: ReadonlyArray<T>, identifiers?: ReadonlyArray<Identifier<T>> | Identifier<T> | null, orders?: ReadonlyArray<Order> | Order | null, locale?: Locale): Array<T>;
|
||||
|
||||
export { type CompareFn, type CompareOptions, type Identifier, type Order, compare, orderBy };
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
export { compare, orderBy };
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable n/no-missing-require */
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./umd/natural-orderby.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./umd/natural-orderby.development.js');
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
const compareNumbers = (numberA, numberB) => {
|
||||
if (numberA < numberB) {
|
||||
return -1;
|
||||
}
|
||||
if (numberA > numberB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const compareUnicode = (stringA, stringB, locale) => {
|
||||
const result = stringA.localeCompare(stringB, locale);
|
||||
return result ? result / Math.abs(result) : 0;
|
||||
};
|
||||
|
||||
const RE_NUMBERS = /(^0x[\da-fA-F]+$|^([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?(?!\.\d+)(?=\D|\s|$))|\d+)/g;
|
||||
const RE_LEADING_OR_TRAILING_WHITESPACES = /^\s+|\s+$/g; // trim pre-post whitespace
|
||||
const RE_WHITESPACES = /\s+/g; // normalize all whitespace to single ' ' character
|
||||
const RE_INT_OR_FLOAT = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/; // identify integers and floats
|
||||
const 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
|
||||
const RE_LEADING_ZERO = /^0+[1-9]{1}[0-9]*$/;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const RE_UNICODE_CHARACTERS = /[^\x00-\x80]/;
|
||||
|
||||
const stringCompare = (stringA, stringB) => {
|
||||
if (stringA < stringB) {
|
||||
return -1;
|
||||
}
|
||||
if (stringA > stringB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const compareChunks = (chunksA, chunksB, locale) => {
|
||||
const lengthA = chunksA.length;
|
||||
const lengthB = chunksB.length;
|
||||
const size = Math.min(lengthA, lengthB);
|
||||
for (let i = 0; i < size; i++) {
|
||||
const chunkA = chunksA[i];
|
||||
const 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
|
||||
const 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;
|
||||
};
|
||||
|
||||
const 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;
|
||||
};
|
||||
|
||||
const 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);
|
||||
};
|
||||
|
||||
const normalizeAlphaChunk = chunk => {
|
||||
return chunk.replace(RE_WHITESPACES, ' ').replace(RE_LEADING_OR_TRAILING_WHITESPACES, '');
|
||||
};
|
||||
|
||||
const parseNumber = value => {
|
||||
if (value.length !== 0) {
|
||||
const parsedNumber = Number(value.replace(/_/g, ''));
|
||||
if (!Number.isNaN(parsedNumber)) {
|
||||
return parsedNumber;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const 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;
|
||||
};
|
||||
|
||||
const createChunkMap = (chunk, index, chunks) => ({
|
||||
parsedNumber: normalizeNumericChunk(chunk, index, chunks),
|
||||
normalizedString: normalizeAlphaChunk(chunk)
|
||||
});
|
||||
|
||||
const createChunks = value => value.replace(RE_NUMBERS, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
|
||||
|
||||
const createChunkMaps = value => {
|
||||
const chunksMaps = createChunks(value).map(createChunkMap);
|
||||
return chunksMaps;
|
||||
};
|
||||
|
||||
const isFunction = value => typeof value === 'function';
|
||||
|
||||
const isNaN = value => Number.isNaN(value) || value instanceof Number && Number.isNaN(value.valueOf());
|
||||
|
||||
const isNull = value => value === null;
|
||||
|
||||
const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Number) && !(value instanceof String) && !(value instanceof Boolean) && !(value instanceof Date);
|
||||
|
||||
const isSymbol = value => typeof value === 'symbol';
|
||||
|
||||
const isUndefined = value => value === undefined;
|
||||
|
||||
const parseDate = value => {
|
||||
try {
|
||||
const parsedDate = Date.parse(value);
|
||||
if (!Number.isNaN(parsedDate)) {
|
||||
if (RE_DATE.test(value)) {
|
||||
return parsedDate;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const numberify = value => {
|
||||
const parsedNumber = parseNumber(value);
|
||||
if (parsedNumber !== undefined) {
|
||||
return parsedNumber;
|
||||
}
|
||||
return parseDate(value);
|
||||
};
|
||||
|
||||
const 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 '';
|
||||
};
|
||||
|
||||
const 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) {
|
||||
const stringValue = stringify(value);
|
||||
const parsedNumber = numberify(stringValue);
|
||||
const chunks = createChunkMaps(parsedNumber ? `${parsedNumber}` : stringValue);
|
||||
return {
|
||||
parsedNumber,
|
||||
chunks,
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
const baseCompare = options => (valueA, valueB) => {
|
||||
const a = getMappedValueRecord(valueA);
|
||||
const b = getMappedValueRecord(valueB);
|
||||
const result = compareValues(a, b, options.locale);
|
||||
return result * (options.order === 'desc' ? -1 : 1);
|
||||
};
|
||||
|
||||
const isValidOrder = value => typeof value === 'string' && (value === 'asc' || value === 'desc');
|
||||
const getOptions = customOptions => {
|
||||
let order = 'asc';
|
||||
let 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,
|
||||
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) {
|
||||
const validatedOptions = getOptions(options);
|
||||
return baseCompare(validatedOptions);
|
||||
}
|
||||
|
||||
const compareMultiple = (recordA, recordB, orders, locale) => {
|
||||
const {
|
||||
index: indexA,
|
||||
values: valuesA
|
||||
} = recordA;
|
||||
const {
|
||||
index: indexB,
|
||||
values: valuesB
|
||||
} = recordB;
|
||||
const {
|
||||
length
|
||||
} = valuesA;
|
||||
const ordersLength = orders.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const order = i < ordersLength ? orders[i] : null;
|
||||
if (order && typeof order === 'function') {
|
||||
const result = order(valuesA[i].value, valuesB[i].value);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
const result = compareValues(valuesA[i], valuesB[i], locale);
|
||||
if (result) {
|
||||
return result * (order === 'desc' ? -1 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexA - indexB;
|
||||
};
|
||||
|
||||
const createIdentifierFn = identifier => {
|
||||
if (typeof identifier === 'function') {
|
||||
// identifier is already a lookup function
|
||||
return identifier;
|
||||
}
|
||||
return value => {
|
||||
if (Array.isArray(value)) {
|
||||
const index = Number(identifier);
|
||||
if (Number.isInteger(index)) {
|
||||
return value[index];
|
||||
}
|
||||
} else if (value && typeof value === 'object') {
|
||||
const result = Object.getOwnPropertyDescriptor(value, identifier);
|
||||
return result?.value;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
const getElementByIndex = (collection, index) => collection[index];
|
||||
|
||||
const getValueByIdentifier = (value, getValue) => getValue(value);
|
||||
|
||||
const baseOrderBy = (collection, identifiers, orders, locale) => {
|
||||
const identifierFns = identifiers.length ? identifiers.map(createIdentifierFn) : [value => value];
|
||||
|
||||
// temporary array holds elements with position and sort-values
|
||||
const mappedCollection = collection.map((element, index) => {
|
||||
const values = identifierFns.map(identifier => getValueByIdentifier(element, identifier)).map(getMappedValueRecord);
|
||||
return {
|
||||
index,
|
||||
values
|
||||
};
|
||||
});
|
||||
|
||||
// iterate over values and compare values until a != b or last value reached
|
||||
mappedCollection.sort((recordA, recordB) => compareMultiple(recordA, recordB, orders, locale));
|
||||
return mappedCollection.map(element => getElementByIndex(collection, element.index));
|
||||
};
|
||||
|
||||
const getIdentifiers = identifiers => {
|
||||
if (!identifiers) {
|
||||
return [];
|
||||
}
|
||||
const identifierList = !Array.isArray(identifiers) ? [identifiers] : [...identifiers];
|
||||
if (identifierList.some(identifier => typeof identifier !== 'string' && typeof identifier !== 'number' && typeof identifier !== 'function')) {
|
||||
return [];
|
||||
}
|
||||
return identifierList;
|
||||
};
|
||||
|
||||
const getOrders = orders => {
|
||||
if (!orders) {
|
||||
return [];
|
||||
}
|
||||
const orderList = !Array.isArray(orders) ? [orders] : [...orders];
|
||||
if (orderList.some(order => 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 [];
|
||||
}
|
||||
const validatedIdentifiers = getIdentifiers(identifiers);
|
||||
const validatedOrders = getOrders(orders);
|
||||
return baseOrderBy(collection, validatedIdentifiers, validatedOrders, locale);
|
||||
}
|
||||
|
||||
export { compare, orderBy };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
const e=(e,r)=>e<r?-1:e>r?1:0,r=(e,r,n)=>{const t=e.localeCompare(r,n);return t?t/Math.abs(t):0},n=/(^0x[\da-fA-F]+$|^([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?(?!\.\d+)(?=\D|\s|$))|\d+)/g,t=/^\s+|\s+$/g,i=/\s+/g,o=/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/,s=/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[/-]\d{1,4}[/-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,a=/^0+[1-9]{1}[0-9]*$/,u=/[^\x00-\x80]/,c=(e,r)=>e<r?-1:e>r?1:0,l=(n,t,i)=>n.value===t.value?0:void 0!==n.parsedNumber&&void 0!==t.parsedNumber?e(n.parsedNumber,t.parsedNumber):n.chunks&&t.chunks?((n,t,i)=>{const o=n.length,s=t.length,a=Math.min(o,s);for(let l=0;l<a;l++){const o=n[l],s=t[l];if(o.normalizedString!==s.normalizedString){if(""===o.normalizedString!=(""===s.normalizedString))return""===o.normalizedString?-1:1;if(void 0!==o.parsedNumber&&void 0!==s.parsedNumber){const r=e(o.parsedNumber,s.parsedNumber);return 0===r?c(o.normalizedString,s.normalizedString):r}return void 0!==o.parsedNumber||void 0!==s.parsedNumber?void 0!==o.parsedNumber?-1:1:u.test(o.normalizedString+s.normalizedString)?r(o.normalizedString,s.normalizedString,i):c(o.normalizedString,s.normalizedString)}}return o>a||s>a?o<=a?-1:1:0})(n.chunks,t.chunks,i):((e,r)=>(e.chunks?!r.chunks:r.chunks)?e.chunks?-1:1:(e.isNaN?!r.isNaN:r.isNaN)?e.isNaN?-1:1:(e.isSymbol?!r.isSymbol:r.isSymbol)?e.isSymbol?-1:1:(e.isObject?!r.isObject:r.isObject)?e.isObject?-1:1:(e.isArray?!r.isArray:r.isArray)?e.isArray?-1:1:(e.isFunction?!r.isFunction:r.isFunction)?e.isFunction?-1:1:(e.isNull?!r.isNull:r.isNull)?e.isNull?-1:1:0)(n,t),d=e=>e.replace(i," ").replace(t,""),f=e=>{if(0!==e.length){const r=Number(e.replace(/_/g,""));if(!Number.isNaN(r))return r}},m=(e,r,n)=>{if(o.test(e)&&(!a.test(e)||0===r||"."!==n[r-1]))return f(e)||0},p=(e,r,n)=>({parsedNumber:m(e,r,n),normalizedString:d(e)}),N=e=>{const r=(e=>e.replace(n,"\0$1\0").replace(/\0$/,"").replace(/^\0/,"").split("\0"))(e).map(p);return r},b=e=>"function"==typeof e,y=e=>Number.isNaN(e)||e instanceof Number&&Number.isNaN(e.valueOf()),g=e=>null===e,S=e=>!(null===e||"object"!=typeof e||Array.isArray(e)||e instanceof Number||e instanceof String||e instanceof Boolean||e instanceof Date),v=e=>"symbol"==typeof e,h=e=>void 0===e,A=e=>{const r=f(e);return void 0!==r?r:(e=>{try{const r=Date.parse(e);return!Number.isNaN(r)&&s.test(e)?r:void 0}catch{return}})(e)},z=e=>{if("string"==typeof e||e instanceof String||("number"==typeof e||e instanceof Number)&&!y(e)||"boolean"==typeof e||e instanceof Boolean||e instanceof Date){const r=(e=>"boolean"==typeof e||e instanceof Boolean?Number(e).toString():"number"==typeof e||e instanceof Number?e.toString():e instanceof Date?e.getTime().toString():"string"==typeof e||e instanceof String?e.toLowerCase().replace(t,""):"")(e),n=A(r);return{parsedNumber:n,chunks:N(n?`${n}`:r),value:e}}return{isArray:Array.isArray(e),isFunction:b(e),isNaN:y(e),isNull:g(e),isObject:S(e),isSymbol:v(e),isUndefined:h(e),value:e}},j=e=>"string"==typeof e&&("asc"===e||"desc"===e);function k(e){return(e=>(r,n)=>{const t=z(r),i=z(n);return l(t,i,e.locale)*("desc"===e.order?-1:1)})((e=>{let r,n="asc";return"string"==typeof e&&j(e)?n=e:e&&"object"==typeof e&&(e.order&&j(e.order)&&(n=e.order),e.locale&&e.locale.length>0&&(r=e.locale)),{order:n,locale:r}})(e))}const w=e=>"function"==typeof e?e:r=>{if(Array.isArray(r)){const n=Number(e);if(Number.isInteger(n))return r[n]}else if(r&&"object"==typeof r){const n=Object.getOwnPropertyDescriptor(r,e);return n?.value}return r},x=(e,r,n,t)=>{const i=r.length?r.map(w):[e=>e],o=e.map(((e,r)=>({index:r,values:i.map((r=>r(e))).map(z)})));return o.sort(((e,r)=>((e,r,n,t)=>{const{index:i,values:o}=e,{index:s,values:a}=r,{length:u}=o,c=n.length;for(let d=0;d<u;d++){const e=d<c?n[d]:null;if(e&&"function"==typeof e){const r=e(o[d].value,a[d].value);if(r)return r}else{const r=l(o[d],a[d],t);if(r)return r*("desc"===e?-1:1)}}return i-s})(e,r,n,t))),o.map((r=>((e,r)=>e[r])(e,r.index)))};function O(e,r,n,t){if(!e||!Array.isArray(e))return[];const i=(e=>{if(!e)return[];const r=Array.isArray(e)?[...e]:[e];return r.some((e=>"string"!=typeof e&&"number"!=typeof e&&"function"!=typeof e))?[]:r})(r),o=(e=>{if(!e)return[];const r=Array.isArray(e)?[...e]:[e];return r.some((e=>"asc"!==e&&"desc"!==e&&"function"!=typeof e))?[]:r})(n);return x(e,i,o,t)}export{k as compare,O as orderBy};
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}));
|
||||
+11
File diff suppressed because one or more lines are too long
+119
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "natural-orderby",
|
||||
"version": "5.0.0",
|
||||
"description": "Lightweight and performant natural sorting of arrays and collections by differentiating between unicode characters, numbers, dates, etc.",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/main.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yobacca/natural-orderby.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && rollup -c",
|
||||
"clean": "git clean -fdX .",
|
||||
"docs": "docsify serve docs",
|
||||
"format": "prettier --ignore-path .eslintignore --write src",
|
||||
"lint": "eslint src",
|
||||
"test": "cross-env TZ=UTC jest src",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
|
||||
"size": "filesize",
|
||||
"prepublishOnly": "yarn build",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"LICENSE.md",
|
||||
"CHANGELOG.md",
|
||||
"README.md"
|
||||
],
|
||||
"keywords": [
|
||||
"sort",
|
||||
"order",
|
||||
"string",
|
||||
"natsort",
|
||||
"natcmp",
|
||||
"compare",
|
||||
"alphanum",
|
||||
"unicode",
|
||||
"date",
|
||||
"number",
|
||||
"float",
|
||||
"numeric",
|
||||
"natural",
|
||||
"human",
|
||||
"javascript",
|
||||
"array",
|
||||
"collection",
|
||||
"list",
|
||||
"sorting",
|
||||
"case sensitive",
|
||||
"case insensitive",
|
||||
"ascending",
|
||||
"descending",
|
||||
"browser",
|
||||
"node.js",
|
||||
"node"
|
||||
],
|
||||
"author": "Olaf Ennen <olaf.ennen@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/yobacca/natural-orderby/issues"
|
||||
},
|
||||
"homepage": "https://yobacca.github.io/natural-orderby",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@ampproject/filesize": "^4.3.0",
|
||||
"@babel/core": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-modules": "^0.1.5",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@changesets/cli": "^2.25.2",
|
||||
"@commitlint/cli": "^19.4.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@commitlint/types": "^19.0.3",
|
||||
"@rollup/plugin-babel": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/jest": "29.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"docsify-cli": "^4.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^28.8.0",
|
||||
"eslint-plugin-n": "^17.10.2",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"husky": "^9.1.5",
|
||||
"jest": "^29.3.1",
|
||||
"lint-staged": "^15.2.9",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup": "^4.21.1",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,md,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{ts,json}": [
|
||||
"eslint --rule \"@typescript-eslint/no-unused-vars: error\""
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"filesize": {
|
||||
"./dist/umd/natural-orderby.production.min.js": {
|
||||
"none": "5.5 kB"
|
||||
},
|
||||
"./dist/natural-orderby.production.min.js": {
|
||||
"none": "4.6 kB"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user