@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
@@ -14,6 +14,10 @@ This property can be bound to, to fetch the selected time. Output is in the same
14
14
  @param {'primary'|'warning'|'info'|'danger'|'dark'|'light'} [ui_color="primary"] - Colour of popup time selection UI, Default: `"primary"`
15
15
  @param {boolean} [time_24hr=false] - Display time selection UI in 24hr format, Default: `false`
16
16
  @param {object} [options={}] - Extended set of options as supported by Flatpicker
17
+ @param {any} [min_time=null] - Set earliest selectable time as string
18
+ **Example:** `"12:00 PM"` Default: `null`
19
+ @param {any} [max_time=null] - Set latest selectable time as string
20
+ **Example:** `"12:00 AM"` Default: `null`
17
21
 
18
22
  See: https://flatpickr.js.org/options/, Default: `{}`
19
23
  @param {string} [class=""] - CSS classes for the input, Default: `""`
@@ -43,11 +47,15 @@ See: https://flatpickr.js.org/options/, Default: `{}`
43
47
  on:close={fireClose} />
44
48
 
45
49
  <script>
46
- import { timepicker } from "./actions";
47
50
  import { createEventDispatcher } from "svelte";
51
+ import { timepicker } from "./actions";
48
52
 
49
53
  const fire = createEventDispatcher();
50
54
 
55
+ /**
56
+ * @typedef {import('@kws3/ui/types').ColorOptions} ColorOptions
57
+ */
58
+
51
59
  /**
52
60
  * Accepts a date value in the format `H:i`
53
61
  *
@@ -62,7 +70,7 @@ See: https://flatpickr.js.org/options/, Default: `{}`
62
70
  export let style = "";
63
71
  /**
64
72
  * Colour of the Time picker input
65
- * @type {''|'primary'|'warning'|'info'|'danger'|'dark'|'light'}
73
+ * @type {ColorOptions} color
66
74
  */
67
75
  export let color = "";
68
76
  /**
@@ -75,7 +83,7 @@ See: https://flatpickr.js.org/options/, Default: `{}`
75
83
  export let placeholder = "Select Time..";
76
84
  /**
77
85
  * Colour of popup time selection UI
78
- * @type {'primary'|'warning'|'info'|'danger'|'dark'|'light'}
86
+ * @type {Exclude<ColorOptions, ''>}
79
87
  */
80
88
  export let ui_color = "primary";
81
89
 
@@ -84,23 +92,64 @@ See: https://flatpickr.js.org/options/, Default: `{}`
84
92
  */
85
93
  export let time_24hr = false;
86
94
 
95
+ /**
96
+ * Set earliest selectable time as string
97
+ *
98
+ * **Example:** `"01:00 PM"` or "13:00"`
99
+ * @type {any}
100
+ */
101
+ export let min_time = null;
102
+ /**
103
+ * Set latest selectable time as string
104
+ *
105
+ * **Example:** `"03:00 PM"` or "15:00"`
106
+ * @type {any}
107
+ */
108
+ export let max_time = null;
109
+
87
110
  /**
88
111
  * Extended set of options as supported by Flatpicker
89
112
  *
90
113
  * See: https://flatpickr.js.org/options/
91
114
  */
92
115
  export let options = {};
116
+
117
+ /**
118
+ * Make input value read-only
119
+ */
120
+ export let readonly = false;
121
+
93
122
  /**
94
123
  * CSS classes for the input
95
124
  */
125
+
96
126
  let klass = "";
97
127
  export { klass as class };
98
128
 
99
129
  let opts;
100
130
 
101
- $: ui_color, options, time_24hr, fillOptions();
131
+ $: ui_color, options, time_24hr, min_time, max_time, readonly, fillOptions();
132
+
133
+ const convertTime12to24 = (time12h) => {
134
+ const [time, modifier] = time12h.split(" ");
135
+ let [hours, minutes] = time.split(":");
136
+ if (hours === "12") {
137
+ hours = "00";
138
+ }
139
+ if (modifier === "PM") {
140
+ hours = parseInt(hours, 10) + 12;
141
+ }
142
+ return {
143
+ hour: String(hours),
144
+ minute: String(minutes),
145
+ time: `${hours}:${minutes}`,
146
+ };
147
+ };
102
148
 
103
149
  function fillOptions() {
150
+ /**
151
+ * @type {object}
152
+ */
104
153
  let _opts = Object.assign(
105
154
  {
106
155
  color: ui_color,
@@ -109,6 +158,22 @@ See: https://flatpickr.js.org/options/, Default: `{}`
109
158
  options
110
159
  );
111
160
 
161
+ if (min_time) {
162
+ let _minTime24 = convertTime12to24(min_time);
163
+ _opts.minTime = _minTime24.time;
164
+ _opts.defaultHour = _minTime24.hour;
165
+ _opts.defaultMinute = _minTime24.minute;
166
+ }
167
+
168
+ if (max_time) {
169
+ let _maxTime24 = convertTime12to24(max_time);
170
+ _opts.maxTime = _maxTime24.time;
171
+ }
172
+
173
+ _opts.clickOpens = true;
174
+ if (readonly) {
175
+ _opts.clickOpens = false;
176
+ }
112
177
  opts = _opts;
113
178
  }
114
179
 
package/forms/actions.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import flatpickr from "flatpickr";
2
2
 
3
- function createFlatpickrAction(defaultOpts, hooks) {
3
+ function createFlatpickrAction(defaultOpts, hooks, type) {
4
4
  return function (
5
5
  node,
6
6
  // eslint-disable-next-line no-unused-vars
@@ -34,9 +34,9 @@ function createFlatpickrAction(defaultOpts, hooks) {
34
34
  _opts["onChange"] = createFirer("dateChange");
35
35
  }
36
36
 
37
- opts = Object.assign(defaultOpts, _opts, opts);
37
+ let OPTS = Object.assign(defaultOpts, _opts, opts);
38
38
 
39
- const picker = flatpickr(node, opts);
39
+ let picker = flatpickr(node, OPTS);
40
40
 
41
41
  function createFirer(name) {
42
42
  return (selectedDates, dateStr, instance) => {
@@ -72,6 +72,10 @@ function createFlatpickrAction(defaultOpts, hooks) {
72
72
 
73
73
  return {
74
74
  update({ opts, value, placeholder, klass, style, disabled, color }) {
75
+ if (!picker.isOpen) {
76
+ picker = flatpickr(node, Object.assign(OPTS, opts));
77
+ }
78
+
75
79
  if (picker) {
76
80
  picker.setDate(value);
77
81
  if (opts) {
@@ -81,19 +85,19 @@ function createFlatpickrAction(defaultOpts, hooks) {
81
85
  if (opts.mode) {
82
86
  picker.set("mode", opts.mode);
83
87
  }
84
- picker.set("minDate", opts.minDate ? opts.minDate : "");
85
- picker.set("maxDate", opts.maxDate ? opts.maxDate : "");
86
- picker.set("enable", opts.enable ? opts.enable : [() => true]);
87
- picker.set("disable", opts.disable ? opts.disable : [() => false]);
88
- picker.set("time_24hr", opts.time_24hr || false);
88
+ if (type === "time") {
89
+ picker.set("altFormat", opts.time_24hr ? "H:i" : "h:i K");
90
+ }
89
91
  }
90
-
91
92
  //respond reactively to props
93
+ /** @type {any} */
92
94
  const visibleInput = picker.input.nextSibling;
93
- visibleInput.className = `input is-${color} ${klass}`;
94
- visibleInput.style = `${style}`;
95
- visibleInput.disabled = disabled;
96
- visibleInput.placeholder = placeholder;
95
+ if (visibleInput) {
96
+ visibleInput.className = `input is-${color} ${klass}`;
97
+ visibleInput.style = `${style}`;
98
+ visibleInput.disabled = disabled;
99
+ visibleInput.placeholder = placeholder;
100
+ }
97
101
  }
98
102
  },
99
103
  destroy() {
@@ -109,7 +113,8 @@ export let datepicker = createFlatpickrAction(
109
113
  altFormat: "d/m/Y",
110
114
  dateFormat: "Y-m-d",
111
115
  },
112
- ["onOpen", "onClose", "onMonthChange", "onYearChange", "onReady"]
116
+ ["onOpen", "onClose", "onMonthChange", "onYearChange", "onReady"],
117
+ "date"
113
118
  );
114
119
 
115
120
  export let timepicker = createFlatpickrAction(
@@ -120,5 +125,6 @@ export let timepicker = createFlatpickrAction(
120
125
  enableTime: true,
121
126
  noCalendar: true,
122
127
  },
123
- ["onOpen", "onClose", "onReady"]
128
+ ["onOpen", "onClose", "onReady"],
129
+ "time"
124
130
  );
@@ -77,7 +77,11 @@ export default (function (win, doc) {
77
77
  (r = v), (g = p), (b = q);
78
78
  break;
79
79
  }
80
- return [round(r * 255), round(g * 255), round(b * 255)];
80
+ return [
81
+ r !== undefined ? round(r * 255) : NaN,
82
+ g !== undefined ? round(g * 255) : NaN,
83
+ b !== undefined ? round(b * 255) : NaN,
84
+ ];
81
85
  }
82
86
 
83
87
  function HSV2HEX(a) {
@@ -116,8 +120,8 @@ export default (function (win, doc) {
116
120
  }
117
121
 
118
122
  function RGB2HEX(a) {
119
- var s = +a[2] | (+a[1] << 8) | (+a[0] << 16);
120
- s = "000000" + s.toString(16);
123
+ var s = (+a[2] | (+a[1] << 8) | (+a[0] << 16)).toString(16);
124
+ s = "000000" + s;
121
125
  return s.slice(-6);
122
126
  }
123
127
 
@@ -166,12 +170,14 @@ export default (function (win, doc) {
166
170
 
167
171
  return (function ($) {
168
172
  // plugin version
173
+ //@ts-ignore
169
174
  $.version = "1.3.2";
170
175
 
171
176
  // collect all instance(s)
172
177
  $[instance] = {};
173
178
 
174
179
  // plug to all instance(s)
180
+ //@ts-ignore
175
181
  $.each = function (fn, t) {
176
182
  return (
177
183
  delay(
@@ -189,27 +195,39 @@ export default (function (win, doc) {
189
195
  };
190
196
 
191
197
  // static method(s)
198
+ //@ts-ignore
192
199
  $.parse = parse;
200
+ //@ts-ignore
193
201
  $._HSV2RGB = HSV2RGB;
202
+ //@ts-ignore
194
203
  $._HSV2HEX = HSV2HEX;
204
+ //@ts-ignore
195
205
  $._RGB2HSV = RGB2HSV;
206
+ //@ts-ignore
196
207
  $._HEX2HSV = HEX2HSV;
208
+ //@ts-ignore
197
209
  $._HEX2RGB = function (a) {
198
210
  return _2RGB_pri(HEX2RGB(a));
199
211
  };
212
+ //@ts-ignore
200
213
  $.HSV2RGB = function (a) {
201
214
  return HSV2RGB(_2HSV_pri(a));
202
215
  };
216
+ //@ts-ignore
203
217
  $.HSV2HEX = function (a) {
204
218
  return HSV2HEX(_2HSV_pri(a));
205
219
  };
220
+ //@ts-ignore
206
221
  $.RGB2HSV = function (a) {
207
222
  return _2HSV_pub(RGB2HSV(a));
208
223
  };
224
+ //@ts-ignore
209
225
  $.RGB2HEX = RGB2HEX;
226
+ //@ts-ignore
210
227
  $.HEX2HSV = function (s) {
211
228
  return _2HSV_pub(HEX2HSV(s));
212
229
  };
230
+ //@ts-ignore
213
231
  $.HEX2RGB = HEX2RGB;
214
232
 
215
233
  return $;
@@ -405,6 +423,7 @@ export default (function (win, doc) {
405
423
  if (is_target) {
406
424
  create();
407
425
  } else {
426
+ //@ts-ignore
408
427
  $.exit();
409
428
  }
410
429
  trigger(is_target ? "enter" : "exit", [$]);
@@ -416,13 +435,16 @@ export default (function (win, doc) {
416
435
  if (events !== false) {
417
436
  on(events, target, click);
418
437
  }
438
+ //@ts-ignore
419
439
  $.create = function () {
420
440
  return create(1), trigger("create", [$]), $;
421
441
  };
442
+ //@ts-ignore
422
443
  $.destroy = function () {
423
444
  if (events !== false) {
424
445
  off(events, target, click);
425
446
  }
447
+ //@ts-ignore
426
448
  $.exit(), set_data(false);
427
449
  return trigger("destroy", [$]), $;
428
450
  };
@@ -435,8 +457,10 @@ export default (function (win, doc) {
435
457
  SV_point.style.right = SV_W - SV_point_W / 2 - SV_W * +HSV[1] + "px";
436
458
  SV_point.style.top = SV_H - SV_point_H / 2 - SV_H * +HSV[2] + "px";
437
459
  };
460
+ //@ts-ignore
438
461
  $.exit = function () {
439
462
  if (visible()) {
463
+ //@ts-ignore
440
464
  visible().removeChild(picker);
441
465
  $.visible = false;
442
466
  }
@@ -499,6 +523,7 @@ export default (function (win, doc) {
499
523
  if (!is_target && !is_picker) {
500
524
  // click outside the target or picker element to exit
501
525
  if (visible() && events !== false)
526
+ //@ts-ignore
502
527
  $.exit(), trigger("exit", [$]), trigger_(0, a);
503
528
  } else {
504
529
  if (is_picker) {
@@ -36,7 +36,7 @@ This property can be bound to, to fetch the current colour, Default: `"000000"`
36
36
  on:focus={focused}
37
37
  {disabled}
38
38
  bind:value={color}
39
- use:colorpicker={color} />
39
+ use:colorpicker />
40
40
  {#if !mini}
41
41
  <Icon icon="hashtag" class="is-left" inner_style="color:#{color}" />
42
42
  {/if}
@@ -84,7 +84,7 @@ This property can be bound to, to fetch the current colour, Default: `"000000"`
84
84
  disabled = false,
85
85
  /**
86
86
  * Size of the colour picker trigger
87
- * @type {''|'small'|'medium'|'large'}
87
+ * @type {import('@kws3/ui/types').SizeOptions}
88
88
  */
89
89
  size = "";
90
90
 
@@ -2,9 +2,9 @@
2
2
  @component
3
3
 
4
4
 
5
- @param {array} [value=[]] - Value of the Input
5
+ @param {Array|?string} [value=undefined] - 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: `undefined`
8
8
  @param {object} [max=null] - Maximum number of selectable items from dropdown list.
9
9
 
10
10
  Accepts a `null` value for unlimited selected items.
@@ -20,11 +20,11 @@ this property of each object will be returned as the value, Default: `"id"`
20
20
 
21
21
  Only send this prop if you want to fetch `options` asynchronously.
22
22
  `options` prop will be ignored if this prop is set., Default: `null`
23
- @param {'fuzzy'|'strict'} [search_strategy="fuzzy"] - Filtered options to be displayed strictly based on search text or perform a fuzzy match.
23
+ @param {string|'fuzzy'|'strict'} [search_strategy="fuzzy"] - Filtered options to be displayed strictly based on search text or perform a fuzzy match.
24
24
  Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching., Default: `"fuzzy"`
25
25
  @param {number} [scoreThreshold=3] - Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches., Default: `3`
26
- @param {''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
27
- @param {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
26
+ @param {string|''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
27
+ @param {string|''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
28
28
  @param {string} [style=""] - Inline CSS for input container, Default: `""`
29
29
  @param {boolean} [readonly=false] - Marks component as read-only, Default: `false`
30
30
  @param {boolean} [disabled=false] - Disables the component, Default: `false`
@@ -132,8 +132,22 @@ Default value: `<span>{option[search_key] || option}</span>`
132
132
  on:mousedown|preventDefault|stopPropagation={() =>
133
133
  handleOptionMouseDown(option)}
134
134
  on:mouseenter|preventDefault|stopPropagation={() => {
135
+ if (mouseTracker.preventSelect) return;
135
136
  activeOption = option;
136
137
  }}
138
+ on:mousemove|preventDefault|stopPropagation={(e) => {
139
+ let { preventSelect, lastX, lastY } = mouseTracker;
140
+ if (
141
+ preventSelect &&
142
+ (lastX !== e.clientX || lastY !== e.clientY)
143
+ ) {
144
+ mouseTracker.preventSelect = false;
145
+ activeOption = option;
146
+ }
147
+ // mouse x,y is not in same position after the scrolling
148
+ mouseTracker.lastX = e.clientX;
149
+ mouseTracker.lastY = e.clientY;
150
+ }}
137
151
  class="is-size-{list_text_size[size]}"
138
152
  class:selected={isSelected(option)}
139
153
  class:active={activeOption === option}>
@@ -164,7 +178,8 @@ Default value: `<span>{option[search_key] || option}</span>`
164
178
  import { debounce } from "@kws3/ui/utils";
165
179
  import { createEventDispatcher, onMount, tick } from "svelte";
166
180
  import { createPopper } from "@popperjs/core";
167
- import fuzzy from "fuzzy.js";
181
+ import { makeSearchEngine } from "@kws3/ui/search";
182
+ import { scrollIntoActiveElement } from "../../internal";
168
183
 
169
184
  const sameWidthPopperModifier = {
170
185
  name: "sameWidth",
@@ -184,10 +199,16 @@ Default value: `<span>{option[search_key] || option}</span>`
184
199
 
185
200
  const rootContainerId = "kws-overlay-root";
186
201
 
202
+ /**
203
+ * @typedef {import('@kws3/ui/types').ColorOptions} ColorOptions
204
+ * @typedef {import('@kws3/ui/types').SizeOptions} SizeOptions
205
+ */
206
+
187
207
  /**
188
208
  * Value of the Input
189
209
  *
190
210
  * This property can be bound to, to fetch the current value
211
+ * @type {Array|?string}
191
212
  */
192
213
  export let value = [];
193
214
  /**
@@ -195,6 +216,7 @@ Default value: `<span>{option[search_key] || option}</span>`
195
216
  *
196
217
  * Accepts a `null` value for unlimited selected items.
197
218
  * Or a number value
219
+ * @type {?number}
198
220
  */
199
221
  export let max = null;
200
222
  /**
@@ -230,22 +252,23 @@ Default value: `<span>{option[search_key] || option}</span>`
230
252
  /**
231
253
  * Filtered options to be displayed strictly based on search text or perform a fuzzy match.
232
254
  * Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching.
233
- * @type {'fuzzy'|'strict'}
255
+ * @type {string|'fuzzy'|'strict'}
234
256
  */
235
257
  export let search_strategy = "fuzzy";
236
258
 
237
259
  /**
238
260
  * Score threshold for fuzzy search strategy, setting high score gives more fuzzy matches.
261
+ * @type {number}
239
262
  */
240
263
  export let scoreThreshold = 3;
241
264
  /**
242
265
  * Size of the input
243
- * @type {''|'small'|'medium'|'large'}
266
+ * @type {import('@kws3/ui/types').SizeOptions}
244
267
  */
245
268
  export let size = "";
246
269
  /**
247
270
  * Color of the input
248
- * @type {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'}
271
+ * @type {import('@kws3/ui/types').ColorOptions}
249
272
  */
250
273
  export let color = "";
251
274
  /**
@@ -326,6 +349,12 @@ Default value: `<span>{option[search_key] || option}</span>`
326
349
  searchText = "",
327
350
  searching = false,
328
351
  showOptions = false,
352
+ mouseTracker = {
353
+ lastX: 0,
354
+ lastY: 0, // to check actual mouse is moving or not, for WebKit compatibility,
355
+ preventSelect: false, //prevent select by mouse when up or down key is pressed
356
+ },
357
+ fuzzysearch = null,
329
358
  filteredOptions = [], //list of options filtered by search query
330
359
  normalisedOptions = [], //list of options normalised
331
360
  selectedOptions = [], //list of options that are selected
@@ -368,11 +397,15 @@ Default value: `<span>{option[search_key] || option}</span>`
368
397
  $: activeOption, searchText, filteredOptions, updateActiveOption();
369
398
 
370
399
  //TODO: optimise isSelected function
400
+ /** @type {(option: array)=> boolean}*/
371
401
  $: isSelected = (option) => {
372
402
  if (single) return matchesValue(value, option);
373
403
  if (!(value && value.length > 0) || value === "") return false;
374
404
  // nothing is selected if `value` is the empty array or string
375
- else return value.some((v) => matchesValue(v, option));
405
+ else
406
+ return Array.isArray(value)
407
+ ? value.some((v) => matchesValue(v, option))
408
+ : false;
376
409
  };
377
410
 
378
411
  $: singleVisibleValue =
@@ -407,7 +440,7 @@ Default value: `<span>{option[search_key] || option}</span>`
407
440
  debouncedTriggerSearch(filter);
408
441
  } else {
409
442
  if (allow_fuzzy_match) {
410
- filteredOptions = fuzzySearch(filter, [...normalisedOptions]);
443
+ fuzzySearch(filter, [...normalisedOptions]);
411
444
  } else {
412
445
  filteredOptions = strictSearch(filter, [...normalisedOptions]);
413
446
  }
@@ -416,7 +449,11 @@ Default value: `<span>{option[search_key] || option}</span>`
416
449
 
417
450
  function updateActiveOption() {
418
451
  if (
419
- (activeOption && searching && !filteredOptions.includes(activeOption)) ||
452
+ (activeOption &&
453
+ searching &&
454
+ !filteredOptions.some(
455
+ (fo) => fo[used_value_key] === activeOption[used_value_key]
456
+ )) ||
420
457
  (!activeOption && searchText)
421
458
  ) {
422
459
  activeOption = filteredOptions[0];
@@ -446,11 +483,15 @@ Default value: `<span>{option[search_key] || option}</span>`
446
483
 
447
484
  selectedOptions = _normalisedOptions
448
485
  .filter(
449
- (v) => value && value.some((vl) => `${v[used_value_key]}` === `${vl}`)
486
+ (v) =>
487
+ Array.isArray(value) &&
488
+ value.some((vl) => `${v[used_value_key]}` === `${vl}`)
450
489
  )
451
490
  .sort(
452
491
  (a, b) =>
453
- value.indexOf(a[used_value_key]) - value.indexOf(b[used_value_key])
492
+ // tweak for 'value is nullable' type error
493
+ (value ? value.indexOf(a[used_value_key]) : 0) -
494
+ (value ? value.indexOf(b[used_value_key]) : 0)
454
495
  );
455
496
  }
456
497
 
@@ -465,11 +506,13 @@ Default value: `<span>{option[search_key] || option}</span>`
465
506
  return;
466
507
  }
467
508
  options_loading = true;
468
- search(filter).then((_options) => {
469
- options = _options;
470
- searching = false;
471
- options_loading = false;
472
- });
509
+ if (search !== null) {
510
+ search(filter).then((_options) => {
511
+ options = _options;
512
+ searching = false;
513
+ options_loading = false;
514
+ });
515
+ }
473
516
  }
474
517
 
475
518
  const debouncedTriggerSearch = debounce(triggerSearch, 150, false);
@@ -478,12 +521,25 @@ Default value: `<span>{option[search_key] || option}</span>`
478
521
  POPPER = createPopper(el, dropdown, {
479
522
  strategy: "fixed",
480
523
  placement: "bottom-start",
524
+ // @ts-ignore
481
525
  modifiers: [sameWidthPopperModifier],
482
526
  });
483
527
 
484
- if (allow_fuzzy_match && fuzzy) {
485
- fuzzy.analyzeSubTerms = true;
486
- fuzzy.analyzeSubTermDepth = 10;
528
+ if (allow_fuzzy_match) {
529
+ let fuzzyOpts = {
530
+ analyzeSubTerms: true,
531
+ analyzeSubTermDepth: 10,
532
+ highlighting: {
533
+ after: "",
534
+ before: "",
535
+ },
536
+ };
537
+ let searchOptions = {
538
+ search_key: used_search_key,
539
+ scoreThreshold,
540
+ fuzzyOpts,
541
+ };
542
+ fuzzysearch = makeSearchEngine(searchOptions);
487
543
  }
488
544
 
489
545
  //normalize value for single versus multiselect
@@ -531,16 +587,20 @@ Default value: `<span>{option[search_key] || option}</span>`
531
587
  setOptionsVisible(false);
532
588
  }
533
589
 
534
- if (!isAlreadySelected && !single && (max === null || value.length < max)) {
590
+ if (
591
+ !isAlreadySelected &&
592
+ !single &&
593
+ (max === null || (value && value.length < max))
594
+ ) {
535
595
  if (asyncMode) {
536
596
  //Do not filter invalid options, as they are async and might not be invalid
537
597
  //but ensure they are unique
538
- value = [...value, token[used_value_key]].filter(
598
+ value = [...(value ? value : []), token[used_value_key]].filter(
539
599
  (v, i, a) => a.indexOf(v) === i
540
600
  );
541
601
  } else {
542
602
  //attach to value array while filtering out invalid values
543
- value = [...value, token[used_value_key]].filter((v) => {
603
+ value = [...(value ? value : []), token[used_value_key]].filter((v) => {
544
604
  return normalisedOptions.filter((nv) => nv[used_value_key] === v)
545
605
  .length;
546
606
  });
@@ -568,7 +628,7 @@ Default value: `<span>{option[search_key] || option}</span>`
568
628
 
569
629
  function remove(token) {
570
630
  if (readonly || disabled || single) return;
571
- value = value.filter
631
+ value = Array.isArray(value)
572
632
  ? value.filter((item) => !matchesValue(item, token))
573
633
  : value;
574
634
 
@@ -633,6 +693,14 @@ Default value: `<span>{option[search_key] || option}</span>`
633
693
  activeOption = filteredOptions[0];
634
694
  else activeOption = filteredOptions[newActiveIdx];
635
695
  }
696
+
697
+ tick().then(() => {
698
+ if (dropdown) {
699
+ mouseTracker.preventSelect = true;
700
+ let activeElem = dropdown.querySelector(".active");
701
+ scrollIntoActiveElement(dropdown, activeElem);
702
+ }
703
+ });
636
704
  } else if (event.key === `Backspace`) {
637
705
  if (single && hasValue) {
638
706
  //for a single select
@@ -716,27 +784,16 @@ Default value: `<span>{option[search_key] || option}</span>`
716
784
  });
717
785
  };
718
786
 
719
- function fuzzySearch(filter, options) {
720
- if (!filter) return options;
721
- if (options.length) {
722
- let OPTS = options.map((item) => {
723
- let output = fuzzy(item[used_search_key], filter);
724
- item = { ...output, original: item };
725
- item.score =
726
- !item.score || (item.score && item.score < output.score)
727
- ? output.score
728
- : item.score || 0;
729
- return item;
730
- });
787
+ const fuzzySearch = debounce(searchInFuzzyMode, 200, false);
731
788
 
732
- let maxScore = Math.max(...OPTS.map((i) => i.score));
733
- let calculatedLimit = maxScore - scoreThreshold;
734
-
735
- OPTS = OPTS.filter(
736
- (r) => r.score > (calculatedLimit > 0 ? calculatedLimit : 0)
737
- ).map((o) => o.original);
738
-
739
- return OPTS;
789
+ function searchInFuzzyMode(filter, options) {
790
+ if (!filter) {
791
+ filteredOptions = options;
792
+ return;
793
+ }
794
+ if (options.length) {
795
+ let result = fuzzysearch(filter, options);
796
+ filteredOptions = result;
740
797
  }
741
798
  }
742
799