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