🎯 MapView v2.0 - Global Deployment Ready

 MAJOR FEATURES:
• Auto-zoom intelligence với smart bounds fitting
• Enhanced 3D GPS markers với pulsing effects
• Professional route display với 6-layer rendering
• Status-based parking icons với availability indicators
• Production-ready build optimizations

🗺️ AUTO-ZOOM FEATURES:
• Smart bounds fitting cho GPS + selected parking
• Adaptive padding (50px) cho visual balance
• Max zoom control (level 16) để tránh quá gần
• Dynamic centering khi không có selection

🎨 ENHANCED VISUALS:
• 3D GPS marker với multi-layer pulse effects
• Advanced parking icons với status colors
• Selection highlighting với animation
• Dimming system cho non-selected items

🛣️ ROUTE SYSTEM:
• OpenRouteService API integration
• Multi-layer route rendering (glow, shadow, main, animated)
• Real-time distance & duration calculation
• Visual route info trong popup

📱 PRODUCTION READY:
• SSR safe với dynamic imports
• Build errors resolved
• Global deployment via Vercel
• Optimized performance

🌍 DEPLOYMENT:
• Vercel: https://whatever-ctk2auuxr-phong12hexdockworks-projects.vercel.app
• Bundle size: 22.8 kB optimized
• Global CDN distribution
• HTTPS enabled

💾 VERSION CONTROL:
• MapView-v2.0.tsx backup created
• MAPVIEW_VERSIONS.md documentation
• Full version history tracking
This commit is contained in:
2025-07-20 19:52:16 +07:00
parent 3203463a6a
commit c65cc97a33
64624 changed files with 7199453 additions and 6462 deletions

20
frontend/node_modules/match-sorter/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2020 Kent C. Dodds
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.

542
frontend/node_modules/match-sorter/README.md generated vendored Normal file
View File

@@ -0,0 +1,542 @@
<div align="center">
<h1>match-sorter</h1>
<p>Simple, expected, and deterministic best-match sorting of an array in JavaScript</p>
</div>
---
**[Demo](https://codesandbox.io/s/wyk856yo48)**
<!-- prettier-ignore-start -->
[![Build Status][build-badge]][build]
[![Code Coverage][coverage-badge]][coverage]
[![version][version-badge]][package]
[![downloads][downloads-badge]][npmtrends]
[![MIT License][license-badge]][license]
[![All Contributors][all-contributors-badge]](#contributors-)
[![PRs Welcome][prs-badge]][prs]
[![Code of Conduct][coc-badge]][coc]
[![Examples][examples-badge]][examples]
<!-- prettier-ignore-end -->
## The problem
1. You have a list of dozens, hundreds, or thousands of items
2. You want to filter and sort those items intelligently (maybe you have a
filter input for the user)
3. You want simple, expected, and deterministic sorting of the items (no fancy
math algorithm that fancily changes the sorting as they type)
## This solution
This follows a simple and sensible (user friendly) algorithm that makes it easy
for you to filter and sort a list of items based on given input. Items are
ranked based on sensible criteria that result in a better user experience.
To explain the ranking system, I'll use countries as an example:
1. **CASE SENSITIVE EQUALS**: Case-sensitive equality trumps all. These will be
first. (ex. `France` would match `France`, but not `france`)
2. **EQUALS**: Case-insensitive equality (ex. `France` would match `france`)
3. **STARTS WITH**: If the item starts with the given value (ex. `Sou` would
match `South Korea` or `South Africa`)
4. **WORD STARTS WITH**: If the item has multiple words, then if one of those
words starts with the given value (ex. `Repub` would match
`Dominican Republic`)
5. **CONTAINS**: If the item contains the given value (ex. `ham` would match
`Bahamas`)
6. **ACRONYM**: If the item's acronym is the given value (ex. `us` would match
`United States`)
7. **SIMPLE MATCH**: If the item has letters in the same order as the letters
of the given value (ex. `iw` would match `Zimbabwe`, but not `Kuwait`
because it must be in the same order). Furthermore, if the item is a closer
match, it will rank higher (ex. `ua` matches `Uruguay` more closely than
`United States of America`, therefore `Uruguay` will be ordered before
`United States of America`)
This ranking seems to make sense in people's minds. At least it does in mine.
Feedback welcome!
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Usage](#usage)
- [Advanced options](#advanced-options)
- [keys: `[string]`](#keys-string)
- [threshold: `number`](#threshold-number)
- [keepDiacritics: `boolean`](#keepdiacritics-boolean)
- [baseSort: `function(itemA, itemB): -1 | 0 | 1`](#basesort-functionitema-itemb--1--0--1)
- [sorter: `function(rankedItems): rankedItems`](#sorter-functionrankeditems-rankeditems)
- [Recipes](#recipes)
- [Match PascalCase, camelCase, snake_case, or kebab-case as words](#match-pascalcase-camelcase-snake_case-or-kebab-case-as-words)
- [Match many words across multiple fields (table filtering)](#match-many-words-across-multiple-fields-table-filtering)
- [Inspiration](#inspiration)
- [Other Solutions](#other-solutions)
- [Issues](#issues)
- [🐛 Bugs](#-bugs)
- [💡 Feature Requests](#-feature-requests)
- [Contributors ✨](#contributors-)
- [LICENSE](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
This module is distributed via [npm][npm] which is bundled with [node][node] and
should be installed as one of your project's `dependencies`:
```
npm install match-sorter
```
## Usage
```javascript
import {matchSorter} from 'match-sorter'
// or const {matchSorter} = require('match-sorter')
// or window.matchSorter.matchSorter
const list = ['hi', 'hey', 'hello', 'sup', 'yo']
matchSorter(list, 'h') // ['hello', 'hey', 'hi']
matchSorter(list, 'y') // ['yo', 'hey']
matchSorter(list, 'z') // []
```
## Advanced options
### keys: `[string]`
_Default: `undefined`_
By default it just uses the value itself as above. Passing an array tells
match-sorter which keys to use for the ranking.
```javascript
const objList = [
{name: 'Janice', color: 'Green'},
{name: 'Fred', color: 'Orange'},
{name: 'George', color: 'Blue'},
{name: 'Jen', color: 'Red'},
]
matchSorter(objList, 'g', {keys: ['name', 'color']})
// [{name: 'George', color: 'Blue'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}]
matchSorter(objList, 're', {keys: ['color', 'name']})
// [{name: 'Jen', color: 'Red'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}, {name: 'George', color: 'Blue'}]
```
**Array of values**: When the specified key matches an array of values, the best
match from the values of in the array is going to be used for the ranking.
```javascript
const iceCreamYum = [
{favoriteIceCream: ['mint', 'chocolate']},
{favoriteIceCream: ['candy cane', 'brownie']},
{favoriteIceCream: ['birthday cake', 'rocky road', 'strawberry']},
]
matchSorter(iceCreamYum, 'cc', {keys: ['favoriteIceCream']})
// [{favoriteIceCream: ['candy cane', 'brownie']}, {favoriteIceCream: ['mint', 'chocolate']}]
```
**Nested Keys**: You can specify nested keys using dot-notation.
```javascript
const nestedObjList = [
{name: {first: 'Janice'}},
{name: {first: 'Fred'}},
{name: {first: 'George'}},
{name: {first: 'Jen'}},
]
matchSorter(nestedObjList, 'j', {keys: ['name.first']})
// [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}]
const nestedObjList = [
{name: [{first: 'Janice'}]},
{name: [{first: 'Fred'}]},
{name: [{first: 'George'}]},
{name: [{first: 'Jen'}]},
]
matchSorter(nestedObjList, 'j', {keys: ['name.0.first']})
// [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}]
// matchSorter(nestedObjList, 'j', {keys: ['name[0].first']}) does not work
```
This even works with arrays of multiple nested objects: just specify the key
using dot-notation with the `*` wildcard instead of a numeric index.
```javascript
const nestedObjList = [
{aliases: [{name: {first: 'Janice'}},{name: {first: 'Jen'}}]},
{aliases: [{name: {first: 'Fred'}},{name: {first: 'Frederic'}}]},
{aliases: [{name: {first: 'George'}},{name: {first: 'Georgie'}}]},
]
matchSorter(nestedObjList, 'jen', {keys: ['aliases.*.name.first']})
// [{aliases: [{name: {first: 'Janice'}},{name: {first: 'Jen'}}]}]
matchSorter(nestedObjList, 'jen', {keys: ['aliases.0.name.first']})
// []
```
**Property Callbacks**: Alternatively, you may also pass in a callback function
that resolves the value of the key(s) you wish to match on. This is especially
useful when interfacing with libraries such as Immutable.js
```javascript
const list = [{name: 'Janice'}, {name: 'Fred'}, {name: 'George'}, {name: 'Jen'}]
matchSorter(list, 'j', {keys: [item => item.name]})
// [{name: 'Janice'}, {name: 'Jen'}]
```
For more complex structures, expanding on the `nestedObjList` example above, you
can use `map`:
```javascript
const nestedObjList = [
{
name: [
{first: 'Janice', last: 'Smith'},
{first: 'Jon', last: 'Doe'},
],
},
{
name: [
{first: 'Fred', last: 'Astaire'},
{first: 'Jenny', last: 'Doe'},
{first: 'Wilma', last: 'Flintstone'},
],
},
]
matchSorter(nestedObjList, 'doe', {
keys: [
item => item.name.map(i => i.first),
item => item.name.map(i => i.last),
],
})
// [name: [{ first: 'Janice', last: 'Smith' },{ first: 'Jon', last: 'Doe' }], name: [{ first: 'Fred', last: 'Astaire' },{ first: 'Jenny', last: 'Doe' },{ first: 'Wilma', last: 'Flintstone' }]]
```
**Threshold**: You may specify an individual threshold for specific keys. A key
will only match if it meets the specified threshold. _For more information
regarding thresholds [see below](#threshold-number)_
```javascript
const list = [
{name: 'Fred', color: 'Orange'},
{name: 'Jen', color: 'Red'},
]
matchSorter(list, 'ed', {
keys: [{threshold: matchSorter.rankings.STARTS_WITH, key: 'name'}, 'color'],
})
//[{name: 'Jen', color: 'Red'}]
```
**Min and Max Ranking**: You may restrict specific keys to a minimum or maximum
ranking by passing in an object. A key with a minimum rank will only get
promoted if there is at least a simple match.
```javascript
const tea = [
{tea: 'Earl Grey', alias: 'A'},
{tea: 'Assam', alias: 'B'},
{tea: 'Black', alias: 'C'},
]
matchSorter(tea, 'A', {
keys: ['tea', {maxRanking: matchSorter.rankings.STARTS_WITH, key: 'alias'}],
})
// without maxRanking, Earl Grey would come first because the alias "A" would be CASE_SENSITIVE_EQUAL
// `tea` key comes before `alias` key, so Assam comes first even though both match as STARTS_WITH
// [{tea: 'Assam', alias: 'B'}, {tea: 'Earl Grey', alias: 'A'},{tea: 'Black', alias: 'C'}]
```
```javascript
const tea = [
{tea: 'Milk', alias: 'moo'},
{tea: 'Oolong', alias: 'B'},
{tea: 'Green', alias: 'C'},
]
matchSorter(tea, 'oo', {
keys: ['tea', {minRanking: matchSorter.rankings.EQUAL, key: 'alias'}],
})
// minRanking bumps Milk up to EQUAL from CONTAINS (alias)
// Oolong matches as STARTS_WITH
// Green is missing due to no match
// [{tea: 'Milk', alias: 'moo'}, {tea: 'Oolong', alias: 'B'}]
```
### threshold: `number`
_Default: `MATCHES`_
Thresholds can be used to specify the criteria used to rank the results.
Available thresholds (from top to bottom) are:
- CASE_SENSITIVE_EQUAL
- EQUAL
- STARTS_WITH
- WORD_STARTS_WITH
- CONTAINS
- ACRONYM
- MATCHES _(default value)_
- NO_MATCH
```javascript
const fruit = ['orange', 'apple', 'grape', 'banana']
matchSorter(fruit, 'ap', {threshold: matchSorter.rankings.NO_MATCH})
// ['apple', 'grape', 'orange', 'banana'] (returns all items, just sorted by best match)
const things = ['google', 'airbnb', 'apple', 'apply', 'app'],
matchSorter(things, 'app', {threshold: matchSorter.rankings.EQUAL})
// ['app'] (only items that are equal)
const otherThings = ['fiji apple', 'google', 'app', 'crabapple', 'apple', 'apply']
matchSorter(otherThings, 'app', {threshold: matchSorter.rankings.WORD_STARTS_WITH})
// ['app', 'apple', 'apply', 'fiji apple'] (everything that matches with "word starts with" or better)
```
### keepDiacritics: `boolean`
_Default: `false`_
By default, match-sorter will strip diacritics before doing any comparisons.
This is the default because it makes the most sense from a UX perspective.
You can disable this behavior by specifying `keepDiacritics: true`
```javascript
const thingsWithDiacritics = [
'jalapeño',
'à la carte',
'café',
'papier-mâché',
'à la mode',
]
matchSorter(thingsWithDiacritics, 'aa')
// ['jalapeño', 'à la carte', 'à la mode', 'papier-mâché']
matchSorter(thingsWithDiacritics, 'aa', {keepDiacritics: true})
// ['jalapeño', 'à la carte']
matchSorter(thingsWithDiacritics, 'à', {keepDiacritics: true})
// ['à la carte', 'à la mode']
```
### baseSort: `function(itemA, itemB): -1 | 0 | 1`
_Default: `(a, b) => String(a.rankedValue).localeCompare(b.rankedValue)`_
By default, match-sorter uses the `String.localeCompare` function to tie-break
items that have the same ranking. This results in a stable, alphabetic sort.
```javascript
const list = ['C apple', 'B apple', 'A apple']
matchSorter(list, 'apple')
// ['A apple', 'B apple', 'C apple']
```
_You can customize this behavior by specifying a custom `baseSort` function:_
```javascript
const list = ['C apple', 'B apple', 'A apple']
// This baseSort function will use the original index of items as the tie breaker
matchSorter(list, 'apple', {baseSort: (a, b) => (a.index < b.index ? -1 : 1)})
// ['C apple', 'B apple', 'A apple']
```
### sorter: `function(rankedItems): rankedItems`
_Default:
`matchedItems => matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort))`_
By default, match-sorter uses an internal `sortRankedValues` function to sort
items after matching them.
_You can customize the core sorting behavior by specifying a custom `sorter`
function:_
Disable sorting entirely:
```javascript
const list = ['appl', 'C apple', 'B apple', 'A apple', 'app', 'applebutter']
matchSorter(list, 'apple', {sorter: rankedItems => rankedItems})
// ['C apple', 'B apple', 'A apple', 'applebutter']
```
Return the unsorted rankedItems, but in reverse order:
```javascript
const list = ['appl', 'C apple', 'B apple', 'A apple', 'app', 'applebutter']
matchSorter(list, 'apple', {sorter: rankedItems => [...rankedItems].reverse()})
// ['applebutter', 'A apple', 'B apple', 'C apple']
```
## Recipes
### Match PascalCase, camelCase, snake_case, or kebab-case as words
By default, `match-sorter` assumes spaces to be the word separator. However, if
your data has a different word separator, you can use a property callback to
replace your separator with spaces. For example, for `snake_case`:
```javascript
const list = [
{name: 'Janice_Kurtis'},
{name: 'Fred_Mertz'},
{name: 'George_Foreman'},
{name: 'Jen_Smith'},
]
matchSorter(list, 'js', {keys: [item => item.name.replace(/_/g, ' ')]})
// [{name: 'Jen_Smith'}, {name: 'Janice_Kurtis'}]
```
### Match many words across multiple fields (table filtering)
By default, `match-sorter` will return matches from objects where one of the
properties matches _the entire_ search term. For multi-column data sets it can
be beneficial to split words in search string and match each word separately.
This can be done by chaining `match-sorter` calls.
The benefit of this is that a filter string of "two words" will match both "two"
and "words", but will return rows where the two words are found in _different_
columns as well as when both words match in the same column. For single-column
matches it will also return matches out of order (column = "wordstwo" will match
just as well as column="twowords", the latter getting a higher score).
```javascript
function fuzzySearchMultipleWords(
rows, // array of data [{a: "a", b: "b"}, {a: "c", b: "d"}]
keys, // keys to search ["a", "b"]
filterValue: string, // potentially multi-word search string "two words"
) {
if (!filterValue || !filterValue.length) {
return rows
}
const terms = filterValue.split(' ')
if (!terms) {
return rows
}
// reduceRight will mean sorting is done by score for the _first_ entered word.
return terms.reduceRight(
(results, term) => matchSorter(results, term, {keys}),
rows,
)
}
```
[Multi-column code sandbox](https://codesandbox.io/s/match-sorter-example-forked-1ko35)
## Inspiration
Actually, most of this code was extracted from the _very first_ library I ever
wrote: [genie][genie]!
## Other Solutions
You might try [Fuse.js](https://github.com/krisk/Fuse). It uses advanced math
fanciness to get the closest match. Unfortunately what's "closest" doesn't
always really make sense. So I extracted this from [genie][genie].
## Issues
_Looking to contribute? Look for the [Good First Issue][good-first-issue]
label._
### 🐛 Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
[**See Bugs**][bugs]
### 💡 Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding
a 👍. This helps maintainers prioritize what to work on.
[**See Feature Requests**][requests]
## Contributors ✨
Thanks goes to these people ([emoji key][emojis]):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://kentcdodds.com"><img src="https://avatars.githubusercontent.com/u/1500684?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Kent C. Dodds</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds" title="Documentation">📖</a> <a href="#infra-kentcdodds" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds" title="Tests">⚠️</a> <a href="https://github.com/kentcdodds/match-sorter/pulls?q=is%3Apr+reviewed-by%3Akentcdodds" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="http://conorhastings.com"><img src="https://avatars.githubusercontent.com/u/8263298?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Conor Hastings</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=conorhastings" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=conorhastings" title="Documentation">📖</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=conorhastings" title="Tests">⚠️</a> <a href="https://github.com/kentcdodds/match-sorter/pulls?q=is%3Apr+reviewed-by%3Aconorhastings" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/rogeliog"><img src="https://avatars.githubusercontent.com/u/574806?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Rogelio Guzman</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=rogeliog" title="Documentation">📖</a></td>
<td align="center"><a href="http://ced.io"><img src="https://avatars.githubusercontent.com/u/1416436?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Claudéric Demers</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=clauderic" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=clauderic" title="Documentation">📖</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=clauderic" title="Tests">⚠️</a></td>
<td align="center"><a href="kevindav.us"><img src="https://avatars3.githubusercontent.com/u/4150097?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Davis</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=osfan501" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=osfan501" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/nfdjps"><img src="https://avatars1.githubusercontent.com/u/19157735?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Denver Chen</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=nfdjps" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=nfdjps" title="Documentation">📖</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=nfdjps" title="Tests">⚠️</a></td>
<td align="center"><a href="http://ruigrok.info"><img src="https://avatars0.githubusercontent.com/u/12719057?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ruigrok</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3AChrisRu" title="Bug reports">🐛</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=ChrisRu" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=ChrisRu" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/hozefaj"><img src="https://avatars1.githubusercontent.com/u/2084833?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hozefa</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3Ahozefaj" title="Bug reports">🐛</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=hozefaj" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=hozefaj" title="Tests">⚠️</a> <a href="#ideas-hozefaj" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/pushpinder107"><img src="https://avatars3.githubusercontent.com/u/9403361?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pushpinder107</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=pushpinder107" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tikotzky"><img src="https://avatars3.githubusercontent.com/u/200528?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mordy Tikotzky</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=tikotzky" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=tikotzky" title="Documentation">📖</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=tikotzky" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/sdbrannum"><img src="https://avatars1.githubusercontent.com/u/11765845?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steven Brannum</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=sdbrannum" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=sdbrannum" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/cmeeren"><img src="https://avatars0.githubusercontent.com/u/7766733?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christer van der Meeren</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3Acmeeren" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://securitynull.net/"><img src="https://avatars0.githubusercontent.com/u/3801362?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Samuel Petrosyan</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=samyan" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3Asamyan" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://brandonkalinowski.com"><img src="https://avatars3.githubusercontent.com/u/4714862?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Kalinowski</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3Abrandonkal" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://codefund.io"><img src="https://avatars2.githubusercontent.com/u/12481?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Berry</b></sub></a><br /><a href="#fundingFinding-coderberry" title="Funding Finding">🔍</a></td>
<td align="center"><a href="https://github.com/skube"><img src="https://avatars3.githubusercontent.com/u/146396?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Skubie Doo</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=skube" title="Documentation">📖</a></td>
<td align="center"><a href="https://michaeldeboey.be"><img src="https://avatars3.githubusercontent.com/u/6643991?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michaël De Boey</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=MichaelDeBoey" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/pulls?q=is%3Apr+reviewed-by%3AMichaelDeBoey" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://tannerlinsley.com"><img src="https://avatars0.githubusercontent.com/u/5580297?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tanner Linsley</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=tannerlinsley" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=tannerlinsley" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/SweVictor"><img src="https://avatars1.githubusercontent.com/u/449347?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Victor</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=SweVictor" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/RebeccaStevens"><img src="https://avatars1.githubusercontent.com/u/7224206?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rebecca Stevens</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/issues?q=author%3ARebeccaStevens" title="Bug reports">🐛</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=RebeccaStevens" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/marcosvega91"><img src="https://avatars2.githubusercontent.com/u/5365582?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marco Moretti</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=marcosvega91" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://ricardobusquet.com"><img src="https://avatars1.githubusercontent.com/u/7198302?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo Busquet</b></sub></a><br /><a href="#ideas-rbusquet" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/kentcdodds/match-sorter/pulls?q=is%3Apr+reviewed-by%3Arbusquet" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=rbusquet" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/weyert"><img src="https://avatars3.githubusercontent.com/u/7049?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Weyert de Boer</b></sub></a><br /><a href="#ideas-weyert" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/kentcdodds/match-sorter/pulls?q=is%3Apr+reviewed-by%3Aweyert" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/PhilGarb"><img src="https://avatars3.githubusercontent.com/u/38015558?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Philipp Garbowsky</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=PhilGarb" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mart-jansink"><img src="https://avatars3.githubusercontent.com/u/4381258?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mart</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=mart-jansink" title="Code">💻</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=mart-jansink" title="Tests">⚠️</a> <a href="https://github.com/kentcdodds/match-sorter/commits?author=mart-jansink" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/levenleven"><img src="https://avatars.githubusercontent.com/u/6463364?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksey Levenstein</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=levenleven" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/diesieben07"><img src="https://avatars.githubusercontent.com/u/1915984?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Take Weiland</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=diesieben07" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/AmitAber"><img src="https://avatars.githubusercontent.com/u/8988867?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amit Abershitz</b></sub></a><br /><a href="https://github.com/kentcdodds/match-sorter/commits?author=AmitAber" title="Documentation">📖</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors][all-contributors] specification.
Contributions of any kind welcome!
## LICENSE
MIT
<!-- prettier-ignore-start -->
[npm]: https://www.npmjs.com
[node]: https://nodejs.org
[build-badge]: https://img.shields.io/github/workflow/status/kentcdodds/match-sorter/validate?logo=github&style=flat-square
[build]: https://github.com/kentcdodds/match-sorter/actions?query=workflow%3Avalidate
[coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/match-sorter.svg?style=flat-square
[coverage]: https://codecov.io/github/kentcdodds/match-sorter
[version-badge]: https://img.shields.io/npm/v/match-sorter.svg?style=flat-square
[package]: https://www.npmjs.com/package/match-sorter
[downloads-badge]: https://img.shields.io/npm/dm/match-sorter.svg?style=flat-square
[npmtrends]: https://www.npmtrends.com/match-sorter
[license-badge]: https://img.shields.io/npm/l/match-sorter.svg?style=flat-square
[license]: https://github.com/kentcdodds/match-sorter/blob/master/LICENSE
[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
[prs]: http://makeapullrequest.com
[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
[coc]: https://github.com/kentcdodds/match-sorter/blob/master/CODE_OF_CONDUCT.md
[examples-badge]: https://img.shields.io/badge/%F0%9F%92%A1-examples-8C8E93.svg?style=flat-square
[examples]: https://github.com/kentcdodds/match-sorter/blob/master/other/EXAMPLES.md
[emojis]: https://github.com/all-contributors/all-contributors#emoji-key
[all-contributors]: https://github.com/all-contributors/all-contributors
[all-contributors-badge]: https://img.shields.io/github/all-contributors/kentcdodds/match-sorter?color=orange&style=flat-square
[bugs]: https://github.com/kentcdodds/match-sorter/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
[requests]: https://github.com/kentcdodds/match-sorter/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
[good-first-issue]: https://github.com/kentcdodds/match-sorter/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement+label%3A%22good+first+issue%22
[genie]: https://github.com/kentcdodds/genie
<!-- prettier-ignore-end -->

View File

@@ -0,0 +1 @@
export {};

74
frontend/node_modules/match-sorter/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,74 @@
type KeyAttributes = {
threshold?: Ranking;
maxRanking: Ranking;
minRanking: Ranking;
};
interface RankingInfo {
rankedValue: string;
rank: Ranking;
keyIndex: number;
keyThreshold: Ranking | undefined;
}
interface ValueGetterKey<ItemType> {
(item: ItemType): string | Array<string>;
}
interface IndexedItem<ItemType> {
item: ItemType;
index: number;
}
interface RankedItem<ItemType> extends RankingInfo, IndexedItem<ItemType> {
}
interface BaseSorter<ItemType> {
(a: RankedItem<ItemType>, b: RankedItem<ItemType>): number;
}
interface Sorter<ItemType> {
(matchItems: Array<RankedItem<ItemType>>): Array<RankedItem<ItemType>>;
}
interface KeyAttributesOptions<ItemType> {
key?: string | ValueGetterKey<ItemType>;
threshold?: Ranking;
maxRanking?: Ranking;
minRanking?: Ranking;
}
type KeyOption<ItemType> = KeyAttributesOptions<ItemType> | ValueGetterKey<ItemType> | string;
interface MatchSorterOptions<ItemType = unknown> {
keys?: ReadonlyArray<KeyOption<ItemType>>;
threshold?: Ranking;
baseSort?: BaseSorter<ItemType>;
keepDiacritics?: boolean;
sorter?: Sorter<ItemType>;
}
declare const rankings: {
readonly CASE_SENSITIVE_EQUAL: 7;
readonly EQUAL: 6;
readonly STARTS_WITH: 5;
readonly WORD_STARTS_WITH: 4;
readonly CONTAINS: 3;
readonly ACRONYM: 2;
readonly MATCHES: 1;
readonly NO_MATCH: 0;
};
type Ranking = typeof rankings[keyof typeof rankings];
declare const defaultBaseSortFn: BaseSorter<unknown>;
/**
* Takes an array of items and a value and returns a new array with the items that match the given value
* @param {Array} items - the items to sort
* @param {String} value - the value to use for ranking
* @param {Object} options - Some options to configure the sorter
* @return {Array} - the new sorted array
*/
declare function matchSorter<ItemType = string>(items: ReadonlyArray<ItemType>, value: string, options?: MatchSorterOptions<ItemType>): Array<ItemType>;
declare namespace matchSorter {
var rankings: {
readonly CASE_SENSITIVE_EQUAL: 7;
readonly EQUAL: 6;
readonly STARTS_WITH: 5;
readonly WORD_STARTS_WITH: 4;
readonly CONTAINS: 3;
readonly ACRONYM: 2;
readonly MATCHES: 1;
readonly NO_MATCH: 0;
};
}
export { matchSorter, rankings, defaultBaseSortFn };
export type { MatchSorterOptions, KeyAttributesOptions, KeyOption, KeyAttributes, RankingInfo, ValueGetterKey, };

View File

@@ -0,0 +1,428 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var removeAccents = require('remove-accents');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var removeAccents__default = /*#__PURE__*/_interopDefaultLegacy(removeAccents);
/**
* @name match-sorter
* @license MIT license.
* @copyright (c) 2020 Kent C. Dodds
* @author Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
*/
const rankings = {
CASE_SENSITIVE_EQUAL: 7,
EQUAL: 6,
STARTS_WITH: 5,
WORD_STARTS_WITH: 4,
CONTAINS: 3,
ACRONYM: 2,
MATCHES: 1,
NO_MATCH: 0
};
const defaultBaseSortFn = (a, b) => String(a.rankedValue).localeCompare(String(b.rankedValue));
/**
* Takes an array of items and a value and returns a new array with the items that match the given value
* @param {Array} items - the items to sort
* @param {String} value - the value to use for ranking
* @param {Object} options - Some options to configure the sorter
* @return {Array} - the new sorted array
*/
function matchSorter(items, value, options) {
if (options === void 0) {
options = {};
}
const {
keys,
threshold = rankings.MATCHES,
baseSort = defaultBaseSortFn,
sorter = matchedItems => matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort))
} = options;
const matchedItems = items.reduce(reduceItemsToRanked, []);
return sorter(matchedItems).map(_ref => {
let {
item
} = _ref;
return item;
});
function reduceItemsToRanked(matches, item, index) {
const rankingInfo = getHighestRanking(item, keys, value, options);
const {
rank,
keyThreshold = threshold
} = rankingInfo;
if (rank >= keyThreshold) {
matches.push({
...rankingInfo,
item,
index
});
}
return matches;
}
}
matchSorter.rankings = rankings;
/**
* Gets the highest ranking for value for the given item based on its values for the given keys
* @param {*} item - the item to rank
* @param {Array} keys - the keys to get values from the item for the ranking
* @param {String} value - the value to rank against
* @param {Object} options - options to control the ranking
* @return {{rank: Number, keyIndex: Number, keyThreshold: Number}} - the highest ranking
*/
function getHighestRanking(item, keys, value, options) {
if (!keys) {
// if keys is not specified, then we assume the item given is ready to be matched
const stringItem = item;
return {
// ends up being duplicate of 'item' in matches but consistent
rankedValue: stringItem,
rank: getMatchRanking(stringItem, value, options),
keyIndex: -1,
keyThreshold: options.threshold
};
}
const valuesToRank = getAllValuesToRank(item, keys);
return valuesToRank.reduce((_ref2, _ref3, i) => {
let {
rank,
rankedValue,
keyIndex,
keyThreshold
} = _ref2;
let {
itemValue,
attributes
} = _ref3;
let newRank = getMatchRanking(itemValue, value, options);
let newRankedValue = rankedValue;
const {
minRanking,
maxRanking,
threshold
} = attributes;
if (newRank < minRanking && newRank >= rankings.MATCHES) {
newRank = minRanking;
} else if (newRank > maxRanking) {
newRank = maxRanking;
}
if (newRank > rank) {
rank = newRank;
keyIndex = i;
keyThreshold = threshold;
newRankedValue = itemValue;
}
return {
rankedValue: newRankedValue,
rank,
keyIndex,
keyThreshold
};
}, {
rankedValue: item,
rank: rankings.NO_MATCH,
keyIndex: -1,
keyThreshold: options.threshold
});
}
/**
* Gives a rankings score based on how well the two strings match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @param {Object} options - options for the match (like keepDiacritics for comparison)
* @returns {Number} the ranking for how well stringToRank matches testString
*/
function getMatchRanking(testString, stringToRank, options) {
testString = prepareValueForComparison(testString, options);
stringToRank = prepareValueForComparison(stringToRank, options);
// too long
if (stringToRank.length > testString.length) {
return rankings.NO_MATCH;
}
// case sensitive equals
if (testString === stringToRank) {
return rankings.CASE_SENSITIVE_EQUAL;
}
// Lower casing before further comparison
testString = testString.toLowerCase();
stringToRank = stringToRank.toLowerCase();
// case insensitive equals
if (testString === stringToRank) {
return rankings.EQUAL;
}
// starts with
if (testString.startsWith(stringToRank)) {
return rankings.STARTS_WITH;
}
// word starts with
if (testString.includes(` ${stringToRank}`)) {
return rankings.WORD_STARTS_WITH;
}
// contains
if (testString.includes(stringToRank)) {
return rankings.CONTAINS;
} else if (stringToRank.length === 1) {
// If the only character in the given stringToRank
// isn't even contained in the testString, then
// it's definitely not a match.
return rankings.NO_MATCH;
}
// acronym
if (getAcronym(testString).includes(stringToRank)) {
return rankings.ACRONYM;
}
// will return a number between rankings.MATCHES and
// rankings.MATCHES + 1 depending on how close of a match it is.
return getClosenessRanking(testString, stringToRank);
}
/**
* Generates an acronym for a string.
*
* @param {String} string the string for which to produce the acronym
* @returns {String} the acronym
*/
function getAcronym(string) {
let acronym = '';
const wordsInString = string.split(' ');
wordsInString.forEach(wordInString => {
const splitByHyphenWords = wordInString.split('-');
splitByHyphenWords.forEach(splitByHyphenWord => {
acronym += splitByHyphenWord.substr(0, 1);
});
});
return acronym;
}
/**
* Returns a score based on how spread apart the
* characters from the stringToRank are within the testString.
* A number close to rankings.MATCHES represents a loose match. A number close
* to rankings.MATCHES + 1 represents a tighter match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @returns {Number} the number between rankings.MATCHES and
* rankings.MATCHES + 1 for how well stringToRank matches testString
*/
function getClosenessRanking(testString, stringToRank) {
let matchingInOrderCharCount = 0;
let charNumber = 0;
function findMatchingCharacter(matchChar, string, index) {
for (let j = index, J = string.length; j < J; j++) {
const stringChar = string[j];
if (stringChar === matchChar) {
matchingInOrderCharCount += 1;
return j + 1;
}
}
return -1;
}
function getRanking(spread) {
const spreadPercentage = 1 / spread;
const inOrderPercentage = matchingInOrderCharCount / stringToRank.length;
const ranking = rankings.MATCHES + inOrderPercentage * spreadPercentage;
return ranking;
}
const firstIndex = findMatchingCharacter(stringToRank[0], testString, 0);
if (firstIndex < 0) {
return rankings.NO_MATCH;
}
charNumber = firstIndex;
for (let i = 1, I = stringToRank.length; i < I; i++) {
const matchChar = stringToRank[i];
charNumber = findMatchingCharacter(matchChar, testString, charNumber);
const found = charNumber > -1;
if (!found) {
return rankings.NO_MATCH;
}
}
const spread = charNumber - firstIndex;
return getRanking(spread);
}
/**
* Sorts items that have a rank, index, and keyIndex
* @param {Object} a - the first item to sort
* @param {Object} b - the second item to sort
* @return {Number} -1 if a should come first, 1 if b should come first, 0 if equal
*/
function sortRankedValues(a, b, baseSort) {
const aFirst = -1;
const bFirst = 1;
const {
rank: aRank,
keyIndex: aKeyIndex
} = a;
const {
rank: bRank,
keyIndex: bKeyIndex
} = b;
const same = aRank === bRank;
if (same) {
if (aKeyIndex === bKeyIndex) {
// use the base sort function as a tie-breaker
return baseSort(a, b);
} else {
return aKeyIndex < bKeyIndex ? aFirst : bFirst;
}
} else {
return aRank > bRank ? aFirst : bFirst;
}
}
/**
* Prepares value for comparison by stringifying it, removing diacritics (if specified)
* @param {String} value - the value to clean
* @param {Object} options - {keepDiacritics: whether to remove diacritics}
* @return {String} the prepared value
*/
function prepareValueForComparison(value, _ref4) {
let {
keepDiacritics
} = _ref4;
// value might not actually be a string at this point (we don't get to choose)
// so part of preparing the value for comparison is ensure that it is a string
value = `${value}`; // toString
if (!keepDiacritics) {
value = removeAccents__default["default"](value);
}
return value;
}
/**
* Gets value for key in item at arbitrarily nested keypath
* @param {Object} item - the item
* @param {Object|Function} key - the potentially nested keypath or property callback
* @return {Array} - an array containing the value(s) at the nested keypath
*/
function getItemValues(item, key) {
if (typeof key === 'object') {
key = key.key;
}
let value;
if (typeof key === 'function') {
value = key(item);
} else if (item == null) {
value = null;
} else if (Object.hasOwnProperty.call(item, key)) {
value = item[key];
} else if (key.includes('.')) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return getNestedValues(key, item);
} else {
value = null;
}
// because `value` can also be undefined
if (value == null) {
return [];
}
if (Array.isArray(value)) {
return value;
}
return [String(value)];
}
/**
* Given path: "foo.bar.baz"
* And item: {foo: {bar: {baz: 'buzz'}}}
* -> 'buzz'
* @param path a dot-separated set of keys
* @param item the item to get the value from
*/
function getNestedValues(path, item) {
const keys = path.split('.');
let values = [item];
for (let i = 0, I = keys.length; i < I; i++) {
const nestedKey = keys[i];
let nestedValues = [];
for (let j = 0, J = values.length; j < J; j++) {
const nestedItem = values[j];
if (nestedItem == null) continue;
if (Object.hasOwnProperty.call(nestedItem, nestedKey)) {
const nestedValue = nestedItem[nestedKey];
if (nestedValue != null) {
nestedValues.push(nestedValue);
}
} else if (nestedKey === '*') {
// ensure that values is an array
nestedValues = nestedValues.concat(nestedItem);
}
}
values = nestedValues;
}
if (Array.isArray(values[0])) {
// keep allowing the implicit wildcard for an array of strings at the end of
// the path; don't use `.flat()` because that's not available in node.js v10
const result = [];
return result.concat(...values);
}
// Based on our logic it should be an array of strings by now...
// assuming the user's path terminated in strings
return values;
}
/**
* Gets all the values for the given keys in the given item and returns an array of those values
* @param item - the item from which the values will be retrieved
* @param keys - the keys to use to retrieve the values
* @return objects with {itemValue, attributes}
*/
function getAllValuesToRank(item, keys) {
const allValues = [];
for (let j = 0, J = keys.length; j < J; j++) {
const key = keys[j];
const attributes = getKeyAttributes(key);
const itemValues = getItemValues(item, key);
for (let i = 0, I = itemValues.length; i < I; i++) {
allValues.push({
itemValue: itemValues[i],
attributes
});
}
}
return allValues;
}
const defaultKeyAttributes = {
maxRanking: Infinity,
minRanking: -Infinity
};
/**
* Gets all the attributes for the given key
* @param key - the key from which the attributes will be retrieved
* @return object containing the key's attributes
*/
function getKeyAttributes(key) {
if (typeof key === 'string') {
return defaultKeyAttributes;
}
return {
...defaultKeyAttributes,
...key
};
}
/*
eslint
no-continue: "off",
*/
exports.defaultBaseSortFn = defaultBaseSortFn;
exports.matchSorter = matchSorter;
exports.rankings = rankings;

View File

@@ -0,0 +1,418 @@
import removeAccents from 'remove-accents';
/**
* @name match-sorter
* @license MIT license.
* @copyright (c) 2020 Kent C. Dodds
* @author Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
*/
const rankings = {
CASE_SENSITIVE_EQUAL: 7,
EQUAL: 6,
STARTS_WITH: 5,
WORD_STARTS_WITH: 4,
CONTAINS: 3,
ACRONYM: 2,
MATCHES: 1,
NO_MATCH: 0
};
const defaultBaseSortFn = (a, b) => String(a.rankedValue).localeCompare(String(b.rankedValue));
/**
* Takes an array of items and a value and returns a new array with the items that match the given value
* @param {Array} items - the items to sort
* @param {String} value - the value to use for ranking
* @param {Object} options - Some options to configure the sorter
* @return {Array} - the new sorted array
*/
function matchSorter(items, value, options) {
if (options === void 0) {
options = {};
}
const {
keys,
threshold = rankings.MATCHES,
baseSort = defaultBaseSortFn,
sorter = matchedItems => matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort))
} = options;
const matchedItems = items.reduce(reduceItemsToRanked, []);
return sorter(matchedItems).map(_ref => {
let {
item
} = _ref;
return item;
});
function reduceItemsToRanked(matches, item, index) {
const rankingInfo = getHighestRanking(item, keys, value, options);
const {
rank,
keyThreshold = threshold
} = rankingInfo;
if (rank >= keyThreshold) {
matches.push({
...rankingInfo,
item,
index
});
}
return matches;
}
}
matchSorter.rankings = rankings;
/**
* Gets the highest ranking for value for the given item based on its values for the given keys
* @param {*} item - the item to rank
* @param {Array} keys - the keys to get values from the item for the ranking
* @param {String} value - the value to rank against
* @param {Object} options - options to control the ranking
* @return {{rank: Number, keyIndex: Number, keyThreshold: Number}} - the highest ranking
*/
function getHighestRanking(item, keys, value, options) {
if (!keys) {
// if keys is not specified, then we assume the item given is ready to be matched
const stringItem = item;
return {
// ends up being duplicate of 'item' in matches but consistent
rankedValue: stringItem,
rank: getMatchRanking(stringItem, value, options),
keyIndex: -1,
keyThreshold: options.threshold
};
}
const valuesToRank = getAllValuesToRank(item, keys);
return valuesToRank.reduce((_ref2, _ref3, i) => {
let {
rank,
rankedValue,
keyIndex,
keyThreshold
} = _ref2;
let {
itemValue,
attributes
} = _ref3;
let newRank = getMatchRanking(itemValue, value, options);
let newRankedValue = rankedValue;
const {
minRanking,
maxRanking,
threshold
} = attributes;
if (newRank < minRanking && newRank >= rankings.MATCHES) {
newRank = minRanking;
} else if (newRank > maxRanking) {
newRank = maxRanking;
}
if (newRank > rank) {
rank = newRank;
keyIndex = i;
keyThreshold = threshold;
newRankedValue = itemValue;
}
return {
rankedValue: newRankedValue,
rank,
keyIndex,
keyThreshold
};
}, {
rankedValue: item,
rank: rankings.NO_MATCH,
keyIndex: -1,
keyThreshold: options.threshold
});
}
/**
* Gives a rankings score based on how well the two strings match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @param {Object} options - options for the match (like keepDiacritics for comparison)
* @returns {Number} the ranking for how well stringToRank matches testString
*/
function getMatchRanking(testString, stringToRank, options) {
testString = prepareValueForComparison(testString, options);
stringToRank = prepareValueForComparison(stringToRank, options);
// too long
if (stringToRank.length > testString.length) {
return rankings.NO_MATCH;
}
// case sensitive equals
if (testString === stringToRank) {
return rankings.CASE_SENSITIVE_EQUAL;
}
// Lower casing before further comparison
testString = testString.toLowerCase();
stringToRank = stringToRank.toLowerCase();
// case insensitive equals
if (testString === stringToRank) {
return rankings.EQUAL;
}
// starts with
if (testString.startsWith(stringToRank)) {
return rankings.STARTS_WITH;
}
// word starts with
if (testString.includes(` ${stringToRank}`)) {
return rankings.WORD_STARTS_WITH;
}
// contains
if (testString.includes(stringToRank)) {
return rankings.CONTAINS;
} else if (stringToRank.length === 1) {
// If the only character in the given stringToRank
// isn't even contained in the testString, then
// it's definitely not a match.
return rankings.NO_MATCH;
}
// acronym
if (getAcronym(testString).includes(stringToRank)) {
return rankings.ACRONYM;
}
// will return a number between rankings.MATCHES and
// rankings.MATCHES + 1 depending on how close of a match it is.
return getClosenessRanking(testString, stringToRank);
}
/**
* Generates an acronym for a string.
*
* @param {String} string the string for which to produce the acronym
* @returns {String} the acronym
*/
function getAcronym(string) {
let acronym = '';
const wordsInString = string.split(' ');
wordsInString.forEach(wordInString => {
const splitByHyphenWords = wordInString.split('-');
splitByHyphenWords.forEach(splitByHyphenWord => {
acronym += splitByHyphenWord.substr(0, 1);
});
});
return acronym;
}
/**
* Returns a score based on how spread apart the
* characters from the stringToRank are within the testString.
* A number close to rankings.MATCHES represents a loose match. A number close
* to rankings.MATCHES + 1 represents a tighter match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @returns {Number} the number between rankings.MATCHES and
* rankings.MATCHES + 1 for how well stringToRank matches testString
*/
function getClosenessRanking(testString, stringToRank) {
let matchingInOrderCharCount = 0;
let charNumber = 0;
function findMatchingCharacter(matchChar, string, index) {
for (let j = index, J = string.length; j < J; j++) {
const stringChar = string[j];
if (stringChar === matchChar) {
matchingInOrderCharCount += 1;
return j + 1;
}
}
return -1;
}
function getRanking(spread) {
const spreadPercentage = 1 / spread;
const inOrderPercentage = matchingInOrderCharCount / stringToRank.length;
const ranking = rankings.MATCHES + inOrderPercentage * spreadPercentage;
return ranking;
}
const firstIndex = findMatchingCharacter(stringToRank[0], testString, 0);
if (firstIndex < 0) {
return rankings.NO_MATCH;
}
charNumber = firstIndex;
for (let i = 1, I = stringToRank.length; i < I; i++) {
const matchChar = stringToRank[i];
charNumber = findMatchingCharacter(matchChar, testString, charNumber);
const found = charNumber > -1;
if (!found) {
return rankings.NO_MATCH;
}
}
const spread = charNumber - firstIndex;
return getRanking(spread);
}
/**
* Sorts items that have a rank, index, and keyIndex
* @param {Object} a - the first item to sort
* @param {Object} b - the second item to sort
* @return {Number} -1 if a should come first, 1 if b should come first, 0 if equal
*/
function sortRankedValues(a, b, baseSort) {
const aFirst = -1;
const bFirst = 1;
const {
rank: aRank,
keyIndex: aKeyIndex
} = a;
const {
rank: bRank,
keyIndex: bKeyIndex
} = b;
const same = aRank === bRank;
if (same) {
if (aKeyIndex === bKeyIndex) {
// use the base sort function as a tie-breaker
return baseSort(a, b);
} else {
return aKeyIndex < bKeyIndex ? aFirst : bFirst;
}
} else {
return aRank > bRank ? aFirst : bFirst;
}
}
/**
* Prepares value for comparison by stringifying it, removing diacritics (if specified)
* @param {String} value - the value to clean
* @param {Object} options - {keepDiacritics: whether to remove diacritics}
* @return {String} the prepared value
*/
function prepareValueForComparison(value, _ref4) {
let {
keepDiacritics
} = _ref4;
// value might not actually be a string at this point (we don't get to choose)
// so part of preparing the value for comparison is ensure that it is a string
value = `${value}`; // toString
if (!keepDiacritics) {
value = removeAccents(value);
}
return value;
}
/**
* Gets value for key in item at arbitrarily nested keypath
* @param {Object} item - the item
* @param {Object|Function} key - the potentially nested keypath or property callback
* @return {Array} - an array containing the value(s) at the nested keypath
*/
function getItemValues(item, key) {
if (typeof key === 'object') {
key = key.key;
}
let value;
if (typeof key === 'function') {
value = key(item);
} else if (item == null) {
value = null;
} else if (Object.hasOwnProperty.call(item, key)) {
value = item[key];
} else if (key.includes('.')) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return getNestedValues(key, item);
} else {
value = null;
}
// because `value` can also be undefined
if (value == null) {
return [];
}
if (Array.isArray(value)) {
return value;
}
return [String(value)];
}
/**
* Given path: "foo.bar.baz"
* And item: {foo: {bar: {baz: 'buzz'}}}
* -> 'buzz'
* @param path a dot-separated set of keys
* @param item the item to get the value from
*/
function getNestedValues(path, item) {
const keys = path.split('.');
let values = [item];
for (let i = 0, I = keys.length; i < I; i++) {
const nestedKey = keys[i];
let nestedValues = [];
for (let j = 0, J = values.length; j < J; j++) {
const nestedItem = values[j];
if (nestedItem == null) continue;
if (Object.hasOwnProperty.call(nestedItem, nestedKey)) {
const nestedValue = nestedItem[nestedKey];
if (nestedValue != null) {
nestedValues.push(nestedValue);
}
} else if (nestedKey === '*') {
// ensure that values is an array
nestedValues = nestedValues.concat(nestedItem);
}
}
values = nestedValues;
}
if (Array.isArray(values[0])) {
// keep allowing the implicit wildcard for an array of strings at the end of
// the path; don't use `.flat()` because that's not available in node.js v10
const result = [];
return result.concat(...values);
}
// Based on our logic it should be an array of strings by now...
// assuming the user's path terminated in strings
return values;
}
/**
* Gets all the values for the given keys in the given item and returns an array of those values
* @param item - the item from which the values will be retrieved
* @param keys - the keys to use to retrieve the values
* @return objects with {itemValue, attributes}
*/
function getAllValuesToRank(item, keys) {
const allValues = [];
for (let j = 0, J = keys.length; j < J; j++) {
const key = keys[j];
const attributes = getKeyAttributes(key);
const itemValues = getItemValues(item, key);
for (let i = 0, I = itemValues.length; i < I; i++) {
allValues.push({
itemValue: itemValues[i],
attributes
});
}
}
return allValues;
}
const defaultKeyAttributes = {
maxRanking: Infinity,
minRanking: -Infinity
};
/**
* Gets all the attributes for the given key
* @param key - the key from which the attributes will be retrieved
* @return object containing the key's attributes
*/
function getKeyAttributes(key) {
if (typeof key === 'string') {
return defaultKeyAttributes;
}
return {
...defaultKeyAttributes,
...key
};
}
/*
eslint
no-continue: "off",
*/
export { defaultBaseSortFn, matchSorter, rankings };

View File

@@ -0,0 +1,908 @@
(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.matchSorter = {}));
})(this, (function (exports) { 'use strict';
var removeAccents$1 = {exports: {}};
var characterMap = {
"À": "A",
"Á": "A",
"Â": "A",
"Ã": "A",
"Ä": "A",
"Å": "A",
"Ấ": "A",
"Ắ": "A",
"Ẳ": "A",
"Ẵ": "A",
"Ặ": "A",
"Æ": "AE",
"Ầ": "A",
"Ằ": "A",
"Ȃ": "A",
"Ả": "A",
"Ạ": "A",
"Ẩ": "A",
"Ẫ": "A",
"Ậ": "A",
"Ç": "C",
"Ḉ": "C",
"È": "E",
"É": "E",
"Ê": "E",
"Ë": "E",
"Ế": "E",
"Ḗ": "E",
"Ề": "E",
"Ḕ": "E",
"Ḝ": "E",
"Ȇ": "E",
"Ẻ": "E",
"Ẽ": "E",
"Ẹ": "E",
"Ể": "E",
"Ễ": "E",
"Ệ": "E",
"Ì": "I",
"Í": "I",
"Î": "I",
"Ï": "I",
"Ḯ": "I",
"Ȋ": "I",
"Ỉ": "I",
"Ị": "I",
"Ð": "D",
"Ñ": "N",
"Ò": "O",
"Ó": "O",
"Ô": "O",
"Õ": "O",
"Ö": "O",
"Ø": "O",
"Ố": "O",
"Ṍ": "O",
"Ṓ": "O",
"Ȏ": "O",
"Ỏ": "O",
"Ọ": "O",
"Ổ": "O",
"Ỗ": "O",
"Ộ": "O",
"Ờ": "O",
"Ở": "O",
"Ỡ": "O",
"Ớ": "O",
"Ợ": "O",
"Ù": "U",
"Ú": "U",
"Û": "U",
"Ü": "U",
"Ủ": "U",
"Ụ": "U",
"Ử": "U",
"Ữ": "U",
"Ự": "U",
"Ý": "Y",
"à": "a",
"á": "a",
"â": "a",
"ã": "a",
"ä": "a",
"å": "a",
"ấ": "a",
"ắ": "a",
"ẳ": "a",
"ẵ": "a",
"ặ": "a",
"æ": "ae",
"ầ": "a",
"ằ": "a",
"ȃ": "a",
"ả": "a",
"ạ": "a",
"ẩ": "a",
"ẫ": "a",
"ậ": "a",
"ç": "c",
"ḉ": "c",
"è": "e",
"é": "e",
"ê": "e",
"ë": "e",
"ế": "e",
"ḗ": "e",
"ề": "e",
"ḕ": "e",
"ḝ": "e",
"ȇ": "e",
"ẻ": "e",
"ẽ": "e",
"ẹ": "e",
"ể": "e",
"ễ": "e",
"ệ": "e",
"ì": "i",
"í": "i",
"î": "i",
"ï": "i",
"ḯ": "i",
"ȋ": "i",
"ỉ": "i",
"ị": "i",
"ð": "d",
"ñ": "n",
"ò": "o",
"ó": "o",
"ô": "o",
"õ": "o",
"ö": "o",
"ø": "o",
"ố": "o",
"ṍ": "o",
"ṓ": "o",
"ȏ": "o",
"ỏ": "o",
"ọ": "o",
"ổ": "o",
"ỗ": "o",
"ộ": "o",
"ờ": "o",
"ở": "o",
"ỡ": "o",
"ớ": "o",
"ợ": "o",
"ù": "u",
"ú": "u",
"û": "u",
"ü": "u",
"ủ": "u",
"ụ": "u",
"ử": "u",
"ữ": "u",
"ự": "u",
"ý": "y",
"ÿ": "y",
"Ā": "A",
"ā": "a",
"Ă": "A",
"ă": "a",
"Ą": "A",
"ą": "a",
"Ć": "C",
"ć": "c",
"Ĉ": "C",
"ĉ": "c",
"Ċ": "C",
"ċ": "c",
"Č": "C",
"č": "c",
"C̆": "C",
"c̆": "c",
"Ď": "D",
"ď": "d",
"Đ": "D",
"đ": "d",
"Ē": "E",
"ē": "e",
"Ĕ": "E",
"ĕ": "e",
"Ė": "E",
"ė": "e",
"Ę": "E",
"ę": "e",
"Ě": "E",
"ě": "e",
"Ĝ": "G",
"Ǵ": "G",
"ĝ": "g",
"ǵ": "g",
"Ğ": "G",
"ğ": "g",
"Ġ": "G",
"ġ": "g",
"Ģ": "G",
"ģ": "g",
"Ĥ": "H",
"ĥ": "h",
"Ħ": "H",
"ħ": "h",
"Ḫ": "H",
"ḫ": "h",
"Ĩ": "I",
"ĩ": "i",
"Ī": "I",
"ī": "i",
"Ĭ": "I",
"ĭ": "i",
"Į": "I",
"į": "i",
"İ": "I",
"ı": "i",
"IJ": "IJ",
"ij": "ij",
"Ĵ": "J",
"ĵ": "j",
"Ķ": "K",
"ķ": "k",
"Ḱ": "K",
"ḱ": "k",
"K̆": "K",
"k̆": "k",
"Ĺ": "L",
"ĺ": "l",
"Ļ": "L",
"ļ": "l",
"Ľ": "L",
"ľ": "l",
"Ŀ": "L",
"ŀ": "l",
"Ł": "l",
"ł": "l",
"Ḿ": "M",
"ḿ": "m",
"M̆": "M",
"m̆": "m",
"Ń": "N",
"ń": "n",
"Ņ": "N",
"ņ": "n",
"Ň": "N",
"ň": "n",
"ʼn": "n",
"N̆": "N",
"n̆": "n",
"Ō": "O",
"ō": "o",
"Ŏ": "O",
"ŏ": "o",
"Ő": "O",
"ő": "o",
"Œ": "OE",
"œ": "oe",
"P̆": "P",
"p̆": "p",
"Ŕ": "R",
"ŕ": "r",
"Ŗ": "R",
"ŗ": "r",
"Ř": "R",
"ř": "r",
"R̆": "R",
"r̆": "r",
"Ȓ": "R",
"ȓ": "r",
"Ś": "S",
"ś": "s",
"Ŝ": "S",
"ŝ": "s",
"Ş": "S",
"Ș": "S",
"ș": "s",
"ş": "s",
"Š": "S",
"š": "s",
"Ţ": "T",
"ţ": "t",
"ț": "t",
"Ț": "T",
"Ť": "T",
"ť": "t",
"Ŧ": "T",
"ŧ": "t",
"T̆": "T",
"t̆": "t",
"Ũ": "U",
"ũ": "u",
"Ū": "U",
"ū": "u",
"Ŭ": "U",
"ŭ": "u",
"Ů": "U",
"ů": "u",
"Ű": "U",
"ű": "u",
"Ų": "U",
"ų": "u",
"Ȗ": "U",
"ȗ": "u",
"V̆": "V",
"v̆": "v",
"Ŵ": "W",
"ŵ": "w",
"Ẃ": "W",
"ẃ": "w",
"X̆": "X",
"x̆": "x",
"Ŷ": "Y",
"ŷ": "y",
"Ÿ": "Y",
"Y̆": "Y",
"y̆": "y",
"Ź": "Z",
"ź": "z",
"Ż": "Z",
"ż": "z",
"Ž": "Z",
"ž": "z",
"ſ": "s",
"ƒ": "f",
"Ơ": "O",
"ơ": "o",
"Ư": "U",
"ư": "u",
"Ǎ": "A",
"ǎ": "a",
"Ǐ": "I",
"ǐ": "i",
"Ǒ": "O",
"ǒ": "o",
"Ǔ": "U",
"ǔ": "u",
"Ǖ": "U",
"ǖ": "u",
"Ǘ": "U",
"ǘ": "u",
"Ǚ": "U",
"ǚ": "u",
"Ǜ": "U",
"ǜ": "u",
"Ứ": "U",
"ứ": "u",
"Ṹ": "U",
"ṹ": "u",
"Ǻ": "A",
"ǻ": "a",
"Ǽ": "AE",
"ǽ": "ae",
"Ǿ": "O",
"ǿ": "o",
"Þ": "TH",
"þ": "th",
"Ṕ": "P",
"ṕ": "p",
"Ṥ": "S",
"ṥ": "s",
"X́": "X",
"x́": "x",
"Ѓ": "Г",
"ѓ": "г",
"Ќ": "К",
"ќ": "к",
"A̋": "A",
"a̋": "a",
"E̋": "E",
"e̋": "e",
"I̋": "I",
"i̋": "i",
"Ǹ": "N",
"ǹ": "n",
"Ồ": "O",
"ồ": "o",
"Ṑ": "O",
"ṑ": "o",
"Ừ": "U",
"ừ": "u",
"Ẁ": "W",
"ẁ": "w",
"Ỳ": "Y",
"ỳ": "y",
"Ȁ": "A",
"ȁ": "a",
"Ȅ": "E",
"ȅ": "e",
"Ȉ": "I",
"ȉ": "i",
"Ȍ": "O",
"ȍ": "o",
"Ȑ": "R",
"ȑ": "r",
"Ȕ": "U",
"ȕ": "u",
"B̌": "B",
"b̌": "b",
"Č̣": "C",
"č̣": "c",
"Ê̌": "E",
"ê̌": "e",
"F̌": "F",
"f̌": "f",
"Ǧ": "G",
"ǧ": "g",
"Ȟ": "H",
"ȟ": "h",
"J̌": "J",
"ǰ": "j",
"Ǩ": "K",
"ǩ": "k",
"M̌": "M",
"m̌": "m",
"P̌": "P",
"p̌": "p",
"Q̌": "Q",
"q̌": "q",
"Ř̩": "R",
"ř̩": "r",
"Ṧ": "S",
"ṧ": "s",
"V̌": "V",
"v̌": "v",
"W̌": "W",
"w̌": "w",
"X̌": "X",
"x̌": "x",
"Y̌": "Y",
"y̌": "y",
"A̧": "A",
"a̧": "a",
"B̧": "B",
"b̧": "b",
"Ḑ": "D",
"ḑ": "d",
"Ȩ": "E",
"ȩ": "e",
"Ɛ̧": "E",
"ɛ̧": "e",
"Ḩ": "H",
"ḩ": "h",
"I̧": "I",
"i̧": "i",
"Ɨ̧": "I",
"ɨ̧": "i",
"M̧": "M",
"m̧": "m",
"O̧": "O",
"o̧": "o",
"Q̧": "Q",
"q̧": "q",
"U̧": "U",
"u̧": "u",
"X̧": "X",
"x̧": "x",
"Z̧": "Z",
"z̧": "z",
"й": "и",
"Й": "И",
"ё": "е",
"Ё": "Е"
};
var chars = Object.keys(characterMap).join('|');
var allAccents = new RegExp(chars, 'g');
var firstAccent = new RegExp(chars, '');
function matcher(match) {
return characterMap[match];
}
var removeAccents = function (string) {
return string.replace(allAccents, matcher);
};
var hasAccents = function (string) {
return !!string.match(firstAccent);
};
removeAccents$1.exports = removeAccents;
removeAccents$1.exports.has = hasAccents;
removeAccents$1.exports.remove = removeAccents;
/**
* @name match-sorter
* @license MIT license.
* @copyright (c) 2020 Kent C. Dodds
* @author Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
*/
const rankings = {
CASE_SENSITIVE_EQUAL: 7,
EQUAL: 6,
STARTS_WITH: 5,
WORD_STARTS_WITH: 4,
CONTAINS: 3,
ACRONYM: 2,
MATCHES: 1,
NO_MATCH: 0
};
const defaultBaseSortFn = (a, b) => String(a.rankedValue).localeCompare(String(b.rankedValue));
/**
* Takes an array of items and a value and returns a new array with the items that match the given value
* @param {Array} items - the items to sort
* @param {String} value - the value to use for ranking
* @param {Object} options - Some options to configure the sorter
* @return {Array} - the new sorted array
*/
function matchSorter(items, value, options) {
if (options === void 0) {
options = {};
}
const {
keys,
threshold = rankings.MATCHES,
baseSort = defaultBaseSortFn,
sorter = matchedItems => matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort))
} = options;
const matchedItems = items.reduce(reduceItemsToRanked, []);
return sorter(matchedItems).map(_ref => {
let {
item
} = _ref;
return item;
});
function reduceItemsToRanked(matches, item, index) {
const rankingInfo = getHighestRanking(item, keys, value, options);
const {
rank,
keyThreshold = threshold
} = rankingInfo;
if (rank >= keyThreshold) {
matches.push({
...rankingInfo,
item,
index
});
}
return matches;
}
}
matchSorter.rankings = rankings;
/**
* Gets the highest ranking for value for the given item based on its values for the given keys
* @param {*} item - the item to rank
* @param {Array} keys - the keys to get values from the item for the ranking
* @param {String} value - the value to rank against
* @param {Object} options - options to control the ranking
* @return {{rank: Number, keyIndex: Number, keyThreshold: Number}} - the highest ranking
*/
function getHighestRanking(item, keys, value, options) {
if (!keys) {
// if keys is not specified, then we assume the item given is ready to be matched
const stringItem = item;
return {
// ends up being duplicate of 'item' in matches but consistent
rankedValue: stringItem,
rank: getMatchRanking(stringItem, value, options),
keyIndex: -1,
keyThreshold: options.threshold
};
}
const valuesToRank = getAllValuesToRank(item, keys);
return valuesToRank.reduce((_ref2, _ref3, i) => {
let {
rank,
rankedValue,
keyIndex,
keyThreshold
} = _ref2;
let {
itemValue,
attributes
} = _ref3;
let newRank = getMatchRanking(itemValue, value, options);
let newRankedValue = rankedValue;
const {
minRanking,
maxRanking,
threshold
} = attributes;
if (newRank < minRanking && newRank >= rankings.MATCHES) {
newRank = minRanking;
} else if (newRank > maxRanking) {
newRank = maxRanking;
}
if (newRank > rank) {
rank = newRank;
keyIndex = i;
keyThreshold = threshold;
newRankedValue = itemValue;
}
return {
rankedValue: newRankedValue,
rank,
keyIndex,
keyThreshold
};
}, {
rankedValue: item,
rank: rankings.NO_MATCH,
keyIndex: -1,
keyThreshold: options.threshold
});
}
/**
* Gives a rankings score based on how well the two strings match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @param {Object} options - options for the match (like keepDiacritics for comparison)
* @returns {Number} the ranking for how well stringToRank matches testString
*/
function getMatchRanking(testString, stringToRank, options) {
testString = prepareValueForComparison(testString, options);
stringToRank = prepareValueForComparison(stringToRank, options);
// too long
if (stringToRank.length > testString.length) {
return rankings.NO_MATCH;
}
// case sensitive equals
if (testString === stringToRank) {
return rankings.CASE_SENSITIVE_EQUAL;
}
// Lower casing before further comparison
testString = testString.toLowerCase();
stringToRank = stringToRank.toLowerCase();
// case insensitive equals
if (testString === stringToRank) {
return rankings.EQUAL;
}
// starts with
if (testString.startsWith(stringToRank)) {
return rankings.STARTS_WITH;
}
// word starts with
if (testString.includes(` ${stringToRank}`)) {
return rankings.WORD_STARTS_WITH;
}
// contains
if (testString.includes(stringToRank)) {
return rankings.CONTAINS;
} else if (stringToRank.length === 1) {
// If the only character in the given stringToRank
// isn't even contained in the testString, then
// it's definitely not a match.
return rankings.NO_MATCH;
}
// acronym
if (getAcronym(testString).includes(stringToRank)) {
return rankings.ACRONYM;
}
// will return a number between rankings.MATCHES and
// rankings.MATCHES + 1 depending on how close of a match it is.
return getClosenessRanking(testString, stringToRank);
}
/**
* Generates an acronym for a string.
*
* @param {String} string the string for which to produce the acronym
* @returns {String} the acronym
*/
function getAcronym(string) {
let acronym = '';
const wordsInString = string.split(' ');
wordsInString.forEach(wordInString => {
const splitByHyphenWords = wordInString.split('-');
splitByHyphenWords.forEach(splitByHyphenWord => {
acronym += splitByHyphenWord.substr(0, 1);
});
});
return acronym;
}
/**
* Returns a score based on how spread apart the
* characters from the stringToRank are within the testString.
* A number close to rankings.MATCHES represents a loose match. A number close
* to rankings.MATCHES + 1 represents a tighter match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @returns {Number} the number between rankings.MATCHES and
* rankings.MATCHES + 1 for how well stringToRank matches testString
*/
function getClosenessRanking(testString, stringToRank) {
let matchingInOrderCharCount = 0;
let charNumber = 0;
function findMatchingCharacter(matchChar, string, index) {
for (let j = index, J = string.length; j < J; j++) {
const stringChar = string[j];
if (stringChar === matchChar) {
matchingInOrderCharCount += 1;
return j + 1;
}
}
return -1;
}
function getRanking(spread) {
const spreadPercentage = 1 / spread;
const inOrderPercentage = matchingInOrderCharCount / stringToRank.length;
const ranking = rankings.MATCHES + inOrderPercentage * spreadPercentage;
return ranking;
}
const firstIndex = findMatchingCharacter(stringToRank[0], testString, 0);
if (firstIndex < 0) {
return rankings.NO_MATCH;
}
charNumber = firstIndex;
for (let i = 1, I = stringToRank.length; i < I; i++) {
const matchChar = stringToRank[i];
charNumber = findMatchingCharacter(matchChar, testString, charNumber);
const found = charNumber > -1;
if (!found) {
return rankings.NO_MATCH;
}
}
const spread = charNumber - firstIndex;
return getRanking(spread);
}
/**
* Sorts items that have a rank, index, and keyIndex
* @param {Object} a - the first item to sort
* @param {Object} b - the second item to sort
* @return {Number} -1 if a should come first, 1 if b should come first, 0 if equal
*/
function sortRankedValues(a, b, baseSort) {
const aFirst = -1;
const bFirst = 1;
const {
rank: aRank,
keyIndex: aKeyIndex
} = a;
const {
rank: bRank,
keyIndex: bKeyIndex
} = b;
const same = aRank === bRank;
if (same) {
if (aKeyIndex === bKeyIndex) {
// use the base sort function as a tie-breaker
return baseSort(a, b);
} else {
return aKeyIndex < bKeyIndex ? aFirst : bFirst;
}
} else {
return aRank > bRank ? aFirst : bFirst;
}
}
/**
* Prepares value for comparison by stringifying it, removing diacritics (if specified)
* @param {String} value - the value to clean
* @param {Object} options - {keepDiacritics: whether to remove diacritics}
* @return {String} the prepared value
*/
function prepareValueForComparison(value, _ref4) {
let {
keepDiacritics
} = _ref4;
// value might not actually be a string at this point (we don't get to choose)
// so part of preparing the value for comparison is ensure that it is a string
value = `${value}`; // toString
if (!keepDiacritics) {
value = removeAccents$1.exports(value);
}
return value;
}
/**
* Gets value for key in item at arbitrarily nested keypath
* @param {Object} item - the item
* @param {Object|Function} key - the potentially nested keypath or property callback
* @return {Array} - an array containing the value(s) at the nested keypath
*/
function getItemValues(item, key) {
if (typeof key === 'object') {
key = key.key;
}
let value;
if (typeof key === 'function') {
value = key(item);
} else if (item == null) {
value = null;
} else if (Object.hasOwnProperty.call(item, key)) {
value = item[key];
} else if (key.includes('.')) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return getNestedValues(key, item);
} else {
value = null;
}
// because `value` can also be undefined
if (value == null) {
return [];
}
if (Array.isArray(value)) {
return value;
}
return [String(value)];
}
/**
* Given path: "foo.bar.baz"
* And item: {foo: {bar: {baz: 'buzz'}}}
* -> 'buzz'
* @param path a dot-separated set of keys
* @param item the item to get the value from
*/
function getNestedValues(path, item) {
const keys = path.split('.');
let values = [item];
for (let i = 0, I = keys.length; i < I; i++) {
const nestedKey = keys[i];
let nestedValues = [];
for (let j = 0, J = values.length; j < J; j++) {
const nestedItem = values[j];
if (nestedItem == null) continue;
if (Object.hasOwnProperty.call(nestedItem, nestedKey)) {
const nestedValue = nestedItem[nestedKey];
if (nestedValue != null) {
nestedValues.push(nestedValue);
}
} else if (nestedKey === '*') {
// ensure that values is an array
nestedValues = nestedValues.concat(nestedItem);
}
}
values = nestedValues;
}
if (Array.isArray(values[0])) {
// keep allowing the implicit wildcard for an array of strings at the end of
// the path; don't use `.flat()` because that's not available in node.js v10
const result = [];
return result.concat(...values);
}
// Based on our logic it should be an array of strings by now...
// assuming the user's path terminated in strings
return values;
}
/**
* Gets all the values for the given keys in the given item and returns an array of those values
* @param item - the item from which the values will be retrieved
* @param keys - the keys to use to retrieve the values
* @return objects with {itemValue, attributes}
*/
function getAllValuesToRank(item, keys) {
const allValues = [];
for (let j = 0, J = keys.length; j < J; j++) {
const key = keys[j];
const attributes = getKeyAttributes(key);
const itemValues = getItemValues(item, key);
for (let i = 0, I = itemValues.length; i < I; i++) {
allValues.push({
itemValue: itemValues[i],
attributes
});
}
}
return allValues;
}
const defaultKeyAttributes = {
maxRanking: Infinity,
minRanking: -Infinity
};
/**
* Gets all the attributes for the given key
* @param key - the key from which the attributes will be retrieved
* @return object containing the key's attributes
*/
function getKeyAttributes(key) {
if (typeof key === 'string') {
return defaultKeyAttributes;
}
return {
...defaultKeyAttributes,
...key
};
}
/*
eslint
no-continue: "off",
*/
exports.defaultBaseSortFn = defaultBaseSortFn;
exports.matchSorter = matchSorter;
exports.rankings = rankings;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=match-sorter.umd.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).matchSorter={})}(this,(function(e){"use strict";var n={exports:{}},t={"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Ấ":"A","Ắ":"A","Ẳ":"A","Ẵ":"A","Ặ":"A","Æ":"AE","Ầ":"A","Ằ":"A","Ȃ":"A","Ả":"A","Ạ":"A","Ẩ":"A","Ẫ":"A","Ậ":"A","Ç":"C","Ḉ":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ế":"E","Ḗ":"E","Ề":"E","Ḕ":"E","Ḝ":"E","Ȇ":"E","Ẻ":"E","Ẽ":"E","Ẹ":"E","Ể":"E","Ễ":"E","Ệ":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ḯ":"I","Ȋ":"I","Ỉ":"I","Ị":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ố":"O","Ṍ":"O","Ṓ":"O","Ȏ":"O","Ỏ":"O","Ọ":"O","Ổ":"O","Ỗ":"O","Ộ":"O","Ờ":"O","Ở":"O","Ỡ":"O","Ớ":"O","Ợ":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ủ":"U","Ụ":"U","Ử":"U","Ữ":"U","Ự":"U","Ý":"Y","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","ấ":"a","ắ":"a","ẳ":"a","ẵ":"a","ặ":"a","æ":"ae","ầ":"a","ằ":"a","ȃ":"a","ả":"a","ạ":"a","ẩ":"a","ẫ":"a","ậ":"a","ç":"c","ḉ":"c","è":"e","é":"e","ê":"e","ë":"e","ế":"e","ḗ":"e","ề":"e","ḕ":"e","ḝ":"e","ȇ":"e","ẻ":"e","ẽ":"e","ẹ":"e","ể":"e","ễ":"e","ệ":"e","ì":"i","í":"i","î":"i","ï":"i","ḯ":"i","ȋ":"i","ỉ":"i","ị":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ố":"o","ṍ":"o","ṓ":"o","ȏ":"o","ỏ":"o","ọ":"o","ổ":"o","ỗ":"o","ộ":"o","ờ":"o","ở":"o","ỡ":"o","ớ":"o","ợ":"o","ù":"u","ú":"u","û":"u","ü":"u","ủ":"u","ụ":"u","ử":"u","ữ":"u","ự":"u","ý":"y","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Ĉ":"C","ĉ":"c","Ċ":"C","ċ":"c","Č":"C","č":"c","C̆":"C","c̆":"c","Ď":"D","ď":"d","Đ":"D","đ":"d","Ē":"E","ē":"e","Ĕ":"E","ĕ":"e","Ė":"E","ė":"e","Ę":"E","ę":"e","Ě":"E","ě":"e","Ĝ":"G","Ǵ":"G","ĝ":"g","ǵ":"g","Ğ":"G","ğ":"g","Ġ":"G","ġ":"g","Ģ":"G","ģ":"g","Ĥ":"H","ĥ":"h","Ħ":"H","ħ":"h","Ḫ":"H","ḫ":"h","Ĩ":"I","ĩ":"i","Ī":"I","ī":"i","Ĭ":"I","ĭ":"i","Į":"I","į":"i","İ":"I","ı":"i","IJ":"IJ","ij":"ij","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","Ḱ":"K","ḱ":"k","K̆":"K","k̆":"k","Ĺ":"L","ĺ":"l","Ļ":"L","ļ":"l","Ľ":"L","ľ":"l","Ŀ":"L","ŀ":"l","Ł":"l","ł":"l","Ḿ":"M","ḿ":"m","M̆":"M","m̆":"m","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","ʼn":"n","N̆":"N","n̆":"n","Ō":"O","ō":"o","Ŏ":"O","ŏ":"o","Ő":"O","ő":"o","Œ":"OE","œ":"oe","P̆":"P","p̆":"p","Ŕ":"R","ŕ":"r","Ŗ":"R","ŗ":"r","Ř":"R","ř":"r","R̆":"R","r̆":"r","Ȓ":"R","ȓ":"r","Ś":"S","ś":"s","Ŝ":"S","ŝ":"s","Ş":"S","Ș":"S","ș":"s","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","ț":"t","Ț":"T","Ť":"T","ť":"t","Ŧ":"T","ŧ":"t","T̆":"T","t̆":"t","Ũ":"U","ũ":"u","Ū":"U","ū":"u","Ŭ":"U","ŭ":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ȗ":"U","ȗ":"u","V̆":"V","v̆":"v","Ŵ":"W","ŵ":"w","Ẃ":"W","ẃ":"w","X̆":"X","x̆":"x","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Y̆":"Y","y̆":"y","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","ſ":"s","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Ǎ":"A","ǎ":"a","Ǐ":"I","ǐ":"i","Ǒ":"O","ǒ":"o","Ǔ":"U","ǔ":"u","Ǖ":"U","ǖ":"u","Ǘ":"U","ǘ":"u","Ǚ":"U","ǚ":"u","Ǜ":"U","ǜ":"u","Ứ":"U","ứ":"u","Ṹ":"U","ṹ":"u","Ǻ":"A","ǻ":"a","Ǽ":"AE","ǽ":"ae","Ǿ":"O","ǿ":"o","Þ":"TH","þ":"th","Ṕ":"P","ṕ":"p","Ṥ":"S","ṥ":"s","X́":"X","x́":"x","Ѓ":"Г","ѓ":"г","Ќ":"К","ќ":"к","A̋":"A","a̋":"a","E̋":"E","e̋":"e","I̋":"I","i̋":"i","Ǹ":"N","ǹ":"n","Ồ":"O","ồ":"o","Ṑ":"O","ṑ":"o","Ừ":"U","ừ":"u","Ẁ":"W","ẁ":"w","Ỳ":"Y","ỳ":"y","Ȁ":"A","ȁ":"a","Ȅ":"E","ȅ":"e","Ȉ":"I","ȉ":"i","Ȍ":"O","ȍ":"o","Ȑ":"R","ȑ":"r","Ȕ":"U","ȕ":"u","B̌":"B","b̌":"b","Č̣":"C","č̣":"c","Ê̌":"E","ê̌":"e","F̌":"F","f̌":"f","Ǧ":"G","ǧ":"g","Ȟ":"H","ȟ":"h","J̌":"J","ǰ":"j","Ǩ":"K","ǩ":"k","M̌":"M","m̌":"m","P̌":"P","p̌":"p","Q̌":"Q","q̌":"q","Ř̩":"R","ř̩":"r","Ṧ":"S","ṧ":"s","V̌":"V","v̌":"v","W̌":"W","w̌":"w","X̌":"X","x̌":"x","Y̌":"Y","y̌":"y","A̧":"A","a̧":"a","B̧":"B","b̧":"b","Ḑ":"D","ḑ":"d","Ȩ":"E","ȩ":"e","Ɛ̧":"E","ɛ̧":"e","Ḩ":"H","ḩ":"h","I̧":"I","i̧":"i","Ɨ̧":"I","ɨ̧":"i","M̧":"M","m̧":"m","O̧":"O","o̧":"o","Q̧":"Q","q̧":"q","U̧":"U","u̧":"u","X̧":"X","x̧":"x","Z̧":"Z","z̧":"z","й":"и","Й":"И","ё":"е","Ё":"Е"},r=Object.keys(t).join("|"),o=new RegExp(r,"g"),u=new RegExp(r,"");function i(e){return t[e]}var a=function(e){return e.replace(o,i)};n.exports=a,n.exports.has=function(e){return!!e.match(u)},n.exports.remove=a;
/**
* @name match-sorter
* @license MIT license.
* @copyright (c) 2020 Kent C. Dodds
* @author Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)
*/
const l={CASE_SENSITIVE_EQUAL:7,EQUAL:6,STARTS_WITH:5,WORD_STARTS_WITH:4,CONTAINS:3,ACRONYM:2,MATCHES:1,NO_MATCH:0},s=(e,n)=>String(e.rankedValue).localeCompare(String(n.rankedValue));function c(e,n,t){void 0===t&&(t={});const{keys:r,threshold:o=l.MATCHES,baseSort:u=s,sorter:i=(e=>e.sort(((e,n)=>f(e,n,u))))}=t,a=e.reduce((function(e,u,i){const a=function(e,n,t,r){if(!n){return{rankedValue:e,rank:A(e,t,r),keyIndex:-1,keyThreshold:r.threshold}}const o=function(e,n){const t=[];for(let r=0,o=n.length;r<o;r++){const o=n[r],u=d(o),i=h(e,o);for(let e=0,n=i.length;e<n;e++)t.push({itemValue:i[e],attributes:u})}return t}(e,n);return o.reduce(((e,n,o)=>{let{rank:u,rankedValue:i,keyIndex:a,keyThreshold:s}=e,{itemValue:c,attributes:f}=n,O=A(c,t,r),h=i;const{minRanking:E,maxRanking:d,threshold:T}=f;return O<E&&O>=l.MATCHES?O=E:O>d&&(O=d),O>u&&(u=O,a=o,s=T,h=c),{rankedValue:h,rank:u,keyIndex:a,keyThreshold:s}}),{rankedValue:e,rank:l.NO_MATCH,keyIndex:-1,keyThreshold:r.threshold})}(u,r,n,t),{rank:s,keyThreshold:c=o}=a;return s>=c&&e.push({...a,item:u,index:i}),e}),[]);return i(a).map((e=>{let{item:n}=e;return n}))}function A(e,n,t){return e=O(e,t),(n=O(n,t)).length>e.length?l.NO_MATCH:e===n?l.CASE_SENSITIVE_EQUAL:(e=e.toLowerCase())===(n=n.toLowerCase())?l.EQUAL:e.startsWith(n)?l.STARTS_WITH:e.includes(` ${n}`)?l.WORD_STARTS_WITH:e.includes(n)?l.CONTAINS:1===n.length?l.NO_MATCH:function(e){let n="";return e.split(" ").forEach((e=>{e.split("-").forEach((e=>{n+=e.substr(0,1)}))})),n}(e).includes(n)?l.ACRONYM:function(e,n){let t=0,r=0;function o(e,n,r){for(let o=r,u=n.length;o<u;o++){if(n[o]===e)return t+=1,o+1}return-1}function u(e){const r=1/e,o=t/n.length;return l.MATCHES+o*r}const i=o(n[0],e,0);if(i<0)return l.NO_MATCH;r=i;for(let t=1,u=n.length;t<u;t++){r=o(n[t],e,r);if(!(r>-1))return l.NO_MATCH}return u(r-i)}(e,n)}function f(e,n,t){const{rank:r,keyIndex:o}=e,{rank:u,keyIndex:i}=n;return r===u?o===i?t(e,n):o<i?-1:1:r>u?-1:1}function O(e,t){let{keepDiacritics:r}=t;return e=`${e}`,r||(e=n.exports(e)),e}function h(e,n){let t;if("object"==typeof n&&(n=n.key),"function"==typeof n)t=n(e);else if(null==e)t=null;else if(Object.hasOwnProperty.call(e,n))t=e[n];else{if(n.includes("."))return function(e,n){const t=e.split(".");let r=[n];for(let e=0,n=t.length;e<n;e++){const n=t[e];let o=[];for(let e=0,t=r.length;e<t;e++){const t=r[e];if(null!=t)if(Object.hasOwnProperty.call(t,n)){const e=t[n];null!=e&&o.push(e)}else"*"===n&&(o=o.concat(t))}r=o}if(Array.isArray(r[0])){return[].concat(...r)}return r}(n,e);t=null}return null==t?[]:Array.isArray(t)?t:[String(t)]}c.rankings=l;const E={maxRanking:1/0,minRanking:-1/0};function d(e){return"string"==typeof e?E:{...E,...e}}e.defaultBaseSortFn=s,e.matchSorter=c,e.rankings=l,Object.defineProperty(e,"__esModule",{value:!0})}));
//# sourceMappingURL=match-sorter.umd.min.js.map

File diff suppressed because one or more lines are too long

59
frontend/node_modules/match-sorter/package.json generated vendored Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "match-sorter",
"version": "6.3.4",
"description": "Simple, expected, and deterministic best-match sorting of an array in JavaScript",
"main": "dist/match-sorter.cjs.js",
"module": "dist/match-sorter.esm.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "kcd-scripts build --bundle --environment BUILD_NAME:matchSorter",
"lint": "kcd-scripts lint",
"setup": "npm install && npm run validate -s",
"test": "kcd-scripts test",
"typecheck": "kcd-scripts typecheck",
"test:update": "npm test -- --updateSnapshot --coverage",
"validate": "kcd-scripts validate"
},
"files": [
"dist"
],
"keywords": [
"autocomplete",
"filter list",
"sort",
"advanced sort",
"user intuitive sort"
],
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.8",
"remove-accents": "0.5.0"
},
"devDependencies": {
"@types/jest": "^26.0.15",
"kcd-scripts": "^15",
"typescript": "^5.3"
},
"eslintConfig": {
"extends": [
"./node_modules/kcd-scripts/eslint.js"
],
"rules": {
"prefer-object-has-own": "off"
}
},
"eslintIgnore": [
"node_modules",
"coverage",
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/kentcdodds/match-sorter"
},
"bugs": {
"url": "https://github.com/kentcdodds/match-sorter/issues"
},
"homepage": "https://github.com/kentcdodds/match-sorter#readme"
}