@nova-design-system/nova-react 3.22.0 → 3.24.0

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 (91) hide show
  1. package/dist/cjs/_commonjsHelpers-B85MJLTf-CFO10eej.js +7 -0
  2. package/dist/cjs/{collapse.animation-6e0b08df-AHWzNGm_.js → collapse.animation-DZDm0vSK-C2TOIhIK.js} +3 -3
  3. package/dist/cjs/events.utils-B6GgGra--01N__3wY.js +23 -0
  4. package/dist/cjs/{fade.animation-9b939939-DV--bM4S.js → fade.animation-DcRL9lcm-DAZeHoKN.js} +75 -75
  5. package/dist/cjs/generated/components.server.js +292 -50
  6. package/dist/cjs/{grow.animation-24ad5cf8-LUp_ITEx.js → grow.animation-D7ep_aVl-BuXEDSK-.js} +5 -26
  7. package/dist/cjs/i18n.utils-IlwlcG9l-ku0bScip.js +2494 -0
  8. package/dist/cjs/{index-WPRkQD3O.js → index-kU2nW5aN.js} +12692 -7009
  9. package/dist/cjs/{nv-dialog.entry-CDSK9pUH.js → index.esm-D3eWMME9-CG1TVKfu.js} +1 -296
  10. package/dist/cjs/index.js +15 -1
  11. package/dist/cjs/inputmask-CSo292ul-DlvupPk6.js +3758 -0
  12. package/dist/cjs/{nv-accordion-item.entry-BuTvA6w9.js → nv-accordion-item.entry-Bu1tAcCq.js} +11 -10
  13. package/dist/cjs/{nv-accordion.entry-Dtsfw6He.js → nv-accordion.entry-jWjLdX8w.js} +9 -11
  14. package/dist/cjs/{nv-alert.entry-TIdfdU7Y.js → nv-alert.entry-E9ZJay_K.js} +22 -23
  15. package/dist/cjs/{nv-avatar.entry-CaxrhPuw.js → nv-avatar.entry-CUX7u0kR.js} +11 -11
  16. package/dist/cjs/{nv-badge_2.entry-CfYvTZxX.js → nv-badge_2.entry-bxpV5gxE.js} +24 -24
  17. package/dist/cjs/{nv-breadcrumb.entry-BCZ4MmfD.js → nv-breadcrumb.entry-Cbbb9Qeh.js} +5 -5
  18. package/dist/cjs/{nv-breadcrumbs.entry-DwzCE7F6.js → nv-breadcrumbs.entry-BTqnp9zO.js} +3 -3
  19. package/dist/cjs/{nv-button.entry-Cr_86bcZ.js → nv-button.entry-upWH19y6.js} +12 -14
  20. package/dist/cjs/{nv-buttongroup.entry-CWjRoHY1.js → nv-buttongroup.entry-CuZCRsnV.js} +3 -3
  21. package/dist/cjs/{nv-calendar.entry-CXfwNt6G.js → nv-calendar.entry-CT3mASW6.js} +113 -97
  22. package/dist/cjs/{nv-col.entry-CJLDS3LY.js → nv-col.entry--pCxkaTh.js} +5 -5
  23. package/dist/cjs/{nv-datagrid.entry-Cns8XSud.js → nv-datagrid.entry-CGCEhO8C.js} +80 -85
  24. package/dist/cjs/{nv-datagridcolumn.entry-CFFAipHF.js → nv-datagridcolumn.entry-Fsqc7CT_.js} +2 -1
  25. package/dist/cjs/nv-dialog.entry-B6OYcZxQ.js +300 -0
  26. package/dist/cjs/{nv-dialogfooter_2.entry-To_dGUn4.js → nv-dialogfooter_2.entry-C4fP_n2-.js} +10 -11
  27. package/dist/cjs/nv-drawer.entry-C5O4KvHU.js +445 -0
  28. package/dist/cjs/nv-drawerfooter_2.entry-C-reYJXG.js +146 -0
  29. package/dist/cjs/nv-fieldcheckbox.entry-bk7UNQny.js +177 -0
  30. package/dist/cjs/{nv-fielddate.entry-C3pXtMHL.js → nv-fielddate.entry-dqZDBVmm.js} +89 -46
  31. package/dist/cjs/{nv-fielddaterange.entry-CjVVPEK_.js → nv-fielddaterange.entry-wNRasXky.js} +151 -103
  32. package/dist/cjs/nv-fielddropdown.entry-BA15piWa.js +678 -0
  33. package/dist/cjs/{nv-fielddropdownitem.entry-Dah-PaE8.js → nv-fielddropdownitem.entry-DEWaf9dC.js} +7 -7
  34. package/dist/cjs/{nv-fieldmultiselect.entry-BMLjhqoJ.js → nv-fieldmultiselect.entry-BWY5xOAd.js} +343 -236
  35. package/dist/cjs/nv-fieldnumber.entry-DoYORd0d.js +187 -0
  36. package/dist/cjs/nv-fieldpassword.entry-CPaLj9aD.js +165 -0
  37. package/dist/cjs/{nv-fieldradio.entry-X_2VT1Dj.js → nv-fieldradio.entry-CvUmEaCa.js} +11 -11
  38. package/dist/cjs/{nv-fieldselect.entry-pSp-2rNn.js → nv-fieldselect.entry-uUIZ6hmN.js} +52 -13
  39. package/dist/cjs/{nv-fieldslider.entry-pZf7zzLU.js → nv-fieldslider.entry-DnvmxxYY.js} +16 -31
  40. package/dist/cjs/nv-fieldtext.entry-BYAJp3n_.js +163 -0
  41. package/dist/cjs/{nv-fieldtextarea.entry-t3Ixxi23.js → nv-fieldtextarea.entry-DU2bWYeg.js} +52 -14
  42. package/dist/cjs/{nv-fieldtime.entry-DY7D5_6K.js → nv-fieldtime.entry-DlMNDTht.js} +128 -84
  43. package/dist/cjs/{nv-icon.entry-6oYPSf4c.js → nv-icon.entry-CnUkRzaA.js} +12 -12
  44. package/dist/cjs/{nv-iconbutton_2.entry-ChULGovr.js → nv-iconbutton_2.entry-hqp4AcRq.js} +10 -12
  45. package/dist/cjs/{nv-menu.entry-sd0tatWq.js → nv-menu.entry-Dc_FvIx7.js} +18 -32
  46. package/dist/cjs/{nv-menuitem.entry-CCOqR7UF.js → nv-menuitem.entry-DzMhx6c_.js} +6 -5
  47. package/dist/cjs/nv-notification-bullet.entry-BwhHCMQF.js +76 -0
  48. package/dist/cjs/{nv-notification.entry-Cc3zE3yY.js → nv-notification.entry-C3m5p5BL.js} +42 -128
  49. package/dist/cjs/{nv-notificationcontainer.entry-DV4D6oOH.js → nv-notificationcontainer.entry-DTRNn7VE.js} +4 -4
  50. package/dist/cjs/{nv-popover.entry-DQSwI2jT.js → nv-popover.entry-B0c-2rO4.js} +51 -47
  51. package/dist/cjs/{nv-row.entry-UUuMSAY5.js → nv-row.entry-CdcjVGZv.js} +4 -4
  52. package/dist/cjs/nv-sidebar.entry-CiN813gQ.js +177 -0
  53. package/dist/cjs/nv-sidebarcontent.entry-D9hpAhK8.js +22 -0
  54. package/dist/cjs/nv-sidebardivider.entry-B4EMyca5.js +22 -0
  55. package/dist/cjs/nv-sidebarfooter.entry-CHi4qOFe.js +22 -0
  56. package/dist/cjs/nv-sidebargroup.entry-RVqrsyIU.js +23 -0
  57. package/dist/cjs/nv-sidebarheader.entry-_7ch0O3G.js +22 -0
  58. package/dist/cjs/nv-sidebarlogo.entry-Ch9F-JnT.js +32 -0
  59. package/dist/cjs/nv-sidebarnavitem.entry-DVrafSMr.js +296 -0
  60. package/dist/cjs/nv-sidebarnavsubitem.entry-C0XDAzma.js +35 -0
  61. package/dist/cjs/{nv-split.entry-CYP2bTTM.js → nv-split.entry-0HTslRAX.js} +47 -45
  62. package/dist/cjs/{nv-stack.entry-D35-75Vw.js → nv-stack.entry-CqO7uTQf.js} +5 -5
  63. package/dist/cjs/{nv-table.entry-DevWJBnL.js → nv-table.entry-DH85n8Mc.js} +9 -11
  64. package/dist/cjs/{nv-tableheader.entry-Hs7nUSOC.js → nv-tableheader.entry-CKfocdxD.js} +7 -7
  65. package/dist/cjs/{nv-toggle.entry-Eje9d--6.js → nv-toggle.entry-BHUl76Im.js} +9 -9
  66. package/dist/cjs/nv-togglebutton.entry-D_9COjY-.js +67 -0
  67. package/dist/cjs/{nv-togglebuttongroup.entry-CWN_Ucry.js → nv-togglebuttongroup.entry-C0NLbsq7.js} +10 -8
  68. package/dist/cjs/{nv-tooltip.entry-BGkKDEFg.js → nv-tooltip.entry-BfViGE_U.js} +5 -5
  69. package/dist/cjs/slide.animation-CmH5d1of-BZuw607U.js +90 -0
  70. package/dist/cjs/{style-value-types.es-f5d10b79-D0QCM8OB.js → style-value-types.es-xlgmw4x8-B1vLqX9m.js} +6 -3
  71. package/dist/cjs/{v4-a79185f4-2n0dOd_Y.js → v4-BdYh22OP-C1vaJ4yP.js} +1 -1
  72. package/dist/components/NvDatatable/NvDatatable.js +40 -24
  73. package/dist/generated/components.js +107 -0
  74. package/dist/generated/components.server.js +260 -50
  75. package/dist/providers/NotificationProvider.js +3 -4
  76. package/dist/types/components/NvDatatable/NvDatatable.d.ts +1 -1
  77. package/dist/types/components/NvDatatable/types.d.ts +8 -3
  78. package/dist/types/generated/components.d.ts +46 -0
  79. package/dist/types/generated/components.server.d.ts +46 -0
  80. package/package.json +2 -2
  81. package/dist/cjs/_commonjsHelpers-1789f0cf-BJu3ubxk.js +0 -10
  82. package/dist/cjs/inputmask-edcad3c1-B9Omti4z.js +0 -3749
  83. package/dist/cjs/nv-fieldcheckbox.entry-fdonR07Z.js +0 -138
  84. package/dist/cjs/nv-fielddropdown.entry-C9mXuNNj.js +0 -392
  85. package/dist/cjs/nv-fieldnumber.entry-DBdJviXu.js +0 -148
  86. package/dist/cjs/nv-fieldpassword.entry-Cim_usSM.js +0 -122
  87. package/dist/cjs/nv-fieldtext.entry-DlI_ExaV.js +0 -124
  88. package/dist/cjs/nv-togglebutton.entry-LGI7pIeK.js +0 -56
  89. /package/dist/cjs/{clsx-297c1ffe-BtxeOLZW.js → clsx-ChV9xqsO-BtxeOLZW.js} +0 -0
  90. /package/dist/cjs/{constants-69bafca2-DpB_ghPF.js → constants-BReL3Lsa-DpB_ghPF.js} +0 -0
  91. /package/dist/cjs/{timeline.animation-79215cd4-KteJaZPb.js → timeline.animation-CgHCo_Ho-KteJaZPb.js} +0 -0
@@ -0,0 +1,2494 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Text normalization utilities for the search engine.
5
+ * Provides functions for normalizing and tokenizing text for search operations.
6
+ */
7
+ /**
8
+ * Normalizes text for search comparison.
9
+ * - Converts to lowercase
10
+ * - Removes accents/diacritics
11
+ * - Replaces punctuation with spaces
12
+ * - Trims whitespace
13
+ * - Collapses multiple spaces into single space
14
+ *
15
+ * @param {string} text - The text to normalize
16
+ * @returns {string} The normalized text
17
+ *
18
+ * @example
19
+ * normalize('Système Grid') // 'systeme grid'
20
+ * normalize('Hello, World!') // 'hello world'
21
+ * normalize(' Multiple Spaces ') // 'multiple spaces'
22
+ */
23
+ function normalize(text) {
24
+ if (!text)
25
+ return '';
26
+ return (text
27
+ // Convert to lowercase
28
+ .toLowerCase()
29
+ // Remove accents/diacritics using Unicode normalization
30
+ .normalize('NFD')
31
+ .replace(/[\u0300-\u036f]/g, '')
32
+ // Replace punctuation and special characters with spaces
33
+ .replace(/[^\w\s]/g, ' ')
34
+ // Collapse multiple spaces into single space
35
+ .replace(/\s+/g, ' ')
36
+ // Trim leading/trailing whitespace
37
+ .trim());
38
+ }
39
+ /**
40
+ * Tokenizes text into an array of normalized tokens.
41
+ * Splits by whitespace and normalizes each token.
42
+ *
43
+ * @param {string} text - The text to tokenize
44
+ * @returns {string[]} Array of normalized tokens
45
+ *
46
+ * @example
47
+ * tokenize('System Grid Control') // ['system', 'grid', 'control']
48
+ * tokenize('Hello, World!') // ['hello', 'world']
49
+ */
50
+ function tokenize(text) {
51
+ const normalized = normalize(text);
52
+ if (!normalized)
53
+ return [];
54
+ return normalized.split(' ').filter(token => token.length > 0);
55
+ }
56
+
57
+ /**
58
+ * @fileoverview Guard functions for validating and clamping search parameters.
59
+ * Ensures safe limits for search operations to protect UI performance.
60
+ */
61
+ /** Default maximum results to return */
62
+ const DEFAULT_MAX_RESULTS = 25;
63
+ /** Minimum allowed max results */
64
+ const MIN_MAX_RESULTS = 10;
65
+ /** Hard cap for max results - protects DOM from rendering too many items */
66
+ const HARD_CAP_MAX_RESULTS = 500;
67
+ /** Default threshold for offloading to Web Worker */
68
+ const DEFAULT_WORKER_THRESHOLD = 2000;
69
+ /** Minimum worker threshold (0 = always use worker) */
70
+ const MIN_WORKER_THRESHOLD = 0;
71
+ /** Maximum worker threshold */
72
+ const MAX_WORKER_THRESHOLD = 50000;
73
+ /** Default fuzzy threshold (0-1, lower = stricter) */
74
+ const DEFAULT_FUZZY_THRESHOLD = 0.3;
75
+ /** Debounce delay for fuzzy mode (ms) */
76
+ const FUZZY_DEBOUNCE_DELAY = 300;
77
+ /**
78
+ * Clamps the maxResults value to safe limits.
79
+ * - Minimum: 10
80
+ * - Default: 200
81
+ * - Hard cap: 500
82
+ *
83
+ * @param {number} [value] - The maxResults value to clamp (undefined uses default)
84
+ * @returns {number} The clamped maxResults value
85
+ *
86
+ * @example
87
+ * clampMaxResults(undefined) // 200 (default)
88
+ * clampMaxResults(5) // 10 (minimum)
89
+ * clampMaxResults(1000) // 500 (hard cap)
90
+ * clampMaxResults(100) // 100 (valid)
91
+ */
92
+ function clampMaxResults(value) {
93
+ if (value === undefined || value === null) {
94
+ return DEFAULT_MAX_RESULTS;
95
+ }
96
+ if (value < MIN_MAX_RESULTS) {
97
+ return MIN_MAX_RESULTS;
98
+ }
99
+ if (value > HARD_CAP_MAX_RESULTS) {
100
+ return HARD_CAP_MAX_RESULTS;
101
+ }
102
+ return Math.floor(value);
103
+ }
104
+ /**
105
+ * Clamps the workerThreshold value to safe limits.
106
+ * - Minimum: 0 (always use worker)
107
+ * - Default: 2000
108
+ * - Maximum: 50000
109
+ *
110
+ * @param {number} [value] - The workerThreshold value to clamp (undefined uses default)
111
+ * @returns {number} The clamped workerThreshold value
112
+ *
113
+ * @example
114
+ * clampWorkerThreshold(undefined) // 2000 (default)
115
+ * clampWorkerThreshold(-100) // 0 (minimum)
116
+ * clampWorkerThreshold(100000) // 50000 (maximum)
117
+ * clampWorkerThreshold(5000) // 5000 (valid)
118
+ */
119
+ function clampWorkerThreshold(value) {
120
+ if (value === undefined || value === null) {
121
+ return DEFAULT_WORKER_THRESHOLD;
122
+ }
123
+ if (value < MIN_WORKER_THRESHOLD) {
124
+ return MIN_WORKER_THRESHOLD;
125
+ }
126
+ if (value > MAX_WORKER_THRESHOLD) {
127
+ return MAX_WORKER_THRESHOLD;
128
+ }
129
+ return Math.floor(value);
130
+ }
131
+ /**
132
+ * Clamps the fuzzyThreshold value to valid range.
133
+ * - Minimum: 0 (strictest)
134
+ * - Default: 0.3
135
+ * - Maximum: 1 (most permissive)
136
+ *
137
+ * @param {number} [value] - The fuzzyThreshold value to clamp (undefined uses default)
138
+ * @returns {number} The clamped fuzzyThreshold value
139
+ *
140
+ * @example
141
+ * clampFuzzyThreshold(undefined) // 0.3 (default)
142
+ * clampFuzzyThreshold(-0.5) // 0 (minimum)
143
+ * clampFuzzyThreshold(1.5) // 1 (maximum)
144
+ * clampFuzzyThreshold(0.5) // 0.5 (valid)
145
+ */
146
+ function clampFuzzyThreshold(value) {
147
+ if (value === undefined || value === null) {
148
+ return DEFAULT_FUZZY_THRESHOLD;
149
+ }
150
+ if (value < 0) {
151
+ return 0;
152
+ }
153
+ if (value > 1) {
154
+ return 1;
155
+ }
156
+ return value;
157
+ }
158
+
159
+ /**
160
+ * @fileoverview Indexing utilities for the search engine.
161
+ * Pre-computes normalized text and tokens for efficient searching.
162
+ */
163
+ /**
164
+ * Builds a search index from raw items.
165
+ * Pre-computes normalized text and tokens for each item for efficient searching.
166
+ *
167
+ * @param {RawItem[]} items - Array of raw items with id and label
168
+ * @returns {IndexedItem[]} Array of indexed items with pre-computed norm and tokens
169
+ *
170
+ * @example
171
+ * const items = [
172
+ * { id: '1', label: 'System Grid Control' },
173
+ * { id: '2', label: 'Network Manager' }
174
+ * ];
175
+ * const indexed = buildIndex(items);
176
+ * // [
177
+ * // { id: '1', label: 'System Grid Control', norm: 'system grid control', tokens: ['system', 'grid', 'control'] },
178
+ * // { id: '2', label: 'Network Manager', norm: 'network manager', tokens: ['network', 'manager'] }
179
+ * // ]
180
+ */
181
+ function buildIndex(items) {
182
+ if (!items || !Array.isArray(items)) {
183
+ return [];
184
+ }
185
+ return items.map(item => {
186
+ const norm = normalize(item.label);
187
+ return {
188
+ id: item.id,
189
+ label: item.label,
190
+ norm,
191
+ tokens: tokenize(item.label),
192
+ };
193
+ });
194
+ }
195
+
196
+ /**
197
+ * @fileoverview Strict search strategy using normalized substring matching.
198
+ * Fast and deterministic - best for IDs, codes, and references.
199
+ */
200
+ /**
201
+ * Searches items using strict substring matching.
202
+ * The normalized query must be a substring of the normalized label.
203
+ *
204
+ * This is the fastest search mode but requires exact substring matches.
205
+ * Word order matters: "Grid System" won't match "System Grid".
206
+ *
207
+ * @param {IndexedItem[]} indexedItems - Array of pre-indexed items
208
+ * @param {string} query - The search query
209
+ * @returns {string[]} Array of matching item IDs
210
+ *
211
+ * @example
212
+ * // Given items: [{ id: '1', label: 'System Grid Control', norm: 'system grid control', tokens: [...] }]
213
+ * searchStrict(items, 'System Grid') // ['1'] - matches
214
+ * searchStrict(items, 'Grid Sys') // ['1'] - matches (substring)
215
+ * searchStrict(items, 'Grid System') // [] - no match (wrong order)
216
+ */
217
+ function searchStrict(indexedItems, query) {
218
+ if (!query || !indexedItems?.length) {
219
+ return indexedItems?.map(item => item.id) || [];
220
+ }
221
+ const normalizedQuery = normalize(query);
222
+ if (!normalizedQuery) {
223
+ return indexedItems.map(item => item.id);
224
+ }
225
+ return indexedItems
226
+ .filter(item => item.norm.includes(normalizedQuery))
227
+ .map(item => item.id);
228
+ }
229
+
230
+ /**
231
+ * @fileoverview Smart search strategy using token-based matching.
232
+ * All query tokens must exist in the target label (order ignored).
233
+ */
234
+ /**
235
+ * Searches items using token-based matching.
236
+ * All query tokens must exist in the item's normalized text (order ignored).
237
+ *
238
+ * This provides a "smart" feel without fuzzy matching overhead.
239
+ * Word order doesn't matter: "Grid System" will match "System Grid".
240
+ *
241
+ * @param {IndexedItem[]} indexedItems - Array of pre-indexed items
242
+ * @param {string} query - The search query
243
+ * @returns {string[]} Array of matching item IDs
244
+ *
245
+ * @example
246
+ * // Given items: [{ id: '1', label: 'System Grid Control', norm: 'system grid control', tokens: [...] }]
247
+ * searchSmart(items, 'System Grid') // ['1'] - matches (all tokens present)
248
+ * searchSmart(items, 'Grid System') // ['1'] - matches (order ignored)
249
+ * searchSmart(items, 'Grid Sys') // ['1'] - matches (partial tokens allowed as substrings)
250
+ * searchSmart(items, 'System Unknown') // [] - no match ('unknown' not present)
251
+ */
252
+ function searchSmart(indexedItems, query) {
253
+ if (!query || !indexedItems?.length) {
254
+ return indexedItems?.map(item => item.id) || [];
255
+ }
256
+ const queryTokens = tokenize(query);
257
+ if (!queryTokens.length) {
258
+ return indexedItems.map(item => item.id);
259
+ }
260
+ return indexedItems
261
+ .filter(item => {
262
+ // All query tokens must be found as substrings in the normalized text
263
+ return queryTokens.every(queryToken => item.norm.includes(queryToken));
264
+ })
265
+ .map(item => item.id);
266
+ }
267
+
268
+ /**
269
+ * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io)
270
+ *
271
+ * Copyright (c) 2023 Kiro Risk (http://kiro.me)
272
+ * All Rights Reserved. Apache Software License 2.0
273
+ *
274
+ * http://www.apache.org/licenses/LICENSE-2.0
275
+ */
276
+
277
+ function isArray(value) {
278
+ return !Array.isArray
279
+ ? getTag(value) === '[object Array]'
280
+ : Array.isArray(value)
281
+ }
282
+ function baseToString(value) {
283
+ // Exit early for strings to avoid a performance hit in some environments.
284
+ if (typeof value == 'string') {
285
+ return value
286
+ }
287
+ let result = value + '';
288
+ return result == '0' && 1 / value == -Infinity ? '-0' : result
289
+ }
290
+
291
+ function toString(value) {
292
+ return value == null ? '' : baseToString(value)
293
+ }
294
+
295
+ function isString(value) {
296
+ return typeof value === 'string'
297
+ }
298
+
299
+ function isNumber(value) {
300
+ return typeof value === 'number'
301
+ }
302
+
303
+ // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js
304
+ function isBoolean(value) {
305
+ return (
306
+ value === true ||
307
+ value === false ||
308
+ (isObjectLike(value) && getTag(value) == '[object Boolean]')
309
+ )
310
+ }
311
+
312
+ function isObject(value) {
313
+ return typeof value === 'object'
314
+ }
315
+
316
+ // Checks if `value` is object-like.
317
+ function isObjectLike(value) {
318
+ return isObject(value) && value !== null
319
+ }
320
+
321
+ function isDefined(value) {
322
+ return value !== undefined && value !== null
323
+ }
324
+
325
+ function isBlank(value) {
326
+ return !value.trim().length
327
+ }
328
+
329
+ // Gets the `toStringTag` of `value`.
330
+ // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js
331
+ function getTag(value) {
332
+ return value == null
333
+ ? value === undefined
334
+ ? '[object Undefined]'
335
+ : '[object Null]'
336
+ : Object.prototype.toString.call(value)
337
+ }
338
+
339
+ const INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
340
+
341
+ const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) =>
342
+ `Invalid value for key ${key}`;
343
+
344
+ const PATTERN_LENGTH_TOO_LARGE = (max) =>
345
+ `Pattern length exceeds max of ${max}.`;
346
+
347
+ const MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`;
348
+
349
+ const INVALID_KEY_WEIGHT_VALUE = (key) =>
350
+ `Property 'weight' in key '${key}' must be a positive integer`;
351
+
352
+ const hasOwn = Object.prototype.hasOwnProperty;
353
+
354
+ class KeyStore {
355
+ constructor(keys) {
356
+ this._keys = [];
357
+ this._keyMap = {};
358
+
359
+ let totalWeight = 0;
360
+
361
+ keys.forEach((key) => {
362
+ let obj = createKey(key);
363
+
364
+ this._keys.push(obj);
365
+ this._keyMap[obj.id] = obj;
366
+
367
+ totalWeight += obj.weight;
368
+ });
369
+
370
+ // Normalize weights so that their sum is equal to 1
371
+ this._keys.forEach((key) => {
372
+ key.weight /= totalWeight;
373
+ });
374
+ }
375
+ get(keyId) {
376
+ return this._keyMap[keyId]
377
+ }
378
+ keys() {
379
+ return this._keys
380
+ }
381
+ toJSON() {
382
+ return JSON.stringify(this._keys)
383
+ }
384
+ }
385
+
386
+ function createKey(key) {
387
+ let path = null;
388
+ let id = null;
389
+ let src = null;
390
+ let weight = 1;
391
+ let getFn = null;
392
+
393
+ if (isString(key) || isArray(key)) {
394
+ src = key;
395
+ path = createKeyPath(key);
396
+ id = createKeyId(key);
397
+ } else {
398
+ if (!hasOwn.call(key, 'name')) {
399
+ throw new Error(MISSING_KEY_PROPERTY('name'))
400
+ }
401
+
402
+ const name = key.name;
403
+ src = name;
404
+
405
+ if (hasOwn.call(key, 'weight')) {
406
+ weight = key.weight;
407
+
408
+ if (weight <= 0) {
409
+ throw new Error(INVALID_KEY_WEIGHT_VALUE(name))
410
+ }
411
+ }
412
+
413
+ path = createKeyPath(name);
414
+ id = createKeyId(name);
415
+ getFn = key.getFn;
416
+ }
417
+
418
+ return { path, id, weight, src, getFn }
419
+ }
420
+
421
+ function createKeyPath(key) {
422
+ return isArray(key) ? key : key.split('.')
423
+ }
424
+
425
+ function createKeyId(key) {
426
+ return isArray(key) ? key.join('.') : key
427
+ }
428
+
429
+ function get(obj, path) {
430
+ let list = [];
431
+ let arr = false;
432
+
433
+ const deepGet = (obj, path, index) => {
434
+ if (!isDefined(obj)) {
435
+ return
436
+ }
437
+ if (!path[index]) {
438
+ // If there's no path left, we've arrived at the object we care about.
439
+ list.push(obj);
440
+ } else {
441
+ let key = path[index];
442
+
443
+ const value = obj[key];
444
+
445
+ if (!isDefined(value)) {
446
+ return
447
+ }
448
+
449
+ // If we're at the last value in the path, and if it's a string/number/bool,
450
+ // add it to the list
451
+ if (
452
+ index === path.length - 1 &&
453
+ (isString(value) || isNumber(value) || isBoolean(value))
454
+ ) {
455
+ list.push(toString(value));
456
+ } else if (isArray(value)) {
457
+ arr = true;
458
+ // Search each item in the array.
459
+ for (let i = 0, len = value.length; i < len; i += 1) {
460
+ deepGet(value[i], path, index + 1);
461
+ }
462
+ } else if (path.length) {
463
+ // An object. Recurse further.
464
+ deepGet(value, path, index + 1);
465
+ }
466
+ }
467
+ };
468
+
469
+ // Backwards compatibility (since path used to be a string)
470
+ deepGet(obj, isString(path) ? path.split('.') : path, 0);
471
+
472
+ return arr ? list : list[0]
473
+ }
474
+
475
+ const MatchOptions = {
476
+ // Whether the matches should be included in the result set. When `true`, each record in the result
477
+ // set will include the indices of the matched characters.
478
+ // These can consequently be used for highlighting purposes.
479
+ includeMatches: false,
480
+ // When `true`, the matching function will continue to the end of a search pattern even if
481
+ // a perfect match has already been located in the string.
482
+ findAllMatches: false,
483
+ // Minimum number of characters that must be matched before a result is considered a match
484
+ minMatchCharLength: 1
485
+ };
486
+
487
+ const BasicOptions = {
488
+ // When `true`, the algorithm continues searching to the end of the input even if a perfect
489
+ // match is found before the end of the same input.
490
+ isCaseSensitive: false,
491
+ // When true, the matching function will continue to the end of a search pattern even if
492
+ includeScore: false,
493
+ // List of properties that will be searched. This also supports nested properties.
494
+ keys: [],
495
+ // Whether to sort the result list, by score
496
+ shouldSort: true,
497
+ // Default sort function: sort by ascending score, ascending index
498
+ sortFn: (a, b) =>
499
+ a.score === b.score ? (a.idx < b.idx ? -1 : 1) : a.score < b.score ? -1 : 1
500
+ };
501
+
502
+ const FuzzyOptions = {
503
+ // Approximately where in the text is the pattern expected to be found?
504
+ location: 0,
505
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
506
+ // (of both letters and location), a threshold of '1.0' would match anything.
507
+ threshold: 0.6,
508
+ // Determines how close the match must be to the fuzzy location (specified above).
509
+ // An exact letter match which is 'distance' characters away from the fuzzy location
510
+ // would score as a complete mismatch. A distance of '0' requires the match be at
511
+ // the exact location specified, a threshold of '1000' would require a perfect match
512
+ // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
513
+ distance: 100
514
+ };
515
+
516
+ const AdvancedOptions = {
517
+ // When `true`, it enables the use of unix-like search commands
518
+ useExtendedSearch: false,
519
+ // The get function to use when fetching an object's properties.
520
+ // The default will search nested paths *ie foo.bar.baz*
521
+ getFn: get,
522
+ // When `true`, search will ignore `location` and `distance`, so it won't matter
523
+ // where in the string the pattern appears.
524
+ // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
525
+ ignoreLocation: false,
526
+ // When `true`, the calculation for the relevance score (used for sorting) will
527
+ // ignore the field-length norm.
528
+ // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
529
+ ignoreFieldNorm: false,
530
+ // The weight to determine how much field length norm effects scoring.
531
+ fieldNormWeight: 1
532
+ };
533
+
534
+ var Config = {
535
+ ...BasicOptions,
536
+ ...MatchOptions,
537
+ ...FuzzyOptions,
538
+ ...AdvancedOptions
539
+ };
540
+
541
+ const SPACE = /[^ ]+/g;
542
+
543
+ // Field-length norm: the shorter the field, the higher the weight.
544
+ // Set to 3 decimals to reduce index size.
545
+ function norm(weight = 1, mantissa = 3) {
546
+ const cache = new Map();
547
+ const m = Math.pow(10, mantissa);
548
+
549
+ return {
550
+ get(value) {
551
+ const numTokens = value.match(SPACE).length;
552
+
553
+ if (cache.has(numTokens)) {
554
+ return cache.get(numTokens)
555
+ }
556
+
557
+ // Default function is 1/sqrt(x), weight makes that variable
558
+ const norm = 1 / Math.pow(numTokens, 0.5 * weight);
559
+
560
+ // In place of `toFixed(mantissa)`, for faster computation
561
+ const n = parseFloat(Math.round(norm * m) / m);
562
+
563
+ cache.set(numTokens, n);
564
+
565
+ return n
566
+ },
567
+ clear() {
568
+ cache.clear();
569
+ }
570
+ }
571
+ }
572
+
573
+ class FuseIndex {
574
+ constructor({
575
+ getFn = Config.getFn,
576
+ fieldNormWeight = Config.fieldNormWeight
577
+ } = {}) {
578
+ this.norm = norm(fieldNormWeight, 3);
579
+ this.getFn = getFn;
580
+ this.isCreated = false;
581
+
582
+ this.setIndexRecords();
583
+ }
584
+ setSources(docs = []) {
585
+ this.docs = docs;
586
+ }
587
+ setIndexRecords(records = []) {
588
+ this.records = records;
589
+ }
590
+ setKeys(keys = []) {
591
+ this.keys = keys;
592
+ this._keysMap = {};
593
+ keys.forEach((key, idx) => {
594
+ this._keysMap[key.id] = idx;
595
+ });
596
+ }
597
+ create() {
598
+ if (this.isCreated || !this.docs.length) {
599
+ return
600
+ }
601
+
602
+ this.isCreated = true;
603
+
604
+ // List is Array<String>
605
+ if (isString(this.docs[0])) {
606
+ this.docs.forEach((doc, docIndex) => {
607
+ this._addString(doc, docIndex);
608
+ });
609
+ } else {
610
+ // List is Array<Object>
611
+ this.docs.forEach((doc, docIndex) => {
612
+ this._addObject(doc, docIndex);
613
+ });
614
+ }
615
+
616
+ this.norm.clear();
617
+ }
618
+ // Adds a doc to the end of the index
619
+ add(doc) {
620
+ const idx = this.size();
621
+
622
+ if (isString(doc)) {
623
+ this._addString(doc, idx);
624
+ } else {
625
+ this._addObject(doc, idx);
626
+ }
627
+ }
628
+ // Removes the doc at the specified index of the index
629
+ removeAt(idx) {
630
+ this.records.splice(idx, 1);
631
+
632
+ // Change ref index of every subsquent doc
633
+ for (let i = idx, len = this.size(); i < len; i += 1) {
634
+ this.records[i].i -= 1;
635
+ }
636
+ }
637
+ getValueForItemAtKeyId(item, keyId) {
638
+ return item[this._keysMap[keyId]]
639
+ }
640
+ size() {
641
+ return this.records.length
642
+ }
643
+ _addString(doc, docIndex) {
644
+ if (!isDefined(doc) || isBlank(doc)) {
645
+ return
646
+ }
647
+
648
+ let record = {
649
+ v: doc,
650
+ i: docIndex,
651
+ n: this.norm.get(doc)
652
+ };
653
+
654
+ this.records.push(record);
655
+ }
656
+ _addObject(doc, docIndex) {
657
+ let record = { i: docIndex, $: {} };
658
+
659
+ // Iterate over every key (i.e, path), and fetch the value at that key
660
+ this.keys.forEach((key, keyIndex) => {
661
+ let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path);
662
+
663
+ if (!isDefined(value)) {
664
+ return
665
+ }
666
+
667
+ if (isArray(value)) {
668
+ let subRecords = [];
669
+ const stack = [{ nestedArrIndex: -1, value }];
670
+
671
+ while (stack.length) {
672
+ const { nestedArrIndex, value } = stack.pop();
673
+
674
+ if (!isDefined(value)) {
675
+ continue
676
+ }
677
+
678
+ if (isString(value) && !isBlank(value)) {
679
+ let subRecord = {
680
+ v: value,
681
+ i: nestedArrIndex,
682
+ n: this.norm.get(value)
683
+ };
684
+
685
+ subRecords.push(subRecord);
686
+ } else if (isArray(value)) {
687
+ value.forEach((item, k) => {
688
+ stack.push({
689
+ nestedArrIndex: k,
690
+ value: item
691
+ });
692
+ });
693
+ } else ;
694
+ }
695
+ record.$[keyIndex] = subRecords;
696
+ } else if (isString(value) && !isBlank(value)) {
697
+ let subRecord = {
698
+ v: value,
699
+ n: this.norm.get(value)
700
+ };
701
+
702
+ record.$[keyIndex] = subRecord;
703
+ }
704
+ });
705
+
706
+ this.records.push(record);
707
+ }
708
+ toJSON() {
709
+ return {
710
+ keys: this.keys,
711
+ records: this.records
712
+ }
713
+ }
714
+ }
715
+
716
+ function createIndex(
717
+ keys,
718
+ docs,
719
+ { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}
720
+ ) {
721
+ const myIndex = new FuseIndex({ getFn, fieldNormWeight });
722
+ myIndex.setKeys(keys.map(createKey));
723
+ myIndex.setSources(docs);
724
+ myIndex.create();
725
+ return myIndex
726
+ }
727
+
728
+ function parseIndex(
729
+ data,
730
+ { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}
731
+ ) {
732
+ const { keys, records } = data;
733
+ const myIndex = new FuseIndex({ getFn, fieldNormWeight });
734
+ myIndex.setKeys(keys);
735
+ myIndex.setIndexRecords(records);
736
+ return myIndex
737
+ }
738
+
739
+ function computeScore$1(
740
+ pattern,
741
+ {
742
+ errors = 0,
743
+ currentLocation = 0,
744
+ expectedLocation = 0,
745
+ distance = Config.distance,
746
+ ignoreLocation = Config.ignoreLocation
747
+ } = {}
748
+ ) {
749
+ const accuracy = errors / pattern.length;
750
+
751
+ if (ignoreLocation) {
752
+ return accuracy
753
+ }
754
+
755
+ const proximity = Math.abs(expectedLocation - currentLocation);
756
+
757
+ if (!distance) {
758
+ // Dodge divide by zero error.
759
+ return proximity ? 1.0 : accuracy
760
+ }
761
+
762
+ return accuracy + proximity / distance
763
+ }
764
+
765
+ function convertMaskToIndices(
766
+ matchmask = [],
767
+ minMatchCharLength = Config.minMatchCharLength
768
+ ) {
769
+ let indices = [];
770
+ let start = -1;
771
+ let end = -1;
772
+ let i = 0;
773
+
774
+ for (let len = matchmask.length; i < len; i += 1) {
775
+ let match = matchmask[i];
776
+ if (match && start === -1) {
777
+ start = i;
778
+ } else if (!match && start !== -1) {
779
+ end = i - 1;
780
+ if (end - start + 1 >= minMatchCharLength) {
781
+ indices.push([start, end]);
782
+ }
783
+ start = -1;
784
+ }
785
+ }
786
+
787
+ // (i-1 - start) + 1 => i - start
788
+ if (matchmask[i - 1] && i - start >= minMatchCharLength) {
789
+ indices.push([start, i - 1]);
790
+ }
791
+
792
+ return indices
793
+ }
794
+
795
+ // Machine word size
796
+ const MAX_BITS = 32;
797
+
798
+ function search$1(
799
+ text,
800
+ pattern,
801
+ patternAlphabet,
802
+ {
803
+ location = Config.location,
804
+ distance = Config.distance,
805
+ threshold = Config.threshold,
806
+ findAllMatches = Config.findAllMatches,
807
+ minMatchCharLength = Config.minMatchCharLength,
808
+ includeMatches = Config.includeMatches,
809
+ ignoreLocation = Config.ignoreLocation
810
+ } = {}
811
+ ) {
812
+ if (pattern.length > MAX_BITS) {
813
+ throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS))
814
+ }
815
+
816
+ const patternLen = pattern.length;
817
+ // Set starting location at beginning text and initialize the alphabet.
818
+ const textLen = text.length;
819
+ // Handle the case when location > text.length
820
+ const expectedLocation = Math.max(0, Math.min(location, textLen));
821
+ // Highest score beyond which we give up.
822
+ let currentThreshold = threshold;
823
+ // Is there a nearby exact match? (speedup)
824
+ let bestLocation = expectedLocation;
825
+
826
+ // Performance: only computer matches when the minMatchCharLength > 1
827
+ // OR if `includeMatches` is true.
828
+ const computeMatches = minMatchCharLength > 1 || includeMatches;
829
+ // A mask of the matches, used for building the indices
830
+ const matchMask = computeMatches ? Array(textLen) : [];
831
+
832
+ let index;
833
+
834
+ // Get all exact matches, here for speed up
835
+ while ((index = text.indexOf(pattern, bestLocation)) > -1) {
836
+ let score = computeScore$1(pattern, {
837
+ currentLocation: index,
838
+ expectedLocation,
839
+ distance,
840
+ ignoreLocation
841
+ });
842
+
843
+ currentThreshold = Math.min(score, currentThreshold);
844
+ bestLocation = index + patternLen;
845
+
846
+ if (computeMatches) {
847
+ let i = 0;
848
+ while (i < patternLen) {
849
+ matchMask[index + i] = 1;
850
+ i += 1;
851
+ }
852
+ }
853
+ }
854
+
855
+ // Reset the best location
856
+ bestLocation = -1;
857
+
858
+ let lastBitArr = [];
859
+ let finalScore = 1;
860
+ let binMax = patternLen + textLen;
861
+
862
+ const mask = 1 << (patternLen - 1);
863
+
864
+ for (let i = 0; i < patternLen; i += 1) {
865
+ // Scan for the best match; each iteration allows for one more error.
866
+ // Run a binary search to determine how far from the match location we can stray
867
+ // at this error level.
868
+ let binMin = 0;
869
+ let binMid = binMax;
870
+
871
+ while (binMin < binMid) {
872
+ const score = computeScore$1(pattern, {
873
+ errors: i,
874
+ currentLocation: expectedLocation + binMid,
875
+ expectedLocation,
876
+ distance,
877
+ ignoreLocation
878
+ });
879
+
880
+ if (score <= currentThreshold) {
881
+ binMin = binMid;
882
+ } else {
883
+ binMax = binMid;
884
+ }
885
+
886
+ binMid = Math.floor((binMax - binMin) / 2 + binMin);
887
+ }
888
+
889
+ // Use the result from this iteration as the maximum for the next.
890
+ binMax = binMid;
891
+
892
+ let start = Math.max(1, expectedLocation - binMid + 1);
893
+ let finish = findAllMatches
894
+ ? textLen
895
+ : Math.min(expectedLocation + binMid, textLen) + patternLen;
896
+
897
+ // Initialize the bit array
898
+ let bitArr = Array(finish + 2);
899
+
900
+ bitArr[finish + 1] = (1 << i) - 1;
901
+
902
+ for (let j = finish; j >= start; j -= 1) {
903
+ let currentLocation = j - 1;
904
+ let charMatch = patternAlphabet[text.charAt(currentLocation)];
905
+
906
+ if (computeMatches) {
907
+ // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`)
908
+ matchMask[currentLocation] = +!!charMatch;
909
+ }
910
+
911
+ // First pass: exact match
912
+ bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch;
913
+
914
+ // Subsequent passes: fuzzy match
915
+ if (i) {
916
+ bitArr[j] |=
917
+ ((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1 | lastBitArr[j + 1];
918
+ }
919
+
920
+ if (bitArr[j] & mask) {
921
+ finalScore = computeScore$1(pattern, {
922
+ errors: i,
923
+ currentLocation,
924
+ expectedLocation,
925
+ distance,
926
+ ignoreLocation
927
+ });
928
+
929
+ // This match will almost certainly be better than any existing match.
930
+ // But check anyway.
931
+ if (finalScore <= currentThreshold) {
932
+ // Indeed it is
933
+ currentThreshold = finalScore;
934
+ bestLocation = currentLocation;
935
+
936
+ // Already passed `loc`, downhill from here on in.
937
+ if (bestLocation <= expectedLocation) {
938
+ break
939
+ }
940
+
941
+ // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
942
+ start = Math.max(1, 2 * expectedLocation - bestLocation);
943
+ }
944
+ }
945
+ }
946
+
947
+ // No hope for a (better) match at greater error levels.
948
+ const score = computeScore$1(pattern, {
949
+ errors: i + 1,
950
+ currentLocation: expectedLocation,
951
+ expectedLocation,
952
+ distance,
953
+ ignoreLocation
954
+ });
955
+
956
+ if (score > currentThreshold) {
957
+ break
958
+ }
959
+
960
+ lastBitArr = bitArr;
961
+ }
962
+
963
+ const result = {
964
+ isMatch: bestLocation >= 0,
965
+ // Count exact matches (those with a score of 0) to be "almost" exact
966
+ score: Math.max(0.001, finalScore)
967
+ };
968
+
969
+ if (computeMatches) {
970
+ const indices = convertMaskToIndices(matchMask, minMatchCharLength);
971
+ if (!indices.length) {
972
+ result.isMatch = false;
973
+ } else if (includeMatches) {
974
+ result.indices = indices;
975
+ }
976
+ }
977
+
978
+ return result
979
+ }
980
+
981
+ function createPatternAlphabet(pattern) {
982
+ let mask = {};
983
+
984
+ for (let i = 0, len = pattern.length; i < len; i += 1) {
985
+ const char = pattern.charAt(i);
986
+ mask[char] = (mask[char] || 0) | (1 << (len - i - 1));
987
+ }
988
+
989
+ return mask
990
+ }
991
+
992
+ class BitapSearch {
993
+ constructor(
994
+ pattern,
995
+ {
996
+ location = Config.location,
997
+ threshold = Config.threshold,
998
+ distance = Config.distance,
999
+ includeMatches = Config.includeMatches,
1000
+ findAllMatches = Config.findAllMatches,
1001
+ minMatchCharLength = Config.minMatchCharLength,
1002
+ isCaseSensitive = Config.isCaseSensitive,
1003
+ ignoreLocation = Config.ignoreLocation
1004
+ } = {}
1005
+ ) {
1006
+ this.options = {
1007
+ location,
1008
+ threshold,
1009
+ distance,
1010
+ includeMatches,
1011
+ findAllMatches,
1012
+ minMatchCharLength,
1013
+ isCaseSensitive,
1014
+ ignoreLocation
1015
+ };
1016
+
1017
+ this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
1018
+
1019
+ this.chunks = [];
1020
+
1021
+ if (!this.pattern.length) {
1022
+ return
1023
+ }
1024
+
1025
+ const addChunk = (pattern, startIndex) => {
1026
+ this.chunks.push({
1027
+ pattern,
1028
+ alphabet: createPatternAlphabet(pattern),
1029
+ startIndex
1030
+ });
1031
+ };
1032
+
1033
+ const len = this.pattern.length;
1034
+
1035
+ if (len > MAX_BITS) {
1036
+ let i = 0;
1037
+ const remainder = len % MAX_BITS;
1038
+ const end = len - remainder;
1039
+
1040
+ while (i < end) {
1041
+ addChunk(this.pattern.substr(i, MAX_BITS), i);
1042
+ i += MAX_BITS;
1043
+ }
1044
+
1045
+ if (remainder) {
1046
+ const startIndex = len - MAX_BITS;
1047
+ addChunk(this.pattern.substr(startIndex), startIndex);
1048
+ }
1049
+ } else {
1050
+ addChunk(this.pattern, 0);
1051
+ }
1052
+ }
1053
+
1054
+ searchIn(text) {
1055
+ const { isCaseSensitive, includeMatches } = this.options;
1056
+
1057
+ if (!isCaseSensitive) {
1058
+ text = text.toLowerCase();
1059
+ }
1060
+
1061
+ // Exact match
1062
+ if (this.pattern === text) {
1063
+ let result = {
1064
+ isMatch: true,
1065
+ score: 0
1066
+ };
1067
+
1068
+ if (includeMatches) {
1069
+ result.indices = [[0, text.length - 1]];
1070
+ }
1071
+
1072
+ return result
1073
+ }
1074
+
1075
+ // Otherwise, use Bitap algorithm
1076
+ const {
1077
+ location,
1078
+ distance,
1079
+ threshold,
1080
+ findAllMatches,
1081
+ minMatchCharLength,
1082
+ ignoreLocation
1083
+ } = this.options;
1084
+
1085
+ let allIndices = [];
1086
+ let totalScore = 0;
1087
+ let hasMatches = false;
1088
+
1089
+ this.chunks.forEach(({ pattern, alphabet, startIndex }) => {
1090
+ const { isMatch, score, indices } = search$1(text, pattern, alphabet, {
1091
+ location: location + startIndex,
1092
+ distance,
1093
+ threshold,
1094
+ findAllMatches,
1095
+ minMatchCharLength,
1096
+ includeMatches,
1097
+ ignoreLocation
1098
+ });
1099
+
1100
+ if (isMatch) {
1101
+ hasMatches = true;
1102
+ }
1103
+
1104
+ totalScore += score;
1105
+
1106
+ if (isMatch && indices) {
1107
+ allIndices = [...allIndices, ...indices];
1108
+ }
1109
+ });
1110
+
1111
+ let result = {
1112
+ isMatch: hasMatches,
1113
+ score: hasMatches ? totalScore / this.chunks.length : 1
1114
+ };
1115
+
1116
+ if (hasMatches && includeMatches) {
1117
+ result.indices = allIndices;
1118
+ }
1119
+
1120
+ return result
1121
+ }
1122
+ }
1123
+
1124
+ class BaseMatch {
1125
+ constructor(pattern) {
1126
+ this.pattern = pattern;
1127
+ }
1128
+ static isMultiMatch(pattern) {
1129
+ return getMatch(pattern, this.multiRegex)
1130
+ }
1131
+ static isSingleMatch(pattern) {
1132
+ return getMatch(pattern, this.singleRegex)
1133
+ }
1134
+ search(/*text*/) {}
1135
+ }
1136
+
1137
+ function getMatch(pattern, exp) {
1138
+ const matches = pattern.match(exp);
1139
+ return matches ? matches[1] : null
1140
+ }
1141
+
1142
+ // Token: 'file
1143
+
1144
+ class ExactMatch extends BaseMatch {
1145
+ constructor(pattern) {
1146
+ super(pattern);
1147
+ }
1148
+ static get type() {
1149
+ return 'exact'
1150
+ }
1151
+ static get multiRegex() {
1152
+ return /^="(.*)"$/
1153
+ }
1154
+ static get singleRegex() {
1155
+ return /^=(.*)$/
1156
+ }
1157
+ search(text) {
1158
+ const isMatch = text === this.pattern;
1159
+
1160
+ return {
1161
+ isMatch,
1162
+ score: isMatch ? 0 : 1,
1163
+ indices: [0, this.pattern.length - 1]
1164
+ }
1165
+ }
1166
+ }
1167
+
1168
+ // Token: !fire
1169
+
1170
+ class InverseExactMatch extends BaseMatch {
1171
+ constructor(pattern) {
1172
+ super(pattern);
1173
+ }
1174
+ static get type() {
1175
+ return 'inverse-exact'
1176
+ }
1177
+ static get multiRegex() {
1178
+ return /^!"(.*)"$/
1179
+ }
1180
+ static get singleRegex() {
1181
+ return /^!(.*)$/
1182
+ }
1183
+ search(text) {
1184
+ const index = text.indexOf(this.pattern);
1185
+ const isMatch = index === -1;
1186
+
1187
+ return {
1188
+ isMatch,
1189
+ score: isMatch ? 0 : 1,
1190
+ indices: [0, text.length - 1]
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ // Token: ^file
1196
+
1197
+ class PrefixExactMatch extends BaseMatch {
1198
+ constructor(pattern) {
1199
+ super(pattern);
1200
+ }
1201
+ static get type() {
1202
+ return 'prefix-exact'
1203
+ }
1204
+ static get multiRegex() {
1205
+ return /^\^"(.*)"$/
1206
+ }
1207
+ static get singleRegex() {
1208
+ return /^\^(.*)$/
1209
+ }
1210
+ search(text) {
1211
+ const isMatch = text.startsWith(this.pattern);
1212
+
1213
+ return {
1214
+ isMatch,
1215
+ score: isMatch ? 0 : 1,
1216
+ indices: [0, this.pattern.length - 1]
1217
+ }
1218
+ }
1219
+ }
1220
+
1221
+ // Token: !^fire
1222
+
1223
+ class InversePrefixExactMatch extends BaseMatch {
1224
+ constructor(pattern) {
1225
+ super(pattern);
1226
+ }
1227
+ static get type() {
1228
+ return 'inverse-prefix-exact'
1229
+ }
1230
+ static get multiRegex() {
1231
+ return /^!\^"(.*)"$/
1232
+ }
1233
+ static get singleRegex() {
1234
+ return /^!\^(.*)$/
1235
+ }
1236
+ search(text) {
1237
+ const isMatch = !text.startsWith(this.pattern);
1238
+
1239
+ return {
1240
+ isMatch,
1241
+ score: isMatch ? 0 : 1,
1242
+ indices: [0, text.length - 1]
1243
+ }
1244
+ }
1245
+ }
1246
+
1247
+ // Token: .file$
1248
+
1249
+ class SuffixExactMatch extends BaseMatch {
1250
+ constructor(pattern) {
1251
+ super(pattern);
1252
+ }
1253
+ static get type() {
1254
+ return 'suffix-exact'
1255
+ }
1256
+ static get multiRegex() {
1257
+ return /^"(.*)"\$$/
1258
+ }
1259
+ static get singleRegex() {
1260
+ return /^(.*)\$$/
1261
+ }
1262
+ search(text) {
1263
+ const isMatch = text.endsWith(this.pattern);
1264
+
1265
+ return {
1266
+ isMatch,
1267
+ score: isMatch ? 0 : 1,
1268
+ indices: [text.length - this.pattern.length, text.length - 1]
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ // Token: !.file$
1274
+
1275
+ class InverseSuffixExactMatch extends BaseMatch {
1276
+ constructor(pattern) {
1277
+ super(pattern);
1278
+ }
1279
+ static get type() {
1280
+ return 'inverse-suffix-exact'
1281
+ }
1282
+ static get multiRegex() {
1283
+ return /^!"(.*)"\$$/
1284
+ }
1285
+ static get singleRegex() {
1286
+ return /^!(.*)\$$/
1287
+ }
1288
+ search(text) {
1289
+ const isMatch = !text.endsWith(this.pattern);
1290
+ return {
1291
+ isMatch,
1292
+ score: isMatch ? 0 : 1,
1293
+ indices: [0, text.length - 1]
1294
+ }
1295
+ }
1296
+ }
1297
+
1298
+ class FuzzyMatch extends BaseMatch {
1299
+ constructor(
1300
+ pattern,
1301
+ {
1302
+ location = Config.location,
1303
+ threshold = Config.threshold,
1304
+ distance = Config.distance,
1305
+ includeMatches = Config.includeMatches,
1306
+ findAllMatches = Config.findAllMatches,
1307
+ minMatchCharLength = Config.minMatchCharLength,
1308
+ isCaseSensitive = Config.isCaseSensitive,
1309
+ ignoreLocation = Config.ignoreLocation
1310
+ } = {}
1311
+ ) {
1312
+ super(pattern);
1313
+ this._bitapSearch = new BitapSearch(pattern, {
1314
+ location,
1315
+ threshold,
1316
+ distance,
1317
+ includeMatches,
1318
+ findAllMatches,
1319
+ minMatchCharLength,
1320
+ isCaseSensitive,
1321
+ ignoreLocation
1322
+ });
1323
+ }
1324
+ static get type() {
1325
+ return 'fuzzy'
1326
+ }
1327
+ static get multiRegex() {
1328
+ return /^"(.*)"$/
1329
+ }
1330
+ static get singleRegex() {
1331
+ return /^(.*)$/
1332
+ }
1333
+ search(text) {
1334
+ return this._bitapSearch.searchIn(text)
1335
+ }
1336
+ }
1337
+
1338
+ // Token: 'file
1339
+
1340
+ class IncludeMatch extends BaseMatch {
1341
+ constructor(pattern) {
1342
+ super(pattern);
1343
+ }
1344
+ static get type() {
1345
+ return 'include'
1346
+ }
1347
+ static get multiRegex() {
1348
+ return /^'"(.*)"$/
1349
+ }
1350
+ static get singleRegex() {
1351
+ return /^'(.*)$/
1352
+ }
1353
+ search(text) {
1354
+ let location = 0;
1355
+ let index;
1356
+
1357
+ const indices = [];
1358
+ const patternLen = this.pattern.length;
1359
+
1360
+ // Get all exact matches
1361
+ while ((index = text.indexOf(this.pattern, location)) > -1) {
1362
+ location = index + patternLen;
1363
+ indices.push([index, location - 1]);
1364
+ }
1365
+
1366
+ const isMatch = !!indices.length;
1367
+
1368
+ return {
1369
+ isMatch,
1370
+ score: isMatch ? 0 : 1,
1371
+ indices
1372
+ }
1373
+ }
1374
+ }
1375
+
1376
+ // ❗Order is important. DO NOT CHANGE.
1377
+ const searchers = [
1378
+ ExactMatch,
1379
+ IncludeMatch,
1380
+ PrefixExactMatch,
1381
+ InversePrefixExactMatch,
1382
+ InverseSuffixExactMatch,
1383
+ SuffixExactMatch,
1384
+ InverseExactMatch,
1385
+ FuzzyMatch
1386
+ ];
1387
+
1388
+ const searchersLen = searchers.length;
1389
+
1390
+ // Regex to split by spaces, but keep anything in quotes together
1391
+ const SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;
1392
+ const OR_TOKEN = '|';
1393
+
1394
+ // Return a 2D array representation of the query, for simpler parsing.
1395
+ // Example:
1396
+ // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]]
1397
+ function parseQuery(pattern, options = {}) {
1398
+ return pattern.split(OR_TOKEN).map((item) => {
1399
+ let query = item
1400
+ .trim()
1401
+ .split(SPACE_RE)
1402
+ .filter((item) => item && !!item.trim());
1403
+
1404
+ let results = [];
1405
+ for (let i = 0, len = query.length; i < len; i += 1) {
1406
+ const queryItem = query[i];
1407
+
1408
+ // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`)
1409
+ let found = false;
1410
+ let idx = -1;
1411
+ while (!found && ++idx < searchersLen) {
1412
+ const searcher = searchers[idx];
1413
+ let token = searcher.isMultiMatch(queryItem);
1414
+ if (token) {
1415
+ results.push(new searcher(token, options));
1416
+ found = true;
1417
+ }
1418
+ }
1419
+
1420
+ if (found) {
1421
+ continue
1422
+ }
1423
+
1424
+ // 2. Handle single query matches (i.e, once that are *not* quoted)
1425
+ idx = -1;
1426
+ while (++idx < searchersLen) {
1427
+ const searcher = searchers[idx];
1428
+ let token = searcher.isSingleMatch(queryItem);
1429
+ if (token) {
1430
+ results.push(new searcher(token, options));
1431
+ break
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+ return results
1437
+ })
1438
+ }
1439
+
1440
+ // These extended matchers can return an array of matches, as opposed
1441
+ // to a singl match
1442
+ const MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]);
1443
+
1444
+ /**
1445
+ * Command-like searching
1446
+ * ======================
1447
+ *
1448
+ * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`,
1449
+ * search in a given text.
1450
+ *
1451
+ * Search syntax:
1452
+ *
1453
+ * | Token | Match type | Description |
1454
+ * | ----------- | -------------------------- | -------------------------------------- |
1455
+ * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` |
1456
+ * | `=scheme` | exact-match | Items that are `scheme` |
1457
+ * | `'python` | include-match | Items that include `python` |
1458
+ * | `!ruby` | inverse-exact-match | Items that do not include `ruby` |
1459
+ * | `^java` | prefix-exact-match | Items that start with `java` |
1460
+ * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` |
1461
+ * | `.js$` | suffix-exact-match | Items that end with `.js` |
1462
+ * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` |
1463
+ *
1464
+ * A single pipe character acts as an OR operator. For example, the following
1465
+ * query matches entries that start with `core` and end with either`go`, `rb`,
1466
+ * or`py`.
1467
+ *
1468
+ * ```
1469
+ * ^core go$ | rb$ | py$
1470
+ * ```
1471
+ */
1472
+ class ExtendedSearch {
1473
+ constructor(
1474
+ pattern,
1475
+ {
1476
+ isCaseSensitive = Config.isCaseSensitive,
1477
+ includeMatches = Config.includeMatches,
1478
+ minMatchCharLength = Config.minMatchCharLength,
1479
+ ignoreLocation = Config.ignoreLocation,
1480
+ findAllMatches = Config.findAllMatches,
1481
+ location = Config.location,
1482
+ threshold = Config.threshold,
1483
+ distance = Config.distance
1484
+ } = {}
1485
+ ) {
1486
+ this.query = null;
1487
+ this.options = {
1488
+ isCaseSensitive,
1489
+ includeMatches,
1490
+ minMatchCharLength,
1491
+ findAllMatches,
1492
+ ignoreLocation,
1493
+ location,
1494
+ threshold,
1495
+ distance
1496
+ };
1497
+
1498
+ this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
1499
+ this.query = parseQuery(this.pattern, this.options);
1500
+ }
1501
+
1502
+ static condition(_, options) {
1503
+ return options.useExtendedSearch
1504
+ }
1505
+
1506
+ searchIn(text) {
1507
+ const query = this.query;
1508
+
1509
+ if (!query) {
1510
+ return {
1511
+ isMatch: false,
1512
+ score: 1
1513
+ }
1514
+ }
1515
+
1516
+ const { includeMatches, isCaseSensitive } = this.options;
1517
+
1518
+ text = isCaseSensitive ? text : text.toLowerCase();
1519
+
1520
+ let numMatches = 0;
1521
+ let allIndices = [];
1522
+ let totalScore = 0;
1523
+
1524
+ // ORs
1525
+ for (let i = 0, qLen = query.length; i < qLen; i += 1) {
1526
+ const searchers = query[i];
1527
+
1528
+ // Reset indices
1529
+ allIndices.length = 0;
1530
+ numMatches = 0;
1531
+
1532
+ // ANDs
1533
+ for (let j = 0, pLen = searchers.length; j < pLen; j += 1) {
1534
+ const searcher = searchers[j];
1535
+ const { isMatch, indices, score } = searcher.search(text);
1536
+
1537
+ if (isMatch) {
1538
+ numMatches += 1;
1539
+ totalScore += score;
1540
+ if (includeMatches) {
1541
+ const type = searcher.constructor.type;
1542
+ if (MultiMatchSet.has(type)) {
1543
+ allIndices = [...allIndices, ...indices];
1544
+ } else {
1545
+ allIndices.push(indices);
1546
+ }
1547
+ }
1548
+ } else {
1549
+ totalScore = 0;
1550
+ numMatches = 0;
1551
+ allIndices.length = 0;
1552
+ break
1553
+ }
1554
+ }
1555
+
1556
+ // OR condition, so if TRUE, return
1557
+ if (numMatches) {
1558
+ let result = {
1559
+ isMatch: true,
1560
+ score: totalScore / numMatches
1561
+ };
1562
+
1563
+ if (includeMatches) {
1564
+ result.indices = allIndices;
1565
+ }
1566
+
1567
+ return result
1568
+ }
1569
+ }
1570
+
1571
+ // Nothing was matched
1572
+ return {
1573
+ isMatch: false,
1574
+ score: 1
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ const registeredSearchers = [];
1580
+
1581
+ function register(...args) {
1582
+ registeredSearchers.push(...args);
1583
+ }
1584
+
1585
+ function createSearcher(pattern, options) {
1586
+ for (let i = 0, len = registeredSearchers.length; i < len; i += 1) {
1587
+ let searcherClass = registeredSearchers[i];
1588
+ if (searcherClass.condition(pattern, options)) {
1589
+ return new searcherClass(pattern, options)
1590
+ }
1591
+ }
1592
+
1593
+ return new BitapSearch(pattern, options)
1594
+ }
1595
+
1596
+ const LogicalOperator = {
1597
+ AND: '$and',
1598
+ OR: '$or'
1599
+ };
1600
+
1601
+ const KeyType = {
1602
+ PATH: '$path',
1603
+ PATTERN: '$val'
1604
+ };
1605
+
1606
+ const isExpression = (query) =>
1607
+ !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
1608
+
1609
+ const isPath = (query) => !!query[KeyType.PATH];
1610
+
1611
+ const isLeaf = (query) =>
1612
+ !isArray(query) && isObject(query) && !isExpression(query);
1613
+
1614
+ const convertToExplicit = (query) => ({
1615
+ [LogicalOperator.AND]: Object.keys(query).map((key) => ({
1616
+ [key]: query[key]
1617
+ }))
1618
+ });
1619
+
1620
+ // When `auto` is `true`, the parse function will infer and initialize and add
1621
+ // the appropriate `Searcher` instance
1622
+ function parse(query, options, { auto = true } = {}) {
1623
+ const next = (query) => {
1624
+ let keys = Object.keys(query);
1625
+
1626
+ const isQueryPath = isPath(query);
1627
+
1628
+ if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
1629
+ return next(convertToExplicit(query))
1630
+ }
1631
+
1632
+ if (isLeaf(query)) {
1633
+ const key = isQueryPath ? query[KeyType.PATH] : keys[0];
1634
+
1635
+ const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key];
1636
+
1637
+ if (!isString(pattern)) {
1638
+ throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key))
1639
+ }
1640
+
1641
+ const obj = {
1642
+ keyId: createKeyId(key),
1643
+ pattern
1644
+ };
1645
+
1646
+ if (auto) {
1647
+ obj.searcher = createSearcher(pattern, options);
1648
+ }
1649
+
1650
+ return obj
1651
+ }
1652
+
1653
+ let node = {
1654
+ children: [],
1655
+ operator: keys[0]
1656
+ };
1657
+
1658
+ keys.forEach((key) => {
1659
+ const value = query[key];
1660
+
1661
+ if (isArray(value)) {
1662
+ value.forEach((item) => {
1663
+ node.children.push(next(item));
1664
+ });
1665
+ }
1666
+ });
1667
+
1668
+ return node
1669
+ };
1670
+
1671
+ if (!isExpression(query)) {
1672
+ query = convertToExplicit(query);
1673
+ }
1674
+
1675
+ return next(query)
1676
+ }
1677
+
1678
+ // Practical scoring function
1679
+ function computeScore(
1680
+ results,
1681
+ { ignoreFieldNorm = Config.ignoreFieldNorm }
1682
+ ) {
1683
+ results.forEach((result) => {
1684
+ let totalScore = 1;
1685
+
1686
+ result.matches.forEach(({ key, norm, score }) => {
1687
+ const weight = key ? key.weight : null;
1688
+
1689
+ totalScore *= Math.pow(
1690
+ score === 0 && weight ? Number.EPSILON : score,
1691
+ (weight || 1) * (ignoreFieldNorm ? 1 : norm)
1692
+ );
1693
+ });
1694
+
1695
+ result.score = totalScore;
1696
+ });
1697
+ }
1698
+
1699
+ function transformMatches(result, data) {
1700
+ const matches = result.matches;
1701
+ data.matches = [];
1702
+
1703
+ if (!isDefined(matches)) {
1704
+ return
1705
+ }
1706
+
1707
+ matches.forEach((match) => {
1708
+ if (!isDefined(match.indices) || !match.indices.length) {
1709
+ return
1710
+ }
1711
+
1712
+ const { indices, value } = match;
1713
+
1714
+ let obj = {
1715
+ indices,
1716
+ value
1717
+ };
1718
+
1719
+ if (match.key) {
1720
+ obj.key = match.key.src;
1721
+ }
1722
+
1723
+ if (match.idx > -1) {
1724
+ obj.refIndex = match.idx;
1725
+ }
1726
+
1727
+ data.matches.push(obj);
1728
+ });
1729
+ }
1730
+
1731
+ function transformScore(result, data) {
1732
+ data.score = result.score;
1733
+ }
1734
+
1735
+ function format(
1736
+ results,
1737
+ docs,
1738
+ {
1739
+ includeMatches = Config.includeMatches,
1740
+ includeScore = Config.includeScore
1741
+ } = {}
1742
+ ) {
1743
+ const transformers = [];
1744
+
1745
+ if (includeMatches) transformers.push(transformMatches);
1746
+ if (includeScore) transformers.push(transformScore);
1747
+
1748
+ return results.map((result) => {
1749
+ const { idx } = result;
1750
+
1751
+ const data = {
1752
+ item: docs[idx],
1753
+ refIndex: idx
1754
+ };
1755
+
1756
+ if (transformers.length) {
1757
+ transformers.forEach((transformer) => {
1758
+ transformer(result, data);
1759
+ });
1760
+ }
1761
+
1762
+ return data
1763
+ })
1764
+ }
1765
+
1766
+ class Fuse {
1767
+ constructor(docs, options = {}, index) {
1768
+ this.options = { ...Config, ...options };
1769
+
1770
+ this._keyStore = new KeyStore(this.options.keys);
1771
+
1772
+ this.setCollection(docs, index);
1773
+ }
1774
+
1775
+ setCollection(docs, index) {
1776
+ this._docs = docs;
1777
+
1778
+ if (index && !(index instanceof FuseIndex)) {
1779
+ throw new Error(INCORRECT_INDEX_TYPE)
1780
+ }
1781
+
1782
+ this._myIndex =
1783
+ index ||
1784
+ createIndex(this.options.keys, this._docs, {
1785
+ getFn: this.options.getFn,
1786
+ fieldNormWeight: this.options.fieldNormWeight
1787
+ });
1788
+ }
1789
+
1790
+ add(doc) {
1791
+ if (!isDefined(doc)) {
1792
+ return
1793
+ }
1794
+
1795
+ this._docs.push(doc);
1796
+ this._myIndex.add(doc);
1797
+ }
1798
+
1799
+ remove(predicate = (/* doc, idx */) => false) {
1800
+ const results = [];
1801
+
1802
+ for (let i = 0, len = this._docs.length; i < len; i += 1) {
1803
+ const doc = this._docs[i];
1804
+ if (predicate(doc, i)) {
1805
+ this.removeAt(i);
1806
+ i -= 1;
1807
+ len -= 1;
1808
+
1809
+ results.push(doc);
1810
+ }
1811
+ }
1812
+
1813
+ return results
1814
+ }
1815
+
1816
+ removeAt(idx) {
1817
+ this._docs.splice(idx, 1);
1818
+ this._myIndex.removeAt(idx);
1819
+ }
1820
+
1821
+ getIndex() {
1822
+ return this._myIndex
1823
+ }
1824
+
1825
+ search(query, { limit = -1 } = {}) {
1826
+ const {
1827
+ includeMatches,
1828
+ includeScore,
1829
+ shouldSort,
1830
+ sortFn,
1831
+ ignoreFieldNorm
1832
+ } = this.options;
1833
+
1834
+ let results = isString(query)
1835
+ ? isString(this._docs[0])
1836
+ ? this._searchStringList(query)
1837
+ : this._searchObjectList(query)
1838
+ : this._searchLogical(query);
1839
+
1840
+ computeScore(results, { ignoreFieldNorm });
1841
+
1842
+ if (shouldSort) {
1843
+ results.sort(sortFn);
1844
+ }
1845
+
1846
+ if (isNumber(limit) && limit > -1) {
1847
+ results = results.slice(0, limit);
1848
+ }
1849
+
1850
+ return format(results, this._docs, {
1851
+ includeMatches,
1852
+ includeScore
1853
+ })
1854
+ }
1855
+
1856
+ _searchStringList(query) {
1857
+ const searcher = createSearcher(query, this.options);
1858
+ const { records } = this._myIndex;
1859
+ const results = [];
1860
+
1861
+ // Iterate over every string in the index
1862
+ records.forEach(({ v: text, i: idx, n: norm }) => {
1863
+ if (!isDefined(text)) {
1864
+ return
1865
+ }
1866
+
1867
+ const { isMatch, score, indices } = searcher.searchIn(text);
1868
+
1869
+ if (isMatch) {
1870
+ results.push({
1871
+ item: text,
1872
+ idx,
1873
+ matches: [{ score, value: text, norm, indices }]
1874
+ });
1875
+ }
1876
+ });
1877
+
1878
+ return results
1879
+ }
1880
+
1881
+ _searchLogical(query) {
1882
+
1883
+ const expression = parse(query, this.options);
1884
+
1885
+ const evaluate = (node, item, idx) => {
1886
+ if (!node.children) {
1887
+ const { keyId, searcher } = node;
1888
+
1889
+ const matches = this._findMatches({
1890
+ key: this._keyStore.get(keyId),
1891
+ value: this._myIndex.getValueForItemAtKeyId(item, keyId),
1892
+ searcher
1893
+ });
1894
+
1895
+ if (matches && matches.length) {
1896
+ return [
1897
+ {
1898
+ idx,
1899
+ item,
1900
+ matches
1901
+ }
1902
+ ]
1903
+ }
1904
+
1905
+ return []
1906
+ }
1907
+
1908
+ const res = [];
1909
+ for (let i = 0, len = node.children.length; i < len; i += 1) {
1910
+ const child = node.children[i];
1911
+ const result = evaluate(child, item, idx);
1912
+ if (result.length) {
1913
+ res.push(...result);
1914
+ } else if (node.operator === LogicalOperator.AND) {
1915
+ return []
1916
+ }
1917
+ }
1918
+ return res
1919
+ };
1920
+
1921
+ const records = this._myIndex.records;
1922
+ const resultMap = {};
1923
+ const results = [];
1924
+
1925
+ records.forEach(({ $: item, i: idx }) => {
1926
+ if (isDefined(item)) {
1927
+ let expResults = evaluate(expression, item, idx);
1928
+
1929
+ if (expResults.length) {
1930
+ // Dedupe when adding
1931
+ if (!resultMap[idx]) {
1932
+ resultMap[idx] = { idx, item, matches: [] };
1933
+ results.push(resultMap[idx]);
1934
+ }
1935
+ expResults.forEach(({ matches }) => {
1936
+ resultMap[idx].matches.push(...matches);
1937
+ });
1938
+ }
1939
+ }
1940
+ });
1941
+
1942
+ return results
1943
+ }
1944
+
1945
+ _searchObjectList(query) {
1946
+ const searcher = createSearcher(query, this.options);
1947
+ const { keys, records } = this._myIndex;
1948
+ const results = [];
1949
+
1950
+ // List is Array<Object>
1951
+ records.forEach(({ $: item, i: idx }) => {
1952
+ if (!isDefined(item)) {
1953
+ return
1954
+ }
1955
+
1956
+ let matches = [];
1957
+
1958
+ // Iterate over every key (i.e, path), and fetch the value at that key
1959
+ keys.forEach((key, keyIndex) => {
1960
+ matches.push(
1961
+ ...this._findMatches({
1962
+ key,
1963
+ value: item[keyIndex],
1964
+ searcher
1965
+ })
1966
+ );
1967
+ });
1968
+
1969
+ if (matches.length) {
1970
+ results.push({
1971
+ idx,
1972
+ item,
1973
+ matches
1974
+ });
1975
+ }
1976
+ });
1977
+
1978
+ return results
1979
+ }
1980
+ _findMatches({ key, value, searcher }) {
1981
+ if (!isDefined(value)) {
1982
+ return []
1983
+ }
1984
+
1985
+ let matches = [];
1986
+
1987
+ if (isArray(value)) {
1988
+ value.forEach(({ v: text, i: idx, n: norm }) => {
1989
+ if (!isDefined(text)) {
1990
+ return
1991
+ }
1992
+
1993
+ const { isMatch, score, indices } = searcher.searchIn(text);
1994
+
1995
+ if (isMatch) {
1996
+ matches.push({
1997
+ score,
1998
+ key,
1999
+ value: text,
2000
+ idx,
2001
+ norm,
2002
+ indices
2003
+ });
2004
+ }
2005
+ });
2006
+ } else {
2007
+ const { v: text, n: norm } = value;
2008
+
2009
+ const { isMatch, score, indices } = searcher.searchIn(text);
2010
+
2011
+ if (isMatch) {
2012
+ matches.push({ score, key, value: text, norm, indices });
2013
+ }
2014
+ }
2015
+
2016
+ return matches
2017
+ }
2018
+ }
2019
+
2020
+ Fuse.version = '7.0.0';
2021
+ Fuse.createIndex = createIndex;
2022
+ Fuse.parseIndex = parseIndex;
2023
+ Fuse.config = Config;
2024
+
2025
+ {
2026
+ Fuse.parseQuery = parse;
2027
+ }
2028
+
2029
+ {
2030
+ register(ExtendedSearch);
2031
+ }
2032
+
2033
+ /**
2034
+ * @fileoverview Fuzzy search strategy using Fuse.js.
2035
+ * Typo-tolerant matching for user-friendly search experience.
2036
+ */
2037
+ /** Cache for Fuse instances to avoid recreation on every search */
2038
+ let fuseCache = null;
2039
+ /**
2040
+ * Gets or creates a Fuse.js instance for the given items.
2041
+ * Caches the instance to improve performance for repeated searches.
2042
+ * Recreates if items or threshold change.
2043
+ * @param {IndexedItem[]} items - Array of pre-indexed items
2044
+ * @param {number} threshold - Fuzzy threshold (0-1, lower = stricter, default 0.3)
2045
+ * @returns {Fuse<IndexedItem>} Fuse.js instance
2046
+ */
2047
+ function getFuseInstance(items, threshold) {
2048
+ // Check if we can reuse cached instance (same items AND same threshold)
2049
+ if (fuseCache &&
2050
+ fuseCache.items === items &&
2051
+ fuseCache.threshold === threshold) {
2052
+ return fuseCache.fuse;
2053
+ }
2054
+ // Create new Fuse instance
2055
+ const fuse = new Fuse(items, {
2056
+ keys: ['label', 'norm'],
2057
+ threshold: threshold,
2058
+ includeScore: true,
2059
+ ignoreLocation: true,
2060
+ findAllMatches: true,
2061
+ });
2062
+ // Cache for reuse
2063
+ fuseCache = { items, threshold, fuse };
2064
+ return fuse;
2065
+ }
2066
+ /**
2067
+ * Searches items using fuzzy matching with Fuse.js.
2068
+ * Tolerates typos and finds approximate matches.
2069
+ *
2070
+ * @param {IndexedItem[]} indexedItems - Array of pre-indexed items
2071
+ * @param {string} query - The search query
2072
+ * @param {number} threshold - Fuzzy threshold (0-1, lower = stricter, default 0.3)
2073
+ * @returns {string[]} Array of matching item IDs sorted by relevance
2074
+ * @example
2075
+ * // Given items: [{ id: '1', label: 'System Grid Control', ... }]
2076
+ * searchFuzzy(items, 'Sytem') // ['1'] - matches despite typo
2077
+ * searchFuzzy(items, 'Systm Grid') // ['1'] - matches despite typo
2078
+ */
2079
+ function searchFuzzy(indexedItems, query, threshold = 0.3) {
2080
+ if (!query || !indexedItems?.length) {
2081
+ return indexedItems?.map(item => item.id) || [];
2082
+ }
2083
+ const fuse = getFuseInstance(indexedItems, threshold);
2084
+ const results = fuse.search(query);
2085
+ // Return IDs sorted by relevance (best matches first)
2086
+ return results.map(result => result.item.id);
2087
+ }
2088
+
2089
+ /**
2090
+ * @fileoverview Main search engine that orchestrates search operations.
2091
+ * Provides a unified interface for all filter modes.
2092
+ */
2093
+ /**
2094
+ * Default search options.
2095
+ */
2096
+ const DEFAULT_OPTIONS = {
2097
+ filterMode: 'strict',
2098
+ startFilterAt: 0,
2099
+ };
2100
+ /**
2101
+ * Main search function that applies the appropriate strategy based on filter mode.
2102
+ *
2103
+ * @param {IndexedItem[]} indexedItems - Array of pre-indexed items
2104
+ * @param {SearchOptions} options - Search options including query, filterMode, maxResults
2105
+ * @returns {SearchResult} Search result with matching IDs, total count, and truncation flag
2106
+ *
2107
+ * @example
2108
+ * const result = search(indexedItems, {
2109
+ * query: 'System Grid',
2110
+ * filterMode: 'smart',
2111
+ * maxResults: 100
2112
+ * });
2113
+ * // { ids: ['1', '2', '3'], total: 3, truncated: false }
2114
+ */
2115
+ function search(indexedItems, options) {
2116
+ const { query, filterMode = DEFAULT_OPTIONS.filterMode, maxResults: rawMaxResults, startFilterAt = DEFAULT_OPTIONS.startFilterAt, fuzzyThreshold: rawFuzzyThreshold, } = options;
2117
+ // Apply guards
2118
+ const maxResults = clampMaxResults(rawMaxResults);
2119
+ const fuzzyThreshold = clampFuzzyThreshold(rawFuzzyThreshold);
2120
+ // If query is too short, return all items (up to maxResults)
2121
+ if (!query || query.length < startFilterAt) {
2122
+ const allIds = indexedItems.map(item => item.id);
2123
+ const truncated = allIds.length > maxResults;
2124
+ return {
2125
+ ids: allIds.slice(0, maxResults),
2126
+ total: allIds.length,
2127
+ truncated,
2128
+ };
2129
+ }
2130
+ // Apply the appropriate search strategy
2131
+ let matchingIds;
2132
+ switch (filterMode) {
2133
+ case 'strict':
2134
+ matchingIds = searchStrict(indexedItems, query);
2135
+ break;
2136
+ case 'smart':
2137
+ matchingIds = searchSmart(indexedItems, query);
2138
+ break;
2139
+ case 'fuzzy':
2140
+ // Fuzzy search using Fuse.js on main thread
2141
+ matchingIds = searchFuzzy(indexedItems, query, fuzzyThreshold);
2142
+ break;
2143
+ default:
2144
+ matchingIds = searchStrict(indexedItems, query);
2145
+ }
2146
+ // Apply maxResults limit
2147
+ const total = matchingIds.length;
2148
+ const truncated = total > maxResults;
2149
+ const limitedIds = truncated ? matchingIds.slice(0, maxResults) : matchingIds;
2150
+ return {
2151
+ ids: limitedIds,
2152
+ total,
2153
+ truncated,
2154
+ };
2155
+ }
2156
+ /**
2157
+ * Determines if filtering should be offloaded to a Web Worker.
2158
+ *
2159
+ * @param {number} itemCount - Number of items to search
2160
+ * @param {FilterMode} filterMode - Current filter mode
2161
+ * @param {number} workerThreshold - Threshold for offloading to worker
2162
+ * @returns {boolean} True if filtering should use Web Worker
2163
+ */
2164
+ function shouldUseWorker(itemCount, filterMode, workerThreshold) {
2165
+ // Fuzzy mode always requires worker (Fuse.js runs there)
2166
+ if (filterMode === 'fuzzy') {
2167
+ return true;
2168
+ }
2169
+ // Large datasets should use worker to keep main thread responsive
2170
+ return itemCount > workerThreshold;
2171
+ }
2172
+ /**
2173
+ * Checks if Web Workers are supported in the current environment.
2174
+ *
2175
+ * @returns {boolean} True if Web Workers are supported
2176
+ */
2177
+ function isWorkerSupported() {
2178
+ return typeof Worker !== 'undefined';
2179
+ }
2180
+ /**
2181
+ * Gets the effective filter mode, falling back if necessary.
2182
+ * If fuzzy mode is requested but Workers aren't supported, falls back to smart.
2183
+ *
2184
+ * @param {FilterMode} requestedMode - The requested filter mode
2185
+ * @returns {FilterMode} The effective filter mode to use
2186
+ */
2187
+ function getEffectiveFilterMode(requestedMode) {
2188
+ if (requestedMode === 'fuzzy' && !isWorkerSupported()) {
2189
+ console.warn('Web Workers not supported. Falling back from fuzzy to smart mode.');
2190
+ return 'smart';
2191
+ }
2192
+ return requestedMode;
2193
+ }
2194
+
2195
+ /**
2196
+ * @fileoverview Client for communicating with the search Web Worker.
2197
+ * Provides a clean async API for search operations.
2198
+ */
2199
+ /**
2200
+ * Client for managing search operations via Web Worker.
2201
+ * Handles worker lifecycle, request tracking, and fallback behavior.
2202
+ */
2203
+ class SearchWorkerClient {
2204
+ /**
2205
+ * Creates a new SearchWorkerClient.
2206
+ * @param {string} [workerUrl] - URL to the worker script (optional, uses inline worker if not provided)
2207
+ */
2208
+ constructor(workerUrl) {
2209
+ this.workerUrl = workerUrl;
2210
+ this.worker = null;
2211
+ this.pendingRequests = new Map();
2212
+ this.requestCounter = 0;
2213
+ this.isInitialized = false;
2214
+ this.initPromise = null;
2215
+ /** Flag indicating if fallback mode is active (no worker available) */
2216
+ this.isFallbackMode = false;
2217
+ }
2218
+ /**
2219
+ * Checks if Web Workers are supported in the current environment.
2220
+ * @returns {boolean} True if Web Workers are supported
2221
+ */
2222
+ static isSupported() {
2223
+ return typeof Worker !== 'undefined';
2224
+ }
2225
+ /**
2226
+ * Generates a unique request ID.
2227
+ * @returns {string} A unique request ID
2228
+ */
2229
+ generateRequestId() {
2230
+ return `req-${++this.requestCounter}-${Date.now()}`;
2231
+ }
2232
+ /**
2233
+ * Creates and initializes the worker.
2234
+ * Note: Workers require a workerUrl to be provided. Inline workers are not
2235
+ * supported due to ES module limitations with importScripts.
2236
+ * @returns {Worker | null} The created Worker instance or null if creation failed
2237
+ */
2238
+ createWorker() {
2239
+ if (!SearchWorkerClient.isSupported()) {
2240
+ console.warn('Web Workers are not supported in this environment.');
2241
+ this.onFallback?.();
2242
+ return null;
2243
+ }
2244
+ try {
2245
+ if (this.workerUrl) {
2246
+ return new Worker(this.workerUrl, { type: 'module' });
2247
+ }
2248
+ // No workerUrl provided - fallback to main thread processing
2249
+ // Inline workers with ES modules are not supported
2250
+ console.warn('No workerUrl provided. Falling back to main thread processing.');
2251
+ this.onFallback?.();
2252
+ return null;
2253
+ }
2254
+ catch (error) {
2255
+ console.error('Failed to create Web Worker:', error);
2256
+ this.onFallback?.();
2257
+ return null;
2258
+ }
2259
+ }
2260
+ /**
2261
+ * Sets up message handlers for the worker.
2262
+ */
2263
+ setupMessageHandler() {
2264
+ if (!this.worker)
2265
+ return;
2266
+ this.worker.onmessage = (event) => {
2267
+ const { requestId, type, payload } = event.data;
2268
+ const pending = this.pendingRequests.get(requestId);
2269
+ if (!pending) {
2270
+ console.warn(`Received response for unknown request: ${requestId}`);
2271
+ return;
2272
+ }
2273
+ this.pendingRequests.delete(requestId);
2274
+ if (type === 'ERROR') {
2275
+ pending.reject(new Error(payload.error));
2276
+ }
2277
+ else {
2278
+ pending.resolve(payload);
2279
+ }
2280
+ };
2281
+ this.worker.onerror = (error) => {
2282
+ console.error('Worker error:', error);
2283
+ // Reject all pending requests
2284
+ this.pendingRequests.forEach(pending => {
2285
+ pending.reject(new Error('Worker error'));
2286
+ });
2287
+ this.pendingRequests.clear();
2288
+ };
2289
+ }
2290
+ /**
2291
+ * Sends a request to the worker and returns a promise for the result.
2292
+ * @param {WorkerRequest} request - The request object to send to the worker
2293
+ * @returns {Promise<SearchResult>} Promise that resolves to the search result
2294
+ */
2295
+ sendRequest(request) {
2296
+ return new Promise((resolve, reject) => {
2297
+ if (!this.worker) {
2298
+ reject(new Error('Worker not initialized'));
2299
+ return;
2300
+ }
2301
+ this.pendingRequests.set(request.requestId, { resolve, reject });
2302
+ this.worker.postMessage(request);
2303
+ });
2304
+ }
2305
+ /**
2306
+ * Initializes the worker with items to search.
2307
+ * @param {RawItem[]} items - Array of items to index
2308
+ * @returns {Promise<void>} Promise that resolves when initialization is complete
2309
+ */
2310
+ async init(items) {
2311
+ // If already initializing, wait for it
2312
+ if (this.initPromise) {
2313
+ await this.initPromise;
2314
+ // Re-init with new items
2315
+ }
2316
+ this.initPromise = this.doInit(items);
2317
+ await this.initPromise;
2318
+ }
2319
+ async doInit(items) {
2320
+ // If already in fallback mode, don't try to create worker again
2321
+ if (this.isFallbackMode) {
2322
+ return;
2323
+ }
2324
+ // Create worker if not exists
2325
+ if (!this.worker) {
2326
+ this.worker = this.createWorker();
2327
+ if (!this.worker) {
2328
+ // Worker creation failed - enter fallback mode silently
2329
+ this.isFallbackMode = true;
2330
+ this.onFallback?.();
2331
+ return;
2332
+ }
2333
+ this.setupMessageHandler();
2334
+ }
2335
+ const requestId = this.generateRequestId();
2336
+ try {
2337
+ await this.sendRequest({
2338
+ type: 'INIT',
2339
+ requestId,
2340
+ payload: { items },
2341
+ });
2342
+ this.isInitialized = true;
2343
+ }
2344
+ catch {
2345
+ // Worker init failed - enter fallback mode
2346
+ this.isFallbackMode = true;
2347
+ this.onFallback?.();
2348
+ this.terminate();
2349
+ }
2350
+ }
2351
+ /**
2352
+ * Performs a search operation.
2353
+ * @param {string} query - Search query string
2354
+ * @param {FilterMode} filterMode - Filter mode (strict/smart/fuzzy)
2355
+ * @param {number} maxResults - Maximum results to return
2356
+ * @param {number} fuzzyThreshold - Threshold for fuzzy matching
2357
+ * @returns {Promise<SearchResult>} Promise resolving to search results
2358
+ */
2359
+ async search(query, filterMode = 'strict', maxResults = DEFAULT_MAX_RESULTS, fuzzyThreshold = DEFAULT_FUZZY_THRESHOLD) {
2360
+ if (!this.isInitialized || !this.worker) {
2361
+ throw new Error('Worker not initialized. Call init() first.');
2362
+ }
2363
+ const requestId = this.generateRequestId();
2364
+ return this.sendRequest({
2365
+ type: 'SEARCH',
2366
+ requestId,
2367
+ payload: {
2368
+ query,
2369
+ filterMode,
2370
+ maxResults: clampMaxResults(maxResults),
2371
+ fuzzyThreshold: clampFuzzyThreshold(fuzzyThreshold),
2372
+ },
2373
+ });
2374
+ }
2375
+ /**
2376
+ * Cancels all pending requests.
2377
+ */
2378
+ cancelPending() {
2379
+ this.pendingRequests.forEach(pending => {
2380
+ pending.reject(new Error('Request cancelled'));
2381
+ });
2382
+ this.pendingRequests.clear();
2383
+ }
2384
+ /**
2385
+ * Terminates the worker and cleans up resources.
2386
+ */
2387
+ terminate() {
2388
+ this.cancelPending();
2389
+ this.worker?.terminate();
2390
+ this.worker = null;
2391
+ this.isInitialized = false;
2392
+ this.initPromise = null;
2393
+ // Note: we don't reset isFallbackMode here to avoid retrying worker creation
2394
+ }
2395
+ /**
2396
+ * Returns whether the worker is ready to perform searches.
2397
+ * @returns {boolean} True if the worker is initialized and ready
2398
+ */
2399
+ get isReady() {
2400
+ return this.isInitialized && this.worker !== null;
2401
+ }
2402
+ /**
2403
+ * Returns whether the client is in fallback mode (no worker available).
2404
+ * @returns {boolean} True if the client is operating in fallback mode
2405
+ */
2406
+ get inFallbackMode() {
2407
+ return this.isFallbackMode;
2408
+ }
2409
+ }
2410
+
2411
+ /**
2412
+ * @fileoverview Internationalization utilities for Nova components.
2413
+ * Provides automatic locale detection and default translations.
2414
+ */
2415
+ /**
2416
+ * Default translations for truncated results text.
2417
+ * Supports placeholders: {shown} and {total}
2418
+ */
2419
+ const TRUNCATED_RESULTS_TRANSLATIONS = {
2420
+ en: '{shown} results shown out of {total} — refine your search',
2421
+ fr: '{shown} résultats affichés sur {total} — affinez votre recherche',
2422
+ de: '{shown} von {total} Ergebnissen angezeigt — verfeinern Sie Ihre Suche',
2423
+ es: '{shown} resultados mostrados de {total} — refine su búsqueda',
2424
+ it: '{shown} risultati mostrati su {total} — perfeziona la tua ricerca',
2425
+ nl: '{shown} resultaten getoond van {total} — verfijn uw zoekopdracht',
2426
+ pt: '{shown} resultados exibidos de {total} — refine sua pesquisa',
2427
+ ru: '{shown} результатов показано из {total} — уточните поиск',
2428
+ ja: '{shown}件の結果が{total}件中表示されています — 検索を絞り込んでください',
2429
+ zh: '{shown}个结果,共{total}个 — 请细化您的搜索',
2430
+ ko: '{shown}개 결과가 {total}개 중 표시됨 — 검색을 구체화하세요',
2431
+ };
2432
+ /**
2433
+ * Gets the browser's locale (language code).
2434
+ * Falls back to 'en' if detection fails.
2435
+ *
2436
+ * @returns {string} The locale code (e.g., 'en', 'fr', 'de')
2437
+ */
2438
+ function getBrowserLocale() {
2439
+ if (typeof navigator === 'undefined') {
2440
+ return 'en';
2441
+ }
2442
+ // Try navigator.language first (e.g., 'en-US', 'fr-FR')
2443
+ const language = navigator.language || navigator.languages?.[0] || 'en';
2444
+ // Extract the base language code (e.g., 'en' from 'en-US')
2445
+ return language.split('-')[0].toLowerCase();
2446
+ }
2447
+ /**
2448
+ * Gets the localized truncated results text.
2449
+ * Automatically detects browser locale and provides appropriate translation.
2450
+ * Falls back to English if translation is not available.
2451
+ *
2452
+ * @param {string} [locale] - Optional locale override. If not provided, uses browser locale.
2453
+ * @returns {string} The localized text with placeholders {shown} and {total}
2454
+ *
2455
+ * @example
2456
+ * getTruncatedResultsText() // Uses browser locale
2457
+ * getTruncatedResultsText('fr') // Returns French translation
2458
+ * getTruncatedResultsText('de') // Returns German translation
2459
+ */
2460
+ function getTruncatedResultsText(locale) {
2461
+ const targetLocale = locale
2462
+ ? locale.split('-')[0].toLowerCase()
2463
+ : getBrowserLocale();
2464
+ return (TRUNCATED_RESULTS_TRANSLATIONS[targetLocale] ||
2465
+ TRUNCATED_RESULTS_TRANSLATIONS.en);
2466
+ }
2467
+ /**
2468
+ * Formats the truncated results text by replacing placeholders with actual values.
2469
+ *
2470
+ * @param {string} template - The text template with {shown} and {total} placeholders
2471
+ * @param {number} shown - The number of items shown
2472
+ * @param {number} total - The total number of items
2473
+ * @returns {string} The formatted text with replaced placeholders
2474
+ *
2475
+ * @example
2476
+ * formatTruncatedResults('{shown} of {total}', 10, 100) // '10 of 100'
2477
+ */
2478
+ function formatTruncatedResults(template, shown, total) {
2479
+ return template
2480
+ .replace(/{shown}/g, String(shown))
2481
+ .replace(/{total}/g, String(total));
2482
+ }
2483
+
2484
+ exports.FUZZY_DEBOUNCE_DELAY = FUZZY_DEBOUNCE_DELAY;
2485
+ exports.SearchWorkerClient = SearchWorkerClient;
2486
+ exports.buildIndex = buildIndex;
2487
+ exports.clampMaxResults = clampMaxResults;
2488
+ exports.clampWorkerThreshold = clampWorkerThreshold;
2489
+ exports.formatTruncatedResults = formatTruncatedResults;
2490
+ exports.getEffectiveFilterMode = getEffectiveFilterMode;
2491
+ exports.getTruncatedResultsText = getTruncatedResultsText;
2492
+ exports.isWorkerSupported = isWorkerSupported;
2493
+ exports.search = search;
2494
+ exports.shouldUseWorker = shouldUseWorker;