Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ matchSorter(list, 'y') // ['yo', 'hey']
matchSorter(list, 'z') // []
```

If you need the ranking metadata that `match-sorter` computes internally, use
`matchSorterWithRankInfo`:

```javascript
import {matchSorterWithRankInfo} from 'match-sorter'

const rankedResults = matchSorterWithRankInfo(list, 'h')
// [
// {
// item: 'hello',
// rankedValue: 'hello',
// rank: 5,
// keyIndex: -1,
// keyThreshold: undefined,
// index: 2,
// },
// // ...
// ]
```

## Advanced options

### keys: `[string]`
Expand Down Expand Up @@ -168,9 +188,9 @@ 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'}}]},
{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'}}]}]
Expand Down Expand Up @@ -355,13 +375,15 @@ _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()})
Expand Down
71 changes: 70 additions & 1 deletion src/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import {matchSorter, rankings, MatchSorterOptions} from '../'
import {
matchSorter,
matchSorterWithRankInfo,
rankings,
MatchSorterOptions,
type RankedItem,
} from '../'

type TestCase = {
input: [Array<unknown>, string, MatchSorterOptions?]
Expand Down Expand Up @@ -663,6 +669,69 @@ for (const [
}
}

test('can return ranked items with ranking metadata attached', () => {
const rankedResults: Array<RankedItem<{tea: string; alias: string}>> =
matchSorterWithRankInfo(
[
{tea: 'Earl Grey', alias: 'A'},
{tea: 'Assam', alias: 'B'},
{tea: 'Black', alias: 'C'},
],
'A',
{
keys: ['tea', {maxRanking: rankings.STARTS_WITH, key: 'alias'}],
},
)

expect(rankedResults).toEqual([
{
item: {tea: 'Assam', alias: 'B'},
rankedValue: 'Assam',
rank: rankings.STARTS_WITH,
keyIndex: 0,
keyThreshold: undefined,
index: 1,
},
{
item: {tea: 'Earl Grey', alias: 'A'},
rankedValue: 'A',
rank: rankings.STARTS_WITH,
keyIndex: 1,
keyThreshold: undefined,
index: 0,
},
{
item: {tea: 'Black', alias: 'C'},
rankedValue: 'Black',
rank: rankings.CONTAINS,
keyIndex: 0,
keyThreshold: undefined,
index: 2,
},
])
})

test('can return ranked items without passing options', () => {
expect(matchSorterWithRankInfo(['hello', 'hey', 'sup'], 'h')).toEqual([
{
item: 'hello',
rankedValue: 'hello',
rank: rankings.STARTS_WITH,
keyIndex: -1,
keyThreshold: undefined,
index: 0,
},
{
item: 'hey',
rankedValue: 'hey',
rank: rankings.STARTS_WITH,
keyIndex: -1,
keyThreshold: undefined,
index: 1,
},
])
})

/*
eslint
jest/prefer-each: "off",
Expand Down
35 changes: 33 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,29 @@ function matchSorter<ItemType = string>(
value: string,
options: MatchSorterOptions<ItemType> = {},
): Array<ItemType> {
return getRankedItems(items, value, options).map(({item}) => item)
}

/**
* Takes an array of items and a value and returns ranked items with metadata attached
* @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 ranked array
*/
function matchSorterWithRankInfo<ItemType = string>(
items: ReadonlyArray<ItemType>,
value: string,
options: MatchSorterOptions<ItemType> = {},
): Array<RankedItem<ItemType>> {
return getRankedItems(items, value, options)
}

function getRankedItems<ItemType = string>(
items: ReadonlyArray<ItemType>,
value: string,
options: MatchSorterOptions<ItemType>,
): Array<RankedItem<ItemType>> {
const {
keys,
threshold = rankings.MATCHES,
Expand All @@ -92,7 +115,7 @@ function matchSorter<ItemType = string>(
matchedItems.sort((a, b) => sortRankedValues(a, b, baseSort)),
} = options
const matchedItems = items.reduce(reduceItemsToRanked, [])
return sorter(matchedItems).map(({item}) => item)
return sorter(matchedItems)

function reduceItemsToRanked(
matches: Array<RankedItem<ItemType>>,
Expand All @@ -109,6 +132,7 @@ function matchSorter<ItemType = string>(
}

matchSorter.rankings = rankings
matchSorterWithRankInfo.rankings = rankings

/**
* Gets the highest ranking for value for the given item based on its values for the given keys
Expand Down Expand Up @@ -520,14 +544,21 @@ function getKeyAttributes<ItemType>(key: KeyOption<ItemType>): KeyAttributes {
return {...defaultKeyAttributes, ...key}
}

export {matchSorter, rankings, defaultBaseSortFn, getItemValues}
export {
matchSorter,
matchSorterWithRankInfo,
rankings,
defaultBaseSortFn,
getItemValues,
}

export type {
MatchSorterOptions,
KeyAttributesOptions,
KeyOption,
KeyAttributes,
RankingInfo,
RankedItem,
ValueGetterKey,
}

Expand Down
Loading