@danielgindi/selectbox 1.0.147 → 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.
package/dist/lib.es6.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @danielgindi/selectbox 1.0.147
2
+ * @danielgindi/selectbox 2.0.0
3
3
  * git://github.com/danielgindi/selectbox.git
4
4
  */
5
5
  import { createElement, closestUntil, setElementAttrs, next, prev } from '@danielgindi/dom-utils/lib/Dom';
@@ -12,9 +12,57 @@ import mitt from 'mitt';
12
12
 
13
13
  var escapeRegex = value => value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
14
14
 
15
+ const throttle = (func, wait, options) => {
16
+ let timeout, context, args, result;
17
+ let previous = 0;
18
+ if (!options) options = {};
19
+
20
+ const later = () => {
21
+ previous = options.leading === false ? 0 : Date.now();
22
+ timeout = null;
23
+ result = func.apply(context, args);
24
+ if (!timeout) context = args = null;
25
+ };
26
+
27
+ const throttled = function () {
28
+ const now = Date.now();
29
+ if (!previous && options.leading === false)
30
+ previous = now;
31
+
32
+ const remaining = wait - (now - previous);
33
+ context = this;
34
+ args = arguments;
35
+ if (remaining <= 0 || remaining > wait) {
36
+ if (timeout) {
37
+ clearTimeout(timeout);
38
+ timeout = null;
39
+ }
40
+ previous = now;
41
+ result = func.apply(context, args);
42
+ if (!timeout) context = args = null;
43
+ } else if (!timeout && options.trailing !== false) {
44
+ timeout = setTimeout(later, remaining);
45
+ }
46
+ return result;
47
+ };
48
+
49
+ throttled.cancel = () => {
50
+ clearTimeout(timeout);
51
+ previous = 0;
52
+ timeout = context = args = null;
53
+ };
54
+
55
+ throttled.isScheduled = () => {
56
+ return !!timeout;
57
+ };
58
+
59
+ return throttled;
60
+ };
61
+
15
62
  const ItemSymbol$1 = Symbol('item');
16
63
  const DestroyedSymbol$1 = Symbol('destroyed');
17
64
  const GhostSymbol = Symbol('ghost');
65
+ const NoResultsItemSymbol = Symbol('no_results_items');
18
66
 
19
67
  const hasOwnProperty = Object.prototype.hasOwnProperty;
20
68
 
@@ -40,7 +88,17 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
40
88
  * @property {string} [valueProp='value']
41
89
  * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderItem] Function to call when rendering an item element
42
90
  * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderItem] Function to call when rendering an item element
91
+ * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderNoResultsItem]
92
+ * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderNoResultsItem]
43
93
  * @property {function(name: string, data: *)} [on]
94
+ * @property {boolean} [searchable=false] include inline search box
95
+ * @property {string} [noResultsText='No matching results'] text for no results (empty for none)
96
+ * @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
97
+ * @property {boolean} [filterOnEmptyTerm=false] call the filter function on empty search term too
98
+ * @property {boolean} [filterGroups=false] should groups be filtered?
99
+ * @property {boolean} [filterEmptyGroups=false] should empty groups be filtered out?
100
+ * @property {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)} [filterFn]
101
+ * @property {function(dropList: DropList):DropList.PositionOptions} [positionOptionsProvider]
44
102
  * */
45
103
  /** */
46
104
 
@@ -104,6 +162,13 @@ let defaultOptions$1 = {
104
162
  valueProp: 'value',
105
163
 
106
164
  on: null,
165
+
166
+ searchable: false,
167
+ noResultsText: 'No matching results',
168
+ filterThrottleWindow: 300,
169
+ filterOnEmptyTerm: false,
170
+ filterGroups: false,
171
+ filterEmptyGroups: false,
107
172
  };
108
173
 
109
174
  /*
@@ -164,17 +229,35 @@ class DropList {
164
229
  valueProp: o.valueProp,
165
230
  renderItem: o.renderItem,
166
231
  unrenderItem: o.unrenderItem,
232
+ renderNoResultsItem: o.renderNoResultsItem,
233
+ unrenderNoResultsItem: o.unrenderNoResultsItem,
167
234
  on: o.on || null,
235
+ positionOptionsProvider: o.positionOptionsProvider ?? null,
236
+
237
+ searchable: o.searchable,
238
+
168
239
  silenceEvents: true,
169
240
  mitt: mitt(),
170
241
 
242
+ filterThrottleWindow: o.filterThrottleWindow,
243
+ filterOnEmptyTerm: o.filterOnEmptyTerm,
244
+ filterGroups: o.filterGroups,
245
+ filterEmptyGroups: o.filterEmptyGroups,
246
+ filterFn: o.filterFn,
247
+ filteredItems: null,
248
+ filterTerm: '',
249
+ needsRefilter: false,
250
+ throttledRefilterItems: null,
251
+
171
252
  focusItemIndex: -1,
172
253
  focusItemEl: null,
173
254
 
174
255
  sink: new DomEventsSink(),
175
256
  };
176
257
 
177
- let classes = [p.baseClassName];
258
+ const baseClass = p.baseClassName + '_wrapper';
259
+
260
+ let classes = [baseClass];
178
261
 
179
262
  if (p.additionalClasses) {
180
263
  classes = classes.concat((p.additionalClasses + '').split(' ').filter(x => x));
@@ -185,31 +268,54 @@ class DropList {
185
268
  top: '-9999px',
186
269
  };
187
270
 
188
- let el = o.el;
189
- if (el instanceof Element) {
190
- p.elOriginalDisplay = el.style.display || '';
191
- el.classList.add(...classes);
192
- el.role = 'menu';
193
- setCssProps(/**@type ElementCSSInlineStyle*/el, initialCss);
271
+ let wrapperEl = o.el;
272
+
273
+ if (wrapperEl instanceof Element) {
274
+ p.elOriginalDisplay = wrapperEl.style.display || '';
275
+ wrapperEl.classList.add(...classes);
276
+ setCssProps(/**@type ElementCSSInlineStyle*/wrapperEl, initialCss);
194
277
  p.ownsEl = false;
195
278
  } else {
196
- el = createElement('ul', {
279
+ wrapperEl = createElement('div', {
197
280
  class: classes.join(' '),
198
- role: 'menu',
199
281
  css: initialCss,
200
282
  });
201
283
  }
202
284
 
203
- p.el = el;
285
+ let menuEl = createElement('ul');
286
+ menuEl.role = 'menu';
204
287
 
205
- p.items = [];
288
+ if (o.searchable) {
289
+ p.headerEl = createElement('div', {
290
+ class: p.baseClassName + '_header',
291
+ });
206
292
 
207
- p.groupCount = 0; // This will keep state of how many `group` items we have
293
+ p.searchInput = createElement('input', {
294
+ type: 'search',
295
+ role: 'searchbox',
296
+ tabindex: '0',
297
+ autocorrect: 'off',
298
+ autocomplete: 'off',
299
+ autocapitalize: 'off',
300
+ spellcheck: 'false',
301
+ 'aria-autocomplete': 'list',
302
+ });
303
+
304
+ p.headerEl.appendChild(p.searchInput);
305
+ wrapperEl.appendChild(p.headerEl);
306
+ }
307
+
308
+ wrapperEl.appendChild(menuEl);
309
+
310
+ p.el = wrapperEl;
311
+ p.menuEl = menuEl;
208
312
 
313
+ p.items = [];
314
+ p.groupCount = 0; // This will keep state of how many `group` items we have
209
315
  p.mouseHandled = false;
210
316
 
211
317
  p.virtualListHelper = new VirtualListHelper({
212
- list: p.el,
318
+ list: p.menuEl,
213
319
  virtual: true,
214
320
  buffer: 5,
215
321
  estimatedItemHeight: o.estimatedItemHeight || 20,
@@ -236,7 +342,19 @@ class DropList {
236
342
  };
237
343
  itemEl.setAttribute('aria-hidden', 'true');
238
344
  } else {
239
- item = p.items[index];
345
+ const items = p.filteredItems ?? p.items;
346
+ if (items.length === 0 && p.noResultsText) {
347
+ item = {
348
+ value: NoResultsItemSymbol,
349
+ label: p.noResultsText,
350
+ _nointeraction: true,
351
+ _nocheck: true,
352
+
353
+ [ItemSymbol$1]: NoResultsItemSymbol,
354
+ };
355
+ } else {
356
+ item = items[index];
357
+ }
240
358
  }
241
359
 
242
360
  if (!item) {
@@ -282,36 +400,20 @@ class DropList {
282
400
  },
283
401
  });
284
402
 
285
- if (typeof p.unrenderItem === 'function') {
286
- const fn = p.unrenderItem;
287
- p.virtualListHelper.setOnItemUnrender(el => {
288
- try {
289
- fn(el[ItemSymbol$1][ItemSymbol$1], el);
290
- } catch (err) {
291
- console.error(err); // eslint-disable-line no-console
292
- }
293
- delete el[ItemSymbol$1];
294
-
295
- if (p.focusItemEl === el)
296
- p.focusItemEl = null;
297
- });
298
- } else {
299
- p.virtualListHelper.setOnItemUnrender(el => {
300
- delete el[ItemSymbol$1];
301
-
302
- if (p.focusItemEl === el)
303
- p.focusItemEl = null;
304
- });
305
- }
403
+ this._setupUnrenderFunction();
306
404
 
307
405
  if (p.capturesFocus) {
308
- el.tabIndex = 0;
406
+ wrapperEl.tabIndex = 0;
309
407
  }
310
408
 
409
+ this.setFilterThrottleWindow(o.filterThrottleWindow);
410
+ this.setNoResultsText(o.noResultsText);
411
+
311
412
  this._hookMouseEvents();
312
413
  this._hookTouchEvents();
313
414
  this._hookFocusEvents();
314
415
  this._hookKeyEvents();
416
+ this._hookSearchEvents();
315
417
 
316
418
  this.silenceEvents = false;
317
419
  }
@@ -329,7 +431,7 @@ class DropList {
329
431
  p.sink.remove();
330
432
  p.virtualListHelper.destroy();
331
433
 
332
- if (p.el) {
434
+ if (p.el?.parentNode) {
333
435
  remove(p.el);
334
436
  }
335
437
 
@@ -338,13 +440,15 @@ class DropList {
338
440
  p.currentSubDropList = null;
339
441
  }
340
442
 
443
+ if (p.throttledRefilterItems)
444
+ p.throttledRefilterItems.cancel();
445
+
341
446
  if (!p.ownsEl) {
342
447
  for (let name of Array.from(p.el.classList)) {
343
448
  if (name.startsWith(p.baseClassName)) {
344
449
  p.el.classList.remove(name);
345
450
  }
346
451
  }
347
- p.el.removeAttribute('role');
348
452
  for (let key of ['position', 'left', 'top', 'right', 'bottom', 'z-index']) {
349
453
  p.el.style[key] = '';
350
454
  }
@@ -359,6 +463,8 @@ class DropList {
359
463
  delete p.lastPositionTarget;
360
464
  }
361
465
 
466
+ delete p.lastPositionOptions;
467
+
362
468
  this._p = null;
363
469
  }
364
470
 
@@ -442,16 +548,47 @@ class DropList {
442
548
  */
443
549
  setUnrenderItem(fn) {
444
550
  const p = this._p;
445
-
446
551
  p.unrenderItem = fn;
552
+ this._setupUnrenderFunction();
553
+ return this;
554
+ }
555
+
556
+ /**
557
+ * @param {(function(item: DropList.ItemBase, itemEl: Element):(*|false))|null} render
558
+ * @param {(function(item: DropList.ItemBase, itemEl: Element))|null} unrender
559
+ * @returns {DropList}
560
+ */
561
+ setRenderNoResultsItem(render, unrender) {
562
+ const p = this._p;
563
+ p.renderNoResultsItem = render;
564
+ p.unrenderNoResultsItem = unrender;
565
+ this._setupUnrenderFunction();
566
+ return this;
567
+ }
447
568
 
448
- if (typeof p.unrenderItem === 'function') {
569
+ /**
570
+ * @private
571
+ */
572
+ _setupUnrenderFunction() {
573
+ const p = this._p;
574
+
575
+ if (typeof p.unrenderItem === 'function' || typeof p.unrenderNoResultsItem === 'function') {
449
576
  const fn = p.unrenderItem;
577
+ const fnNoResults = p.unrenderNoResultsItem;
450
578
  p.virtualListHelper.setOnItemUnrender(el => {
451
- try {
452
- fn(el[ItemSymbol$1][ItemSymbol$1], el);
453
- } catch (err) {
454
- console.error(err); // eslint-disable-line no-console
579
+ const item = el[ItemSymbol$1];
580
+ if (item === NoResultsItemSymbol) {
581
+ try {
582
+ fnNoResults(item, el);
583
+ } catch (err) {
584
+ console.error(err); // eslint-disable-line no-console
585
+ }
586
+ } else {
587
+ try {
588
+ fn(item[ItemSymbol$1], el);
589
+ } catch (err) {
590
+ console.error(err); // eslint-disable-line no-console
591
+ }
455
592
  }
456
593
  delete el[ItemSymbol$1];
457
594
 
@@ -486,7 +623,8 @@ class DropList {
486
623
  if (!el)
487
624
  return;
488
625
 
489
- let classes = [p.baseClassName];
626
+ const baseClass = p.baseClassName + '_wrapper';
627
+ let classes = [baseClass];
490
628
 
491
629
  if (p.direction === 'ltr' || p.direction === 'rtl')
492
630
  classes.push(`${p.baseClassName}__` + p.direction);
@@ -513,7 +651,8 @@ class DropList {
513
651
  p.focusItemEl = null;
514
652
  }
515
653
 
516
- const item = p.items[p.focusItemIndex];
654
+ const items = p.filteredItems ?? p.items;
655
+ const item = items[p.focusItemIndex];
517
656
  p.focusItemIndex = -1;
518
657
 
519
658
  if (!item) {
@@ -664,9 +803,24 @@ class DropList {
664
803
  }
665
804
  }
666
805
 
667
- p.virtualListHelper
668
- .addItemsAt(itemsToAdd.length, atIndex === -1 ? atIndex : (atIndex - itemsToAdd.length))
669
- .render();
806
+ if (!p.filteredItems) {
807
+ let hadNoResultItem = p.hasNoResultsItem;
808
+ p.hasNoResultsItem = p.items.length === 0 && !!p.noResultsText;
809
+ if (p.hasNoResultsItem) {
810
+ p.virtualListHelper
811
+ .setCount(1)
812
+ .render();
813
+ } else {
814
+ if (hadNoResultItem)
815
+ p.virtualListHelper.removeItemsAt(1, 0);
816
+
817
+ p.virtualListHelper
818
+ .addItemsAt(itemsToAdd.length, atIndex === -1 ? atIndex : (atIndex - itemsToAdd.length))
819
+ .render();
820
+ }
821
+ } else {
822
+ p.needsRefilter = true;
823
+ }
670
824
 
671
825
  return this;
672
826
  }
@@ -680,9 +834,11 @@ class DropList {
680
834
  const p = this._p;
681
835
 
682
836
  p.items.length = 0;
837
+ p.filteredItems = null;
683
838
  p.groupCount = 0;
684
839
 
685
- p.virtualListHelper.setCount(0);
840
+ p.hasNoResultsItem = !!p.noResultsText;
841
+ p.virtualListHelper.setCount(p.hasNoResultsItem ? 1 : 0);
686
842
 
687
843
  this.addItems(items);
688
844
  this.updateSublist();
@@ -733,9 +889,18 @@ class DropList {
733
889
  if (hasOwnProperty.call(newItem, '_child'))
734
890
  item._child = !!newItem._child;
735
891
 
736
- if (p.virtualListHelper.isItemRendered(itemIndex)) {
892
+ let virtualItemIndex = itemIndex;
893
+ if (p.filteredItems) {
894
+ virtualItemIndex = p.filteredItems.indexOf(item);
895
+ if (virtualItemIndex !== -1) {
896
+ p.filteredItems.splice(virtualItemIndex, 1);
897
+ }
898
+ }
899
+
900
+ if (virtualItemIndex !== -1 &&
901
+ p.virtualListHelper.isItemRendered(virtualItemIndex)) {
737
902
  p.virtualListHelper
738
- .refreshItemAt(itemIndex)
903
+ .refreshItemAt(virtualItemIndex)
739
904
  .render();
740
905
  }
741
906
 
@@ -754,9 +919,27 @@ class DropList {
754
919
  p.groupCount--;
755
920
  }
756
921
 
757
- p.virtualListHelper
758
- .removeItemsAt(itemIndex, 1)
759
- .render();
922
+ let virtualItemIndex = itemIndex;
923
+ if (p.filteredItems) {
924
+ virtualItemIndex = p.filteredItems.indexOf(spliced[0]);
925
+ if (virtualItemIndex !== -1) {
926
+ p.filteredItems.splice(virtualItemIndex, 1);
927
+ }
928
+ }
929
+
930
+ if (virtualItemIndex !== -1) {
931
+ p.hasNoResultsItem = (p.filteredItems ?? p.items).length === 0 && !!p.noResultsText;
932
+
933
+ if (p.hasNoResultsItem) {
934
+ p.virtualListHelper
935
+ .setCount(1)
936
+ .render();
937
+ } else {
938
+ p.virtualListHelper
939
+ .removeItemsAt(virtualItemIndex, 1)
940
+ .render();
941
+ }
942
+ }
760
943
 
761
944
  return this;
762
945
  }
@@ -765,10 +948,13 @@ class DropList {
765
948
  const p = this._p;
766
949
 
767
950
  p.items.length = 0;
951
+ p.filteredItems = null;
768
952
  p.groupCount = 0;
769
953
 
954
+ p.hasNoResultsItem = !!p.noResultsText;
955
+
770
956
  p.virtualListHelper
771
- .setCount(0)
957
+ .setCount(p.hasNoResultsItem ? 1 : 0)
772
958
  .render();
773
959
 
774
960
  return this;
@@ -804,8 +990,23 @@ class DropList {
804
990
  itemIndexByValue(value) {
805
991
  const p = this._p;
806
992
 
807
- for (let i = 0, count = p.items.length; i < count; i++) {
808
- let item = p.items[i];
993
+ const items = p.items;
994
+ for (let i = 0, count = items.length; i < count; i++) {
995
+ let item = items[i];
996
+ if (item.value === value) {
997
+ return i;
998
+ }
999
+ }
1000
+
1001
+ return -1;
1002
+ }
1003
+
1004
+ filteredItemIndexByValue(value) {
1005
+ const p = this._p;
1006
+
1007
+ const items = p.filteredItems ?? p.items;
1008
+ for (let i = 0, count = items.length; i < count; i++) {
1009
+ let item = items[i];
809
1010
  if (item.value === value) {
810
1011
  return i;
811
1012
  }
@@ -817,8 +1018,23 @@ class DropList {
817
1018
  itemIndexByValueOrLabel(value, label) {
818
1019
  const p = this._p;
819
1020
 
820
- for (let i = 0, count = p.items.length; i < count; i++) {
821
- let item = p.items[i];
1021
+ const items = p.items;
1022
+ for (let i = 0, count = items.length; i < count; i++) {
1023
+ let item = items[i];
1024
+ if (item.value === value || item.label === label) {
1025
+ return i;
1026
+ }
1027
+ }
1028
+
1029
+ return -1;
1030
+ }
1031
+
1032
+ filteredItemIndexByValueOrLabel(value, label) {
1033
+ const p = this._p;
1034
+
1035
+ const items = p.filteredItems ?? p.items;
1036
+ for (let i = 0, count = items.length; i < count; i++) {
1037
+ let item = items[i];
822
1038
  if (item.value === value || item.label === label) {
823
1039
  return i;
824
1040
  }
@@ -827,6 +1043,36 @@ class DropList {
827
1043
  return -1;
828
1044
  }
829
1045
 
1046
+ itemIndexByItem(item) {
1047
+ const p = this._p;
1048
+
1049
+ const items = p.items;
1050
+
1051
+ for (let i = 0, count = items.length; i < count; i++) {
1052
+ let it = items[i];
1053
+ if (it[ItemSymbol$1] === item) {
1054
+ return i;
1055
+ }
1056
+ }
1057
+
1058
+ return -1;
1059
+ }
1060
+
1061
+ filteredItemIndexByItem(item) {
1062
+ const p = this._p;
1063
+
1064
+ const items = p.filteredItems ?? p.items;
1065
+
1066
+ for (let i = 0, count = items.length; i < count; i++) {
1067
+ let it = items[i];
1068
+ if (it[ItemSymbol$1] === item) {
1069
+ return i;
1070
+ }
1071
+ }
1072
+
1073
+ return -1;
1074
+ }
1075
+
830
1076
  items() {
831
1077
  return this._p.items.map(x => x[ItemSymbol$1]);
832
1078
  }
@@ -843,19 +1089,320 @@ class DropList {
843
1089
  return this._p.items[index]?.[ItemSymbol$1];
844
1090
  }
845
1091
 
1092
+ filteredItemAtIndex(index) {
1093
+ const p = this._p;
1094
+ const items = p.filteredItems ?? p.items;
1095
+ return items[index]?.[ItemSymbol$1];
1096
+ }
1097
+
1098
+ /**
1099
+ * @param {function(dropList: DropList):DropList.PositionOptions} fn
1100
+ * @returns {DropList}
1101
+ */
1102
+ setPositionOptionsProvider(fn) {
1103
+ const p = this._p;
1104
+ if (p.positionOptionsProvider === fn)
1105
+ return this;
1106
+ p.positionOptionsProvider = fn ?? null;
1107
+ this.relayout();
1108
+ return this;
1109
+ }
1110
+
1111
+ /**
1112
+ * @returns {function(dropList: DropList):DropList.PositionOptions|null}
1113
+ */
1114
+ getPositionOptionsProvider() {
1115
+ const p = this._p;
1116
+ return p.positionOptionsProvider;
1117
+ }
1118
+
1119
+ /**
1120
+ * @param {string} term
1121
+ * @param {boolean} [performSearch=false] should actually perform the search, or just set the input's text?
1122
+ * @returns {DropList}
1123
+ */
1124
+ setSearchTerm(term, performSearch = false) {
1125
+ const p = this._p;
1126
+
1127
+ if (p.searchInput) {
1128
+ p.searchInput.value = term;
1129
+ }
1130
+
1131
+ if (performSearch) {
1132
+ p.filterTerm = term.trim();
1133
+ p.filteredItems = null;
1134
+
1135
+ this._trigger('search', { value: term });
1136
+ p.throttledRefilterItems();
1137
+ }
1138
+
1139
+ return this;
1140
+ }
1141
+
1142
+ isFilterPending() {
1143
+ const p = this._p;
1144
+
1145
+ return !!(p.throttledRefilterItems.isScheduled() ||
1146
+ (!p.filteredItems && (p.filterTerm || (p.filterOnEmptyTerm && p.filterFn))));
1147
+ }
1148
+
1149
+ /** @private */
1150
+ _refilterItems() {
1151
+ const p = this._p;
1152
+
1153
+ const term = p.filterTerm;
1154
+ const filterGroups = p.filterGroups;
1155
+ const filterEmptyGroups = p.filterEmptyGroups;
1156
+
1157
+ if (term || (p.filterOnEmptyTerm && p.filterFn)) {
1158
+ let fn = p.filterFn;
1159
+
1160
+ let filteredItems;
1161
+
1162
+ if (typeof fn === 'function') {
1163
+ filteredItems = p.filterFn(p.items, term);
1164
+ }
1165
+
1166
+ // If there was no filter function, or it gave up on filtering.
1167
+ if (!Array.isArray(filteredItems)) {
1168
+ if (term) {
1169
+ const matcher = new RegExp(escapeRegex(term), 'i');
1170
+ const labelProp = p.labelProp;
1171
+
1172
+ filteredItems = p.items.filter(x => {
1173
+ if (!filterGroups && x._group) return true;
1174
+ return matcher.test(x[labelProp]);
1175
+ });
1176
+ } else {
1177
+ filteredItems = null;
1178
+ }
1179
+ }
1180
+
1181
+ p.filteredItems = filteredItems;
1182
+
1183
+ if (filteredItems && filterEmptyGroups) {
1184
+ // Clean up groups without children
1185
+
1186
+ let lastGroup = -1;
1187
+ let len = filteredItems.length;
1188
+
1189
+ for (let i = 0; i < len; i++) {
1190
+ let item = filteredItems[i];
1191
+
1192
+ if (item._group) {
1193
+ if (lastGroup !== -1 && lastGroup === i - 1) {
1194
+ // It was an empty group
1195
+ filteredItems.splice(lastGroup, 1);
1196
+ i--;
1197
+ len--;
1198
+ }
1199
+
1200
+ lastGroup = i;
1201
+ }
1202
+ }
1203
+
1204
+ if (lastGroup !== -1) {
1205
+ if (lastGroup === len - 1) {
1206
+ // It was an empty group
1207
+ filteredItems.splice(lastGroup, 1);
1208
+ }
1209
+ }
1210
+ }
1211
+ } else {
1212
+ p.filteredItems = null;
1213
+ }
1214
+
1215
+ this.needsRefilter = false;
1216
+
1217
+ const items = p.filteredItems ?? p.items;
1218
+ p.hasNoResultsItem = items.length === 0 && !!p.noResultsText;
1219
+ p.virtualListHelper
1220
+ .setCount(items.length + (p.hasNoResultsItem ? 1 : 0))
1221
+ .render();
1222
+
1223
+ this._trigger('itemschanged', { term: term, mutated: false, count: this.getFilteredItemCount() });
1224
+ this.relayout();
1225
+ }
1226
+
1227
+ invokeRefilter() {
1228
+ const p = this._p;
1229
+ if (!p.filterTerm && !p.filterOnEmptyTerm && !p.filteredItems)
1230
+ return this;
1231
+ p.filteredItems = null;
1232
+ p.throttledRefilterItems();
1233
+ return this;
1234
+ }
1235
+
1236
+ getFilteredItemCount() {
1237
+ const p = this._p;
1238
+
1239
+ if (p.needsRefilter)
1240
+ this._refilterItems();
1241
+
1242
+ if (p.filteredItems)
1243
+ return p.filteredItems.length;
1244
+
1245
+ if (p.items)
1246
+ return p.items.length;
1247
+
1248
+ return 0;
1249
+ }
1250
+
1251
+ /**
1252
+ * @param {string} noResultsText
1253
+ * @returns {DropList}
1254
+ */
1255
+ setNoResultsText(noResultsText) {
1256
+ this._p.noResultsText = noResultsText;
1257
+ return this;
1258
+ }
1259
+
1260
+ /**
1261
+ * @returns {string}
1262
+ */
1263
+ getNoResultsText() {
1264
+ return this._p.noResultsText;
1265
+ }
1266
+
1267
+ /**
1268
+ * @param {number} window
1269
+ * @returns {DropList}
1270
+ */
1271
+ setFilterThrottleWindow(window) {
1272
+ const p = this._p;
1273
+ p.filterThrottleWindow = window;
1274
+
1275
+ let isScheduled = p.throttledRefilterItems ? p.throttledRefilterItems.isScheduled() : false;
1276
+
1277
+ if (p.throttledRefilterItems)
1278
+ p.throttledRefilterItems.cancel();
1279
+
1280
+ p.throttledRefilterItems = throttle(() => this._refilterItems(), p.filterThrottleWindow, true);
1281
+
1282
+ if (isScheduled)
1283
+ p.throttledRefilterItems();
1284
+
1285
+ return this;
1286
+ }
1287
+
1288
+ /**
1289
+ * @returns {number}
1290
+ */
1291
+ getFilterThrottleWindow() {
1292
+ return this._p.filterThrottleWindow;
1293
+ }
1294
+
1295
+ /**
1296
+ * @param {boolean} value
1297
+ * @returns {DropList}
1298
+ */
1299
+ setFilterOnEmptyTerm(value) {
1300
+ const p = this._p;
1301
+ if (p.filterOnEmptyTerm === value)
1302
+ return this;
1303
+ p.filterOnEmptyTerm = value;
1304
+ p.throttledRefilterItems();
1305
+ return this;
1306
+ }
1307
+
1308
+ /**
1309
+ * @returns {boolean}
1310
+ */
1311
+ getFilterOnEmptyTerm() {
1312
+ return this._p.filterOnEmptyTerm;
1313
+ }
1314
+
1315
+ /**
1316
+ * @param {boolean} value
1317
+ * @returns {DropList}
1318
+ */
1319
+ setFilterGroups(value) {
1320
+ const p = this._p;
1321
+ if (p.filterGroups === value)
1322
+ return this;
1323
+ p.filterGroups = value;
1324
+ p.throttledRefilterItems();
1325
+ return this;
1326
+ }
1327
+
1328
+ /**
1329
+ * @returns {boolean}
1330
+ */
1331
+ getFilterGroups() {
1332
+ return this._p.filterGroups;
1333
+ }
1334
+
1335
+ /**
1336
+ * @param {boolean} value
1337
+ * @returns {DropList}
1338
+ */
1339
+ setFilterEmptyGroups(value) {
1340
+ const p = this._p;
1341
+ if (p.filterEmptyGroups === value)
1342
+ return this;
1343
+ p.filterEmptyGroups = value;
1344
+ p.throttledRefilterItems();
1345
+ return this;
1346
+ }
1347
+
1348
+ /**
1349
+ * @returns {boolean}
1350
+ */
1351
+ getFilterEmptyGroups() {
1352
+ return this._p.filterEmptyGroups;
1353
+ }
1354
+
1355
+ /**
1356
+ * @param {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)} fn
1357
+ * @returns {DropList}
1358
+ */
1359
+ setFilterFn(fn) {
1360
+ const p = this._p;
1361
+ if (p.filterFn === fn)
1362
+ return this;
1363
+ p.filterFn = fn;
1364
+ p.throttledRefilterItems();
1365
+ return this;
1366
+ }
1367
+
1368
+ /**
1369
+ * @returns {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)}
1370
+ */
1371
+ getFilterFn() {
1372
+ return this._p.filterFn;
1373
+ }
1374
+
846
1375
  /**
847
1376
  *
848
- * @param {DropList.PositionOptions} positionOptions
1377
+ * @param {DropList.PositionOptions} [positionOptions]
849
1378
  * @returns {DropList}
850
1379
  * @public
851
1380
  */
852
1381
  relayout(positionOptions) {
853
- const p = this._p, el = p.el;
1382
+ const p = this._p, el = p.el, menuEl = p.menuEl;
854
1383
 
855
1384
  if (!this.isVisible()) return this;
856
1385
 
857
1386
  let w = window;
858
1387
 
1388
+ if (!positionOptions)
1389
+ positionOptions = p.positionOptionsProvider?.() ?? p.lastPositionOptions;
1390
+
1391
+ // Supply some default for extreme cases, no crashing
1392
+ if (!positionOptions) {
1393
+ positionOptions = {
1394
+ targetOffset: {
1395
+ left: window.innerWidth / 2,
1396
+ top: window.innerHeight / 2,
1397
+ },
1398
+ targetWidth: 0,
1399
+ targetHeight: 0,
1400
+ position: { x: 'center', y: 'center' },
1401
+ anchor: { x: 'center', y: 'center' },
1402
+ targetRtl: getComputedStyle(document.body).direction === 'rtl',
1403
+ };
1404
+ }
1405
+
859
1406
  let targetBox = {};
860
1407
 
861
1408
  let offset = positionOptions.targetOffset || getElementOffset(positionOptions.target);
@@ -918,18 +1465,25 @@ class DropList {
918
1465
  let verticalBorderWidth = (parseFloat(elComputedStyle.borderTopWidth) || 0) +
919
1466
  (parseFloat(elComputedStyle.borderBottomWidth) || 0);
920
1467
 
1468
+ let headerHeight = 0;
1469
+ if (p.headerEl) {
1470
+ headerHeight = getElementHeight(p.headerEl, true, true);
1471
+ }
1472
+
921
1473
  if (p.virtualListHelper.isVirtual()) {
922
1474
  maxViewHeight =
923
1475
  p.virtualListHelper.estimateFullHeight() +
924
1476
  verticalPadding +
925
- verticalBorderWidth;
1477
+ verticalBorderWidth +
1478
+ headerHeight;
926
1479
  } else {
927
1480
  // Another method to calculate height is measuring the whole thing at once.
928
1481
  // This causes relayout of course.
929
1482
  el.style.height = '';
1483
+ menuEl.style.height = '';
930
1484
  el.style.top = '-9999px';
931
1485
 
932
- maxViewHeight = Math.max(getElementHeight(p.el), el.scrollHeight);
1486
+ maxViewHeight = Math.max(getElementHeight(p.el), menuEl.scrollHeight);
933
1487
  maxViewHeight += verticalPadding + verticalBorderWidth;
934
1488
  }
935
1489
 
@@ -1064,6 +1618,11 @@ class DropList {
1064
1618
  setCssProps(el, viewCss);
1065
1619
  setElementHeight(el, viewSize.height, true, true);
1066
1620
 
1621
+ if (menuEl !== el) {
1622
+ let menuHeight = viewSize.height - headerHeight - verticalBorderWidth - verticalPadding;
1623
+ setElementHeight(menuEl, menuHeight, true, true);
1624
+ }
1625
+
1067
1626
  // Update the scroll position for virtual lists
1068
1627
  p.virtualListHelper.render();
1069
1628
 
@@ -1201,7 +1760,7 @@ class DropList {
1201
1760
 
1202
1761
  /**
1203
1762
  *
1204
- * @param {DropList.PositionOptions?} positionOptions
1763
+ * @param {DropList.PositionOptions?} [positionOptions]
1205
1764
  * @returns {DropList}
1206
1765
  * @public
1207
1766
  */
@@ -1228,6 +1787,10 @@ class DropList {
1228
1787
  }
1229
1788
  });
1230
1789
 
1790
+ if (p.needsRefilter) {
1791
+ this._refilterItems();
1792
+ }
1793
+
1231
1794
  const el = p.el;
1232
1795
  el.style.position = 'absolute';
1233
1796
  el.classList.remove(`${p.baseClassName}__is-hiding`);
@@ -1239,6 +1802,14 @@ class DropList {
1239
1802
  if (getComputedStyle(p.el).display === 'none')
1240
1803
  p.el.style.display = 'block';
1241
1804
 
1805
+ p.lastPositionOptions = null;
1806
+
1807
+ if (positionOptions === undefined) {
1808
+ positionOptions = p.positionOptionsProvider?.() ?? p.lastPositionOptions;
1809
+ } else {
1810
+ p.lastPositionOptions = positionOptions;
1811
+ }
1812
+
1242
1813
  if (positionOptions) {
1243
1814
  const elComputedStyle = getComputedStyle(el);
1244
1815
 
@@ -1285,6 +1856,11 @@ class DropList {
1285
1856
 
1286
1857
  p.hiding = true;
1287
1858
 
1859
+ if (this.isFilterPending()) {
1860
+ p.throttledRefilterItems.cancel();
1861
+ p.needsRefilter = true;
1862
+ }
1863
+
1288
1864
  if (el) {
1289
1865
 
1290
1866
  el.classList.add(`${p.baseClassName}__is-hiding`);
@@ -1305,7 +1881,8 @@ class DropList {
1305
1881
  }
1306
1882
  });
1307
1883
  } else {
1308
- remove(el);
1884
+ if (el.parentNode)
1885
+ remove(el);
1309
1886
  el.classList.remove(`${p.baseClassName}__is-hiding`);
1310
1887
  }
1311
1888
  }
@@ -1354,15 +1931,22 @@ class DropList {
1354
1931
  setFocusedItemAtIndex(itemIndex) {
1355
1932
  const p = this._p;
1356
1933
 
1934
+ if (p.filteredItems) {
1935
+ const item = p.items[itemIndex];
1936
+ itemIndex = p.items.indexOf(item);
1937
+ }
1938
+
1357
1939
  p.focusItemIndex = itemIndex;
1358
1940
 
1941
+ const items = p.filteredItems ?? p.items;
1942
+
1359
1943
  let item = null;
1360
1944
  if (itemIndex > -1)
1361
- item = p.items[itemIndex];
1945
+ item = items[itemIndex];
1362
1946
  if (item && item._nointeraction)
1363
1947
  item = null;
1364
1948
  if (itemIndex > -1) {
1365
- this.scrollItemIndexIntoView(itemIndex);
1949
+ this._scrollItemIndexIntoView(itemIndex);
1366
1950
  }
1367
1951
  let itemElement = item ? p.virtualListHelper.getItemElementAt(itemIndex) : null;
1368
1952
 
@@ -1417,6 +2001,7 @@ class DropList {
1417
2001
  valueProp: p.valueProp,
1418
2002
  renderItem: p.renderItem,
1419
2003
  unrenderItem: p.unrenderItem,
2004
+ positionOptionsProvider: () => p.currentSubDropList.showOptions,
1420
2005
  });
1421
2006
 
1422
2007
  let onBlur = event => {
@@ -1464,7 +2049,7 @@ class DropList {
1464
2049
  },
1465
2050
  };
1466
2051
 
1467
- droplist.show(p.currentSubDropList.showOptions);
2052
+ droplist.show();
1468
2053
 
1469
2054
  droplist.el.focus();
1470
2055
  }
@@ -1518,7 +2103,7 @@ class DropList {
1518
2103
 
1519
2104
  if (itemElement) {
1520
2105
  p.currentSubDropList.showOptions.target = itemElement;
1521
- p.currentSubDropList.droplist.relayout(p.currentSubDropList.showOptions);
2106
+ p.currentSubDropList.droplist.relayout();
1522
2107
  }
1523
2108
  }
1524
2109
  }
@@ -1529,7 +2114,8 @@ class DropList {
1529
2114
 
1530
2115
  let itemIndex = item._nointeraction ? -1 : this._getItemIndex(item);
1531
2116
 
1532
- if (itemIndex > -1 && p.items[itemIndex]._nointeraction)
2117
+ const items = p.items;
2118
+ if (itemIndex > -1 && items[itemIndex]._nointeraction)
1533
2119
  itemIndex = -1;
1534
2120
 
1535
2121
  return this.setFocusedItemAtIndex(itemIndex);
@@ -1545,7 +2131,14 @@ class DropList {
1545
2131
  let itemEl = null;
1546
2132
 
1547
2133
  if (itemIndex > -1 && !p.items[itemIndex]._nointeraction) {
1548
- itemEl = p.virtualListHelper.getItemElementAt(itemIndex);
2134
+ if (p.filteredItems) {
2135
+ const item = p.items[itemIndex];
2136
+ itemIndex = p.filteredItems.indexOf(item);
2137
+ }
2138
+
2139
+ if (itemIndex > -1) {
2140
+ itemEl = p.virtualListHelper.getItemElementAt(itemIndex);
2141
+ }
1549
2142
  }
1550
2143
 
1551
2144
  this._setSingleSelectedItemEl(itemEl);
@@ -1558,7 +2151,8 @@ class DropList {
1558
2151
 
1559
2152
  let itemIndex = item._nointeraction ? -1 : this._getItemIndex(item);
1560
2153
 
1561
- if (itemIndex > -1 && p.items[itemIndex]._nointeraction)
2154
+ const items = p.items;
2155
+ if (itemIndex > -1 && items[itemIndex]._nointeraction)
1562
2156
  itemIndex = -1;
1563
2157
 
1564
2158
  return this.setSingleSelectedItemAtIndex(itemIndex);
@@ -1578,24 +2172,42 @@ class DropList {
1578
2172
 
1579
2173
  isFirstItem() {
1580
2174
  const p = this._p;
1581
- return p.focusItemIndex === 0 && p.focusItemIndex < p.items.length;
2175
+ const items = p.filteredItems ?? p.items;
2176
+ return p.focusItemIndex === 0 && p.focusItemIndex < items.length;
1582
2177
  }
1583
2178
 
1584
2179
  isLastItem() {
1585
2180
  const p = this._p;
1586
- return p.focusItemIndex > -1 && p.focusItemIndex === p.items.length - 1;
2181
+ const items = p.filteredItems ?? p.items;
2182
+ return p.focusItemIndex > -1 && p.focusItemIndex === items.length - 1;
1587
2183
  }
1588
2184
 
1589
2185
  scrollItemIndexIntoView(itemIndex) {
1590
2186
  const p = this._p;
1591
2187
 
2188
+ if (this._hasScroll()) {
2189
+ if (p.filteredItems) {
2190
+ const item = p.items[itemIndex];
2191
+ itemIndex = p.items.indexOf(item);
2192
+ }
2193
+
2194
+ if (itemIndex !== -1) {
2195
+ this._scrollItemIndexIntoView(itemIndex);
2196
+ }
2197
+ }
2198
+
2199
+ return this;
2200
+ }
2201
+
2202
+ _scrollItemIndexIntoView(itemIndex) {
2203
+ const p = this._p;
2204
+
1592
2205
  if (this._hasScroll()) {
1593
2206
  const el = p.el, scrollTop = el.scrollTop;
1594
2207
 
1595
2208
  let itemPos, previousPos = -1;
1596
2209
  let maxIterations = 30; // Some zoom/scroll issues can make it so that it takes almost forever
1597
2210
 
1598
-
1599
2211
  while (maxIterations-- > 0) {
1600
2212
  itemPos = p.virtualListHelper.getItemPosition(itemIndex);
1601
2213
 
@@ -1618,8 +2230,6 @@ class DropList {
1618
2230
  p.virtualListHelper.render();
1619
2231
  }
1620
2232
  }
1621
-
1622
- return this;
1623
2233
  }
1624
2234
 
1625
2235
  /**
@@ -1680,7 +2290,8 @@ class DropList {
1680
2290
  let itemIndex = -1;
1681
2291
 
1682
2292
  if (item) {
1683
- itemIndex = p.items.indexOf(item);
2293
+ const items = p.filteredItems ?? p.items;
2294
+ itemIndex = items.indexOf(item);
1684
2295
  if (itemIndex === -1) {
1685
2296
  let value = (item && item.value !== undefined) ? item.value : item;
1686
2297
  let label = (item && item.label) ? item.label : value;
@@ -1828,12 +2439,25 @@ class DropList {
1828
2439
 
1829
2440
  p.sink
1830
2441
  .add(p.el, 'focus', event => {
2442
+ if (event.target === this.el && p.searchable)
2443
+ p.searchInput.focus();
2444
+
2445
+ if (event.relatedTarget &&
2446
+ this.elContains(event.relatedTarget, true) &&
2447
+ this.elContains(event.target, true))
2448
+ return;
2449
+
1831
2450
  let itemEl = p.focusItemEl || // focused item
1832
2451
  p.el.firstChild; // or the first item
1833
2452
 
1834
2453
  this._focus(event, itemEl, null, false);
1835
- })
2454
+ }, true)
1836
2455
  .add(p.el, 'blur', event => {
2456
+ if (event.relatedTarget &&
2457
+ this.elContains(event.relatedTarget, true) &&
2458
+ this.elContains(event.target, true))
2459
+ return;
2460
+
1837
2461
  setTimeout(() => {
1838
2462
  if (this[DestroyedSymbol$1]) return;
1839
2463
 
@@ -1844,7 +2468,7 @@ class DropList {
1844
2468
  this._delayBlurItemOnBlur();
1845
2469
  this._trigger('blur', event);
1846
2470
  });
1847
- });
2471
+ }, true);
1848
2472
  }
1849
2473
 
1850
2474
  _hookKeyEvents() {
@@ -1853,6 +2477,21 @@ class DropList {
1853
2477
  p.sink.add(p.el, 'keydown', evt => this._keydown(evt));
1854
2478
  }
1855
2479
 
2480
+ _hookSearchEvents() {
2481
+ const p = this._p;
2482
+
2483
+ if (!p.searchInput)
2484
+ return;
2485
+
2486
+ p.sink.add(p.searchInput, 'input', () => {
2487
+ p.filterTerm = p.searchInput.value.trim();
2488
+ p.filteredItems = null;
2489
+
2490
+ this._trigger('search', { value: p.searchInput.value });
2491
+ p.throttledRefilterItems();
2492
+ });
2493
+ }
2494
+
1856
2495
  _keydown(event) {
1857
2496
  const p = this._p;
1858
2497
 
@@ -1897,7 +2536,8 @@ class DropList {
1897
2536
  case VALUE_RIGHT:
1898
2537
  if (event.key === VALUE_RIGHT && getComputedStyle(event.target).direction !== 'rtl' ||
1899
2538
  event.key === VALUE_LEFT && getComputedStyle(event.target).direction === 'rtl') {
1900
- let item = p.items[p.focusItemIndex];
2539
+ const items = p.filteredItems ?? p.items;
2540
+ let item = items[p.focusItemIndex];
1901
2541
  if (p.focusItemIndex > -1 && item._subitems)
1902
2542
  this._showSublist(item, p.focusItemEl);
1903
2543
  } else {
@@ -1927,7 +2567,11 @@ class DropList {
1927
2567
 
1928
2568
  default: {
1929
2569
  if (event.type === 'keydown') return;
1930
- this._keydownFreeType(event);
2570
+
2571
+ // Inline search box not available, then support typing to focus by first letters
2572
+ if (!p.searchable)
2573
+ this._keydownFreeType(event);
2574
+
1931
2575
  preventDefault = false;
1932
2576
  }
1933
2577
  }
@@ -1952,12 +2596,14 @@ class DropList {
1952
2596
 
1953
2597
  let focusItemIndex = p.focusItemIndex;
1954
2598
 
2599
+ const items = p.filteredItems ?? p.items;
2600
+
1955
2601
  // These are all the possible matches for the text typed in so far
1956
- for (let i = 0, count = p.items.length; i < count; i++) {
2602
+ for (let i = 0, count = items.length; i < count; i++) {
1957
2603
  if (matchIndex !== -1 && i < focusItemIndex)
1958
2604
  continue; // We are only interested in first match + match after the focused item
1959
2605
 
1960
- item = p.items[i];
2606
+ item = items[i];
1961
2607
  if (regex.test(item.label)) {
1962
2608
  matchIndex = i;
1963
2609
  if (focusItemIndex === -1 || i >= focusItemIndex)
@@ -1971,11 +2617,11 @@ class DropList {
1971
2617
  keyword = character;
1972
2618
  regex = new RegExp(`^${escapeRegex(keyword)}`, 'i');
1973
2619
 
1974
- for (let i = 0, count = p.items.length; i < count; i++) {
2620
+ for (let i = 0, count = items.length; i < count; i++) {
1975
2621
  if (matchIndex !== -1 && i < focusItemIndex)
1976
2622
  continue; // We are only interested in first match + match after the focused item
1977
2623
 
1978
- item = p.items[i];
2624
+ item = items[i];
1979
2625
  if (regex.test(item.label)) {
1980
2626
  matchIndex = i;
1981
2627
  if (focusItemIndex === -1 || i >= focusItemIndex)
@@ -1989,7 +2635,7 @@ class DropList {
1989
2635
  this._focus(evt, next || null, matchIndex, true);
1990
2636
 
1991
2637
  if (!this.isVisible()) {
1992
- this.triggerItemSelection(next ? null : p.items[matchIndex], evt);
2638
+ this.triggerItemSelection(next ? null : items[matchIndex], evt);
1993
2639
  }
1994
2640
 
1995
2641
  // Record the last filter used
@@ -2016,6 +2662,10 @@ class DropList {
2016
2662
  itemIndex = p.virtualListHelper.getItemIndexFromElement(itemEl);
2017
2663
  }
2018
2664
 
2665
+ if (itemIndex > -1 && itemEl?.[ItemSymbol$1]?.[ItemSymbol$1] === NoResultsItemSymbol) {
2666
+ itemIndex = undefined;
2667
+ }
2668
+
2019
2669
  if (itemIndex > -1) {
2020
2670
  this.scrollItemIndexIntoView(itemIndex);
2021
2671
  } else if (itemIndex === undefined) {
@@ -2041,7 +2691,8 @@ class DropList {
2041
2691
  p.focusItemEl = focusItemEl;
2042
2692
  p.focusItemIndex = itemIndex;
2043
2693
 
2044
- const item = p.items[itemIndex];
2694
+ const items = p.filteredItems ?? p.items;
2695
+ const item = items[itemIndex];
2045
2696
  this._trigger('itemfocus', {
2046
2697
  value: item.value,
2047
2698
  item: item[ItemSymbol$1] ?? item,
@@ -2076,12 +2727,13 @@ class DropList {
2076
2727
  const p = this._p;
2077
2728
 
2078
2729
  let next, nextIndex, directionUp = false;
2730
+ const items = p.filteredItems ?? p.items;
2079
2731
 
2080
2732
  if (direction === 'first') {
2081
2733
  nextIndex = 0;
2082
2734
  directionUp = false;
2083
2735
  } else if (direction === 'last') {
2084
- nextIndex = p.items.length - 1;
2736
+ nextIndex = items.length - 1;
2085
2737
  directionUp = true;
2086
2738
  } else if (direction === 'prev') {
2087
2739
  if (!this.hasFocusedItem())
@@ -2089,7 +2741,7 @@ class DropList {
2089
2741
 
2090
2742
  nextIndex = p.focusItemIndex - 1;
2091
2743
  if (nextIndex === -1) {
2092
- nextIndex = p.items.length - 1;
2744
+ nextIndex = items.length - 1;
2093
2745
  }
2094
2746
 
2095
2747
  directionUp = true;
@@ -2098,7 +2750,7 @@ class DropList {
2098
2750
  return this._move('first', event);
2099
2751
 
2100
2752
  nextIndex = p.focusItemIndex + 1;
2101
- if (nextIndex === p.items.length) {
2753
+ if (nextIndex === items.length) {
2102
2754
  nextIndex = 0;
2103
2755
  }
2104
2756
 
@@ -2127,8 +2779,8 @@ class DropList {
2127
2779
 
2128
2780
  if (nextIndex < 0) {
2129
2781
  nextIndex = 0;
2130
- } else if (nextIndex >= p.items.length) {
2131
- nextIndex = p.items.length;
2782
+ } else if (nextIndex >= items.length) {
2783
+ nextIndex = items.length;
2132
2784
  }
2133
2785
  } else if (p.focusItemEl) {
2134
2786
  let base = getElementOffset(p.focusItemEl).top;
@@ -2165,13 +2817,13 @@ class DropList {
2165
2817
  return;
2166
2818
  }
2167
2819
 
2168
- let itemCount = p.items.length;
2820
+ let itemCount = items.length;
2169
2821
 
2170
2822
  if (nextIndex >= itemCount) {
2171
2823
  return;
2172
2824
  }
2173
2825
 
2174
- let item = p.items[nextIndex];
2826
+ let item = items[nextIndex];
2175
2827
  // noinspection UnnecessaryLocalVariableJS
2176
2828
  let startedAtIndex = nextIndex;
2177
2829
 
@@ -2188,7 +2840,7 @@ class DropList {
2188
2840
  }
2189
2841
  }
2190
2842
 
2191
- item = p.items[nextIndex];
2843
+ item = items[nextIndex];
2192
2844
 
2193
2845
  if (nextIndex === startedAtIndex) {
2194
2846
  break;
@@ -2204,7 +2856,9 @@ class DropList {
2204
2856
  }
2205
2857
 
2206
2858
  _hasScroll() {
2207
- return this.el.clientHeight < this.el.scrollHeight;
2859
+ const p = this._p;
2860
+ const menuEl = p.menuEl;
2861
+ return menuEl.clientHeight < menuEl.scrollHeight;
2208
2862
  }
2209
2863
 
2210
2864
  _updateGroupStateForItem(item) {
@@ -2219,7 +2873,7 @@ class DropList {
2219
2873
  let affectedItems = 0;
2220
2874
 
2221
2875
  if (p.autoCheckGroupChildren) {
2222
- let items = p.items;
2876
+ let items = p.filteredItems ?? p.items;
2223
2877
  let groupIndex = items.indexOf(item);
2224
2878
 
2225
2879
  for (let i = groupIndex + 1, len = items.length; i < len; i++) {
@@ -2262,7 +2916,7 @@ class DropList {
2262
2916
  affectedItems: affectedItems,
2263
2917
  });
2264
2918
  } else if (p.groupCount > 0 && p.autoCheckGroupChildren) {
2265
- let items = p.items;
2919
+ let items = p.filteredItems ?? p.items;
2266
2920
  let itemIndex = items.indexOf(item);
2267
2921
  let groupIndex = -1;
2268
2922
 
@@ -2288,7 +2942,7 @@ class DropList {
2288
2942
  if (!(p.multi && p.autoCheckGroupChildren && groupIndex > -1))
2289
2943
  return this;
2290
2944
 
2291
- let items = p.items;
2945
+ let items = p.filteredItems ?? p.items;
2292
2946
  let groupItem = items[groupIndex];
2293
2947
 
2294
2948
  if (!groupItem || !groupItem._group) return this;
@@ -2414,7 +3068,7 @@ class DropList {
2414
3068
  _determineVirtualMode(targetItemCount) {
2415
3069
  const p = this._p;
2416
3070
 
2417
- let items = p.items;
3071
+ let items = p.filteredItems ?? p.items;
2418
3072
  if (targetItemCount === undefined) {
2419
3073
  targetItemCount = items.length;
2420
3074
  }
@@ -2434,7 +3088,21 @@ class DropList {
2434
3088
  // NOTE: a "measure" item will not have full data of original item.
2435
3089
  // so for a custom renderer - we try to send original item, and fallback to our private list item.
2436
3090
 
2437
- if (!p.renderItem || p.renderItem(item[ItemSymbol$1] || item, itemEl) === false) {
3091
+ const originalItem = item[ItemSymbol$1];
3092
+
3093
+ if (originalItem === NoResultsItemSymbol) {
3094
+ if (p.renderNoResultsItem && p.renderNoResultsItem(item, itemEl) !== false) {
3095
+ return true;
3096
+ }
3097
+
3098
+ itemEl.appendChild(createElement('div', {
3099
+ class: 'droplist-no-results-content',
3100
+ textContent: p.noResultsText,
3101
+ }));
3102
+ return;
3103
+ }
3104
+
3105
+ if (!p.renderItem || p.renderItem(originalItem || item, itemEl) === false) {
2438
3106
  itemEl.appendChild(createElement('span', {
2439
3107
  class: `${p.baseClassName}__item_label`,
2440
3108
  textContent: item.label,
@@ -2476,7 +3144,7 @@ class DropList {
2476
3144
  }
2477
3145
 
2478
3146
  let autoWidth = 0;
2479
- if (!p.useExactTargetWidth) {
3147
+ if (!p.useExactTargetWidth || !targetWidth) {
2480
3148
  if (p.estimateWidth || p.virtualListHelper.isVirtual()) {
2481
3149
  autoWidth = p.lastMeasureItemWidth;
2482
3150
  } else {
@@ -2503,56 +3171,8 @@ class DropList {
2503
3171
  }
2504
3172
  }
2505
3173
 
2506
- const throttle = (func, wait, options) => {
2507
- let timeout, context, args, result;
2508
- let previous = 0;
2509
- if (!options) options = {};
2510
-
2511
- const later = () => {
2512
- previous = options.leading === false ? 0 : Date.now();
2513
- timeout = null;
2514
- result = func.apply(context, args);
2515
- if (!timeout) context = args = null;
2516
- };
2517
-
2518
- const throttled = function () {
2519
- const now = Date.now();
2520
- if (!previous && options.leading === false)
2521
- previous = now;
2522
-
2523
- const remaining = wait - (now - previous);
2524
- context = this;
2525
- args = arguments;
2526
- if (remaining <= 0 || remaining > wait) {
2527
- if (timeout) {
2528
- clearTimeout(timeout);
2529
- timeout = null;
2530
- }
2531
- previous = now;
2532
- result = func.apply(context, args);
2533
- if (!timeout) context = args = null;
2534
- } else if (!timeout && options.trailing !== false) {
2535
- timeout = setTimeout(later, remaining);
2536
- }
2537
- return result;
2538
- };
2539
-
2540
- throttled.cancel = () => {
2541
- clearTimeout(timeout);
2542
- previous = 0;
2543
- timeout = context = args = null;
2544
- };
2545
-
2546
- throttled.isScheduled = () => {
2547
- return !!timeout;
2548
- };
2549
-
2550
- return throttled;
2551
- };
2552
-
2553
3174
  const ItemSymbol = Symbol('item');
2554
3175
  const DestroyedSymbol = Symbol('destroyed');
2555
- const NoResultsItemSymbol = Symbol('no_results_items');
2556
3176
  const RestMultiItemsSymbol = Symbol('rest_multi_items');
2557
3177
 
2558
3178
  const hasTouchCapability = !!('ontouchstart' in window
@@ -2610,10 +3230,6 @@ const inputBackbufferCssProps = [
2610
3230
  * @property {boolean} [showSelection=true] show selection? if false, the placeholder will take effect
2611
3231
  * @property {boolean} [showPlaceholderInTooltip=false] show placeholder in title attribute
2612
3232
  * @property {function(items: DropList.ItemBase[]):string} [multiPlaceholderFormatter] formatter for placeholder for multi items mode
2613
- * @property {boolean} [searchable=false] is it searchable?
2614
- * @property {string} [noResultsText='No matching results'] text for no results (empty for none)
2615
- * @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
2616
- * @property {boolean} [filterOnEmptyTerm=false] call the filter function on empty search term too
2617
3233
  * @property {string} [labelProp='label']
2618
3234
  * @property {string} [valueProp='value']
2619
3235
  * @property {string} [multiItemLabelProp='short_label']
@@ -2630,6 +3246,10 @@ const inputBackbufferCssProps = [
2630
3246
  * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderRestMultiItem]
2631
3247
  * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderNoResultsItem]
2632
3248
  * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderNoResultsItem]
3249
+ * @property {boolean} [searchable=false] is it searchable?
3250
+ * @property {string} [noResultsText='No matching results'] text for no results (empty for none)
3251
+ * @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
3252
+ * @property {boolean} [filterOnEmptyTerm=false] call the filter function on empty search term too
2633
3253
  * @property {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)} [filterFn]
2634
3254
  * @property {function(name: string, ...args)} [on]
2635
3255
  * @property {boolean} [isLoadingMode]
@@ -2748,8 +3368,6 @@ class SelectBox {
2748
3368
  multiPlaceholderFormatter: o.multiPlaceholderFormatter,
2749
3369
  searchable: o.searchable,
2750
3370
  noResultsText: o.noResultsText,
2751
- filterThrottleWindow: o.filterThrottleWindow,
2752
- filterOnEmptyTerm: o.filterOnEmptyTerm,
2753
3371
 
2754
3372
  labelProp: o.labelProp,
2755
3373
  valueProp: o.valueProp,
@@ -2766,7 +3384,6 @@ class SelectBox {
2766
3384
  unrenderRestMultiItem: o.unrenderRestMultiItem,
2767
3385
  renderNoResultsItem: o.renderNoResultsItem,
2768
3386
  unrenderNoResultsItem: o.unrenderNoResultsItem,
2769
- filterFn: o.filterFn,
2770
3387
  on: o.on || null,
2771
3388
  silenceEvents: true,
2772
3389
  mitt: mitt(),
@@ -2774,8 +3391,6 @@ class SelectBox {
2774
3391
  isLoadingMode: !!o.isLoadingMode,
2775
3392
 
2776
3393
  items: [],
2777
- filteredItems: null,
2778
- currentItemsView: [], // contains the final version of items sent to DropList
2779
3394
  itemsChanged: true,
2780
3395
 
2781
3396
  sink: new DomEventsSink(),
@@ -2788,10 +3403,9 @@ class SelectBox {
2788
3403
  selectionChanged: true,
2789
3404
  resortBySelectionNeeded: false,
2790
3405
 
2791
- throttledUpdateListItems: throttle(() => this._updateListItems(),
2792
- o.filterThrottleWindow,
2793
- { leading: true, trailing: true }),
2794
-
3406
+ filterThrottleWindow: o.filterThrottleWindow,
3407
+ filterOnEmptyTerm: o.filterOnEmptyTerm,
3408
+ filterFn: null,
2795
3409
  filterTerm: '',
2796
3410
  };
2797
3411
 
@@ -2903,6 +3517,9 @@ class SelectBox {
2903
3517
  p.resizeObserver.observe(p.el);
2904
3518
  }
2905
3519
 
3520
+ if (o.filterFn)
3521
+ this.setFilterFn(o.filterFn);
3522
+
2906
3523
  this.setItems(o.items);
2907
3524
  delete o.items; // we do not need this in memory anymore
2908
3525
 
@@ -2936,9 +3553,6 @@ class SelectBox {
2936
3553
  p.sink.remove();
2937
3554
  p.dropList && p.dropList.destroy();
2938
3555
 
2939
- if (p.throttledUpdateListItems)
2940
- p.throttledUpdateListItems.cancel();
2941
-
2942
3556
  this._cleanupSingleWrapper();
2943
3557
 
2944
3558
  if (p.unrenderMultiItem || p.unrenderRestMultiItem) {
@@ -3067,7 +3681,6 @@ class SelectBox {
3067
3681
  items = [];
3068
3682
 
3069
3683
  p.items = items.slice(0);
3070
- p.filteredItems = null;
3071
3684
  p.itemsChanged = true;
3072
3685
 
3073
3686
  this._updateItemByValueMap();
@@ -3084,8 +3697,8 @@ class SelectBox {
3084
3697
  getFilteredItemCount() {
3085
3698
  const p = this._p;
3086
3699
 
3087
- if (p.filteredItems)
3088
- return p.filteredItems.length;
3700
+ if (p.dropList)
3701
+ return p.dropList.getFilteredItemCount();
3089
3702
 
3090
3703
  if (p.items)
3091
3704
  return p.items.length;
@@ -3095,9 +3708,7 @@ class SelectBox {
3095
3708
 
3096
3709
  isFilterPending() {
3097
3710
  const p = this._p;
3098
-
3099
- return !!(p.throttledUpdateListItems.isScheduled() ||
3100
- (!p.filteredItems && (p.filterTerm || (p.filterOnEmptyTerm && p.filterFn))));
3711
+ return p.dropList?.isFilterPending() === true;
3101
3712
  }
3102
3713
 
3103
3714
  updateItemByValue(value, newItem) {
@@ -3197,11 +3808,7 @@ class SelectBox {
3197
3808
 
3198
3809
  if (performSearch) {
3199
3810
  p.filterTerm = p.input.value.trim();
3200
- p.filteredItems = null;
3201
- p.itemsChanged = true;
3202
-
3203
- this._trigger('search', { value: p.input.value });
3204
- p.throttledUpdateListItems();
3811
+ p.dropList?.setSearchTerm(p.filterTerm, performSearch);
3205
3812
  }
3206
3813
 
3207
3814
  return this;
@@ -3219,11 +3826,7 @@ class SelectBox {
3219
3826
 
3220
3827
  invokeRefilter() {
3221
3828
  const p = this._p;
3222
- if (!p.filterTerm && !p.filterOnEmptyTerm && !p.filteredItems)
3223
- return this;
3224
- p.filteredItems = null;
3225
- p.itemsChanged = true;
3226
- p.throttledUpdateListItems();
3829
+ p.dropList?.invokeRefilter();
3227
3830
  return this;
3228
3831
  }
3229
3832
 
@@ -3260,7 +3863,6 @@ class SelectBox {
3260
3863
  return this;
3261
3864
 
3262
3865
  p.sortListItems = sortListItems;
3263
- p.filteredItems = null;
3264
3866
  p.itemsChanged = true;
3265
3867
  this._scheduleSync('render_list');
3266
3868
  return this;
@@ -3349,6 +3951,8 @@ class SelectBox {
3349
3951
  return this;
3350
3952
 
3351
3953
  p.treatGroupSelectionAsItems = treatGroupSelectionAsItems;
3954
+ p.dropList?.setFilterGroups(treatGroupSelectionAsItems);
3955
+ p.dropList?.setFilterEmptyGroups(!treatGroupSelectionAsItems);
3352
3956
  p.itemsChanged = true;
3353
3957
  this._scheduleSync('render_list');
3354
3958
  return this;
@@ -3528,8 +4132,7 @@ class SelectBox {
3528
4132
  * @returns {SelectBox}
3529
4133
  */
3530
4134
  setNoResultsText(noResultsText) {
3531
- this._p.noResultsText = noResultsText;
3532
- this._scheduleSync('render_list');
4135
+ this._p.dropList?.setNoResultsText(noResultsText);
3533
4136
  return this;
3534
4137
  }
3535
4138
 
@@ -3547,17 +4150,7 @@ class SelectBox {
3547
4150
  setFilterThrottleWindow(window) {
3548
4151
  const p = this._p;
3549
4152
  p.filterThrottleWindow = window;
3550
-
3551
- let isScheduled = p.throttledUpdateListItems ? p.throttledUpdateListItems.isScheduled() : false;
3552
-
3553
- if (p.throttledUpdateListItems)
3554
- p.throttledUpdateListItems.cancel();
3555
-
3556
- p.throttledUpdateListItems = throttle(() => this._updateListItems(), p.filterThrottleWindow, true);
3557
-
3558
- if (isScheduled)
3559
- p.throttledUpdateListItems();
3560
-
4153
+ p.dropList?.setFilterThrottleWindow(window);
3561
4154
  return this;
3562
4155
  }
3563
4156
 
@@ -3576,8 +4169,7 @@ class SelectBox {
3576
4169
  const p = this._p;
3577
4170
  if (p.filterOnEmptyTerm === value)
3578
4171
  return this;
3579
- p.filterOnEmptyTerm = value;
3580
- p.throttledUpdateListItems();
4172
+ p.dropList?.setFilterOnEmptyTerm(value);
3581
4173
  return this;
3582
4174
  }
3583
4175
 
@@ -3719,8 +4311,21 @@ class SelectBox {
3719
4311
  const p = this._p;
3720
4312
  if (p.filterFn === fn)
3721
4313
  return this;
4314
+ if (!fn) {
4315
+ // Add search by multi-item label
4316
+ fn = (items, term) => {
4317
+ const matcher = new RegExp(escapeRegex(term), 'i');
4318
+ const labelProp = p.labelProp,
4319
+ multiItemLabelProp = p.multiItemLabelProp;
4320
+
4321
+ return p.items.filter(x => {
4322
+ if (!p.treatGroupSelectionAsItems && x._group) return true;
4323
+ return matcher.test(x[labelProp] || x[multiItemLabelProp]);
4324
+ });
4325
+ };
4326
+ }
3722
4327
  p.filterFn = fn;
3723
- p.throttledUpdateListItems();
4328
+ p.dropList?.setFilterFn(fn);
3724
4329
  return this;
3725
4330
  }
3726
4331
 
@@ -3900,7 +4505,7 @@ class SelectBox {
3900
4505
  // Propagate direction to droplist
3901
4506
  p.dropList.setDirection(getComputedStyle(p.el).direction);
3902
4507
 
3903
- p.dropList.show(this._getDropListPositionOptions());
4508
+ p.dropList.show();
3904
4509
  this._repositionDropList();
3905
4510
 
3906
4511
  // Another one in case the droplist position messed with screen layout.
@@ -4293,31 +4898,14 @@ class SelectBox {
4293
4898
  const customUnrenderItem = (p.listOptions || {}).unrenderItem;
4294
4899
 
4295
4900
  const renderItem = (renderNoResultsItem || customRenderItem) ? (item, itemEl) => {
4296
- if (item && item[valueProp] === NoResultsItemSymbol) {
4297
- if (renderNoResultsItem && renderNoResultsItem(item, itemEl) !== false) {
4298
- return true;
4299
- }
4300
-
4301
- itemEl.appendChild(createElement('div', {
4302
- class: 'droplist-no-results-content',
4303
- textContent: p.noResultsText,
4304
- }));
4305
- return true;
4306
- } else {
4307
- if (customRenderItem)
4308
- return customRenderItem(item, itemEl);
4309
- }
4901
+ if (customRenderItem)
4902
+ return customRenderItem(item, itemEl);
4310
4903
  return false;
4311
4904
  } : null;
4312
4905
 
4313
4906
  const unrenderItem = (unrenderNoResultsItem || customRenderItem) ? (item, itemEl) => {
4314
- if (item && item[valueProp] === NoResultsItemSymbol) {
4315
- if (unrenderNoResultsItem)
4316
- return unrenderNoResultsItem(item, itemEl);
4317
- } else {
4318
- if (customUnrenderItem)
4319
- return customUnrenderItem(item, itemEl);
4320
- }
4907
+ if (customUnrenderItem)
4908
+ return customUnrenderItem(item, itemEl);
4321
4909
  return false;
4322
4910
  } : null;
4323
4911
 
@@ -4335,6 +4923,16 @@ class SelectBox {
4335
4923
  labelProp: p.labelProp,
4336
4924
  valueProp: p.valueProp,
4337
4925
 
4926
+ searchable: false,
4927
+ noResultsText: p.noResultsText,
4928
+ filterThrottleWindow: p.filterThrottleWindow,
4929
+ filterOnEmptyTerm: p.filterOnEmptyTerm,
4930
+ filterGroups: p.treatGroupSelectionAsItems,
4931
+ filterEmptyGroups: p.treatGroupSelectionAsItems,
4932
+ filterFn: p.filterFn,
4933
+
4934
+ positionOptionsProvider: () => this._getDropListPositionOptions(),
4935
+
4338
4936
  on: (name, event) => {
4339
4937
  switch (name) {
4340
4938
  case 'show:before': {
@@ -4489,6 +5087,15 @@ class SelectBox {
4489
5087
 
4490
5088
  case 'blur':
4491
5089
  this._handleOnBlur();
5090
+ break;
5091
+
5092
+ case 'search':
5093
+ this._trigger('search', event);
5094
+ break;
5095
+
5096
+ case 'itemschanged':
5097
+ this._trigger('itemschanged', event);
5098
+ break;
4492
5099
  }
4493
5100
  },
4494
5101
  });
@@ -4519,8 +5126,7 @@ class SelectBox {
4519
5126
  p.dropList && this.droplistElContains(document.activeElement, true))) {
4520
5127
  return;
4521
5128
  }
4522
- if (p.throttledUpdateListItems)
4523
- p.throttledUpdateListItems.cancel();
5129
+
4524
5130
  this.closeList();
4525
5131
  });
4526
5132
  }
@@ -4667,12 +5273,9 @@ class SelectBox {
4667
5273
  if (p.disabled) return;
4668
5274
 
4669
5275
  p.filterTerm = p.input.value.trim();
4670
- p.filteredItems = null;
4671
- p.itemsChanged = true;
5276
+ p.dropList?.setSearchTerm(p.filterTerm, true);
4672
5277
 
4673
5278
  this._trigger('search', { value: p.input.value });
4674
-
4675
- p.throttledUpdateListItems();
4676
5279
  })
4677
5280
  .add(p.input, 'click.dropdown', () => {
4678
5281
  if (p.disabled) return;
@@ -4777,15 +5380,16 @@ class SelectBox {
4777
5380
  if (this.isMultiEnabled()) return;
4778
5381
 
4779
5382
  let selectedItems = this.getSelectedItems();
4780
- let items = p.filteredItems ?? p.items;
4781
- if (p.currentItemsView && p.currentItemsView.length === items.length)
4782
- items = p.currentItemsView;
4783
- if ((items.length + (p.clearable ? 1 : 0)) > 1) {
4784
- let nextIndex = selectedItems.length > 0 ? items.indexOf(selectedItems[0]) - 1 : (items.length - 1);
5383
+ let finalItemCount = p.dropList.getFilteredItemCount();
5384
+
5385
+ if ((finalItemCount + (p.clearable ? 1 : 0)) > 1) {
5386
+ let nextIndex = selectedItems.length > 0
5387
+ ? p.dropList.filteredItemIndexByItem(selectedItems[0]) - 1
5388
+ : (finalItemCount - 1);
4785
5389
  if (nextIndex === -1 && !p.clearable)
4786
- nextIndex = items.length - 1;
5390
+ nextIndex = finalItemCount - 1;
4787
5391
 
4788
- let item = nextIndex === -1 ? null : items[nextIndex];
5392
+ let item = nextIndex === -1 ? null : p.dropList.filteredItemAtIndex(nextIndex);
4789
5393
  if (item) {
4790
5394
  this._performSelectWithEvent(item, item[p.valueProp]);
4791
5395
  } else {
@@ -4800,15 +5404,16 @@ class SelectBox {
4800
5404
  if (this.isMultiEnabled()) return;
4801
5405
 
4802
5406
  let selectedItems = this.getSelectedItems();
4803
- let items = p.filteredItems ?? p.items;
4804
- if (p.currentItemsView && p.currentItemsView.length === items.length)
4805
- items = p.currentItemsView;
4806
- if ((items.length + (p.clearable ? 1 : 0)) > 1) {
4807
- let nextIndex = selectedItems.length > 0 ? items.indexOf(selectedItems[0]) + 1 : 0;
4808
- if (nextIndex === items.length)
5407
+ let finalItemCount = p.dropList.getFilteredItemCount();
5408
+
5409
+ if ((finalItemCount + (p.clearable ? 1 : 0)) > 1) {
5410
+ let nextIndex = selectedItems.length > 0
5411
+ ? p.dropList.filteredItemIndexByItem(selectedItems[0]) + 1
5412
+ : 0;
5413
+ if (nextIndex === finalItemCount)
4809
5414
  nextIndex = p.clearable ? -1 : 0;
4810
5415
 
4811
- let item = nextIndex === -1 ? null : items[nextIndex];
5416
+ let item = nextIndex === -1 ? null : p.dropList.filteredItemAtIndex(nextIndex);
4812
5417
  if (item) {
4813
5418
  this._performSelectWithEvent(item, item[p.valueProp]);
4814
5419
  } else {
@@ -4825,37 +5430,19 @@ class SelectBox {
4825
5430
  if (!dropList || !p.dropListVisible)
4826
5431
  return;
4827
5432
 
4828
- // For every search change, filteredItems is cleared.
4829
- // If it's non-null here, then it means we have an extra call to _updateListItems, and not need to refilter.
4830
- if (!p.filteredItems && (p.filterTerm || (p.filterOnEmptyTerm && p.filterFn))) {
4831
- this._refilterItems();
4832
- }
4833
-
4834
5433
  if (p.itemsChanged || p.selectionChanged) {
4835
5434
  p.dropList._lastSerializedBox = null;
4836
5435
  }
4837
5436
 
4838
5437
  if (p.itemsChanged) {
4839
- let items = p.filteredItems || p.items;
5438
+ let items = p.items;
4840
5439
  if (p.sortListItems || (p.sortListCheckedFirst && p.multi)) {
4841
5440
  items = this._sortItems(items,
4842
5441
  p.sortListItems,
4843
5442
  p.sortListCheckedFirst && p.multi,
4844
5443
  p.splitListCheckedGroups);
4845
5444
  }
4846
- dropList.removeAllItems();
4847
-
4848
- if (items.length === 0 && p.noResultsText) {
4849
- items = [{
4850
- [p.labelProp]: p.noResultsText,
4851
- [p.valueProp]: NoResultsItemSymbol,
4852
- _nointeraction: true,
4853
- _nocheck: true,
4854
- }];
4855
- }
4856
-
4857
- dropList.addItems(items);
4858
- p.currentItemsView = items;
5445
+ dropList.setItems(items);
4859
5446
  p.itemsChanged = false;
4860
5447
  p.selectionChanged = true;
4861
5448
  p.resortBySelectionNeeded = false;
@@ -4885,77 +5472,6 @@ class SelectBox {
4885
5472
  }
4886
5473
  }
4887
5474
 
4888
- /** @private */
4889
- _refilterItems() {
4890
- const p = this._p;
4891
-
4892
- const term = p.filterTerm;
4893
- const treatGroupSelectionAsItems = p.treatGroupSelectionAsItems;
4894
-
4895
- if (term || (p.filterOnEmptyTerm && p.filterFn)) {
4896
- let fn = p.filterFn;
4897
-
4898
- let filteredItems;
4899
-
4900
- if (typeof fn === 'function') {
4901
- filteredItems = p.filterFn(p.items, term);
4902
- }
4903
-
4904
- // If there was no filter function, or it gave up on filtering.
4905
- if (!Array.isArray(filteredItems)) {
4906
- if (term) {
4907
- const matcher = new RegExp(escapeRegex(term), 'i');
4908
- const labelProp = p.labelProp,
4909
- multiItemLabelProp = p.multiItemLabelProp;
4910
-
4911
- filteredItems = p.items.filter(x => {
4912
- if (!treatGroupSelectionAsItems && x._group) return true;
4913
- return matcher.test(x[labelProp] || x[multiItemLabelProp]);
4914
- });
4915
- } else {
4916
- filteredItems = null;
4917
- }
4918
- }
4919
-
4920
- p.filteredItems = filteredItems;
4921
-
4922
- if (filteredItems && !treatGroupSelectionAsItems) {
4923
- // Clean up groups without children
4924
-
4925
- let lastGroup = -1;
4926
- let len = filteredItems.length;
4927
-
4928
- for (let i = 0; i < len; i++) {
4929
- let item = filteredItems[i];
4930
-
4931
- if (item._group) {
4932
- if (lastGroup !== -1 && lastGroup === i - 1) {
4933
- // It was an empty group
4934
- filteredItems.splice(lastGroup, 1);
4935
- i--;
4936
- len--;
4937
- }
4938
-
4939
- lastGroup = i;
4940
- }
4941
- }
4942
-
4943
- if (lastGroup !== -1) {
4944
- if (lastGroup === len - 1) {
4945
- // It was an empty group
4946
- filteredItems.splice(lastGroup, 1);
4947
- }
4948
- }
4949
- }
4950
- } else {
4951
- p.filteredItems = null;
4952
- }
4953
-
4954
- this._trigger('itemschanged', { term: term, mutated: false, count: this.getFilteredItemCount() });
4955
-
4956
- p.itemsChanged = true;
4957
- }
4958
-
4959
5475
  /** @private */
4960
5476
  _setSelectedItems(items) {
4961
5477
  const p = this._p, valueProp = p.valueProp;
@@ -5594,8 +6110,7 @@ class SelectBox {
5594
6110
  p.input.value = value == null ? '' : String(value);
5595
6111
 
5596
6112
  p.filterTerm = '';
5597
- p.filteredItems = null;
5598
- p.itemsChanged = true;
6113
+ p.dropList?.setSearchTerm('', true);
5599
6114
  }
5600
6115
 
5601
6116
  /**
@@ -5716,7 +6231,7 @@ class SelectBox {
5716
6231
  const serialized = box.left + ',' + box.top + ',' + box.right + ',' + box.bottom;
5717
6232
 
5718
6233
  if (p.dropList._lastSerializedBox !== serialized) {
5719
- p.dropList.relayout(this._getDropListPositionOptions());
6234
+ p.dropList.relayout();
5720
6235
  p.dropList._lastSerializedBox = serialized;
5721
6236
  }
5722
6237