@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/lib/DropList.js CHANGED
@@ -28,10 +28,12 @@ import {
28
28
  VALUE_UP,
29
29
  } from 'keycode-js';
30
30
  import mitt from 'mitt';
31
+ import throttle from './utils/throttle';
31
32
 
32
33
  const ItemSymbol = Symbol('item');
33
34
  const DestroyedSymbol = Symbol('destroyed');
34
35
  const GhostSymbol = Symbol('ghost');
36
+ const NoResultsItemSymbol = Symbol('no_results_items');
35
37
 
36
38
  const hasOwnProperty = Object.prototype.hasOwnProperty;
37
39
 
@@ -57,7 +59,17 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
57
59
  * @property {string} [valueProp='value']
58
60
  * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderItem] Function to call when rendering an item element
59
61
  * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderItem] Function to call when rendering an item element
62
+ * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderNoResultsItem]
63
+ * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderNoResultsItem]
60
64
  * @property {function(name: string, data: *)} [on]
65
+ * @property {boolean} [searchable=false] include inline search box
66
+ * @property {string} [noResultsText='No matching results'] text for no results (empty for none)
67
+ * @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
68
+ * @property {boolean} [filterOnEmptyTerm=false] call the filter function on empty search term too
69
+ * @property {boolean} [filterGroups=false] should groups be filtered?
70
+ * @property {boolean} [filterEmptyGroups=false] should empty groups be filtered out?
71
+ * @property {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)} [filterFn]
72
+ * @property {function(dropList: DropList):DropList.PositionOptions} [positionOptionsProvider]
61
73
  * */
62
74
  /** */
63
75
 
@@ -121,6 +133,13 @@ let defaultOptions = {
121
133
  valueProp: 'value',
122
134
 
123
135
  on: null,
136
+
137
+ searchable: false,
138
+ noResultsText: 'No matching results',
139
+ filterThrottleWindow: 300,
140
+ filterOnEmptyTerm: false,
141
+ filterGroups: false,
142
+ filterEmptyGroups: false,
124
143
  };
125
144
 
126
145
  /*
@@ -181,17 +200,35 @@ class DropList {
181
200
  valueProp: o.valueProp,
182
201
  renderItem: o.renderItem,
183
202
  unrenderItem: o.unrenderItem,
203
+ renderNoResultsItem: o.renderNoResultsItem,
204
+ unrenderNoResultsItem: o.unrenderNoResultsItem,
184
205
  on: o.on || null,
206
+ positionOptionsProvider: o.positionOptionsProvider ?? null,
207
+
208
+ searchable: o.searchable,
209
+
185
210
  silenceEvents: true,
186
211
  mitt: mitt(),
187
212
 
213
+ filterThrottleWindow: o.filterThrottleWindow,
214
+ filterOnEmptyTerm: o.filterOnEmptyTerm,
215
+ filterGroups: o.filterGroups,
216
+ filterEmptyGroups: o.filterEmptyGroups,
217
+ filterFn: o.filterFn,
218
+ filteredItems: null,
219
+ filterTerm: '',
220
+ needsRefilter: false,
221
+ throttledRefilterItems: null,
222
+
188
223
  focusItemIndex: -1,
189
224
  focusItemEl: null,
190
225
 
191
226
  sink: new DomEventsSink(),
192
227
  };
193
228
 
194
- let classes = [p.baseClassName];
229
+ const baseClass = p.baseClassName + '_wrapper';
230
+
231
+ let classes = [baseClass];
195
232
 
196
233
  if (p.additionalClasses) {
197
234
  classes = classes.concat((p.additionalClasses + '').split(' ').filter(x => x));
@@ -202,31 +239,54 @@ class DropList {
202
239
  top: '-9999px',
203
240
  };
204
241
 
205
- let el = o.el;
206
- if (el instanceof Element) {
207
- p.elOriginalDisplay = el.style.display || '';
208
- el.classList.add(...classes);
209
- el.role = 'menu';
210
- setCssProps(/**@type ElementCSSInlineStyle*/el, initialCss);
242
+ let wrapperEl = o.el;
243
+
244
+ if (wrapperEl instanceof Element) {
245
+ p.elOriginalDisplay = wrapperEl.style.display || '';
246
+ wrapperEl.classList.add(...classes);
247
+ setCssProps(/**@type ElementCSSInlineStyle*/wrapperEl, initialCss);
211
248
  p.ownsEl = false;
212
249
  } else {
213
- el = createElement('ul', {
250
+ wrapperEl = createElement('div', {
214
251
  class: classes.join(' '),
215
- role: 'menu',
216
252
  css: initialCss,
217
253
  });
218
254
  }
219
255
 
220
- p.el = el;
256
+ let menuEl = createElement('ul');
257
+ menuEl.role = 'menu';
221
258
 
222
- p.items = [];
259
+ if (o.searchable) {
260
+ p.headerEl = createElement('div', {
261
+ class: p.baseClassName + '_header',
262
+ });
223
263
 
224
- p.groupCount = 0; // This will keep state of how many `group` items we have
264
+ p.searchInput = createElement('input', {
265
+ type: 'search',
266
+ role: 'searchbox',
267
+ tabindex: '0',
268
+ autocorrect: 'off',
269
+ autocomplete: 'off',
270
+ autocapitalize: 'off',
271
+ spellcheck: 'false',
272
+ 'aria-autocomplete': 'list',
273
+ });
274
+
275
+ p.headerEl.appendChild(p.searchInput);
276
+ wrapperEl.appendChild(p.headerEl);
277
+ }
278
+
279
+ wrapperEl.appendChild(menuEl);
280
+
281
+ p.el = wrapperEl;
282
+ p.menuEl = menuEl;
225
283
 
284
+ p.items = [];
285
+ p.groupCount = 0; // This will keep state of how many `group` items we have
226
286
  p.mouseHandled = false;
227
287
 
228
288
  p.virtualListHelper = new VirtualListHelper({
229
- list: p.el,
289
+ list: p.menuEl,
230
290
  virtual: true,
231
291
  buffer: 5,
232
292
  estimatedItemHeight: o.estimatedItemHeight || 20,
@@ -253,7 +313,19 @@ class DropList {
253
313
  };
254
314
  itemEl.setAttribute('aria-hidden', 'true');
255
315
  } else {
256
- item = p.items[index];
316
+ const items = p.filteredItems ?? p.items;
317
+ if (items.length === 0 && p.noResultsText) {
318
+ item = {
319
+ value: NoResultsItemSymbol,
320
+ label: p.noResultsText,
321
+ _nointeraction: true,
322
+ _nocheck: true,
323
+
324
+ [ItemSymbol]: NoResultsItemSymbol,
325
+ };
326
+ } else {
327
+ item = items[index];
328
+ }
257
329
  }
258
330
 
259
331
  if (!item) {
@@ -299,36 +371,20 @@ class DropList {
299
371
  },
300
372
  });
301
373
 
302
- if (typeof p.unrenderItem === 'function') {
303
- const fn = p.unrenderItem;
304
- p.virtualListHelper.setOnItemUnrender(el => {
305
- try {
306
- fn(el[ItemSymbol][ItemSymbol], el);
307
- } catch (err) {
308
- console.error(err); // eslint-disable-line no-console
309
- }
310
- delete el[ItemSymbol];
311
-
312
- if (p.focusItemEl === el)
313
- p.focusItemEl = null;
314
- });
315
- } else {
316
- p.virtualListHelper.setOnItemUnrender(el => {
317
- delete el[ItemSymbol];
318
-
319
- if (p.focusItemEl === el)
320
- p.focusItemEl = null;
321
- });
322
- }
374
+ this._setupUnrenderFunction();
323
375
 
324
376
  if (p.capturesFocus) {
325
- el.tabIndex = 0;
377
+ wrapperEl.tabIndex = 0;
326
378
  }
327
379
 
380
+ this.setFilterThrottleWindow(o.filterThrottleWindow);
381
+ this.setNoResultsText(o.noResultsText);
382
+
328
383
  this._hookMouseEvents();
329
384
  this._hookTouchEvents();
330
385
  this._hookFocusEvents();
331
386
  this._hookKeyEvents();
387
+ this._hookSearchEvents();
332
388
 
333
389
  this.silenceEvents = false;
334
390
  }
@@ -346,7 +402,7 @@ class DropList {
346
402
  p.sink.remove();
347
403
  p.virtualListHelper.destroy();
348
404
 
349
- if (p.el) {
405
+ if (p.el?.parentNode) {
350
406
  remove(p.el);
351
407
  }
352
408
 
@@ -355,13 +411,15 @@ class DropList {
355
411
  p.currentSubDropList = null;
356
412
  }
357
413
 
414
+ if (p.throttledRefilterItems)
415
+ p.throttledRefilterItems.cancel();
416
+
358
417
  if (!p.ownsEl) {
359
418
  for (let name of Array.from(p.el.classList)) {
360
419
  if (name.startsWith(p.baseClassName)) {
361
420
  p.el.classList.remove(name);
362
421
  }
363
422
  }
364
- p.el.removeAttribute('role');
365
423
  for (let key of ['position', 'left', 'top', 'right', 'bottom', 'z-index']) {
366
424
  p.el.style[key] = '';
367
425
  }
@@ -376,6 +434,8 @@ class DropList {
376
434
  delete p.lastPositionTarget;
377
435
  }
378
436
 
437
+ delete p.lastPositionOptions;
438
+
379
439
  this._p = null;
380
440
  }
381
441
 
@@ -459,16 +519,47 @@ class DropList {
459
519
  */
460
520
  setUnrenderItem(fn) {
461
521
  const p = this._p;
462
-
463
522
  p.unrenderItem = fn;
523
+ this._setupUnrenderFunction();
524
+ return this;
525
+ }
526
+
527
+ /**
528
+ * @param {(function(item: DropList.ItemBase, itemEl: Element):(*|false))|null} render
529
+ * @param {(function(item: DropList.ItemBase, itemEl: Element))|null} unrender
530
+ * @returns {DropList}
531
+ */
532
+ setRenderNoResultsItem(render, unrender) {
533
+ const p = this._p;
534
+ p.renderNoResultsItem = render;
535
+ p.unrenderNoResultsItem = unrender;
536
+ this._setupUnrenderFunction();
537
+ return this;
538
+ }
464
539
 
465
- if (typeof p.unrenderItem === 'function') {
540
+ /**
541
+ * @private
542
+ */
543
+ _setupUnrenderFunction() {
544
+ const p = this._p;
545
+
546
+ if (typeof p.unrenderItem === 'function' || typeof p.unrenderNoResultsItem === 'function') {
466
547
  const fn = p.unrenderItem;
548
+ const fnNoResults = p.unrenderNoResultsItem;
467
549
  p.virtualListHelper.setOnItemUnrender(el => {
468
- try {
469
- fn(el[ItemSymbol][ItemSymbol], el);
470
- } catch (err) {
471
- console.error(err); // eslint-disable-line no-console
550
+ const item = el[ItemSymbol];
551
+ if (item === NoResultsItemSymbol) {
552
+ try {
553
+ fnNoResults(item, el);
554
+ } catch (err) {
555
+ console.error(err); // eslint-disable-line no-console
556
+ }
557
+ } else {
558
+ try {
559
+ fn(item[ItemSymbol], el);
560
+ } catch (err) {
561
+ console.error(err); // eslint-disable-line no-console
562
+ }
472
563
  }
473
564
  delete el[ItemSymbol];
474
565
 
@@ -503,7 +594,8 @@ class DropList {
503
594
  if (!el)
504
595
  return;
505
596
 
506
- let classes = [p.baseClassName];
597
+ const baseClass = p.baseClassName + '_wrapper';
598
+ let classes = [baseClass];
507
599
 
508
600
  if (p.direction === 'ltr' || p.direction === 'rtl')
509
601
  classes.push(`${p.baseClassName}__` + p.direction);
@@ -530,7 +622,8 @@ class DropList {
530
622
  p.focusItemEl = null;
531
623
  }
532
624
 
533
- const item = p.items[p.focusItemIndex];
625
+ const items = p.filteredItems ?? p.items;
626
+ const item = items[p.focusItemIndex];
534
627
  p.focusItemIndex = -1;
535
628
 
536
629
  if (!item) {
@@ -681,9 +774,24 @@ class DropList {
681
774
  }
682
775
  }
683
776
 
684
- p.virtualListHelper
685
- .addItemsAt(itemsToAdd.length, atIndex === -1 ? atIndex : (atIndex - itemsToAdd.length))
686
- .render();
777
+ if (!p.filteredItems) {
778
+ let hadNoResultItem = p.hasNoResultsItem;
779
+ p.hasNoResultsItem = p.items.length === 0 && !!p.noResultsText;
780
+ if (p.hasNoResultsItem) {
781
+ p.virtualListHelper
782
+ .setCount(1)
783
+ .render();
784
+ } else {
785
+ if (hadNoResultItem)
786
+ p.virtualListHelper.removeItemsAt(1, 0);
787
+
788
+ p.virtualListHelper
789
+ .addItemsAt(itemsToAdd.length, atIndex === -1 ? atIndex : (atIndex - itemsToAdd.length))
790
+ .render();
791
+ }
792
+ } else {
793
+ p.needsRefilter = true;
794
+ }
687
795
 
688
796
  return this;
689
797
  }
@@ -697,9 +805,11 @@ class DropList {
697
805
  const p = this._p;
698
806
 
699
807
  p.items.length = 0;
808
+ p.filteredItems = null;
700
809
  p.groupCount = 0;
701
810
 
702
- p.virtualListHelper.setCount(0);
811
+ p.hasNoResultsItem = !!p.noResultsText;
812
+ p.virtualListHelper.setCount(p.hasNoResultsItem ? 1 : 0);
703
813
 
704
814
  this.addItems(items);
705
815
  this.updateSublist();
@@ -750,9 +860,18 @@ class DropList {
750
860
  if (hasOwnProperty.call(newItem, '_child'))
751
861
  item._child = !!newItem._child;
752
862
 
753
- if (p.virtualListHelper.isItemRendered(itemIndex)) {
863
+ let virtualItemIndex = itemIndex;
864
+ if (p.filteredItems) {
865
+ virtualItemIndex = p.filteredItems.indexOf(item);
866
+ if (virtualItemIndex !== -1) {
867
+ p.filteredItems.splice(virtualItemIndex, 1);
868
+ }
869
+ }
870
+
871
+ if (virtualItemIndex !== -1 &&
872
+ p.virtualListHelper.isItemRendered(virtualItemIndex)) {
754
873
  p.virtualListHelper
755
- .refreshItemAt(itemIndex)
874
+ .refreshItemAt(virtualItemIndex)
756
875
  .render();
757
876
  }
758
877
 
@@ -771,9 +890,27 @@ class DropList {
771
890
  p.groupCount--;
772
891
  }
773
892
 
774
- p.virtualListHelper
775
- .removeItemsAt(itemIndex, 1)
776
- .render();
893
+ let virtualItemIndex = itemIndex;
894
+ if (p.filteredItems) {
895
+ virtualItemIndex = p.filteredItems.indexOf(spliced[0]);
896
+ if (virtualItemIndex !== -1) {
897
+ p.filteredItems.splice(virtualItemIndex, 1);
898
+ }
899
+ }
900
+
901
+ if (virtualItemIndex !== -1) {
902
+ p.hasNoResultsItem = (p.filteredItems ?? p.items).length === 0 && !!p.noResultsText;
903
+
904
+ if (p.hasNoResultsItem) {
905
+ p.virtualListHelper
906
+ .setCount(1)
907
+ .render();
908
+ } else {
909
+ p.virtualListHelper
910
+ .removeItemsAt(virtualItemIndex, 1)
911
+ .render();
912
+ }
913
+ }
777
914
 
778
915
  return this;
779
916
  }
@@ -782,10 +919,13 @@ class DropList {
782
919
  const p = this._p;
783
920
 
784
921
  p.items.length = 0;
922
+ p.filteredItems = null;
785
923
  p.groupCount = 0;
786
924
 
925
+ p.hasNoResultsItem = !!p.noResultsText;
926
+
787
927
  p.virtualListHelper
788
- .setCount(0)
928
+ .setCount(p.hasNoResultsItem ? 1 : 0)
789
929
  .render();
790
930
 
791
931
  return this;
@@ -821,8 +961,23 @@ class DropList {
821
961
  itemIndexByValue(value) {
822
962
  const p = this._p;
823
963
 
824
- for (let i = 0, count = p.items.length; i < count; i++) {
825
- let item = p.items[i];
964
+ const items = p.items;
965
+ for (let i = 0, count = items.length; i < count; i++) {
966
+ let item = items[i];
967
+ if (item.value === value) {
968
+ return i;
969
+ }
970
+ }
971
+
972
+ return -1;
973
+ }
974
+
975
+ filteredItemIndexByValue(value) {
976
+ const p = this._p;
977
+
978
+ const items = p.filteredItems ?? p.items;
979
+ for (let i = 0, count = items.length; i < count; i++) {
980
+ let item = items[i];
826
981
  if (item.value === value) {
827
982
  return i;
828
983
  }
@@ -834,8 +989,23 @@ class DropList {
834
989
  itemIndexByValueOrLabel(value, label) {
835
990
  const p = this._p;
836
991
 
837
- for (let i = 0, count = p.items.length; i < count; i++) {
838
- let item = p.items[i];
992
+ const items = p.items;
993
+ for (let i = 0, count = items.length; i < count; i++) {
994
+ let item = items[i];
995
+ if (item.value === value || item.label === label) {
996
+ return i;
997
+ }
998
+ }
999
+
1000
+ return -1;
1001
+ }
1002
+
1003
+ filteredItemIndexByValueOrLabel(value, label) {
1004
+ const p = this._p;
1005
+
1006
+ const items = p.filteredItems ?? p.items;
1007
+ for (let i = 0, count = items.length; i < count; i++) {
1008
+ let item = items[i];
839
1009
  if (item.value === value || item.label === label) {
840
1010
  return i;
841
1011
  }
@@ -844,6 +1014,36 @@ class DropList {
844
1014
  return -1;
845
1015
  }
846
1016
 
1017
+ itemIndexByItem(item) {
1018
+ const p = this._p;
1019
+
1020
+ const items = p.items;
1021
+
1022
+ for (let i = 0, count = items.length; i < count; i++) {
1023
+ let it = items[i];
1024
+ if (it[ItemSymbol] === item) {
1025
+ return i;
1026
+ }
1027
+ }
1028
+
1029
+ return -1;
1030
+ }
1031
+
1032
+ filteredItemIndexByItem(item) {
1033
+ const p = this._p;
1034
+
1035
+ const items = p.filteredItems ?? p.items;
1036
+
1037
+ for (let i = 0, count = items.length; i < count; i++) {
1038
+ let it = items[i];
1039
+ if (it[ItemSymbol] === item) {
1040
+ return i;
1041
+ }
1042
+ }
1043
+
1044
+ return -1;
1045
+ }
1046
+
847
1047
  items() {
848
1048
  return this._p.items.map(x => x[ItemSymbol]);
849
1049
  }
@@ -860,19 +1060,320 @@ class DropList {
860
1060
  return this._p.items[index]?.[ItemSymbol];
861
1061
  }
862
1062
 
1063
+ filteredItemAtIndex(index) {
1064
+ const p = this._p;
1065
+ const items = p.filteredItems ?? p.items;
1066
+ return items[index]?.[ItemSymbol];
1067
+ }
1068
+
1069
+ /**
1070
+ * @param {function(dropList: DropList):DropList.PositionOptions} fn
1071
+ * @returns {DropList}
1072
+ */
1073
+ setPositionOptionsProvider(fn) {
1074
+ const p = this._p;
1075
+ if (p.positionOptionsProvider === fn)
1076
+ return this;
1077
+ p.positionOptionsProvider = fn ?? null;
1078
+ this.relayout();
1079
+ return this;
1080
+ }
1081
+
1082
+ /**
1083
+ * @returns {function(dropList: DropList):DropList.PositionOptions|null}
1084
+ */
1085
+ getPositionOptionsProvider() {
1086
+ const p = this._p;
1087
+ return p.positionOptionsProvider;
1088
+ }
1089
+
1090
+ /**
1091
+ * @param {string} term
1092
+ * @param {boolean} [performSearch=false] should actually perform the search, or just set the input's text?
1093
+ * @returns {DropList}
1094
+ */
1095
+ setSearchTerm(term, performSearch = false) {
1096
+ const p = this._p;
1097
+
1098
+ if (p.searchInput) {
1099
+ p.searchInput.value = term;
1100
+ }
1101
+
1102
+ if (performSearch) {
1103
+ p.filterTerm = term.trim();
1104
+ p.filteredItems = null;
1105
+
1106
+ this._trigger('search', { value: term });
1107
+ p.throttledRefilterItems();
1108
+ }
1109
+
1110
+ return this;
1111
+ }
1112
+
1113
+ isFilterPending() {
1114
+ const p = this._p;
1115
+
1116
+ return !!(p.throttledRefilterItems.isScheduled() ||
1117
+ (!p.filteredItems && (p.filterTerm || (p.filterOnEmptyTerm && p.filterFn))));
1118
+ }
1119
+
1120
+ /** @private */
1121
+ _refilterItems() {
1122
+ const p = this._p;
1123
+
1124
+ const term = p.filterTerm;
1125
+ const filterGroups = p.filterGroups;
1126
+ const filterEmptyGroups = p.filterEmptyGroups;
1127
+
1128
+ if (term || (p.filterOnEmptyTerm && p.filterFn)) {
1129
+ let fn = p.filterFn;
1130
+
1131
+ let filteredItems;
1132
+
1133
+ if (typeof fn === 'function') {
1134
+ filteredItems = p.filterFn(p.items, term);
1135
+ }
1136
+
1137
+ // If there was no filter function, or it gave up on filtering.
1138
+ if (!Array.isArray(filteredItems)) {
1139
+ if (term) {
1140
+ const matcher = new RegExp(escapeRegex(term), 'i');
1141
+ const labelProp = p.labelProp;
1142
+
1143
+ filteredItems = p.items.filter(x => {
1144
+ if (!filterGroups && x._group) return true;
1145
+ return matcher.test(x[labelProp]);
1146
+ });
1147
+ } else {
1148
+ filteredItems = null;
1149
+ }
1150
+ }
1151
+
1152
+ p.filteredItems = filteredItems;
1153
+
1154
+ if (filteredItems && filterEmptyGroups) {
1155
+ // Clean up groups without children
1156
+
1157
+ let lastGroup = -1;
1158
+ let len = filteredItems.length;
1159
+
1160
+ for (let i = 0; i < len; i++) {
1161
+ let item = filteredItems[i];
1162
+
1163
+ if (item._group) {
1164
+ if (lastGroup !== -1 && lastGroup === i - 1) {
1165
+ // It was an empty group
1166
+ filteredItems.splice(lastGroup, 1);
1167
+ i--;
1168
+ len--;
1169
+ }
1170
+
1171
+ lastGroup = i;
1172
+ }
1173
+ }
1174
+
1175
+ if (lastGroup !== -1) {
1176
+ if (lastGroup === len - 1) {
1177
+ // It was an empty group
1178
+ filteredItems.splice(lastGroup, 1);
1179
+ }
1180
+ }
1181
+ }
1182
+ } else {
1183
+ p.filteredItems = null;
1184
+ }
1185
+
1186
+ this.needsRefilter = false;
1187
+
1188
+ const items = p.filteredItems ?? p.items;
1189
+ p.hasNoResultsItem = items.length === 0 && !!p.noResultsText;
1190
+ p.virtualListHelper
1191
+ .setCount(items.length + (p.hasNoResultsItem ? 1 : 0))
1192
+ .render();
1193
+
1194
+ this._trigger('itemschanged', { term: term, mutated: false, count: this.getFilteredItemCount() });
1195
+ this.relayout();
1196
+ }
1197
+
1198
+ invokeRefilter() {
1199
+ const p = this._p;
1200
+ if (!p.filterTerm && !p.filterOnEmptyTerm && !p.filteredItems)
1201
+ return this;
1202
+ p.filteredItems = null;
1203
+ p.throttledRefilterItems();
1204
+ return this;
1205
+ }
1206
+
1207
+ getFilteredItemCount() {
1208
+ const p = this._p;
1209
+
1210
+ if (p.needsRefilter)
1211
+ this._refilterItems();
1212
+
1213
+ if (p.filteredItems)
1214
+ return p.filteredItems.length;
1215
+
1216
+ if (p.items)
1217
+ return p.items.length;
1218
+
1219
+ return 0;
1220
+ }
1221
+
1222
+ /**
1223
+ * @param {string} noResultsText
1224
+ * @returns {DropList}
1225
+ */
1226
+ setNoResultsText(noResultsText) {
1227
+ this._p.noResultsText = noResultsText;
1228
+ return this;
1229
+ }
1230
+
1231
+ /**
1232
+ * @returns {string}
1233
+ */
1234
+ getNoResultsText() {
1235
+ return this._p.noResultsText;
1236
+ }
1237
+
1238
+ /**
1239
+ * @param {number} window
1240
+ * @returns {DropList}
1241
+ */
1242
+ setFilterThrottleWindow(window) {
1243
+ const p = this._p;
1244
+ p.filterThrottleWindow = window;
1245
+
1246
+ let isScheduled = p.throttledRefilterItems ? p.throttledRefilterItems.isScheduled() : false;
1247
+
1248
+ if (p.throttledRefilterItems)
1249
+ p.throttledRefilterItems.cancel();
1250
+
1251
+ p.throttledRefilterItems = throttle(() => this._refilterItems(), p.filterThrottleWindow, true);
1252
+
1253
+ if (isScheduled)
1254
+ p.throttledRefilterItems();
1255
+
1256
+ return this;
1257
+ }
1258
+
1259
+ /**
1260
+ * @returns {number}
1261
+ */
1262
+ getFilterThrottleWindow() {
1263
+ return this._p.filterThrottleWindow;
1264
+ }
1265
+
1266
+ /**
1267
+ * @param {boolean} value
1268
+ * @returns {DropList}
1269
+ */
1270
+ setFilterOnEmptyTerm(value) {
1271
+ const p = this._p;
1272
+ if (p.filterOnEmptyTerm === value)
1273
+ return this;
1274
+ p.filterOnEmptyTerm = value;
1275
+ p.throttledRefilterItems();
1276
+ return this;
1277
+ }
1278
+
1279
+ /**
1280
+ * @returns {boolean}
1281
+ */
1282
+ getFilterOnEmptyTerm() {
1283
+ return this._p.filterOnEmptyTerm;
1284
+ }
1285
+
1286
+ /**
1287
+ * @param {boolean} value
1288
+ * @returns {DropList}
1289
+ */
1290
+ setFilterGroups(value) {
1291
+ const p = this._p;
1292
+ if (p.filterGroups === value)
1293
+ return this;
1294
+ p.filterGroups = value;
1295
+ p.throttledRefilterItems();
1296
+ return this;
1297
+ }
1298
+
1299
+ /**
1300
+ * @returns {boolean}
1301
+ */
1302
+ getFilterGroups() {
1303
+ return this._p.filterGroups;
1304
+ }
1305
+
1306
+ /**
1307
+ * @param {boolean} value
1308
+ * @returns {DropList}
1309
+ */
1310
+ setFilterEmptyGroups(value) {
1311
+ const p = this._p;
1312
+ if (p.filterEmptyGroups === value)
1313
+ return this;
1314
+ p.filterEmptyGroups = value;
1315
+ p.throttledRefilterItems();
1316
+ return this;
1317
+ }
1318
+
1319
+ /**
1320
+ * @returns {boolean}
1321
+ */
1322
+ getFilterEmptyGroups() {
1323
+ return this._p.filterEmptyGroups;
1324
+ }
1325
+
1326
+ /**
1327
+ * @param {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)} fn
1328
+ * @returns {DropList}
1329
+ */
1330
+ setFilterFn(fn) {
1331
+ const p = this._p;
1332
+ if (p.filterFn === fn)
1333
+ return this;
1334
+ p.filterFn = fn;
1335
+ p.throttledRefilterItems();
1336
+ return this;
1337
+ }
1338
+
1339
+ /**
1340
+ * @returns {function(items: DropList.ItemBase[], term: string):(DropList.ItemBase[]|null)}
1341
+ */
1342
+ getFilterFn() {
1343
+ return this._p.filterFn;
1344
+ }
1345
+
863
1346
  /**
864
1347
  *
865
- * @param {DropList.PositionOptions} positionOptions
1348
+ * @param {DropList.PositionOptions} [positionOptions]
866
1349
  * @returns {DropList}
867
1350
  * @public
868
1351
  */
869
1352
  relayout(positionOptions) {
870
- const p = this._p, el = p.el;
1353
+ const p = this._p, el = p.el, menuEl = p.menuEl;
871
1354
 
872
1355
  if (!this.isVisible()) return this;
873
1356
 
874
1357
  let w = window;
875
1358
 
1359
+ if (!positionOptions)
1360
+ positionOptions = p.positionOptionsProvider?.() ?? p.lastPositionOptions;
1361
+
1362
+ // Supply some default for extreme cases, no crashing
1363
+ if (!positionOptions) {
1364
+ positionOptions = {
1365
+ targetOffset: {
1366
+ left: window.innerWidth / 2,
1367
+ top: window.innerHeight / 2,
1368
+ },
1369
+ targetWidth: 0,
1370
+ targetHeight: 0,
1371
+ position: { x: 'center', y: 'center' },
1372
+ anchor: { x: 'center', y: 'center' },
1373
+ targetRtl: getComputedStyle(document.body).direction === 'rtl',
1374
+ };
1375
+ }
1376
+
876
1377
  let targetBox = {};
877
1378
 
878
1379
  let offset = positionOptions.targetOffset || getElementOffset(positionOptions.target);
@@ -935,18 +1436,25 @@ class DropList {
935
1436
  let verticalBorderWidth = (parseFloat(elComputedStyle.borderTopWidth) || 0) +
936
1437
  (parseFloat(elComputedStyle.borderBottomWidth) || 0);
937
1438
 
1439
+ let headerHeight = 0;
1440
+ if (p.headerEl) {
1441
+ headerHeight = getElementHeight(p.headerEl, true, true);
1442
+ }
1443
+
938
1444
  if (p.virtualListHelper.isVirtual()) {
939
1445
  maxViewHeight =
940
1446
  p.virtualListHelper.estimateFullHeight() +
941
1447
  verticalPadding +
942
- verticalBorderWidth;
1448
+ verticalBorderWidth +
1449
+ headerHeight;
943
1450
  } else {
944
1451
  // Another method to calculate height is measuring the whole thing at once.
945
1452
  // This causes relayout of course.
946
1453
  el.style.height = '';
1454
+ menuEl.style.height = '';
947
1455
  el.style.top = '-9999px';
948
1456
 
949
- maxViewHeight = Math.max(getElementHeight(p.el), el.scrollHeight);
1457
+ maxViewHeight = Math.max(getElementHeight(p.el), menuEl.scrollHeight);
950
1458
  maxViewHeight += verticalPadding + verticalBorderWidth;
951
1459
  }
952
1460
 
@@ -1081,6 +1589,11 @@ class DropList {
1081
1589
  setCssProps(el, viewCss);
1082
1590
  setElementHeight(el, viewSize.height, true, true);
1083
1591
 
1592
+ if (menuEl !== el) {
1593
+ let menuHeight = viewSize.height - headerHeight - verticalBorderWidth - verticalPadding;
1594
+ setElementHeight(menuEl, menuHeight, true, true);
1595
+ }
1596
+
1084
1597
  // Update the scroll position for virtual lists
1085
1598
  p.virtualListHelper.render();
1086
1599
 
@@ -1218,7 +1731,7 @@ class DropList {
1218
1731
 
1219
1732
  /**
1220
1733
  *
1221
- * @param {DropList.PositionOptions?} positionOptions
1734
+ * @param {DropList.PositionOptions?} [positionOptions]
1222
1735
  * @returns {DropList}
1223
1736
  * @public
1224
1737
  */
@@ -1245,6 +1758,10 @@ class DropList {
1245
1758
  }
1246
1759
  });
1247
1760
 
1761
+ if (p.needsRefilter) {
1762
+ this._refilterItems();
1763
+ }
1764
+
1248
1765
  const el = p.el;
1249
1766
  el.style.position = 'absolute';
1250
1767
  el.classList.remove(`${p.baseClassName}__is-hiding`);
@@ -1256,6 +1773,14 @@ class DropList {
1256
1773
  if (getComputedStyle(p.el).display === 'none')
1257
1774
  p.el.style.display = 'block';
1258
1775
 
1776
+ p.lastPositionOptions = null;
1777
+
1778
+ if (positionOptions === undefined) {
1779
+ positionOptions = p.positionOptionsProvider?.() ?? p.lastPositionOptions;
1780
+ } else {
1781
+ p.lastPositionOptions = positionOptions;
1782
+ }
1783
+
1259
1784
  if (positionOptions) {
1260
1785
  const elComputedStyle = getComputedStyle(el);
1261
1786
 
@@ -1302,6 +1827,11 @@ class DropList {
1302
1827
 
1303
1828
  p.hiding = true;
1304
1829
 
1830
+ if (this.isFilterPending()) {
1831
+ p.throttledRefilterItems.cancel();
1832
+ p.needsRefilter = true;
1833
+ }
1834
+
1305
1835
  if (el) {
1306
1836
 
1307
1837
  el.classList.add(`${p.baseClassName}__is-hiding`);
@@ -1322,7 +1852,8 @@ class DropList {
1322
1852
  }
1323
1853
  });
1324
1854
  } else {
1325
- remove(el);
1855
+ if (el.parentNode)
1856
+ remove(el);
1326
1857
  el.classList.remove(`${p.baseClassName}__is-hiding`);
1327
1858
  }
1328
1859
  }
@@ -1371,15 +1902,22 @@ class DropList {
1371
1902
  setFocusedItemAtIndex(itemIndex) {
1372
1903
  const p = this._p;
1373
1904
 
1905
+ if (p.filteredItems) {
1906
+ const item = p.items[itemIndex];
1907
+ itemIndex = p.items.indexOf(item);
1908
+ }
1909
+
1374
1910
  p.focusItemIndex = itemIndex;
1375
1911
 
1912
+ const items = p.filteredItems ?? p.items;
1913
+
1376
1914
  let item = null;
1377
1915
  if (itemIndex > -1)
1378
- item = p.items[itemIndex];
1916
+ item = items[itemIndex];
1379
1917
  if (item && item._nointeraction)
1380
1918
  item = null;
1381
1919
  if (itemIndex > -1) {
1382
- this.scrollItemIndexIntoView(itemIndex);
1920
+ this._scrollItemIndexIntoView(itemIndex);
1383
1921
  }
1384
1922
  let itemElement = item ? p.virtualListHelper.getItemElementAt(itemIndex) : null;
1385
1923
 
@@ -1434,6 +1972,7 @@ class DropList {
1434
1972
  valueProp: p.valueProp,
1435
1973
  renderItem: p.renderItem,
1436
1974
  unrenderItem: p.unrenderItem,
1975
+ positionOptionsProvider: () => p.currentSubDropList.showOptions,
1437
1976
  });
1438
1977
 
1439
1978
  let onBlur = event => {
@@ -1481,7 +2020,7 @@ class DropList {
1481
2020
  },
1482
2021
  };
1483
2022
 
1484
- droplist.show(p.currentSubDropList.showOptions);
2023
+ droplist.show();
1485
2024
 
1486
2025
  droplist.el.focus();
1487
2026
  }
@@ -1535,7 +2074,7 @@ class DropList {
1535
2074
 
1536
2075
  if (itemElement) {
1537
2076
  p.currentSubDropList.showOptions.target = itemElement;
1538
- p.currentSubDropList.droplist.relayout(p.currentSubDropList.showOptions);
2077
+ p.currentSubDropList.droplist.relayout();
1539
2078
  }
1540
2079
  }
1541
2080
  }
@@ -1546,7 +2085,8 @@ class DropList {
1546
2085
 
1547
2086
  let itemIndex = item._nointeraction ? -1 : this._getItemIndex(item);
1548
2087
 
1549
- if (itemIndex > -1 && p.items[itemIndex]._nointeraction)
2088
+ const items = p.items;
2089
+ if (itemIndex > -1 && items[itemIndex]._nointeraction)
1550
2090
  itemIndex = -1;
1551
2091
 
1552
2092
  return this.setFocusedItemAtIndex(itemIndex);
@@ -1562,7 +2102,14 @@ class DropList {
1562
2102
  let itemEl = null;
1563
2103
 
1564
2104
  if (itemIndex > -1 && !p.items[itemIndex]._nointeraction) {
1565
- itemEl = p.virtualListHelper.getItemElementAt(itemIndex);
2105
+ if (p.filteredItems) {
2106
+ const item = p.items[itemIndex];
2107
+ itemIndex = p.filteredItems.indexOf(item);
2108
+ }
2109
+
2110
+ if (itemIndex > -1) {
2111
+ itemEl = p.virtualListHelper.getItemElementAt(itemIndex);
2112
+ }
1566
2113
  }
1567
2114
 
1568
2115
  this._setSingleSelectedItemEl(itemEl);
@@ -1575,7 +2122,8 @@ class DropList {
1575
2122
 
1576
2123
  let itemIndex = item._nointeraction ? -1 : this._getItemIndex(item);
1577
2124
 
1578
- if (itemIndex > -1 && p.items[itemIndex]._nointeraction)
2125
+ const items = p.items;
2126
+ if (itemIndex > -1 && items[itemIndex]._nointeraction)
1579
2127
  itemIndex = -1;
1580
2128
 
1581
2129
  return this.setSingleSelectedItemAtIndex(itemIndex);
@@ -1595,24 +2143,42 @@ class DropList {
1595
2143
 
1596
2144
  isFirstItem() {
1597
2145
  const p = this._p;
1598
- return p.focusItemIndex === 0 && p.focusItemIndex < p.items.length;
2146
+ const items = p.filteredItems ?? p.items;
2147
+ return p.focusItemIndex === 0 && p.focusItemIndex < items.length;
1599
2148
  }
1600
2149
 
1601
2150
  isLastItem() {
1602
2151
  const p = this._p;
1603
- return p.focusItemIndex > -1 && p.focusItemIndex === p.items.length - 1;
2152
+ const items = p.filteredItems ?? p.items;
2153
+ return p.focusItemIndex > -1 && p.focusItemIndex === items.length - 1;
1604
2154
  }
1605
2155
 
1606
2156
  scrollItemIndexIntoView(itemIndex) {
1607
2157
  const p = this._p;
1608
2158
 
2159
+ if (this._hasScroll()) {
2160
+ if (p.filteredItems) {
2161
+ const item = p.items[itemIndex];
2162
+ itemIndex = p.items.indexOf(item);
2163
+ }
2164
+
2165
+ if (itemIndex !== -1) {
2166
+ this._scrollItemIndexIntoView(itemIndex);
2167
+ }
2168
+ }
2169
+
2170
+ return this;
2171
+ }
2172
+
2173
+ _scrollItemIndexIntoView(itemIndex) {
2174
+ const p = this._p;
2175
+
1609
2176
  if (this._hasScroll()) {
1610
2177
  const el = p.el, scrollTop = el.scrollTop;
1611
2178
 
1612
2179
  let itemPos, previousPos = -1;
1613
2180
  let maxIterations = 30; // Some zoom/scroll issues can make it so that it takes almost forever
1614
2181
 
1615
-
1616
2182
  while (maxIterations-- > 0) {
1617
2183
  itemPos = p.virtualListHelper.getItemPosition(itemIndex);
1618
2184
 
@@ -1635,8 +2201,6 @@ class DropList {
1635
2201
  p.virtualListHelper.render();
1636
2202
  }
1637
2203
  }
1638
-
1639
- return this;
1640
2204
  }
1641
2205
 
1642
2206
  /**
@@ -1697,7 +2261,8 @@ class DropList {
1697
2261
  let itemIndex = -1;
1698
2262
 
1699
2263
  if (item) {
1700
- itemIndex = p.items.indexOf(item);
2264
+ const items = p.filteredItems ?? p.items;
2265
+ itemIndex = items.indexOf(item);
1701
2266
  if (itemIndex === -1) {
1702
2267
  let value = (item && item.value !== undefined) ? item.value : item;
1703
2268
  let label = (item && item.label) ? item.label : value;
@@ -1845,12 +2410,25 @@ class DropList {
1845
2410
 
1846
2411
  p.sink
1847
2412
  .add(p.el, 'focus', event => {
2413
+ if (event.target === this.el && p.searchable)
2414
+ p.searchInput.focus();
2415
+
2416
+ if (event.relatedTarget &&
2417
+ this.elContains(event.relatedTarget, true) &&
2418
+ this.elContains(event.target, true))
2419
+ return;
2420
+
1848
2421
  let itemEl = p.focusItemEl || // focused item
1849
2422
  p.el.firstChild; // or the first item
1850
2423
 
1851
2424
  this._focus(event, itemEl, null, false);
1852
- })
2425
+ }, true)
1853
2426
  .add(p.el, 'blur', event => {
2427
+ if (event.relatedTarget &&
2428
+ this.elContains(event.relatedTarget, true) &&
2429
+ this.elContains(event.target, true))
2430
+ return;
2431
+
1854
2432
  setTimeout(() => {
1855
2433
  if (this[DestroyedSymbol]) return;
1856
2434
 
@@ -1861,7 +2439,7 @@ class DropList {
1861
2439
  this._delayBlurItemOnBlur();
1862
2440
  this._trigger('blur', event);
1863
2441
  });
1864
- });
2442
+ }, true);
1865
2443
  }
1866
2444
 
1867
2445
  _hookKeyEvents() {
@@ -1870,6 +2448,21 @@ class DropList {
1870
2448
  p.sink.add(p.el, 'keydown', evt => this._keydown(evt));
1871
2449
  }
1872
2450
 
2451
+ _hookSearchEvents() {
2452
+ const p = this._p;
2453
+
2454
+ if (!p.searchInput)
2455
+ return;
2456
+
2457
+ p.sink.add(p.searchInput, 'input', () => {
2458
+ p.filterTerm = p.searchInput.value.trim();
2459
+ p.filteredItems = null;
2460
+
2461
+ this._trigger('search', { value: p.searchInput.value });
2462
+ p.throttledRefilterItems();
2463
+ });
2464
+ }
2465
+
1873
2466
  _keydown(event) {
1874
2467
  const p = this._p;
1875
2468
 
@@ -1914,7 +2507,8 @@ class DropList {
1914
2507
  case VALUE_RIGHT:
1915
2508
  if (event.key === VALUE_RIGHT && getComputedStyle(event.target).direction !== 'rtl' ||
1916
2509
  event.key === VALUE_LEFT && getComputedStyle(event.target).direction === 'rtl') {
1917
- let item = p.items[p.focusItemIndex];
2510
+ const items = p.filteredItems ?? p.items;
2511
+ let item = items[p.focusItemIndex];
1918
2512
  if (p.focusItemIndex > -1 && item._subitems)
1919
2513
  this._showSublist(item, p.focusItemEl);
1920
2514
  } else {
@@ -1944,7 +2538,11 @@ class DropList {
1944
2538
 
1945
2539
  default: {
1946
2540
  if (event.type === 'keydown') return;
1947
- this._keydownFreeType(event);
2541
+
2542
+ // Inline search box not available, then support typing to focus by first letters
2543
+ if (!p.searchable)
2544
+ this._keydownFreeType(event);
2545
+
1948
2546
  preventDefault = false;
1949
2547
  }
1950
2548
  }
@@ -1969,12 +2567,14 @@ class DropList {
1969
2567
 
1970
2568
  let focusItemIndex = p.focusItemIndex;
1971
2569
 
2570
+ const items = p.filteredItems ?? p.items;
2571
+
1972
2572
  // These are all the possible matches for the text typed in so far
1973
- for (let i = 0, count = p.items.length; i < count; i++) {
2573
+ for (let i = 0, count = items.length; i < count; i++) {
1974
2574
  if (matchIndex !== -1 && i < focusItemIndex)
1975
2575
  continue; // We are only interested in first match + match after the focused item
1976
2576
 
1977
- item = p.items[i];
2577
+ item = items[i];
1978
2578
  if (regex.test(item.label)) {
1979
2579
  matchIndex = i;
1980
2580
  if (focusItemIndex === -1 || i >= focusItemIndex)
@@ -1988,11 +2588,11 @@ class DropList {
1988
2588
  keyword = character;
1989
2589
  regex = new RegExp(`^${escapeRegex(keyword)}`, 'i');
1990
2590
 
1991
- for (let i = 0, count = p.items.length; i < count; i++) {
2591
+ for (let i = 0, count = items.length; i < count; i++) {
1992
2592
  if (matchIndex !== -1 && i < focusItemIndex)
1993
2593
  continue; // We are only interested in first match + match after the focused item
1994
2594
 
1995
- item = p.items[i];
2595
+ item = items[i];
1996
2596
  if (regex.test(item.label)) {
1997
2597
  matchIndex = i;
1998
2598
  if (focusItemIndex === -1 || i >= focusItemIndex)
@@ -2006,7 +2606,7 @@ class DropList {
2006
2606
  this._focus(evt, next || null, matchIndex, true);
2007
2607
 
2008
2608
  if (!this.isVisible()) {
2009
- this.triggerItemSelection(next ? null : p.items[matchIndex], evt);
2609
+ this.triggerItemSelection(next ? null : items[matchIndex], evt);
2010
2610
  }
2011
2611
 
2012
2612
  // Record the last filter used
@@ -2033,6 +2633,10 @@ class DropList {
2033
2633
  itemIndex = p.virtualListHelper.getItemIndexFromElement(itemEl);
2034
2634
  }
2035
2635
 
2636
+ if (itemIndex > -1 && itemEl?.[ItemSymbol]?.[ItemSymbol] === NoResultsItemSymbol) {
2637
+ itemIndex = undefined;
2638
+ }
2639
+
2036
2640
  if (itemIndex > -1) {
2037
2641
  this.scrollItemIndexIntoView(itemIndex);
2038
2642
  } else if (itemIndex === undefined) {
@@ -2058,7 +2662,8 @@ class DropList {
2058
2662
  p.focusItemEl = focusItemEl;
2059
2663
  p.focusItemIndex = itemIndex;
2060
2664
 
2061
- const item = p.items[itemIndex];
2665
+ const items = p.filteredItems ?? p.items;
2666
+ const item = items[itemIndex];
2062
2667
  this._trigger('itemfocus', {
2063
2668
  value: item.value,
2064
2669
  item: item[ItemSymbol] ?? item,
@@ -2093,12 +2698,13 @@ class DropList {
2093
2698
  const p = this._p;
2094
2699
 
2095
2700
  let next, nextIndex, directionUp = false;
2701
+ const items = p.filteredItems ?? p.items;
2096
2702
 
2097
2703
  if (direction === 'first') {
2098
2704
  nextIndex = 0;
2099
2705
  directionUp = false;
2100
2706
  } else if (direction === 'last') {
2101
- nextIndex = p.items.length - 1;
2707
+ nextIndex = items.length - 1;
2102
2708
  directionUp = true;
2103
2709
  } else if (direction === 'prev') {
2104
2710
  if (!this.hasFocusedItem())
@@ -2106,7 +2712,7 @@ class DropList {
2106
2712
 
2107
2713
  nextIndex = p.focusItemIndex - 1;
2108
2714
  if (nextIndex === -1) {
2109
- nextIndex = p.items.length - 1;
2715
+ nextIndex = items.length - 1;
2110
2716
  }
2111
2717
 
2112
2718
  directionUp = true;
@@ -2115,7 +2721,7 @@ class DropList {
2115
2721
  return this._move('first', event);
2116
2722
 
2117
2723
  nextIndex = p.focusItemIndex + 1;
2118
- if (nextIndex === p.items.length) {
2724
+ if (nextIndex === items.length) {
2119
2725
  nextIndex = 0;
2120
2726
  }
2121
2727
 
@@ -2144,8 +2750,8 @@ class DropList {
2144
2750
 
2145
2751
  if (nextIndex < 0) {
2146
2752
  nextIndex = 0;
2147
- } else if (nextIndex >= p.items.length) {
2148
- nextIndex = p.items.length;
2753
+ } else if (nextIndex >= items.length) {
2754
+ nextIndex = items.length;
2149
2755
  }
2150
2756
  } else if (p.focusItemEl) {
2151
2757
  let base = getElementOffset(p.focusItemEl).top;
@@ -2182,13 +2788,13 @@ class DropList {
2182
2788
  return;
2183
2789
  }
2184
2790
 
2185
- let itemCount = p.items.length;
2791
+ let itemCount = items.length;
2186
2792
 
2187
2793
  if (nextIndex >= itemCount) {
2188
2794
  return;
2189
2795
  }
2190
2796
 
2191
- let item = p.items[nextIndex];
2797
+ let item = items[nextIndex];
2192
2798
  // noinspection UnnecessaryLocalVariableJS
2193
2799
  let startedAtIndex = nextIndex;
2194
2800
 
@@ -2205,7 +2811,7 @@ class DropList {
2205
2811
  }
2206
2812
  }
2207
2813
 
2208
- item = p.items[nextIndex];
2814
+ item = items[nextIndex];
2209
2815
 
2210
2816
  if (nextIndex === startedAtIndex) {
2211
2817
  break;
@@ -2221,7 +2827,9 @@ class DropList {
2221
2827
  }
2222
2828
 
2223
2829
  _hasScroll() {
2224
- return this.el.clientHeight < this.el.scrollHeight;
2830
+ const p = this._p;
2831
+ const menuEl = p.menuEl;
2832
+ return menuEl.clientHeight < menuEl.scrollHeight;
2225
2833
  }
2226
2834
 
2227
2835
  _updateGroupStateForItem(item) {
@@ -2236,7 +2844,7 @@ class DropList {
2236
2844
  let affectedItems = 0;
2237
2845
 
2238
2846
  if (p.autoCheckGroupChildren) {
2239
- let items = p.items;
2847
+ let items = p.filteredItems ?? p.items;
2240
2848
  let groupIndex = items.indexOf(item);
2241
2849
 
2242
2850
  for (let i = groupIndex + 1, len = items.length; i < len; i++) {
@@ -2279,7 +2887,7 @@ class DropList {
2279
2887
  affectedItems: affectedItems,
2280
2888
  });
2281
2889
  } else if (p.groupCount > 0 && p.autoCheckGroupChildren) {
2282
- let items = p.items;
2890
+ let items = p.filteredItems ?? p.items;
2283
2891
  let itemIndex = items.indexOf(item);
2284
2892
  let groupIndex = -1;
2285
2893
 
@@ -2305,7 +2913,7 @@ class DropList {
2305
2913
  if (!(p.multi && p.autoCheckGroupChildren && groupIndex > -1))
2306
2914
  return this;
2307
2915
 
2308
- let items = p.items;
2916
+ let items = p.filteredItems ?? p.items;
2309
2917
  let groupItem = items[groupIndex];
2310
2918
 
2311
2919
  if (!groupItem || !groupItem._group) return this;
@@ -2431,7 +3039,7 @@ class DropList {
2431
3039
  _determineVirtualMode(targetItemCount) {
2432
3040
  const p = this._p;
2433
3041
 
2434
- let items = p.items;
3042
+ let items = p.filteredItems ?? p.items;
2435
3043
  if (targetItemCount === undefined) {
2436
3044
  targetItemCount = items.length;
2437
3045
  }
@@ -2451,7 +3059,21 @@ class DropList {
2451
3059
  // NOTE: a "measure" item will not have full data of original item.
2452
3060
  // so for a custom renderer - we try to send original item, and fallback to our private list item.
2453
3061
 
2454
- if (!p.renderItem || p.renderItem(item[ItemSymbol] || item, itemEl) === false) {
3062
+ const originalItem = item[ItemSymbol];
3063
+
3064
+ if (originalItem === NoResultsItemSymbol) {
3065
+ if (p.renderNoResultsItem && p.renderNoResultsItem(item, itemEl) !== false) {
3066
+ return true;
3067
+ }
3068
+
3069
+ itemEl.appendChild(createElement('div', {
3070
+ class: 'droplist-no-results-content',
3071
+ textContent: p.noResultsText,
3072
+ }));
3073
+ return;
3074
+ }
3075
+
3076
+ if (!p.renderItem || p.renderItem(originalItem || item, itemEl) === false) {
2455
3077
  itemEl.appendChild(createElement('span', {
2456
3078
  class: `${p.baseClassName}__item_label`,
2457
3079
  textContent: item.label,
@@ -2493,7 +3115,7 @@ class DropList {
2493
3115
  }
2494
3116
 
2495
3117
  let autoWidth = 0;
2496
- if (!p.useExactTargetWidth) {
3118
+ if (!p.useExactTargetWidth || !targetWidth) {
2497
3119
  if (p.estimateWidth || p.virtualListHelper.isVirtual()) {
2498
3120
  autoWidth = p.lastMeasureItemWidth;
2499
3121
  } else {