@kws3/ui 1.7.3 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,16 +13,60 @@ squeezed into small spaces, Default: `false`
13
13
  @param {array} [colors=null] - Chart colors, can be modified globally in framework settings
14
14
 
15
15
  Send an array of colors to override the default colors, or do not send anything to use the default colors, Default: `null`
16
+ @param {array} [captured_events=[]] - String array of event names that will be captured and fired as svelte events.
17
+ This is to prevent unnecessary event subscriptions., Default: `[]`
16
18
  @param {string} [class=""] - CSS classes for container, Default: `""`
19
+ @method `getInstance()` - Returns the ApexCharts instance
20
+
21
+ ### Events
22
+ - `animationEnd` - All chart events only fire when they are mentioned in `captured_events` list. See ApexChart Events https://apexcharts.com/docs/options/chart/events/ for full list of supported events.
23
+ - `beforeMount`
24
+ - `mounted`
25
+ - `updated`
26
+ - `mouseMove`
27
+ - `mouseLeave`
28
+ - `click`
29
+ - `legendClick`
30
+ - `markerClick`
31
+ - `selection`
32
+ - `dataPointSelection`
33
+ - `dataPointMouseEnter`
34
+ - `dataPointMouseLeave`
35
+ - `beforeZoom`
36
+ - `beforeResetZoom`
37
+ - `zoomed`
38
+ - `scrolled`
39
+ - `brushScrolled`
17
40
 
18
41
  -->
42
+ <!-- All chart events only fire when they are mentioned in `captured_events` list. See ApexChart Events https://apexcharts.com/docs/options/chart/events/ for full list of supported events. -->
19
43
  <Chart
44
+ bind:this={chart}
45
+ on:animationEnd
46
+ on:beforeMount
47
+ on:mounted
48
+ on:updated
49
+ on:mouseMove
50
+ on:mouseLeave
51
+ on:click
52
+ on:legendClick
53
+ on:markerClick
54
+ on:selection
55
+ on:dataPointSelection
56
+ on:dataPointMouseEnter
57
+ on:dataPointMouseLeave
58
+ on:beforeZoom
59
+ on:beforeResetZoom
60
+ on:zoomed
61
+ on:scrolled
62
+ on:brushScrolled
20
63
  class={__class}
21
64
  options={_options}
22
65
  series={_data}
23
66
  type="pie"
24
67
  {height}
25
- {width} />
68
+ {width}
69
+ {captured_events} />
26
70
 
27
71
  <script>
28
72
  import { Chart } from "@kws3/ui";
@@ -61,7 +105,12 @@ Send an array of colors to override the default colors, or do not send anything
61
105
  * Send an array of colors to override the default colors, or do not send anything to use the default colors
62
106
  * @type {array}
63
107
  */
64
- colors = null;
108
+ colors = null,
109
+ /**
110
+ * String array of event names that will be captured and fired as svelte events.
111
+ * This is to prevent unnecessary event subscriptions.
112
+ */
113
+ captured_events = [];
65
114
 
66
115
  /**
67
116
  * CSS classes for container
@@ -69,6 +118,14 @@ Send an array of colors to override the default colors, or do not send anything
69
118
  let klass = "";
70
119
  export { klass as class };
71
120
 
121
+ let chart;
122
+ /**
123
+ * Returns the ApexCharts instance
124
+ */
125
+ export function getInstance() {
126
+ return chart.getInstance();
127
+ }
128
+
72
129
  $: __class =
73
130
  "kws-pie-chart " + `${sparklines ? "kws-sparklines" : ""} ` + klass;
74
131
 
@@ -15,16 +15,60 @@ squeezed into small spaces, Default: `false`
15
15
  @param {array} [colors=null] - Chart colors, can be modified globally in framework settings
16
16
 
17
17
  Send an array of colors to override the default colors, or do not send anything to use the default colors, Default: `null`
18
+ @param {array} [captured_events=[]] - String array of event names that will be captured and fired as svelte events.
19
+ This is to prevent unnecessary event subscriptions., Default: `[]`
18
20
  @param {string} [class=""] - CSS classes for container, Default: `""`
21
+ @method `getInstance()` - Returns the ApexCharts instance
22
+
23
+ ### Events
24
+ - `animationEnd` - All chart events only fire when they are mentioned in `captured_events` list. See ApexChart Events https://apexcharts.com/docs/options/chart/events/ for full list of supported events.
25
+ - `beforeMount`
26
+ - `mounted`
27
+ - `updated`
28
+ - `mouseMove`
29
+ - `mouseLeave`
30
+ - `click`
31
+ - `legendClick`
32
+ - `markerClick`
33
+ - `selection`
34
+ - `dataPointSelection`
35
+ - `dataPointMouseEnter`
36
+ - `dataPointMouseLeave`
37
+ - `beforeZoom`
38
+ - `beforeResetZoom`
39
+ - `zoomed`
40
+ - `scrolled`
41
+ - `brushScrolled`
19
42
 
20
43
  -->
44
+ <!-- All chart events only fire when they are mentioned in `captured_events` list. See ApexChart Events https://apexcharts.com/docs/options/chart/events/ for full list of supported events. -->
21
45
  <Chart
46
+ bind:this={chart}
47
+ on:animationEnd
48
+ on:beforeMount
49
+ on:mounted
50
+ on:updated
51
+ on:mouseMove
52
+ on:mouseLeave
53
+ on:click
54
+ on:legendClick
55
+ on:markerClick
56
+ on:selection
57
+ on:dataPointSelection
58
+ on:dataPointMouseEnter
59
+ on:dataPointMouseLeave
60
+ on:beforeZoom
61
+ on:beforeResetZoom
62
+ on:zoomed
63
+ on:scrolled
64
+ on:brushScrolled
22
65
  class={__class}
23
66
  options={_options}
24
67
  series={_data}
25
68
  type="radialBar"
26
69
  {height}
27
- {width} />
70
+ {width}
71
+ {captured_events} />
28
72
 
29
73
  <script>
30
74
  import { Chart } from "@kws3/ui";
@@ -71,7 +115,12 @@ Send an array of colors to override the default colors, or do not send anything
71
115
  * Send an array of colors to override the default colors, or do not send anything to use the default colors
72
116
  * @type {array}
73
117
  */
74
- colors = null;
118
+ colors = null,
119
+ /**
120
+ * String array of event names that will be captured and fired as svelte events.
121
+ * This is to prevent unnecessary event subscriptions.
122
+ */
123
+ captured_events = [];
75
124
 
76
125
  /**
77
126
  * CSS classes for container
@@ -79,6 +128,14 @@ Send an array of colors to override the default colors, or do not send anything
79
128
  let klass = "";
80
129
  export { klass as class };
81
130
 
131
+ let chart;
132
+ /**
133
+ * Returns the ApexCharts instance
134
+ */
135
+ export function getInstance() {
136
+ return chart.getInstance();
137
+ }
138
+
82
139
  let _data = [],
83
140
  _labels = [];
84
141
 
package/charts/utils.js CHANGED
@@ -99,6 +99,9 @@ export function mixedChartOptions(xAxis, yAxis, is_sparkline) {
99
99
  sparkline: {
100
100
  enabled: is_sparkline ? true : false,
101
101
  },
102
+ zoom: {
103
+ enabled: false,
104
+ },
102
105
  },
103
106
  colors: themeColors,
104
107
  fill: {
@@ -189,6 +192,7 @@ export function barChartOptions(
189
192
  yAxis,
190
193
  is_sparkline,
191
194
  is_horizontal,
195
+ is_distributed,
192
196
  is_stacked,
193
197
  is_stacked_full
194
198
  ) {
@@ -197,6 +201,7 @@ export function barChartOptions(
197
201
  opts.chart.stacked = is_stacked ? true : false;
198
202
  opts.chart.stackType = is_stacked_full ? "100%" : false;
199
203
  opts.plotOptions.bar.horizontal = is_horizontal ? true : false;
204
+ opts.plotOptions.bar.distributed = is_distributed ? true : false;
200
205
  return opts;
201
206
  }
202
207
 
@@ -28,7 +28,6 @@ This will be overridden if `min` is higher, or `max` is lower, Default: `0`
28
28
  <div class="field has-addons">
29
29
  <div class="control">
30
30
  <button
31
- role="button"
32
31
  type="button"
33
32
  class="button is-{size} is-{minus_button_color}"
34
33
  style="box-shadow:none;"
@@ -58,7 +57,6 @@ This will be overridden if `min` is higher, or `max` is lower, Default: `0`
58
57
  </div>
59
58
  <div class="control">
60
59
  <button
61
- role="button"
62
60
  type="button"
63
61
  class="button is-{size} is-{plus_button_color}"
64
62
  style="box-shadow:none;"
@@ -16,6 +16,12 @@ Used to populate the list of options in the dropdown, Default: `[]`
16
16
  this property of each object will be searched, Default: `"name"`
17
17
  @param {string} [value_key="id"] - If `options` is an array of objects,
18
18
  this property of each object will be returned as the value, Default: `"id"`
19
+ @param {function|null} [search=null] - Async function to fetch options
20
+
21
+ Only send this prop if you want to fetch `options` asynchronously.
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.
24
+ Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching., Default: `"fuzzy"`
19
25
  @param {''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
20
26
  @param {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
21
27
  @param {string} [style=""] - Inline CSS for input container, Default: `""`
@@ -24,6 +30,7 @@ this property of each object will be returned as the value, Default: `"id"`
24
30
  @param {string} [selected_icon="check"] - Icon used to mark selected items in dropdown list, Default: `"check"`
25
31
  @param {boolean} [summary_mode=false] - Shows only the number of items selected, instead of listing all the selected items in the input., Default: `false`
26
32
  @param {string} [no_options_msg="No matching options"] - Message to display when no matching options are found, Default: `"No matching options"`
33
+ @param {string} [async_search_prompt="Start typing to search..."] - Message to display in dropdown when async search can be performed, Default: `"Start typing to search..."`
27
34
  @param {string} [remove_btn_tip="Remove"] - Tooltip text for Remove Item button. This `string` will precede the selected Item Name in the tooltip., Default: `"Remove"`
28
35
  @param {string} [remove_all_tip="Remove all"] - Tooltip text for the Clear All button, Default: `"Remove all"`
29
36
  @param {HTMLElement|string} [dropdown_portal=undefined] - Where to render the dropdown list.
@@ -75,7 +82,6 @@ Default value: `<span>{option[search_key] || option}</span>`
75
82
  {#if !readonly && !disabled}
76
83
  <button
77
84
  on:click|self|stopPropagation={() => remove(tag)}
78
- role="button"
79
85
  type="button"
80
86
  class="delete is-small"
81
87
  data-tooltip="{remove_btn_tip} {tag[used_search_key]}" />
@@ -84,6 +90,7 @@ Default value: `<span>{option[search_key] || option}</span>`
84
90
  {/each}
85
91
  {/if}
86
92
  {/if}
93
+ {#if single}<span>{singleVisibleValue}</span>{/if}
87
94
  <input
88
95
  class="input is-{size}"
89
96
  bind:this={input}
@@ -98,9 +105,13 @@ Default value: `<span>{option[search_key] || option}</span>`
98
105
  on:blur={() => setOptionsVisible(false)}
99
106
  placeholder={_placeholder} />
100
107
  </ul>
101
- {#if !readonly && !disabled}
108
+ {#if search && options_loading}
109
+ <button
110
+ type="button"
111
+ style="border: none;"
112
+ class="button is-paddingless delete is-medium is-loading" />
113
+ {:else if !readonly && !disabled}
102
114
  <button
103
- role="button"
104
115
  type="button"
105
116
  class="remove-all delete is-small"
106
117
  data-tooltip={remove_all_tip}
@@ -133,7 +144,11 @@ Default value: `<span>{option[search_key] || option}</span>`
133
144
  {option}>{option[used_search_key] || option}</slot>
134
145
  </li>
135
146
  {:else}
136
- <li class="no-options">{no_options_msg}</li>
147
+ {#if !options_loading}
148
+ <li class="no-options">
149
+ {searchText ? no_options_msg : async_search_prompt}
150
+ </li>
151
+ {/if}
137
152
  {/each}
138
153
  </ul>
139
154
  </div>
@@ -142,8 +157,10 @@ Default value: `<span>{option[search_key] || option}</span>`
142
157
 
143
158
  <script>
144
159
  import { Icon, portal } from "@kws3/ui";
145
- import { createEventDispatcher, onMount } from "svelte";
160
+ import { debounce } from "@kws3/ui/utils";
161
+ import { createEventDispatcher, onMount, tick } from "svelte";
146
162
  import { createPopper } from "@popperjs/core";
163
+ import fuzzysearch from "fuzzysearch";
147
164
 
148
165
  const sameWidthPopperModifier = {
149
166
  name: "sameWidth",
@@ -196,6 +213,22 @@ Default value: `<span>{option[search_key] || option}</span>`
196
213
  * this property of each object will be returned as the value
197
214
  */
198
215
  export let value_key = "id";
216
+ /**
217
+ * Async function to fetch options
218
+ *
219
+ * Only send this prop if you want to fetch `options` asynchronously.
220
+ * `options` prop will be ignored if this prop is set.
221
+ *
222
+ * @type {function|null}
223
+ */
224
+ export let search = null;
225
+
226
+ /**
227
+ * Filtered options to be displayed strictly based on search text or perform a fuzzy match.
228
+ * Fuzzy match will not work if `search` function is set, as the backend service is meant to do the matching.
229
+ * @type {'fuzzy'|'strict'}
230
+ */
231
+ export let search_strategy = "fuzzy";
199
232
  /**
200
233
  * Size of the input
201
234
  * @type {''|'small'|'medium'|'large'}
@@ -230,6 +263,10 @@ Default value: `<span>{option[search_key] || option}</span>`
230
263
  * Message to display when no matching options are found
231
264
  */
232
265
  export let no_options_msg = "No matching options";
266
+ /**
267
+ * Message to display in dropdown when async search can be performed
268
+ */
269
+ export let async_search_prompt = "Start typing to search...";
233
270
  /**
234
271
  * Tooltip text for Remove Item button. This `string` will precede the selected Item Name in the tooltip.
235
272
  * */
@@ -254,7 +291,8 @@ Default value: `<span>{option[search_key] || option}</span>`
254
291
  let klass = "";
255
292
  export { klass as class };
256
293
 
257
- if (!options || !options.length) console.error(`Missing options`);
294
+ if (!search && (!options || !options.length))
295
+ console.error(`Missing options`);
258
296
 
259
297
  if (max !== null && max < 0) {
260
298
  throw new TypeError(`max must be null or positive integer, got ${max}`);
@@ -281,9 +319,11 @@ Default value: `<span>{option[search_key] || option}</span>`
281
319
  showOptions = false,
282
320
  filteredOptions = [], //list of options filtered by search query
283
321
  normalisedOptions = [], //list of options normalised
284
- selectedOptions = []; //list of options that are selected
322
+ selectedOptions = [], //list of options that are selected
323
+ options_loading = false; //indictaes whether async search function is running
285
324
 
286
325
  $: single = max === 1;
326
+ $: asyncMode = search && typeof search === "function";
287
327
  $: hasValue = single
288
328
  ? value !== null && typeof value != "undefined"
289
329
  ? true
@@ -324,6 +364,13 @@ Default value: `<span>{option[search_key] || option}</span>`
324
364
  else return value.some((v) => matchesValue(v, option));
325
365
  };
326
366
 
367
+ $: singleVisibleValue =
368
+ !searching && single && hasValue && selectedOptions && selectedOptions[0]
369
+ ? selectedOptions[0][used_search_key]
370
+ : "";
371
+
372
+ $: allow_fuzzy_match = !search && search_strategy === "fuzzy";
373
+
327
374
  //convert arrays of strings into normalised arrays of objects
328
375
  function normaliseOptions() {
329
376
  let _items = options || [];
@@ -332,15 +379,7 @@ Default value: `<span>{option[search_key] || option}</span>`
332
379
  return;
333
380
  }
334
381
 
335
- normalisedOptions = _items.slice().map((item) => {
336
- if (typeof item === "object") {
337
- return item;
338
- }
339
- let __obj = {};
340
- __obj[used_search_key] = item;
341
- __obj[used_value_key] = item;
342
- return __obj;
343
- });
382
+ normalisedOptions = normaliseArraysToObjects(_items);
344
383
  }
345
384
 
346
385
  function updateFilteredOptions() {
@@ -353,29 +392,27 @@ Default value: `<span>{option[search_key] || option}</span>`
353
392
  } else {
354
393
  filter = searchText.toLowerCase();
355
394
  }
356
-
357
- filteredOptions = normalisedOptions.slice().filter((item) => {
358
- // filter out items that don't match `filter`
359
- if (typeof item === "object") {
360
- if (used_search_key) {
361
- if (
362
- typeof item[used_search_key] === "string" &&
363
- item[used_search_key].toLowerCase().indexOf(filter) > -1
364
- )
365
- return true;
366
- } else {
367
- for (var key in item) {
368
- if (
369
- typeof item[key] === "string" &&
370
- item[key].toLowerCase().indexOf(filter) > -1
371
- )
372
- return true;
395
+ if (asyncMode && searching) {
396
+ debouncedTriggerSearch(filter);
397
+ } else {
398
+ filteredOptions = normalisedOptions.slice().filter((item) => {
399
+ // filter out items that don't match `filter`
400
+ if (typeof item === "object") {
401
+ if (used_search_key) {
402
+ return (
403
+ typeof item[used_search_key] === "string" &&
404
+ match(filter, item[used_search_key])
405
+ );
406
+ } else {
407
+ for (var key in item) {
408
+ return typeof item[key] === "string" && match(filter, item[key]);
409
+ }
373
410
  }
411
+ } else {
412
+ return match(filter, item);
374
413
  }
375
- } else {
376
- return item.toLowerCase().indexOf(filter) > -1;
377
- }
378
- });
414
+ });
415
+ }
379
416
  }
380
417
 
381
418
  function fillSelectedOptions() {
@@ -383,9 +420,17 @@ Default value: `<span>{option[search_key] || option}</span>`
383
420
  selectedOptions = normalisedOptions.filter(
384
421
  (v) => `${v[used_value_key]}` === `${value}`
385
422
  );
386
- setSingleVisibleValue();
387
423
  } else {
388
- selectedOptions = normalisedOptions
424
+ let _normalisedOptions = asyncMode
425
+ ? [...selectedOptions, ...normalisedOptions].filter(
426
+ //de-dupe by `used_value_key` when in asyncMode
427
+ (value, idx, self) =>
428
+ idx ===
429
+ self.findIndex((v) => v[used_value_key] === value[used_value_key])
430
+ )
431
+ : normalisedOptions;
432
+
433
+ selectedOptions = _normalisedOptions
389
434
  .filter(
390
435
  (v) => value && value.some((vl) => `${v[used_value_key]}` === `${vl}`)
391
436
  )
@@ -398,6 +443,23 @@ Default value: `<span>{option[search_key] || option}</span>`
398
443
  POPPER && POPPER.update();
399
444
  }
400
445
 
446
+ function triggerSearch(filter) {
447
+ if (filter === "") {
448
+ //do not trigger async search if filter is empty
449
+ options = [];
450
+ searching = false;
451
+ return;
452
+ }
453
+ options_loading = true;
454
+ search(filter).then((_options) => {
455
+ options = _options;
456
+ searching = false;
457
+ options_loading = false;
458
+ });
459
+ }
460
+
461
+ const debouncedTriggerSearch = debounce(triggerSearch, 150, false);
462
+
401
463
  onMount(() => {
402
464
  POPPER = createPopper(el, dropdown, {
403
465
  strategy: "fixed",
@@ -406,10 +468,25 @@ Default value: `<span>{option[search_key] || option}</span>`
406
468
  });
407
469
 
408
470
  //normalize value for single versus multiselect
409
- if (value === null || typeof value == "undefined")
471
+ if (value === null || typeof value == "undefined") {
410
472
  value = single ? null : [];
473
+ }
411
474
 
412
- setSingleVisibleValue();
475
+ if (asyncMode) {
476
+ // initally on async mode options are empty
477
+ // so we need to fill selectedOptions with value if value is avaliable
478
+ options = value ? [...(single ? [value] : [...value])] : [];
479
+ searching = false;
480
+ tick().then(() => {
481
+ normaliseOptions();
482
+ value = normaliseArraysToObjects(options).map((v) => v[used_value_key]);
483
+ if (single && Array.isArray(value)) {
484
+ value = value[0];
485
+ }
486
+ fillSelectedOptions();
487
+ clearDropDownResults();
488
+ });
489
+ }
413
490
 
414
491
  return () => {
415
492
  POPPER.destroy();
@@ -424,24 +501,39 @@ Default value: `<span>{option[search_key] || option}</span>`
424
501
  let isAlreadySelected = isSelected(token);
425
502
 
426
503
  if (single) {
427
- if (isAlreadySelected) {
428
- setSingleVisibleValue();
429
- } else {
504
+ if (!isAlreadySelected) {
430
505
  value = token[used_value_key];
431
- input && input.blur();
432
- setOptionsVisible(false);
433
506
  fire("change", { token, type: `add` });
507
+ //clear dropdown results in asyncMode
508
+ if (asyncMode) {
509
+ clearDropDownResults();
510
+ }
434
511
  }
512
+ setOptionsVisible(false);
435
513
  }
436
514
 
437
515
  if (!isAlreadySelected && !single && (max === null || value.length < max)) {
438
- //attach to value array while filtering out invalid values
439
- value = [...value, token[used_value_key]].filter((v) => {
440
- return normalisedOptions.filter((nv) => nv[used_value_key] === v)
441
- .length;
442
- });
516
+ if (asyncMode) {
517
+ //Do not filter invalid options, as they are async and might not be invalid
518
+ //but ensure they are unique
519
+ value = [...value, token[used_value_key]].filter(
520
+ (v, i, a) => a.indexOf(v) === i
521
+ );
522
+ } else {
523
+ //attach to value array while filtering out invalid values
524
+ value = [...value, token[used_value_key]].filter((v) => {
525
+ return normalisedOptions.filter((nv) => nv[used_value_key] === v)
526
+ .length;
527
+ });
528
+ }
529
+
443
530
  searchText = ""; // reset search string on selection
444
531
 
532
+ //clear dropdown results in asyncMode
533
+ if (asyncMode) {
534
+ clearDropDownResults();
535
+ }
536
+
445
537
  if (value && value.length && value.length === max) {
446
538
  input && input.blur();
447
539
  setOptionsVisible(false);
@@ -460,6 +552,11 @@ Default value: `<span>{option[search_key] || option}</span>`
460
552
  value = value.filter
461
553
  ? value.filter((item) => !matchesValue(item, token))
462
554
  : value;
555
+
556
+ //clear dropdown results in asyncMode
557
+ if (asyncMode) {
558
+ clearDropDownResults();
559
+ }
463
560
  /**
464
561
  * Triggered when an item is removed from selected Items
465
562
  */
@@ -483,25 +580,16 @@ Default value: `<span>{option[search_key] || option}</span>`
483
580
  showOptions = show;
484
581
  if (show) {
485
582
  input && input.focus();
486
- }
487
- POPPER && POPPER.update();
488
- }
489
-
490
- function setSingleVisibleValue() {
491
- if (single && hasValue) {
492
- searchText =
493
- selectedOptions && selectedOptions[0]
494
- ? selectedOptions[0][used_search_key]
495
- : "";
583
+ } else {
584
+ searchText = "";
496
585
  searching = false;
497
586
  }
587
+ POPPER && POPPER.update();
498
588
  }
499
589
 
500
590
  function handleKeydown(event) {
501
591
  if (event.key === `Escape`) {
502
- if (!single) {
503
- searchText = "";
504
- }
592
+ searchText = "";
505
593
  } else {
506
594
  setOptionsVisible(true);
507
595
  }
@@ -510,9 +598,6 @@ Default value: `<span>{option[search_key] || option}</span>`
510
598
  event.preventDefault();
511
599
  if (activeOption) {
512
600
  handleOptionMouseDown(activeOption);
513
- if (!single) {
514
- searchText = "";
515
- }
516
601
  } else {
517
602
  // no active option means the options are closed in which case enter means open
518
603
  setOptionsVisible(true);
@@ -529,24 +614,31 @@ Default value: `<span>{option[search_key] || option}</span>`
529
614
  else activeOption = filteredOptions[newActiveIdx];
530
615
  }
531
616
  } else if (event.key === `Backspace`) {
532
- // only remove selected tags on backspace if there are any and no searchText characters remain
533
- if (searchText.length === 0) {
534
- if (single) {
535
- if (value) {
536
- value = null;
537
- }
538
- } else {
539
- if (value && value.length > 0) {
540
- value = value.slice(0, value.length - 1);
541
- }
617
+ if (single && hasValue) {
618
+ //for a single select
619
+ //if a value is already selected, backspace will clear the value
620
+ value = null;
621
+ searchText = "";
622
+ } else if (!single && searchText.length === 0) {
623
+ //for a multi select
624
+ // only remove selected tags on backspace if there are any and no searchText characters remain
625
+ if (value && value.length > 0) {
626
+ value = value.slice(0, value.length - 1);
542
627
  }
543
628
  } else {
544
- if (single) {
545
- searching = true;
546
- }
629
+ searching = true;
547
630
  }
548
631
  } else {
632
+ //for a single select
633
+ //if a value is already selected,
634
+ //ignore keys other than navigation, enter and backspace
549
635
  if (single) {
636
+ if (hasValue) {
637
+ event.preventDefault();
638
+ } else {
639
+ searching = true;
640
+ }
641
+ } else {
550
642
  searching = true;
551
643
  }
552
644
  }
@@ -565,6 +657,9 @@ Default value: `<span>{option[search_key] || option}</span>`
565
657
  fire("change", { token: value, type: `remove` });
566
658
  value = single ? null : [];
567
659
  searchText = "";
660
+ if (asyncMode) {
661
+ clearDropDownResults();
662
+ }
568
663
  };
569
664
 
570
665
  const matchesValue = (_value, _option) => {
@@ -575,4 +670,30 @@ Default value: `<span>{option[search_key] || option}</span>`
575
670
  `${_value[used_value_key] || _value}` === `${_option[used_value_key]}`
576
671
  );
577
672
  };
673
+
674
+ const match = (needle, haystack) => {
675
+ let _hayStack = haystack.toLowerCase();
676
+ return allow_fuzzy_match
677
+ ? fuzzysearch(needle, _hayStack)
678
+ : _hayStack.indexOf(needle) > -1;
679
+ };
680
+
681
+ const normaliseArraysToObjects = (arr) => {
682
+ return arr.slice().map((item) => {
683
+ if (typeof item === "object") {
684
+ return item;
685
+ }
686
+ let __obj = {};
687
+ __obj[used_search_key] = item;
688
+ __obj[used_value_key] = item;
689
+ return __obj;
690
+ });
691
+ };
692
+
693
+ const clearDropDownResults = () => {
694
+ tick().then(() => {
695
+ options = [];
696
+ searching = false;
697
+ });
698
+ };
578
699
  </script>