@oscarpalmer/atoms 0.193.0 → 0.193.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/package.json +3 -4
  2. package/src/array/difference.ts +0 -73
  3. package/src/array/exists.ts +0 -93
  4. package/src/array/filter.ts +0 -182
  5. package/src/array/find.ts +0 -183
  6. package/src/array/first.ts +0 -196
  7. package/src/array/flatten.ts +0 -20
  8. package/src/array/from.ts +0 -129
  9. package/src/array/get.ts +0 -131
  10. package/src/array/group-by.ts +0 -369
  11. package/src/array/index.ts +0 -26
  12. package/src/array/insert.ts +0 -52
  13. package/src/array/intersection.ts +0 -72
  14. package/src/array/last.ts +0 -194
  15. package/src/array/match.ts +0 -436
  16. package/src/array/move.ts +0 -315
  17. package/src/array/partition.ts +0 -91
  18. package/src/array/push.ts +0 -23
  19. package/src/array/reverse.ts +0 -33
  20. package/src/array/select.ts +0 -260
  21. package/src/array/single.ts +0 -93
  22. package/src/array/slice.ts +0 -335
  23. package/src/array/sort.ts +0 -607
  24. package/src/array/splice.ts +0 -106
  25. package/src/array/swap.ts +0 -322
  26. package/src/array/to-map.ts +0 -371
  27. package/src/array/to-record.ts +0 -351
  28. package/src/array/to-set.ts +0 -80
  29. package/src/array/toggle.ts +0 -79
  30. package/src/array/union.ts +0 -72
  31. package/src/array/unique.ts +0 -67
  32. package/src/array/update.ts +0 -75
  33. package/src/beacon.ts +0 -378
  34. package/src/color/constants.ts +0 -137
  35. package/src/color/index.ts +0 -83
  36. package/src/color/instance.ts +0 -162
  37. package/src/color/misc/alpha.ts +0 -56
  38. package/src/color/misc/format.ts +0 -29
  39. package/src/color/misc/get.ts +0 -210
  40. package/src/color/misc/is.ts +0 -221
  41. package/src/color/misc/state.ts +0 -243
  42. package/src/color/models.ts +0 -389
  43. package/src/color/space/hex.ts +0 -184
  44. package/src/color/space/hsl.ts +0 -174
  45. package/src/color/space/hwb.ts +0 -153
  46. package/src/color/space/rgb.ts +0 -240
  47. package/src/function/assert.ts +0 -174
  48. package/src/function/index.ts +0 -2
  49. package/src/function/limit.ts +0 -83
  50. package/src/function/memoize.ts +0 -202
  51. package/src/function/once.ts +0 -200
  52. package/src/function/retry.ts +0 -175
  53. package/src/function/work.ts +0 -1007
  54. package/src/herald.ts +0 -216
  55. package/src/index.ts +0 -68
  56. package/src/internal/array/callbacks.ts +0 -46
  57. package/src/internal/array/chunk.ts +0 -51
  58. package/src/internal/array/compact.ts +0 -56
  59. package/src/internal/array/find.ts +0 -200
  60. package/src/internal/array/group.ts +0 -42
  61. package/src/internal/array/index-of.ts +0 -182
  62. package/src/internal/array/insert.ts +0 -80
  63. package/src/internal/array/overlap.ts +0 -35
  64. package/src/internal/array/sets.ts +0 -74
  65. package/src/internal/array/shuffle.ts +0 -33
  66. package/src/internal/array/update.ts +0 -52
  67. package/src/internal/function/misc.ts +0 -8
  68. package/src/internal/function/timer.ts +0 -168
  69. package/src/internal/is.ts +0 -244
  70. package/src/internal/math/aggregate.ts +0 -164
  71. package/src/internal/math/misc.ts +0 -28
  72. package/src/internal/number.ts +0 -129
  73. package/src/internal/random.ts +0 -49
  74. package/src/internal/result.ts +0 -92
  75. package/src/internal/sized.ts +0 -25
  76. package/src/internal/string.ts +0 -128
  77. package/src/internal/subscription.ts +0 -264
  78. package/src/internal/value/compare.ts +0 -170
  79. package/src/internal/value/equal.ts +0 -465
  80. package/src/internal/value/get.ts +0 -57
  81. package/src/internal/value/handlers.ts +0 -95
  82. package/src/internal/value/has.ts +0 -118
  83. package/src/internal/value/misc.ts +0 -125
  84. package/src/internal/value/partial.ts +0 -46
  85. package/src/internal/value/set.ts +0 -132
  86. package/src/is.ts +0 -182
  87. package/src/logger.ts +0 -230
  88. package/src/math.ts +0 -281
  89. package/src/models.ts +0 -286
  90. package/src/number.ts +0 -1
  91. package/src/promise/delay.ts +0 -62
  92. package/src/promise/helpers.ts +0 -96
  93. package/src/promise/index.ts +0 -300
  94. package/src/promise/misc.ts +0 -96
  95. package/src/promise/models.ts +0 -195
  96. package/src/promise/timed.ts +0 -91
  97. package/src/query.ts +0 -147
  98. package/src/queue.ts +0 -814
  99. package/src/random.ts +0 -125
  100. package/src/result/index.ts +0 -105
  101. package/src/result/match.ts +0 -124
  102. package/src/result/misc.ts +0 -129
  103. package/src/result/models.ts +0 -83
  104. package/src/result/work/flow.ts +0 -488
  105. package/src/result/work/pipe.ts +0 -496
  106. package/src/sized/map.ts +0 -110
  107. package/src/sized/set.ts +0 -107
  108. package/src/string/case.ts +0 -236
  109. package/src/string/fuzzy.ts +0 -444
  110. package/src/string/index.ts +0 -199
  111. package/src/string/match.ts +0 -80
  112. package/src/string/normalize.ts +0 -205
  113. package/src/string/template.ts +0 -161
  114. package/src/value/clone.ts +0 -319
  115. package/src/value/collection.ts +0 -63
  116. package/src/value/diff.ts +0 -232
  117. package/src/value/freeze.ts +0 -322
  118. package/src/value/handle.ts +0 -3
  119. package/src/value/index.ts +0 -5
  120. package/src/value/merge.ts +0 -278
  121. package/src/value/omit.ts +0 -20
  122. package/src/value/pick.ts +0 -20
  123. package/src/value/shake.ts +0 -45
  124. package/src/value/smush.ts +0 -89
  125. package/src/value/transform.ts +0 -153
  126. package/src/value/unsmush.ts +0 -84
@@ -1,236 +0,0 @@
1
- import {memoize, type Memoized} from '../function/memoize';
2
- import {join, words} from '../internal/string';
3
-
4
- // #region Types
5
-
6
- type Case = 'camel' | 'kebab' | 'pascal' | 'snake';
7
-
8
- type Options = {
9
- capitalizeAny: boolean;
10
- capitalizeFirst: boolean;
11
- type: Case;
12
- };
13
-
14
- // #endregion
15
-
16
- // #region Functions
17
-
18
- /**
19
- * Convert a string to camel case _(thisIsCamelCase)_
20
- *
21
- * @param value String to convert
22
- * @returns Camel-cased string
23
- */
24
- export function camelCase(value: string): string {
25
- return toCase(CASE_CAMEL, value, true, false);
26
- }
27
-
28
- /**
29
- * Capitalize the first letter of a string _(and lowercase the rest)_
30
- *
31
- * @param value String to capitalize
32
- * @returns Capitalized string
33
- */
34
- export function capitalize(value: string): string {
35
- if (typeof value !== 'string' || value.length === 0) {
36
- return '';
37
- }
38
-
39
- memoizedCapitalize ??= memoize(v =>
40
- v.length === 1
41
- ? v.toLocaleUpperCase()
42
- : `${v.charAt(0).toLocaleUpperCase()}${v.slice(1).toLocaleLowerCase()}`,
43
- );
44
-
45
- return memoizedCapitalize.run(value);
46
- }
47
-
48
- /**
49
- * Convert a string to kebab case _(this-is-kebab-case)_
50
- *
51
- * @param value String to convert
52
- * @returns Kebab-cased string
53
- */
54
- export function kebabCase(value: string): string {
55
- return toCase(CASE_KEBAB, value, false, false);
56
- }
57
-
58
- /**
59
- * Convert a string to lower case
60
- *
61
- * @param value String to convert
62
- * @returns Lower-cased string
63
- */
64
- export function lowerCase(value: string): string {
65
- if (typeof value !== 'string') {
66
- return '';
67
- }
68
-
69
- memoizedLowerCase ??= memoize(v => v.toLocaleLowerCase());
70
-
71
- return memoizedLowerCase.run(value);
72
- }
73
-
74
- /**
75
- * Convert a string to pascal case _(ThisIsPascalCase)_
76
- *
77
- * @param value String to convert
78
- * @returns Pascal-cased string
79
- */
80
- export function pascalCase(value: string): string {
81
- return toCase(CASE_PASCAL, value, true, true);
82
- }
83
-
84
- /**
85
- * Convert a string to snake case _(this_is_snake_case)_
86
- *
87
- * @param value String to convert
88
- * @returns Snake-cased string
89
- */
90
- export function snakeCase(value: string): string {
91
- return toCase(CASE_SNAKE, value, false, false);
92
- }
93
-
94
- /**
95
- * Convert a string to title case _(Capitalizing Every Word)_
96
- *
97
- * @param value String to convert
98
- * @returns Title-cased string
99
- */
100
- export function titleCase(value: string): string {
101
- if (typeof value !== 'string' || value.length === 0) {
102
- return '';
103
- }
104
-
105
- memoizedTitleCase ??= memoize(v =>
106
- v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), ' '),
107
- );
108
-
109
- return memoizedTitleCase.run(value);
110
- }
111
-
112
- function toCase(
113
- type: Case,
114
- value: string,
115
- capitalizeAny: boolean,
116
- capitalizeFirst: boolean,
117
- ): string {
118
- caseMemoizers[type] ??= memoize(toCaseCallback.bind({type, capitalizeAny, capitalizeFirst}));
119
-
120
- return caseMemoizers[type].run(value);
121
- }
122
-
123
- function toCaseCallback(this: Options, value: string): string {
124
- if (typeof value !== 'string') {
125
- return '';
126
- }
127
-
128
- if (value.length < 1) {
129
- return value;
130
- }
131
-
132
- const {capitalizeAny, capitalizeFirst, type} = this;
133
-
134
- const parts = words(value);
135
- const partsLength = parts.length;
136
-
137
- const cased: string[] = [];
138
-
139
- for (let partIndex = 0; partIndex < partsLength; partIndex += 1) {
140
- const part = parts[partIndex];
141
-
142
- const acronymParts = part.replace(EXPRESSION_ACRONYM, (full, one, two, three) =>
143
- three === S ? full : `${one}-${two}${three}`,
144
- );
145
-
146
- const camelCaseParts = acronymParts.replace(EXPRESSION_CAMEL_CASE, REPLACEMENT_CAMEL_CASE);
147
-
148
- const items = camelCaseParts.split('-');
149
- const itemsLength = items.length;
150
-
151
- const partResult: string[] = [];
152
-
153
- let itemCount = 0;
154
-
155
- for (let itemIndex = 0; itemIndex < itemsLength; itemIndex += 1) {
156
- const item = items[itemIndex];
157
-
158
- if (item.length === 0) {
159
- continue;
160
- }
161
-
162
- if (!capitalizeAny || (itemCount === 0 && partIndex === 0 && !capitalizeFirst)) {
163
- partResult.push(item.toLocaleLowerCase());
164
- } else {
165
- partResult.push(capitalize(item));
166
- }
167
-
168
- itemCount += 1;
169
- }
170
-
171
- cased.push(join(partResult, delimiters[type]));
172
- }
173
-
174
- return join(cased, delimiters[type]);
175
- }
176
-
177
- /**
178
- * Convert a string to upper case
179
- *
180
- * @param value String to convert
181
- * @returns Upper-cased string
182
- */
183
- export function upperCase(value: string): string {
184
- if (typeof value !== 'string' || value.length === 0) {
185
- return '';
186
- }
187
-
188
- memoizedUpperCase ??= memoize(v => v.toLocaleUpperCase());
189
-
190
- return memoizedUpperCase.run(value);
191
- }
192
-
193
- // #endregion
194
-
195
- // #region Variables
196
-
197
- const CASE_CAMEL: Case = 'camel';
198
-
199
- const CASE_KEBAB: Case = 'kebab';
200
-
201
- const CASE_PASCAL: Case = 'pascal';
202
-
203
- const CASE_SNAKE: Case = 'snake';
204
-
205
- const DELIMTER_EMPTY = '';
206
-
207
- const DELIMITER_HYPHEN = '-';
208
-
209
- const DELIMITER_UNDERSCORE = '_';
210
-
211
- const EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
212
-
213
- const EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
214
-
215
- const REPLACEMENT_CAMEL_CASE = '$1-$2';
216
-
217
- const S = 's';
218
-
219
- const caseMemoizers: Partial<Record<string, Memoized<typeof toCaseCallback>>> = {};
220
-
221
- const delimiters: Record<Case, string> = {
222
- [CASE_CAMEL]: DELIMTER_EMPTY,
223
- [CASE_KEBAB]: DELIMITER_HYPHEN,
224
- [CASE_PASCAL]: DELIMTER_EMPTY,
225
- [CASE_SNAKE]: DELIMITER_UNDERSCORE,
226
- };
227
-
228
- let memoizedCapitalize: Memoized<(value: string) => string>;
229
-
230
- let memoizedLowerCase: Memoized<(value: string) => string>;
231
-
232
- let memoizedTitleCase: Memoized<(value: string) => string>;
233
-
234
- let memoizedUpperCase: Memoized<(value: string) => string>;
235
-
236
- // #endregion
@@ -1,444 +0,0 @@
1
- import {isPlainObject} from '../internal/is';
2
- import {getString} from '../internal/string';
3
- import type {PlainObject, RequiredKeys} from '../models';
4
- import {lowerCase} from './case';
5
- import {includes} from './match';
6
-
7
- // #region Types
8
-
9
- /**
10
- * Fuzzy searcher for an array of items
11
- */
12
- export type Fuzzy<Item> = {
13
- /**
14
- * Get items currently being searched through
15
- *
16
- * @returns Original items
17
- */
18
- get items(): Item[];
19
-
20
- /**
21
- * Set new items to search through
22
- *
23
- * @param items New items to search through
24
- */
25
- set items(items: Item[]);
26
-
27
- /**
28
- * Get strings currently being searched through _(the stringified version of `items`)_
29
- *
30
- * @returns Stringified items
31
- */
32
- get strings(): string[];
33
-
34
- /**
35
- * Search for items matching the given value
36
- *
37
- * @param value Value to search for
38
- * @param options Search options
39
- * @returns Search results, with exact matches _(ordered)_ and similar matches _(ordered by relevance)_
40
- */
41
- search(value: string, options?: FuzzyOptions): FuzzyResult<Item>;
42
-
43
- /**
44
- * Search for items matching the given value
45
- *
46
- * @param value Value to search for
47
- * @param limit Maximum number of combined items to return in `exact` and `similar`
48
- * @returns Search results, with exact matches _(ordered)_ and similar matches _(ordered by relevance)_
49
- */
50
- search(value: string, limit: number): FuzzyResult<Item>;
51
- };
52
-
53
- export type FuzzyConfiguration<Item> = {
54
- /**
55
- * Handler to stringify items
56
- *
57
- * - May be a function that takes an item and returns a string, or if items are plain objects, a key of the item to use to grab a string value from
58
- * - Defaults to `getString`
59
- */
60
- handler?: (item: Item) => string;
61
- } & (Item extends PlainObject
62
- ? {
63
- /**
64
- * Key to use to stringify items
65
- *
66
- * _Prioritized over `handler`_
67
- */
68
- key?: keyof Item;
69
- }
70
- : {}) &
71
- FuzzyOptions;
72
-
73
- type FuzzyItem<Item> = {
74
- item: Item;
75
- haystack: string;
76
- };
77
-
78
- export type FuzzyOptions = {
79
- /**
80
- * Maximum number of combined items to return in `exact` and `similar` _(defaults to all matches)_
81
- */
82
- limit?: number;
83
- /**
84
- * Maximum score difference between the best and worst similar matches included in results
85
- *
86
- * - Higher values cast a wider net
87
- * - Defaults to `5`
88
- */
89
- tolerance?: number;
90
- };
91
-
92
- /**
93
- * Search results from a fuzzy search, with exact and similar matches
94
- */
95
- export type FuzzyResult<Item> = {
96
- /**
97
- * Ordered items that exactly match the search value
98
- */
99
- exact: Item[];
100
- /**
101
- * Ordered items that are similar to the search value, ranked by relevance
102
- */
103
- similar: Item[];
104
- };
105
-
106
- /**
107
- * Options for fuzzy searching
108
- */
109
- export type FuzzySearchOptions = FuzzyOptions;
110
-
111
- type FuzzyState<Item> = {
112
- handler: (item: Item) => string;
113
- items: Item[];
114
- limit?: number;
115
- strings: string[];
116
- tolerance: number;
117
- };
118
-
119
- // #endregion
120
-
121
- // #region Functions
122
-
123
- function getHandler<Item>(input: unknown): (item: Item) => string {
124
- if (input == null || input === getString) {
125
- return getString;
126
- }
127
-
128
- switch (typeof input) {
129
- case 'function':
130
- return input as (item: Item) => string;
131
-
132
- case 'string':
133
- return (item: Item) => (item as PlainObject)[input] as string;
134
-
135
- default: {
136
- if (isPlainObject(input)) {
137
- return getHandler(
138
- (input as FuzzyConfiguration<PlainObject>).key ??
139
- (input as FuzzyConfiguration<Item>).handler,
140
- );
141
- }
142
-
143
- throw new TypeError(MESSAGE_HANDLER);
144
- }
145
- }
146
- }
147
-
148
- function getItems<Item>(items: FuzzyItem<Item>[]): Item[] {
149
- return items
150
- .sort((first, second) => first.haystack.localeCompare(second.haystack))
151
- .map(({item}) => item);
152
- }
153
-
154
- function getFuzzyOptions<Item>(
155
- input: unknown,
156
- state?: FuzzyState<Item>,
157
- ): RequiredKeys<FuzzyOptions, 'tolerance'> {
158
- const options: FuzzyOptions = isPlainObject(input) ? input : {};
159
-
160
- const limit = typeof input === 'number' ? input : options.limit;
161
-
162
- if (typeof limit === 'number' && !Number.isNaN(limit) && limit >= 1) {
163
- options.limit = Math.floor(limit);
164
- } else {
165
- options.limit = state?.limit;
166
- }
167
-
168
- options.tolerance = getTolerance(options.tolerance, state);
169
-
170
- return options as RequiredKeys<FuzzyOptions, 'tolerance'>;
171
- }
172
-
173
- function getFuzzyState<Item>(items: Item[], input: unknown): FuzzyState<Item> {
174
- const handler = getHandler(input);
175
- const options = getFuzzyOptions(input);
176
-
177
- return {
178
- handler,
179
- items: items.slice(),
180
- limit: options.limit,
181
- strings: items.map(handler),
182
- tolerance: options.tolerance,
183
- };
184
- }
185
-
186
- function getTolerance<Item>(input: unknown, state?: FuzzyState<Item>): number {
187
- if (typeof input === 'number' && !Number.isNaN(input)) {
188
- return input < 0 ? 0 : Math.floor(input);
189
- }
190
-
191
- return state?.tolerance ?? PROXIMITY_THRESHOLD;
192
- }
193
-
194
- /**
195
- * Create a fuzzy searcher for an array of items
196
- *
197
- * @param items Items to search through
198
- * @param key Key to use to stringify items
199
- * @returns Fuzzy searcher
200
- */
201
- export function fuzzy<Item extends PlainObject, ItemKey extends keyof Item>(
202
- items: Item[],
203
- key?: ItemKey,
204
- ): Fuzzy<Item>;
205
-
206
- /**
207
- * Create a fuzzy searcher for an array of items
208
- *
209
- * @param items Items to search through
210
- * @param handler Handler to stringify items
211
- * @returns Fuzzy searcher
212
- */
213
- export function fuzzy<Item>(items: Item[], handler?: (item: Item) => string): Fuzzy<Item>;
214
-
215
- /**
216
- * Create a fuzzy searcher for an array of items
217
- *
218
- * @param items Items to search through
219
- * @param configuration Fuzzy configuration
220
- * @returns Fuzzy searcher
221
- */
222
- export function fuzzy<Item>(items: Item[], configuration?: FuzzyConfiguration<Item>): Fuzzy<Item>;
223
-
224
- export function fuzzy(items: unknown[], configuration?: unknown): Fuzzy<unknown> {
225
- if (!Array.isArray(items)) {
226
- throw new TypeError(MESSAGE_ARRAY);
227
- }
228
-
229
- const state = getFuzzyState(items, configuration);
230
-
231
- const instance = {
232
- search: (value: string, options?: number | FuzzyOptions) =>
233
- search(
234
- state.items,
235
- state.strings,
236
- value,
237
- options == null ? state : getFuzzyOptions(options, state),
238
- ),
239
- };
240
-
241
- Object.defineProperties(instance, {
242
- items: {
243
- get: () => state.items.slice(),
244
- set: (items: unknown[]) => {
245
- if (!Array.isArray(items)) {
246
- throw new TypeError(MESSAGE_ARRAY);
247
- }
248
-
249
- state.items = items.slice();
250
- state.strings = items.map(state.handler);
251
- },
252
- },
253
- strings: {
254
- get: () => state.strings.slice(),
255
- },
256
- });
257
-
258
- return Object.freeze(instance) as Fuzzy<unknown>;
259
- }
260
-
261
- fuzzy.match = fuzzyMatch;
262
-
263
- /**
264
- * Does the needle match the haystack in a fuzzy way?
265
- *
266
- * @param haystack Haystack to search through
267
- * @param needle Needle to search for
268
- * @returns `true` if the needle matches the haystack in a fuzzy way, otherwise `false`
269
- */
270
- export function fuzzyMatch(haystack: string, needle: string): boolean {
271
- if (typeof haystack !== 'string' || typeof needle !== 'string') {
272
- return false;
273
- }
274
-
275
- const trimmed = needle.trim();
276
-
277
- if (includes(haystack, trimmed, true)) {
278
- return true;
279
- }
280
-
281
- return getScore(haystack, trimmed) > -1;
282
- }
283
-
284
- function isSubsequence(haystack: string, needle: string): boolean {
285
- const lowerCaseHaystack = lowerCase(haystack);
286
- const lowerCaseNeedle = lowerCase(needle);
287
-
288
- const haystackLength = lowerCaseHaystack.length;
289
- const needleLength = lowerCaseNeedle.length;
290
-
291
- let needleIndex = 0;
292
-
293
- for (let haystackIndex = 0; haystackIndex < haystackLength; haystackIndex += 1) {
294
- // Advance needle pointer only on a matching character
295
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) {
296
- needleIndex += 1;
297
- }
298
-
299
- // All needle characters matched in order
300
- if (needleIndex === needleLength) {
301
- return true;
302
- }
303
- }
304
-
305
- return false;
306
- }
307
-
308
- function getScore(haystack: string, needle: string): number {
309
- if (!isSubsequence(haystack, needle)) {
310
- return -1;
311
- }
312
-
313
- const lowerCaseHaystack = lowerCase(haystack);
314
- const lowerCaseNeedle = lowerCase(needle);
315
-
316
- const needleLength = lowerCaseNeedle.length;
317
-
318
- let needleIndex = 0;
319
- let previousMatchIndex = -1;
320
- let score = 0;
321
-
322
- for (let haystackIndex = 0; haystackIndex < lowerCaseHaystack.length; haystackIndex += 1) {
323
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) {
324
- // +1 for each matched character
325
- score += 1;
326
-
327
- // Bonus for matching at the start of the string
328
- if (haystackIndex === 0) {
329
- score += 1;
330
- }
331
-
332
- // Proximity bonus: decays as gap between consecutive matches widens
333
- if (previousMatchIndex !== -1) {
334
- const gap = haystackIndex - previousMatchIndex - 1;
335
-
336
- score += Math.max(0, PROXIMITY_THRESHOLD - gap);
337
- }
338
-
339
- previousMatchIndex = haystackIndex;
340
-
341
- needleIndex += 1;
342
- }
343
-
344
- // All needle characters matched; no need to scan further
345
- if (needleIndex === needleLength) {
346
- break;
347
- }
348
- }
349
-
350
- // Penalty for longer strings to favour tighter matches
351
- score -= Math.floor(lowerCaseHaystack.length / LENGTH_DIVISOR);
352
-
353
- return Math.max(0, score);
354
- }
355
-
356
- function search<Item>(
357
- items: Item[],
358
- strings: string[],
359
- input: string,
360
- options: RequiredKeys<FuzzyOptions, 'tolerance'>,
361
- ) {
362
- const result: FuzzyResult<Item> = {
363
- exact: [],
364
- similar: [],
365
- };
366
-
367
- const value = typeof input === 'string' ? input.trim() : '';
368
-
369
- if (value.length === 0) {
370
- result.exact = items.slice(0, options.limit);
371
-
372
- return result;
373
- }
374
-
375
- let {length} = items;
376
-
377
- const exact: Array<FuzzyItem<Item>> = [];
378
- const similar: Array<Item> = [];
379
-
380
- const scored: Record<number, Array<FuzzyItem<Item>>> = {};
381
-
382
- for (let index = 0; index < length; index += 1) {
383
- const item = items[index];
384
- const haystack = strings[index];
385
-
386
- if (includes(haystack, value, true)) {
387
- exact.push({item, haystack});
388
-
389
- continue;
390
- }
391
-
392
- const score = getScore(haystack, value);
393
-
394
- if (score > -1) {
395
- scored[score] ??= [];
396
-
397
- scored[score].push({item, haystack});
398
- }
399
- }
400
-
401
- const keys = Object.keys(scored)
402
- .map(Number)
403
- .sort((first, second) => second - first);
404
-
405
- length = keys.length;
406
-
407
- if (length > 0 && options.tolerance > 0) {
408
- const maxScore = keys[0];
409
-
410
- for (let index = 0; index < length; index += 1) {
411
- const key = keys[index];
412
-
413
- if (maxScore - key > options.tolerance) {
414
- break;
415
- }
416
-
417
- similar.push(...getItems(scored[key]));
418
- }
419
- }
420
-
421
- result.exact = getItems(options.limit == null ? exact : exact.slice(0, options.limit));
422
-
423
- if (options.limit == null) {
424
- result.similar = similar;
425
- } else {
426
- result.similar = similar.slice(0, options.limit - result.exact.length);
427
- }
428
-
429
- return result;
430
- }
431
-
432
- // #endregion
433
-
434
- // #region Variables
435
-
436
- const LENGTH_DIVISOR = 3;
437
-
438
- const MESSAGE_ARRAY = 'Fuzzy requires an array of items';
439
-
440
- const MESSAGE_HANDLER = 'Fuzzy requires a key or function to stringify items';
441
-
442
- const PROXIMITY_THRESHOLD = 5;
443
-
444
- // #endregion