@kws3/ui 1.9.1 → 2.0.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 (80) hide show
  1. package/CHANGELOG.mdx +76 -48
  2. package/buttons/ConfirmButton.svelte +11 -3
  3. package/buttons/DeleteButton.svelte +2 -3
  4. package/buttons/ProcessButton.svelte +3 -4
  5. package/buttons/SubmitButton.svelte +11 -3
  6. package/charts/AreaChart.svelte +1 -1
  7. package/charts/BarChart.svelte +1 -1
  8. package/charts/Chart.svelte +1 -0
  9. package/charts/DonutChart.svelte +1 -1
  10. package/charts/LineChart.svelte +1 -1
  11. package/charts/MixedChart.svelte +1 -1
  12. package/charts/PieChart.svelte +1 -1
  13. package/charts/RadialChart.svelte +1 -1
  14. package/charts/utils.js +1 -0
  15. package/controls/Checkbox.svelte +10 -4
  16. package/controls/FileUpload.svelte +14 -8
  17. package/controls/NumberInput.svelte +19 -10
  18. package/controls/Radio.svelte +8 -2
  19. package/controls/RangeSlider.svelte +8 -2
  20. package/controls/Toggle.svelte +9 -2
  21. package/controls/ToggleButtons.svelte +10 -3
  22. package/datagrid/DataSearch/DataSearch.svelte +1 -1
  23. package/datagrid/GridView/GridCell.svelte +3 -0
  24. package/datagrid/GridView/GridRow.svelte +15 -0
  25. package/datagrid/GridView/GridView.svelte +21 -3
  26. package/datagrid/Pagination/Pagination.svelte +3 -3
  27. package/datagrid/TileView/TileView.svelte +46 -5
  28. package/datagrid/TileView/TileViewItem.svelte +4 -0
  29. package/form/index.js +160 -0
  30. package/forms/AutoComplete.svelte +126 -76
  31. package/forms/Datepicker.svelte +22 -5
  32. package/forms/PasswordValidator/PasswordValidator.svelte +8 -8
  33. package/forms/PasswordValidator/validatePassword.js +13 -2
  34. package/forms/SearchInput.svelte +180 -0
  35. package/forms/Timepicker.svelte +69 -4
  36. package/forms/actions.js +21 -15
  37. package/forms/colorpicker/Colorpicker.js +28 -3
  38. package/forms/colorpicker/Colorpicker.svelte +2 -2
  39. package/forms/select/MultiSelect.svelte +103 -46
  40. package/forms/select/SearchableSelect.svelte +6 -5
  41. package/helpers/CardModal.svelte +2 -1
  42. package/helpers/ClipboardCopier.svelte +4 -1
  43. package/helpers/Dialog/Dialog.svelte +13 -8
  44. package/helpers/Dialog/index.js +6 -0
  45. package/helpers/Divider.svelte +2 -2
  46. package/helpers/FloatingUI/Floatie.svelte +6 -6
  47. package/helpers/FloatingUI/index.js +2 -1
  48. package/helpers/Icon.svelte +25 -9
  49. package/helpers/Loader.svelte +10 -3
  50. package/helpers/Message.svelte +2 -2
  51. package/helpers/Modal.svelte +2 -1
  52. package/helpers/Notification.svelte +1 -1
  53. package/helpers/Popover.svelte +4 -4
  54. package/helpers/ScrollableList.svelte +12 -8
  55. package/helpers/Skeleton.svelte +4 -1
  56. package/helpers/Timeline/Timeline.svelte +1 -1
  57. package/helpers/Timeline/TimelineItem.svelte +5 -5
  58. package/helpers/Tooltip.js +1 -1
  59. package/index.js +10 -4
  60. package/internal/fuzzy.js +116 -0
  61. package/internal/index.js +27 -0
  62. package/internal/scrollIntoActiveElement.js +22 -0
  63. package/keyboard/index.js +94 -0
  64. package/package.json +6 -4
  65. package/{utils/resizeObserver.js → resizeObserver/index.js} +0 -0
  66. package/search/index.js +52 -0
  67. package/settings.js +1 -1
  68. package/sliding-panes/SlidingPane.svelte +1 -4
  69. package/styles/AutoComplete.scss +2 -1
  70. package/styles/Datepicker.scss +1 -1
  71. package/styles/Grid.scss +14 -0
  72. package/styles/Select.scss +2 -1
  73. package/transitions/components/Scale.svelte +1 -0
  74. package/transitions/components/getEasing.js +18 -5
  75. package/types/ambient.d.ts +16 -0
  76. package/types/index.d.ts +46 -0
  77. package/types/type-defs/index.ts +14 -0
  78. package/utils/index.js +110 -9
  79. package/utils/fuzzysearch.js +0 -20
  80. package/utils/keyboard-events.js +0 -32
@@ -2,9 +2,9 @@
2
2
  @component
3
3
 
4
4
 
5
- @param {string} [value=""] - Value of the Input
5
+ @param {?string} [value=] - Value of the Input
6
6
 
7
- This property can be bound to, to fetch the current value, Default: `""`
7
+ This property can be bound to, to fetch the current value, Default: ``
8
8
  @param {string} [placeholder=""] - Placeholder text for the input, Default: `""`
9
9
  @param {array} [options=[]] - Array of strings, or objects.
10
10
  Used to populate the list of options in the dropdown, Default: `[]`
@@ -12,12 +12,12 @@ Used to populate the list of options in the dropdown, Default: `[]`
12
12
 
13
13
  Only send this prop if you want to fetch `options` asynchronously.
14
14
  `options` prop will be ignored if this prop is set., Default: `null`
15
- @param {'fuzzy'|'strict'} [search_strategy="fuzzy"] - Filtered options to be displayed strictly based on search text or perform a fuzzy match.
15
+ @param {string|'fuzzy'|'strict'} [search_strategy="fuzzy"] - Filtered options to be displayed strictly based on search text or perform a fuzzy match.
16
16
  Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching., Default: `"fuzzy"`
17
17
  @param {boolean} [highlighted_results=true] - Whether to show the highlighted or plain results in the dropdown., Default: `true`
18
18
  @param {number} [scoreThreshold=5] - Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches., Default: `5`
19
- @param {''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
20
- @param {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
19
+ @param {string|''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
20
+ @param {string|''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
21
21
  @param {string} [style=""] - Inline CSS for input container, Default: `""`
22
22
  @param {boolean} [readonly=false] - Marks component as read-only, Default: `false`
23
23
  @param {boolean} [disabled=false] - Disables the component, Default: `false`
@@ -75,8 +75,22 @@ Default value: `<span>{option.label}</span>`
75
75
  on:mousedown|preventDefault|stopPropagation={() =>
76
76
  handleOptionMouseDown(option)}
77
77
  on:mouseenter|preventDefault|stopPropagation={() => {
78
+ if (mouseTracker.preventSelect) return;
78
79
  active_option = option;
79
80
  }}
81
+ on:mousemove|preventDefault|stopPropagation={(e) => {
82
+ let { preventSelect, lastX, lastY } = mouseTracker;
83
+ if (
84
+ preventSelect &&
85
+ (lastX !== e.clientX || lastY !== e.clientY)
86
+ ) {
87
+ mouseTracker.preventSelect = false;
88
+ active_option = option;
89
+ }
90
+ // mouse x,y is not in same position after the scrolling
91
+ mouseTracker.lastX = e.clientX;
92
+ mouseTracker.lastY = e.clientY;
93
+ }}
80
94
  class="is-size-{list_text_size[size]}"
81
95
  class:active={active_option === option}>
82
96
  <!--
@@ -100,7 +114,8 @@ Default value: `<span>{option.label}</span>`
100
114
  import { debounce } from "@kws3/ui/utils";
101
115
  import { createEventDispatcher, onMount, tick } from "svelte";
102
116
  import { createPopper } from "@popperjs/core";
103
- import fuzzy from "fuzzy.js";
117
+ import { makeSearchEngine } from "@kws3/ui/search";
118
+ import { scrollIntoActiveElement } from "../internal";
104
119
 
105
120
  const sameWidthPopperModifier = {
106
121
  name: "sameWidth",
@@ -120,10 +135,16 @@ Default value: `<span>{option.label}</span>`
120
135
 
121
136
  const rootContainerId = "kws-overlay-root";
122
137
 
138
+ /**
139
+ * @typedef {import('@kws3/ui/types').ColorOptions} ColorOptions
140
+ * @typedef {import('@kws3/ui/types').SizeOptions} SizeOptions
141
+ */
142
+
123
143
  /**
124
144
  * Value of the Input
125
145
  *
126
146
  * This property can be bound to, to fetch the current value
147
+ * @type {?string}
127
148
  */
128
149
  export let value = "";
129
150
 
@@ -149,7 +170,7 @@ Default value: `<span>{option.label}</span>`
149
170
  /**
150
171
  * Filtered options to be displayed strictly based on search text or perform a fuzzy match.
151
172
  * Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching.
152
- * @type {'fuzzy'|'strict'}
173
+ * @type {string|'fuzzy'|'strict'}
153
174
  */
154
175
  export let search_strategy = "fuzzy";
155
176
 
@@ -160,16 +181,17 @@ Default value: `<span>{option.label}</span>`
160
181
 
161
182
  /**
162
183
  * Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches.
184
+ * @type {number}
163
185
  */
164
186
  export let scoreThreshold = 5;
165
187
  /**
166
188
  * Size of the input
167
- * @type {''|'small'|'medium'|'large'}
189
+ * @type {import('@kws3/ui/types').SizeOptions}
168
190
  */
169
191
  export let size = "";
170
192
  /**
171
193
  * Color of the input
172
- * @type {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'}
194
+ * @type {import('@kws3/ui/types').ColorOptions}
173
195
  */
174
196
  export let color = "";
175
197
  /**
@@ -222,10 +244,16 @@ Default value: `<span>{option.label}</span>`
222
244
  active_option = "",
223
245
  searching = true,
224
246
  show_options = false,
247
+ mouseTracker = {
248
+ lastX: 0,
249
+ lastY: 0, // to check actual mouse is moving or not, for WebKit compatibility,
250
+ preventSelect: false, //prevent select by mouse when up or down key is pressed
251
+ },
225
252
  filtered_options = [], //list of options filtered by search query
226
253
  normalised_options = [], //list of options normalised
227
254
  options_loading = false, //indictaes whether async search function is running
228
- mounted = false; //indicates whether component is mounted
255
+ mounted = false, //indicates whether component is mounted
256
+ fuzzysearch = null;
229
257
 
230
258
  let list_text_size = {
231
259
  small: "7",
@@ -262,38 +290,11 @@ Default value: `<span>{option.label}</span>`
262
290
  }
263
291
 
264
292
  function triggerSearch(filters) {
265
- let cache = {};
266
- //TODO - can optimize more for very long lists
267
- filters.forEach((word, idx) => {
268
- // iterate over each word in the search query
269
- let opts = [];
270
- if (word) {
271
- if (allow_fuzzy_match) {
272
- opts = fuzzySearch(word, normalised_options);
273
- } else {
274
- opts = [...normalised_options].filter((item) => {
275
- // filter out items that don't match `filter`
276
- if (typeof item === "object" && item.value) {
277
- return (
278
- typeof item.value === "string" &&
279
- item.value.toLowerCase().indexOf(word) > -1
280
- );
281
- }
282
- });
283
- }
284
- }
285
-
286
- cache[idx] = opts; // storing options to current index on cache
287
- });
288
-
289
- filtered_options = Object.values(cache) // get values from cache
290
- .flat() // flatten array
291
- .filter((v, i, self) => i === self.findIndex((t) => t.value === v.value)); // remove duplicates
292
-
293
- if (highlighted_results && !allow_fuzzy_match) {
294
- filtered_options = highlightMatches(filtered_options, filters);
293
+ if (allow_fuzzy_match) {
294
+ debouncedFuzzySearch(filters, [...normalised_options]);
295
+ } else {
296
+ searchInStrictMode(filters, [...normalised_options]);
295
297
  }
296
- setOptionsVisible(true);
297
298
  }
298
299
 
299
300
  function triggerExternalSearch(filters) {
@@ -303,19 +304,20 @@ Default value: `<span>{option.label}</span>`
303
304
  return;
304
305
  }
305
306
  options_loading = true;
306
- // filtered_options = [];
307
- search(filters).then((_options) => {
308
- searching = false;
309
- options_loading = false;
310
- tick().then(() => {
311
- filtered_options = normaliseArraysToObjects(_options);
312
-
313
- if (highlighted_results) {
314
- filtered_options = highlightMatches(filtered_options, filters);
315
- }
316
- setOptionsVisible(true);
307
+ if (search) {
308
+ search(filters).then((_options) => {
309
+ searching = false;
310
+ options_loading = false;
311
+ tick().then(() => {
312
+ filtered_options = normaliseArraysToObjects(_options);
313
+
314
+ if (highlighted_results) {
315
+ filtered_options = highlightMatches(filtered_options, filters);
316
+ }
317
+ setOptionsVisible(true);
318
+ });
317
319
  });
318
- });
320
+ }
319
321
  }
320
322
 
321
323
  const debouncedTriggerSearch = debounce(triggerExternalSearch, 150, false);
@@ -324,18 +326,29 @@ Default value: `<span>{option.label}</span>`
324
326
  POPPER = createPopper(el, dropdown, {
325
327
  strategy: "fixed",
326
328
  placement: "bottom-start",
329
+ // @ts-ignore
327
330
  modifiers: [sameWidthPopperModifier],
328
331
  });
329
332
 
330
- if (allow_fuzzy_match && fuzzy) {
331
- fuzzy.analyzeSubTerms = true;
332
- fuzzy.analyzeSubTermDepth = 10;
333
- fuzzy.highlighting.before = "";
334
- fuzzy.highlighting.after = "";
333
+ if (allow_fuzzy_match) {
334
+ let fuzzyOpts = {
335
+ analyzeSubTerms: true,
336
+ analyzeSubTermDepth: 10,
337
+ highlighting: {
338
+ after: "",
339
+ before: "",
340
+ },
341
+ };
335
342
  if (highlighted_results) {
336
- fuzzy.highlighting.before = `<span class="h">`;
337
- fuzzy.highlighting.after = "</span>";
343
+ fuzzyOpts.highlighting.before = `<span class="h">`;
344
+ fuzzyOpts.highlighting.after = "</span>";
338
345
  }
346
+ let searchOptions = {
347
+ search_key: "label",
348
+ scoreThreshold,
349
+ fuzzyOpts,
350
+ };
351
+ fuzzysearch = makeSearchEngine(searchOptions);
339
352
  }
340
353
 
341
354
  //normalize value
@@ -384,6 +397,14 @@ Default value: `<span>{option.label}</span>`
384
397
  active_option = filtered_options[0];
385
398
  else active_option = filtered_options[newActiveIdx];
386
399
  }
400
+
401
+ tick().then(() => {
402
+ if (dropdown) {
403
+ mouseTracker.preventSelect = true;
404
+ let activeElem = dropdown.querySelector(".active");
405
+ scrollIntoActiveElement(dropdown, activeElem);
406
+ }
407
+ });
387
408
  } else {
388
409
  active_option = "";
389
410
  searching = true;
@@ -446,25 +467,54 @@ Default value: `<span>{option.label}</span>`
446
467
  return v && v.trim() ? v.toLowerCase().trim().split(/\s+/) : [];
447
468
  }
448
469
 
449
- function fuzzySearch(word, options) {
450
- let OPTS = options.map((item) => {
451
- let output = fuzzy(item.value, word);
452
- item = { ...output, ...item };
453
- item.label = output.highlightedTerm;
454
- item.score =
455
- !item.score || (item.score && item.score < output.score)
456
- ? output.score
457
- : item.score || 0;
458
- return item;
470
+ const debouncedFuzzySearch = debounce(searchInFuzzyMode, 200, false);
471
+
472
+ function searchInFuzzyMode(filters, options) {
473
+ let cache = {};
474
+ //TODO - can optimize more for very long lists
475
+ filters.forEach((word, idx) => {
476
+ // iterate over each word in the search query
477
+ let opts = [];
478
+ if (word) {
479
+ let result = fuzzysearch(word, options);
480
+ opts = result;
481
+ }
482
+
483
+ cache[idx] = opts; // storing options to current index on cache
459
484
  });
485
+ setFilteredOptions(cache);
486
+ }
487
+
488
+ function searchInStrictMode(filters, options) {
489
+ let cache = {};
490
+ filters.forEach((word, idx) => {
491
+ // iterate over each word in the search query
492
+ let opts = [];
493
+ if (word) {
494
+ opts = options.filter((item) => {
495
+ // filter out items that don't match `filter`
496
+ if (typeof item === "object" && item.value) {
497
+ return (
498
+ typeof item.value === "string" &&
499
+ item.value.toLowerCase().indexOf(word) > -1
500
+ );
501
+ }
502
+ });
503
+ }
460
504
 
461
- let maxScore = Math.max(...OPTS.map((i) => i.score));
462
- let calculatedLimit = maxScore - scoreThreshold;
505
+ cache[idx] = opts; // storing options to current index on cache
506
+ });
507
+ setFilteredOptions(cache, filters);
508
+ }
463
509
 
464
- OPTS = OPTS.filter(
465
- (r) => r.score > (calculatedLimit > 0 ? calculatedLimit : 0)
466
- );
510
+ function setFilteredOptions(cache, filters) {
511
+ filtered_options = Object.values(cache) // get values from cache
512
+ .flat() // flatten array
513
+ .filter((v, i, self) => i === self.findIndex((t) => t.value === v.value)); // remove duplicates
467
514
 
468
- return OPTS;
515
+ if (highlighted_results && !allow_fuzzy_match) {
516
+ filtered_options = highlightMatches(filtered_options, filters);
517
+ }
518
+ setOptionsVisible(true);
469
519
  }
470
520
  </script>
@@ -8,10 +8,10 @@ In `range_mode`, the expected format is `yyyy-mm-dd to yyyy-mm-dd`
8
8
 
9
9
  This property can be bound to, to fetch the selected date or date range. Output is in the same format as input., Default: `""`
10
10
  @param {string} [style=""] - Inline CSS for the input, Default: `""`
11
- @param {''|'primary'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Colour of the Date picker input, Default: `""`
11
+ @param {string|''|'primary'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Colour of the Date picker input, Default: `""`
12
12
  @param {boolean} [disabled=false] - Disables the component, Default: `false`
13
13
  @param {string} [placeholder="Select Date.."] - Placeholder text for the input, Default: `"Select Date.."`
14
- @param {'primary'|'warning'|'info'|'danger'|'dark'|'light'} [calendar_color="primary"] - Colour of the Calendar, Default: `"primary"`
14
+ @param {string|'primary'|'warning'|'info'|'danger'|'dark'|'light'} [calendar_color="primary"] - Colour of the Calendar, Default: `"primary"`
15
15
  @param {any} [min_date=null] - Set earliest selectable date as an object or string
16
16
 
17
17
  **Example:** `"2021-06-06"` or `"(new Date('2021-01-01'))"`, Default: `null`
@@ -55,11 +55,15 @@ See: https://flatpickr.js.org/options/, Default: `{}`
55
55
  on:yearChange={fireYearChange} />
56
56
 
57
57
  <script>
58
- import { datepicker } from "./actions";
59
58
  import { createEventDispatcher } from "svelte";
59
+ import { datepicker } from "./actions";
60
60
 
61
61
  const fire = createEventDispatcher();
62
62
 
63
+ /**
64
+ * @typedef {import('@kws3/ui/types').ColorOptions} ColorOptions
65
+ */
66
+
63
67
  /**
64
68
  * Accepts a date value in the format `yyyy-mm-dd`
65
69
  *
@@ -74,20 +78,24 @@ See: https://flatpickr.js.org/options/, Default: `{}`
74
78
  export let style = "";
75
79
  /**
76
80
  * Colour of the Date picker input
77
- * @type {''|'primary'|'warning'|'info'|'danger'|'dark'|'light'}
81
+ * @type {ColorOptions} color
78
82
  */
79
83
  export let color = "";
80
84
  /**
81
85
  * Disables the component
82
86
  */
83
87
  export let disabled = false;
88
+ /**
89
+ * Make input value read-only
90
+ */
91
+ export let readonly = false;
84
92
  /**
85
93
  * Placeholder text for the input
86
94
  */
87
95
  export let placeholder = "Select Date..";
88
96
  /**
89
97
  * Colour of the Calendar
90
- * @type {'primary'|'warning'|'info'|'danger'|'dark'|'light'}
98
+ * @type {Exclude<ColorOptions, ''>}
91
99
  */
92
100
  export let calendar_color = "primary";
93
101
  /**
@@ -140,9 +148,13 @@ See: https://flatpickr.js.org/options/, Default: `{}`
140
148
  min_date,
141
149
  max_date,
142
150
  options,
151
+ readonly,
143
152
  fillOptions();
144
153
 
145
154
  function fillOptions() {
155
+ /**
156
+ * @type {object}
157
+ */
146
158
  let _opts = Object.assign(
147
159
  {
148
160
  color: calendar_color,
@@ -164,6 +176,11 @@ See: https://flatpickr.js.org/options/, Default: `{}`
164
176
  if (max_date) {
165
177
  _opts.maxDate = max_date;
166
178
  }
179
+
180
+ _opts.clickOpens = true;
181
+ if (readonly) {
182
+ _opts.clickOpens = false;
183
+ }
167
184
  opts = _opts;
168
185
  }
169
186
 
@@ -30,22 +30,22 @@ Should be used with `bind` from parent component, Default: `false`
30
30
  <li>
31
31
  <span class="help expanded">
32
32
  <span
33
- class="tag is-small is-normal is-light pv-identifier {opt.passed
33
+ class="tag is-small is-normal is-light pv-identifier {opt['passed']
34
34
  ? 'is-success'
35
- : 'is-danger'}">{opt.identifier}</span>
36
- <span class="pv-text">{opt.text}</span>
35
+ : 'is-danger'}">{opt["identifier"]}</span>
36
+ <span class="pv-text">{opt["text"]}</span>
37
37
  <Icon
38
38
  class="pv-icon"
39
- icon={opt.passed ? "check" : "ban"}
40
- color={opt.passed ? "success" : "danger"} />
39
+ icon={opt["passed"] ? "check" : "ban"}
40
+ color={opt["passed"] ? "success" : "danger"} />
41
41
  </span>
42
42
  <span class="summarized">
43
43
  <span
44
- data-tooltip={opt.text}
44
+ data-tooltip={opt["text"]}
45
45
  data-tippy-hideOnClick="false"
46
- class="tag is-small is-normal is-light pv-identifier {opt.passed
46
+ class="tag is-small is-normal is-light pv-identifier {opt['passed']
47
47
  ? 'is-success'
48
- : 'is-danger'}">{opt.identifier}</span>
48
+ : 'is-danger'}">{opt["identifier"]}</span>
49
49
  </span>
50
50
  </li>
51
51
  {/each}
@@ -4,11 +4,20 @@ export default function (password, options) {
4
4
  overall: false,
5
5
  };
6
6
 
7
+ /**
8
+ * @typedef {import('@kws3/ui/types').ValidatePasswordOptions} ValidatePasswordOptions - contains ValidatePassword options
9
+ */
10
+
7
11
  result.items = (options || []).slice().map((_opt) => {
12
+ /** @type {ValidatePasswordOptions} */
8
13
  const opt = Object.assign({}, _opt);
9
14
  if (opt && opt.active) {
10
15
  if (opt.name === "kws_pv_min_length") {
11
- if (password && password.length >= opt.value) {
16
+ if (
17
+ typeof opt.value != "undefined" &&
18
+ password &&
19
+ password.length >= opt.value
20
+ ) {
12
21
  opt.passed = true;
13
22
  }
14
23
  } else {
@@ -24,7 +33,9 @@ export default function (password, options) {
24
33
  });
25
34
 
26
35
  result.overall =
27
- result.items.filter((el) => el.passed).length === result.items.length;
36
+ result.items.filter(
37
+ (/** @type {ValidatePasswordOptions} */ el) => el.passed
38
+ ).length === result.items.length;
28
39
 
29
40
  return result;
30
41
  }
@@ -0,0 +1,180 @@
1
+ <!--
2
+ @component
3
+
4
+
5
+ @param {string|''|'small'|'medium'|'large'} [size="small"] - Size of the input, Default: `"small"`
6
+ @param {string|''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
7
+ @param {string} [placeholder="Search"] - Placeholder text for the input, Default: `"Search"`
8
+ @param {boolean} [readonly=false] - Marks component as read-only, Default: `false`
9
+ @param {boolean} [disabled=false] - Disables the component, Default: `false`
10
+ @param {array} [options=[]] - Array of objects., Default: `[]`
11
+ @param {array} [searchableKeys=[]] - array of object properties to search in., Default: `[]`
12
+ @param {boolean} [highlighted_results=true] - Whether to show the highlighted or plain results in the dropdown., Default: `true`
13
+ @param {number} [scoreThreshold=2] - Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches., Default: `2`
14
+ @param {boolean} [word_match=false] - Whether to match against each word seperatly or whole sentence in flow., Default: `false`
15
+ @param {string} [style=""] - Inline CSS for the input, Default: `""`
16
+ @param {string} [class=""] - CSS classes for the input, Default: `""`
17
+
18
+ -->
19
+ <div
20
+ class="
21
+ field has-addons is-marginless
22
+ {readonly ? 'is-readonly' : ''}
23
+ {disabled ? 'is-disabled' : ''}
24
+ ">
25
+ <div class="control is-expanded has-icons-left">
26
+ <input
27
+ class="input is-{size} is-{color} {klass}"
28
+ {placeholder}
29
+ {disabled}
30
+ {readonly}
31
+ {style}
32
+ bind:value={keywords}
33
+ on:keyup={debouncedSearch} />
34
+ <Icon icon="search" size="small" class="is-left" />
35
+ </div>
36
+ {#if keywords}
37
+ <div class="control">
38
+ <button class="button is-danger is-{size}" type="button" on:click={reset}>
39
+ <Icon icon="times" size="small" />
40
+ </button>
41
+ </div>
42
+ {/if}
43
+ </div>
44
+
45
+ <script>
46
+ import { Icon } from "@kws3/ui";
47
+ import { debounce } from "@kws3/ui/utils";
48
+ import { onDestroy, onMount } from "svelte";
49
+ import { makeSearchEngine } from "@kws3/ui/search";
50
+
51
+ /**
52
+ * Size of the input
53
+ * @type {import('@kws3/ui/types').SizeOptions} size
54
+ */
55
+ export let size = "small";
56
+ /**
57
+ * Color of the input
58
+ * @type {import('@kws3/ui/types').ColorOptions} color
59
+ */
60
+ export let color = "";
61
+ /**
62
+ * Placeholder text for the input
63
+ */
64
+ export let placeholder = "Search";
65
+ /**
66
+ * Marks component as read-only
67
+ */
68
+ export let readonly = false;
69
+ /**
70
+ * Disables the component
71
+ */
72
+ export let disabled = false;
73
+ /**
74
+ * Array of objects.
75
+ * @type {array}
76
+ */
77
+ export let options = [];
78
+ /**
79
+ * array of object properties to search in.
80
+ * @type {array}
81
+ */
82
+ export let searchableKeys = [];
83
+ /**
84
+ * Whether to show the highlighted or plain results in the dropdown.
85
+ */
86
+ export let highlighted_results = true;
87
+
88
+ /**
89
+ * Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches.
90
+ * @type {number}
91
+ */
92
+ export let scoreThreshold = 2;
93
+ /**
94
+ * Whether to match against each word seperatly or whole sentence in flow.
95
+ */
96
+ export let word_match = false;
97
+ /**
98
+ * Inline CSS for the input
99
+ */
100
+ export let style = "";
101
+ /**
102
+ * CSS classes for the input
103
+ */
104
+ let klass = "";
105
+ export { klass as class };
106
+
107
+ let keywords = "",
108
+ orginalItems = [],
109
+ fuzzysearch = null;
110
+
111
+ const debouncedSearch = debounce(search, 300);
112
+
113
+ const sanitizeValue = (v) =>
114
+ v && v.trim() ? v.toLowerCase().trim().split(/\s+/) : [];
115
+
116
+ onMount(() => {
117
+ let fuzzyOpts = {
118
+ analyzeSubTerms: true,
119
+ analyzeSubTermDepth: 10,
120
+ highlighting: {
121
+ after: "",
122
+ before: "",
123
+ },
124
+ };
125
+ if (highlighted_results) {
126
+ fuzzyOpts.highlighting.before = `<span class="h">`;
127
+ fuzzyOpts.highlighting.after = "</span>";
128
+ }
129
+
130
+ let searchOptions = {
131
+ search_key: searchableKeys,
132
+ scoreThreshold,
133
+ fuzzyOpts,
134
+ };
135
+
136
+ fuzzysearch = makeSearchEngine(searchOptions);
137
+
138
+ if (word_match) {
139
+ options.forEach((item, i) => {
140
+ item._uid = i;
141
+ });
142
+ orginalItems = [...options];
143
+ } else {
144
+ orginalItems = [...options];
145
+ }
146
+ });
147
+
148
+ onDestroy(reset);
149
+
150
+ function search() {
151
+ if (!keywords) {
152
+ reset();
153
+ return;
154
+ }
155
+ let result = [];
156
+
157
+ if (word_match) {
158
+ let cache = {},
159
+ filters = sanitizeValue(keywords);
160
+ filters.forEach((word, idx) => {
161
+ // iterate over each word in the search query
162
+ let opts = [];
163
+ if (word) opts = fuzzysearch(word, orginalItems);
164
+ cache[idx] = opts; // storing options to current index on cache
165
+ });
166
+
167
+ result = Object.values(cache) // get values from cache
168
+ .flat()
169
+ .filter((v, i, self) => i === self.findIndex((t) => t._uid === v._uid)); // flatten array
170
+ } else {
171
+ result = fuzzysearch(keywords, orginalItems);
172
+ }
173
+ options = result;
174
+ }
175
+
176
+ function reset() {
177
+ keywords = "";
178
+ options = [...orginalItems];
179
+ }
180
+ </script>