@danielgindi/selectbox 2.0.0 → 2.0.2

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.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @danielgindi/selectbox 2.0.0
2
+ * @danielgindi/selectbox 2.0.2
3
3
  * git://github.com/danielgindi/selectbox.git
4
4
  */
5
5
  (function (global, factory) {
@@ -57,7 +57,7 @@
57
57
  return throttled;
58
58
  };
59
59
 
60
- const ItemSymbol$1 = Symbol('item');
60
+ const ItemSymbol = Symbol('item');
61
61
  const DestroyedSymbol$1 = Symbol('destroyed');
62
62
  const GhostSymbol = Symbol('ghost');
63
63
  const NoResultsItemSymbol = Symbol('no_results_items');
@@ -89,6 +89,7 @@
89
89
  * @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderNoResultsItem]
90
90
  * @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderNoResultsItem]
91
91
  * @property {function(name: string, data: *)} [on]
92
+ * @property {boolean} [isHeaderVisible=false] show header element
92
93
  * @property {boolean} [searchable=false] include inline search box
93
94
  * @property {string} [noResultsText='No matching results'] text for no results (empty for none)
94
95
  * @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
@@ -161,6 +162,7 @@
161
162
 
162
163
  on: null,
163
164
 
165
+ isHeaderVisible: false,
164
166
  searchable: false,
165
167
  noResultsText: 'No matching results',
166
168
  filterThrottleWindow: 300,
@@ -233,6 +235,7 @@
233
235
  positionOptionsProvider: o.positionOptionsProvider ?? null,
234
236
 
235
237
  searchable: o.searchable,
238
+ isHeaderVisible: o.isHeaderVisible,
236
239
 
237
240
  silenceEvents: true,
238
241
  mitt: mitt(),
@@ -280,33 +283,24 @@
280
283
  });
281
284
  }
282
285
 
286
+ p.el = wrapperEl;
287
+
283
288
  let menuEl = Dom.createElement('ul');
284
289
  menuEl.role = 'menu';
290
+ p.menuEl = menuEl;
285
291
 
286
- if (o.searchable) {
287
- p.headerEl = Dom.createElement('div', {
288
- class: p.baseClassName + '_header'
289
- });
290
-
291
- p.searchInput = Dom.createElement('input', {
292
- type: 'search',
293
- role: 'searchbox',
294
- tabindex: '0',
295
- autocorrect: 'off',
296
- autocomplete: 'off',
297
- autocapitalize: 'off',
298
- spellcheck: 'false',
299
- 'aria-autocomplete': 'list'
300
- });
292
+ p.headerEl = Dom.createElement('div', {
293
+ class: p.baseClassName + '_header'
294
+ });
301
295
 
302
- p.headerEl.appendChild(p.searchInput);
303
- wrapperEl.appendChild(p.headerEl);
296
+ if (o.searchable) {
297
+ this.setSearchable(true);
304
298
  }
305
299
 
306
- wrapperEl.appendChild(menuEl);
300
+ if (o.isHeaderVisible)
301
+ this.setHeaderVisible(o.isHeaderVisible);
307
302
 
308
- p.el = wrapperEl;
309
- p.menuEl = menuEl;
303
+ wrapperEl.appendChild(menuEl);
310
304
 
311
305
  p.items = [];
312
306
  p.groupCount = 0; // This will keep state of how many `group` items we have
@@ -333,7 +327,7 @@
333
327
  label: p.lastMeasureLongestLabelText,
334
328
  value: 'Measure',
335
329
 
336
- [ItemSymbol$1]: {
330
+ [ItemSymbol]: {
337
331
  [p.labelProp]: p.lastMeasureLongestLabelText,
338
332
  [p.valueProp]: 'Measure'
339
333
  }
@@ -348,7 +342,7 @@
348
342
  _nointeraction: true,
349
343
  _nocheck: true,
350
344
 
351
- [ItemSymbol$1]: NoResultsItemSymbol
345
+ [ItemSymbol]: NoResultsItemSymbol
352
346
  };
353
347
  } else {
354
348
  item = items[index];
@@ -394,7 +388,7 @@
394
388
 
395
389
  this._renderItemContent(item, itemEl);
396
390
 
397
- itemEl[ItemSymbol$1] = item;
391
+ itemEl[ItemSymbol] = item;
398
392
  }
399
393
  });
400
394
 
@@ -574,7 +568,7 @@
574
568
  const fn = p.unrenderItem;
575
569
  const fnNoResults = p.unrenderNoResultsItem;
576
570
  p.virtualListHelper.setOnItemUnrender((el) => {
577
- const item = el[ItemSymbol$1];
571
+ const item = el[ItemSymbol];
578
572
  if (item === NoResultsItemSymbol) {
579
573
  try {
580
574
  fnNoResults(item, el);
@@ -583,19 +577,19 @@
583
577
  }
584
578
  } else {
585
579
  try {
586
- fn(item[ItemSymbol$1], el);
580
+ fn(item[ItemSymbol], el);
587
581
  } catch (err) {
588
582
  console.error(err); // eslint-disable-line no-console
589
583
  }
590
584
  }
591
- delete el[ItemSymbol$1];
585
+ delete el[ItemSymbol];
592
586
 
593
587
  if (p.focusItemEl === el)
594
588
  p.focusItemEl = null;
595
589
  });
596
590
  } else {
597
591
  p.virtualListHelper.setOnItemUnrender((el) => {
598
- delete el[ItemSymbol$1];
592
+ delete el[ItemSymbol];
599
593
 
600
594
  if (p.focusItemEl === el)
601
595
  p.focusItemEl = null;
@@ -661,7 +655,7 @@
661
655
  this._hideSublist();
662
656
  }
663
657
 
664
- this._trigger('itemblur', { value: item.value, item: item[ItemSymbol$1] ?? item });
658
+ this._trigger('itemblur', { value: item.value, item: item[ItemSymbol] ?? item });
665
659
  }
666
660
 
667
661
  nextPage(event) {
@@ -693,7 +687,7 @@
693
687
  }
694
688
  this._trigger('check', {
695
689
  value: item.value,
696
- item: item[ItemSymbol$1] ?? item,
690
+ item: item[ItemSymbol] ?? item,
697
691
  checked: item._checked,
698
692
  isGroup: item._group,
699
693
  isCheckingGroup: false
@@ -713,7 +707,7 @@
713
707
  if (p.focusItemIndex === undefined)
714
708
  p.focusItemIndex = -1;
715
709
 
716
- item = item ?? p.focusItemEl[ItemSymbol$1];
710
+ item = item ?? p.focusItemEl[ItemSymbol];
717
711
  if (item._nointeraction) {
718
712
  return false;
719
713
  }
@@ -724,7 +718,7 @@
724
718
 
725
719
  this._trigger('select', {
726
720
  value: item ? item.value : undefined,
727
- item: item[ItemSymbol$1] ?? item,
721
+ item: item[ItemSymbol] ?? item,
728
722
  event: event,
729
723
  el: p.focusItemEl
730
724
  });
@@ -765,7 +759,7 @@
765
759
  let oitem = itemsToAdd[i];
766
760
  //noinspection PointlessBooleanExpressionJS
767
761
  let item = {
768
- [ItemSymbol$1]: oitem,
762
+ [ItemSymbol]: oitem,
769
763
  label: oitem[labelProp],
770
764
  value: oitem[valueProp],
771
765
  _nocheck: !!oitem._nocheck,
@@ -852,7 +846,7 @@
852
846
  if (itemIndex === -1) return this;
853
847
 
854
848
  let item = this.itemAtIndex(itemIndex);
855
- item[ItemSymbol$1] = newItem;
849
+ item[ItemSymbol] = newItem;
856
850
 
857
851
  if (hasOwnProperty.call(newItem, p.labelProp))
858
852
  item.label = newItem[p.labelProp];
@@ -978,7 +972,7 @@
978
972
  for (let i = 0, count = p.items.length; i < count; i++) {
979
973
  let item = p.items[i];
980
974
  if (item.value === value) {
981
- return item[ItemSymbol$1];
975
+ return item[ItemSymbol];
982
976
  }
983
977
  }
984
978
 
@@ -1048,7 +1042,7 @@
1048
1042
 
1049
1043
  for (let i = 0, count = items.length; i < count; i++) {
1050
1044
  let it = items[i];
1051
- if (it[ItemSymbol$1] === item) {
1045
+ if (it[ItemSymbol] === item) {
1052
1046
  return i;
1053
1047
  }
1054
1048
  }
@@ -1063,7 +1057,7 @@
1063
1057
 
1064
1058
  for (let i = 0, count = items.length; i < count; i++) {
1065
1059
  let it = items[i];
1066
- if (it[ItemSymbol$1] === item) {
1060
+ if (it[ItemSymbol] === item) {
1067
1061
  return i;
1068
1062
  }
1069
1063
  }
@@ -1072,7 +1066,7 @@
1072
1066
  }
1073
1067
 
1074
1068
  items() {
1075
- return this._p.items.map((x) => x[ItemSymbol$1]);
1069
+ return this._p.items.map((x) => x[ItemSymbol]);
1076
1070
  }
1077
1071
 
1078
1072
  itemsReference() {
@@ -1084,13 +1078,42 @@
1084
1078
  }
1085
1079
 
1086
1080
  itemAtIndex(index) {
1087
- return this._p.items[index]?.[ItemSymbol$1];
1081
+ return this._p.items[index]?.[ItemSymbol];
1088
1082
  }
1089
1083
 
1090
1084
  filteredItemAtIndex(index) {
1091
1085
  const p = this._p;
1092
1086
  const items = p.filteredItems ?? p.items;
1093
- return items[index]?.[ItemSymbol$1];
1087
+ return items[index]?.[ItemSymbol];
1088
+ }
1089
+
1090
+ /**
1091
+ * Return the item element at the given original index, if it exists.
1092
+ * The item may not be currently rendered, and null will be returned
1093
+ * @param index
1094
+ * @returns {HTMLElement|null}
1095
+ */
1096
+ itemElementAtIndex(index) {
1097
+ const p = this._p;
1098
+ if (!p.filteredItems)
1099
+ return this.filteredElementItemAtIndex(index);
1100
+
1101
+ index = p.filteredItems.indexOf(this._p.items[index]);
1102
+
1103
+ const li = p.virtualListHelper.getItemElementAt(index);
1104
+ return li ?? null;
1105
+ }
1106
+
1107
+ /**
1108
+ * Return the item element at the given index, if it exists.
1109
+ * The item may not be currently rendered, and null will be returned
1110
+ * @param index
1111
+ * @returns {HTMLElement|null}
1112
+ */
1113
+ filteredElementItemAtIndex(index) {
1114
+ const p = this._p;
1115
+ const li = p.virtualListHelper.getItemElementAt(index);
1116
+ return li ?? null;
1094
1117
  }
1095
1118
 
1096
1119
  /**
@@ -1158,7 +1181,30 @@
1158
1181
  let filteredItems;
1159
1182
 
1160
1183
  if (typeof fn === 'function') {
1161
- filteredItems = p.filterFn(p.items, term);
1184
+ // Send the original items to the filter function
1185
+ filteredItems = p.filterFn(
1186
+ p.items.map((x) => x[ItemSymbol] ?? x),
1187
+ term);
1188
+
1189
+ if (Array.isArray(filteredItems)) {
1190
+ // And back
1191
+ filteredItems = filteredItems.map((oitem) => {
1192
+ let our = oitem[ItemSymbol];
1193
+ if (!our) {
1194
+ our = {
1195
+ [ItemSymbol]: oitem,
1196
+ label: oitem[p.labelProp],
1197
+ value: oitem[p.valueProp],
1198
+ _nocheck: !!oitem._nocheck,
1199
+ _nointeraction: !!oitem._nointeraction,
1200
+ _subitems: oitem._subitems,
1201
+ _group: !!oitem._group,
1202
+ _checked: !!oitem._checked
1203
+ };
1204
+ }
1205
+ return our;
1206
+ });
1207
+ }
1162
1208
  }
1163
1209
 
1164
1210
  // If there was no filter function, or it gave up on filtering.
@@ -1657,7 +1703,7 @@
1657
1703
  let li = p.virtualListHelper.getItemElementAt(index);
1658
1704
  if (!li) return this;
1659
1705
 
1660
- let item = li[ItemSymbol$1];
1706
+ let item = li[ItemSymbol];
1661
1707
 
1662
1708
  checked = checked && !item._nocheck;
1663
1709
 
@@ -1750,7 +1796,7 @@
1750
1796
  let item = p.items[i];
1751
1797
  if (!item._checked) continue;
1752
1798
  if (excludeGroups && item._group) continue;
1753
- items.push(item[ItemSymbol$1]);
1799
+ items.push(item[ItemSymbol]);
1754
1800
  }
1755
1801
 
1756
1802
  return items;
@@ -1916,6 +1962,72 @@
1916
1962
  return p.el.parentNode && getComputedStyle(p.el).display !== 'none';
1917
1963
  }
1918
1964
 
1965
+ /**
1966
+ * Change visibility of the header element
1967
+ * You should probably call `relayout()` after this.
1968
+ * @param {boolean} visible
1969
+ */
1970
+ setHeaderVisible(visible) {
1971
+ let isVisible = this.isHeaderVisible();
1972
+ if (isVisible === !!visible)
1973
+ return;
1974
+
1975
+ if (visible) {
1976
+ this._p.el.insertBefore(this._p.headerEl, this._p.el.firstChild ?? null);
1977
+ } else {
1978
+ this._p.headerEl.remove();
1979
+ }
1980
+ }
1981
+
1982
+ /**
1983
+ * Is the header element visible?
1984
+ * @returns {boolean}
1985
+ */
1986
+ isHeaderVisible() {
1987
+ return !!this._p.headerEl.parentNode;
1988
+ }
1989
+
1990
+ /**
1991
+ * Get a reference to the header element in order to add custom content.
1992
+ * @returns {Element}
1993
+ */
1994
+ getHeaderElement() {
1995
+ return this._p.headerEl;
1996
+ }
1997
+
1998
+ /**
1999
+ * Set inline search visibility
2000
+ * You should probably call `relayout()` after this.
2001
+ * @param {boolean} searchable
2002
+ */
2003
+ setSearchable(searchable) {
2004
+ const p = this._p;
2005
+
2006
+ if (!!p.searchInput === !!searchable)
2007
+ return;
2008
+
2009
+ if (searchable) {
2010
+ p.searchInput = Dom.createElement('input', {
2011
+ type: 'search',
2012
+ role: 'searchbox',
2013
+ tabindex: '0',
2014
+ autocorrect: 'off',
2015
+ autocomplete: 'off',
2016
+ autocapitalize: 'off',
2017
+ spellcheck: 'false',
2018
+ 'aria-autocomplete': 'list'
2019
+ });
2020
+
2021
+ p.headerEl.appendChild(p.searchInput);
2022
+ } else {
2023
+ if (p.searchInput.parentNode)
2024
+ p.searchInput.remove();
2025
+ p.searchInput = null;
2026
+ }
2027
+
2028
+ this.setHeaderVisible(searchable);
2029
+ }
2030
+
1919
2031
  hasFocusedItem() {
1920
2032
  return this._p.focusItemIndex > -1;
1921
2033
  }
@@ -1958,11 +2070,11 @@
1958
2070
  itemElement.classList.add(`${p.baseClassName}__item_focus`);
1959
2071
  p.focusItemEl = itemElement;
1960
2072
 
1961
- const item = itemElement[ItemSymbol$1];
2073
+ const item = itemElement[ItemSymbol];
1962
2074
 
1963
2075
  this._trigger('itemfocus', {
1964
2076
  value: item.value,
1965
- item: item[ItemSymbol$1] ?? item,
2077
+ item: item[ItemSymbol] ?? item,
1966
2078
  event: null,
1967
2079
  el: itemElement
1968
2080
  });
@@ -2029,7 +2141,7 @@
2029
2141
 
2030
2142
  this._trigger('show_subitems', {
2031
2143
  value: item.value,
2032
- item: item[ItemSymbol$1] ?? item,
2144
+ item: item[ItemSymbol] ?? item,
2033
2145
  el: itemElement,
2034
2146
  droplist: droplist
2035
2147
  });
@@ -2201,7 +2313,7 @@
2201
2313
  const p = this._p;
2202
2314
 
2203
2315
  if (this._hasScroll()) {
2204
- const el = p.el,scrollTop = el.scrollTop;
2316
+ const menuEl = p.menuEl,scrollTop = menuEl.scrollTop;
2205
2317
 
2206
2318
  let itemPos,previousPos = -1;
2207
2319
  let maxIterations = 30; // Some zoom/scroll issues can make it so that it takes almost forever
@@ -2216,12 +2328,12 @@
2216
2328
 
2217
2329
  let itemSize = p.virtualListHelper.getItemSize(itemIndex);
2218
2330
 
2219
- let listHeight = el.clientHeight;
2331
+ let listHeight = menuEl.clientHeight;
2220
2332
 
2221
2333
  if (itemPos < scrollTop) {
2222
- el.scrollTop = itemPos;
2334
+ menuEl.scrollTop = itemPos;
2223
2335
  } else if (itemPos + itemSize > scrollTop + listHeight) {
2224
- el.scrollTop = itemPos + itemSize - listHeight;
2336
+ menuEl.scrollTop = itemPos + itemSize - listHeight;
2225
2337
  }
2226
2338
 
2227
2339
  // force update items, until the positions and sizes are final
@@ -2506,6 +2618,8 @@
2506
2618
  case keycodeJs.VALUE_END:
2507
2619
  case keycodeJs.VALUE_UP:
2508
2620
  case keycodeJs.VALUE_DOWN:
2621
+ p.lastKeyWasChar = false;
2622
+
2509
2623
  event.preventDefault();
2510
2624
 
2511
2625
  switch (event.key) {
@@ -2532,6 +2646,7 @@
2532
2646
 
2533
2647
  case keycodeJs.VALUE_LEFT:
2534
2648
  case keycodeJs.VALUE_RIGHT:
2649
+ p.lastKeyWasChar = false;
2535
2650
  if (event.key === keycodeJs.VALUE_RIGHT && getComputedStyle(event.target).direction !== 'rtl' ||
2536
2651
  event.key === keycodeJs.VALUE_LEFT && getComputedStyle(event.target).direction === 'rtl') {
2537
2652
  const items = p.filteredItems ?? p.items;
@@ -2549,22 +2664,31 @@
2549
2664
  break;
2550
2665
 
2551
2666
  case keycodeJs.VALUE_ENTER:
2667
+ p.lastKeyWasChar = false;
2552
2668
  this.triggerItemSelection(null, event);
2553
2669
  event.preventDefault();
2554
2670
  break;
2555
2671
 
2556
2672
  case keycodeJs.VALUE_SPACE:
2673
+ if (event.target.tagName === 'BUTTON' ||
2674
+ event.target.tagName === 'INPUT' && p.lastKeyWasChar) return;
2675
+
2557
2676
  this.toggleFocusedItem();
2558
2677
  event.preventDefault();
2559
2678
  break;
2560
2679
 
2561
2680
  case keycodeJs.VALUE_ESCAPE:
2681
+ p.lastKeyWasChar = false;
2562
2682
  event.preventDefault();
2563
2683
  this.hide();
2564
2684
  break;
2565
2685
 
2566
2686
  default:{
2567
- if (event.type === 'keydown') return;
2687
+ if (event.target.tagName === 'INPUT' ||
2688
+ event.target.tagName === 'TEXTAREA') {
2689
+ const character = event.key || String.fromCharCode(event.keyCode);
2690
+ p.lastKeyWasChar = character.length === 1 || event.key === keycodeJs.VALUE_BACK_SPACE;
2691
+ }
2568
2692
 
2569
2693
  // Inline search box not available, then support typing to focus by first letters
2570
2694
  if (!p.searchable)
@@ -2579,8 +2703,10 @@
2579
2703
  const p = this._p;
2580
2704
 
2581
2705
  // noinspection JSDeprecatedSymbols
2582
- let character = evt.key || String.fromCharCode(evt.keyCode);
2583
- if (character.length !== 1) return;
2706
+ const character = evt.key || String.fromCharCode(evt.keyCode);
2707
+ const isChar = character.length === 1;
2708
+ p.lastKeyWasChar = isChar || evt.key === keycodeJs.VALUE_BACK_SPACE;
2709
+ if (!isChar) return;
2584
2710
 
2585
2711
  clearTimeout(p.filterTimer);
2586
2712
 
@@ -2660,7 +2786,7 @@
2660
2786
  itemIndex = p.virtualListHelper.getItemIndexFromElement(itemEl);
2661
2787
  }
2662
2788
 
2663
- if (itemIndex > -1 && itemEl?.[ItemSymbol$1]?.[ItemSymbol$1] === NoResultsItemSymbol) {
2789
+ if (itemIndex > -1 && itemEl?.[ItemSymbol]?.[ItemSymbol] === NoResultsItemSymbol) {
2664
2790
  itemIndex = undefined;
2665
2791
  }
2666
2792
 
@@ -2671,7 +2797,7 @@
2671
2797
  }
2672
2798
 
2673
2799
  let focusItemEl = itemEl || p.virtualListHelper.getItemElementAt(itemIndex);
2674
- if (!focusItemEl || focusItemEl[ItemSymbol$1]._nointeraction) {
2800
+ if (!focusItemEl || focusItemEl[ItemSymbol]._nointeraction) {
2675
2801
  p._isFocusingItem = false;
2676
2802
  this.blurFocusedItem();
2677
2803
  return;
@@ -2693,7 +2819,7 @@
2693
2819
  const item = items[itemIndex];
2694
2820
  this._trigger('itemfocus', {
2695
2821
  value: item.value,
2696
- item: item[ItemSymbol$1] ?? item,
2822
+ item: item[ItemSymbol] ?? item,
2697
2823
  event: event,
2698
2824
  el: focusItemEl
2699
2825
  });
@@ -2899,7 +3025,7 @@
2899
3025
  // Fire event
2900
3026
  this._trigger('check', {
2901
3027
  value: next.value,
2902
- item: next[ItemSymbol$1] ?? next,
3028
+ item: next[ItemSymbol] ?? next,
2903
3029
  checked: next._checked,
2904
3030
  isGroup: next._group,
2905
3031
  isCheckingGroup: true
@@ -2910,7 +3036,7 @@
2910
3036
  // Fire event
2911
3037
  this._trigger('groupcheck', {
2912
3038
  value: item.value,
2913
- item: item[ItemSymbol$1] ?? item,
3039
+ item: item[ItemSymbol] ?? item,
2914
3040
  affectedItems: affectedItems
2915
3041
  });
2916
3042
  } else if (p.groupCount > 0 && p.autoCheckGroupChildren) {
@@ -2981,7 +3107,7 @@
2981
3107
  // Fire event
2982
3108
  this._trigger('check', {
2983
3109
  value: groupItem.value,
2984
- item: groupItem[ItemSymbol$1] ?? groupItem,
3110
+ item: groupItem[ItemSymbol] ?? groupItem,
2985
3111
  checked: groupItem._checked,
2986
3112
  isGroup: groupItem._group,
2987
3113
  isCheckingGroup: false
@@ -3086,7 +3212,7 @@
3086
3212
  // NOTE: a "measure" item will not have full data of original item.
3087
3213
  // so for a custom renderer - we try to send original item, and fallback to our private list item.
3088
3214
 
3089
- const originalItem = item[ItemSymbol$1];
3215
+ const originalItem = item[ItemSymbol];
3090
3216
 
3091
3217
  if (originalItem === NoResultsItemSymbol) {
3092
3218
  if (p.renderNoResultsItem && p.renderNoResultsItem(item, itemEl) !== false) {
@@ -3169,7 +3295,6 @@
3169
3295
  }
3170
3296
  }
3171
3297
 
3172
- const ItemSymbol = Symbol('item');
3173
3298
  const DestroyedSymbol = Symbol('destroyed');
3174
3299
  const RestMultiItemsSymbol = Symbol('rest_multi_items');
3175
3300