@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.cjs.js +187 -62
- package/dist/lib.cjs.js.map +1 -1
- package/dist/lib.cjs.min.js +2 -2
- package/dist/lib.cjs.min.js.map +1 -1
- package/dist/lib.es6.js +188 -63
- package/dist/lib.es6.js.map +1 -1
- package/dist/lib.es6.min.js +2 -2
- package/dist/lib.es6.min.js.map +1 -1
- package/dist/lib.umd.js +187 -62
- package/dist/lib.umd.js.map +1 -1
- package/dist/lib.umd.min.js +2 -2
- package/dist/lib.umd.min.js.map +1 -1
- package/lib/DropList.js +156 -28
- package/lib/SelectBox.js +1 -2
- package/package.json +2 -2
- package/vue/DropList.vue +25 -2
package/lib/DropList.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import DomEventsSink from '@danielgindi/dom-utils/lib/DomEventsSink';
|
|
19
19
|
import VirtualListHelper from '@danielgindi/virtual-list-helper';
|
|
20
20
|
import {
|
|
21
|
+
VALUE_BACK_SPACE,
|
|
21
22
|
VALUE_DOWN,
|
|
22
23
|
VALUE_END,
|
|
23
24
|
VALUE_ENTER, VALUE_ESCAPE,
|
|
@@ -62,6 +63,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
62
63
|
* @property {function(item: DropList.ItemBase, itemEl: Element):(*|false)} [renderNoResultsItem]
|
|
63
64
|
* @property {function(item: DropList.ItemBase, itemEl: Element)} [unrenderNoResultsItem]
|
|
64
65
|
* @property {function(name: string, data: *)} [on]
|
|
66
|
+
* @property {boolean} [isHeaderVisible=false] show header element
|
|
65
67
|
* @property {boolean} [searchable=false] include inline search box
|
|
66
68
|
* @property {string} [noResultsText='No matching results'] text for no results (empty for none)
|
|
67
69
|
* @property {number} [filterThrottleWindow=300] throttle time (milliseconds) for filtering
|
|
@@ -134,6 +136,7 @@ let defaultOptions = {
|
|
|
134
136
|
|
|
135
137
|
on: null,
|
|
136
138
|
|
|
139
|
+
isHeaderVisible: false,
|
|
137
140
|
searchable: false,
|
|
138
141
|
noResultsText: 'No matching results',
|
|
139
142
|
filterThrottleWindow: 300,
|
|
@@ -206,6 +209,7 @@ class DropList {
|
|
|
206
209
|
positionOptionsProvider: o.positionOptionsProvider ?? null,
|
|
207
210
|
|
|
208
211
|
searchable: o.searchable,
|
|
212
|
+
isHeaderVisible: o.isHeaderVisible,
|
|
209
213
|
|
|
210
214
|
silenceEvents: true,
|
|
211
215
|
mitt: mitt(),
|
|
@@ -253,33 +257,24 @@ class DropList {
|
|
|
253
257
|
});
|
|
254
258
|
}
|
|
255
259
|
|
|
260
|
+
p.el = wrapperEl;
|
|
261
|
+
|
|
256
262
|
let menuEl = createElement('ul');
|
|
257
263
|
menuEl.role = 'menu';
|
|
264
|
+
p.menuEl = menuEl;
|
|
258
265
|
|
|
259
|
-
|
|
260
|
-
p.
|
|
261
|
-
|
|
262
|
-
});
|
|
263
|
-
|
|
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
|
-
});
|
|
266
|
+
p.headerEl = createElement('div', {
|
|
267
|
+
class: p.baseClassName + '_header',
|
|
268
|
+
});
|
|
274
269
|
|
|
275
|
-
|
|
276
|
-
|
|
270
|
+
if (o.searchable) {
|
|
271
|
+
this.setSearchable(true);
|
|
277
272
|
}
|
|
278
273
|
|
|
279
|
-
|
|
274
|
+
if (o.isHeaderVisible)
|
|
275
|
+
this.setHeaderVisible(o.isHeaderVisible);
|
|
280
276
|
|
|
281
|
-
|
|
282
|
-
p.menuEl = menuEl;
|
|
277
|
+
wrapperEl.appendChild(menuEl);
|
|
283
278
|
|
|
284
279
|
p.items = [];
|
|
285
280
|
p.groupCount = 0; // This will keep state of how many `group` items we have
|
|
@@ -1066,6 +1061,35 @@ class DropList {
|
|
|
1066
1061
|
return items[index]?.[ItemSymbol];
|
|
1067
1062
|
}
|
|
1068
1063
|
|
|
1064
|
+
/**
|
|
1065
|
+
* Return the item element at the given original index, if it exists.
|
|
1066
|
+
* The item may not be currently rendered, and null will be returned
|
|
1067
|
+
* @param index
|
|
1068
|
+
* @returns {HTMLElement|null}
|
|
1069
|
+
*/
|
|
1070
|
+
itemElementAtIndex(index) {
|
|
1071
|
+
const p = this._p;
|
|
1072
|
+
if (!p.filteredItems)
|
|
1073
|
+
return this.filteredElementItemAtIndex(index);
|
|
1074
|
+
|
|
1075
|
+
index = p.filteredItems.indexOf(this._p.items[index]);
|
|
1076
|
+
|
|
1077
|
+
const li = p.virtualListHelper.getItemElementAt(index);
|
|
1078
|
+
return li ?? null;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Return the item element at the given index, if it exists.
|
|
1083
|
+
* The item may not be currently rendered, and null will be returned
|
|
1084
|
+
* @param index
|
|
1085
|
+
* @returns {HTMLElement|null}
|
|
1086
|
+
*/
|
|
1087
|
+
filteredElementItemAtIndex(index) {
|
|
1088
|
+
const p = this._p;
|
|
1089
|
+
const li = p.virtualListHelper.getItemElementAt(index);
|
|
1090
|
+
return li ?? null;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1069
1093
|
/**
|
|
1070
1094
|
* @param {function(dropList: DropList):DropList.PositionOptions} fn
|
|
1071
1095
|
* @returns {DropList}
|
|
@@ -1131,7 +1155,30 @@ class DropList {
|
|
|
1131
1155
|
let filteredItems;
|
|
1132
1156
|
|
|
1133
1157
|
if (typeof fn === 'function') {
|
|
1134
|
-
|
|
1158
|
+
// Send the original items to the filter function
|
|
1159
|
+
filteredItems = p.filterFn(
|
|
1160
|
+
p.items.map(x => x[ItemSymbol] ?? x),
|
|
1161
|
+
term);
|
|
1162
|
+
|
|
1163
|
+
if (Array.isArray(filteredItems)) {
|
|
1164
|
+
// And back
|
|
1165
|
+
filteredItems = filteredItems.map(oitem => {
|
|
1166
|
+
let our = oitem[ItemSymbol];
|
|
1167
|
+
if (!our) {
|
|
1168
|
+
our = {
|
|
1169
|
+
[ItemSymbol]: oitem,
|
|
1170
|
+
label: oitem[p.labelProp],
|
|
1171
|
+
value: oitem[p.valueProp],
|
|
1172
|
+
_nocheck: !!oitem._nocheck,
|
|
1173
|
+
_nointeraction: !!oitem._nointeraction,
|
|
1174
|
+
_subitems: oitem._subitems,
|
|
1175
|
+
_group: !!oitem._group,
|
|
1176
|
+
_checked: !!oitem._checked,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
return our;
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1135
1182
|
}
|
|
1136
1183
|
|
|
1137
1184
|
// If there was no filter function, or it gave up on filtering.
|
|
@@ -1889,6 +1936,72 @@ class DropList {
|
|
|
1889
1936
|
return p.el.parentNode && getComputedStyle(p.el).display !== 'none';
|
|
1890
1937
|
}
|
|
1891
1938
|
|
|
1939
|
+
/**
|
|
1940
|
+
* Change visibility of the header element
|
|
1941
|
+
* You should probably call `relayout()` after this.
|
|
1942
|
+
* @param {boolean} visible
|
|
1943
|
+
*/
|
|
1944
|
+
setHeaderVisible(visible) {
|
|
1945
|
+
let isVisible = this.isHeaderVisible();
|
|
1946
|
+
if (isVisible === !!visible)
|
|
1947
|
+
return;
|
|
1948
|
+
|
|
1949
|
+
if (visible) {
|
|
1950
|
+
this._p.el.insertBefore(this._p.headerEl, this._p.el.firstChild ?? null);
|
|
1951
|
+
} else {
|
|
1952
|
+
this._p.headerEl.remove();
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Is the header element visible?
|
|
1958
|
+
* @returns {boolean}
|
|
1959
|
+
*/
|
|
1960
|
+
isHeaderVisible() {
|
|
1961
|
+
return !!this._p.headerEl.parentNode;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/**
|
|
1965
|
+
* Get a reference to the header element in order to add custom content.
|
|
1966
|
+
* @returns {Element}
|
|
1967
|
+
*/
|
|
1968
|
+
getHeaderElement() {
|
|
1969
|
+
return this._p.headerEl;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/**
|
|
1973
|
+
* Set inline search visibility
|
|
1974
|
+
* You should probably call `relayout()` after this.
|
|
1975
|
+
* @param {boolean} searchable
|
|
1976
|
+
*/
|
|
1977
|
+
setSearchable(searchable) {
|
|
1978
|
+
const p = this._p;
|
|
1979
|
+
|
|
1980
|
+
if (!!p.searchInput === !!searchable)
|
|
1981
|
+
return;
|
|
1982
|
+
|
|
1983
|
+
if (searchable) {
|
|
1984
|
+
p.searchInput = createElement('input', {
|
|
1985
|
+
type: 'search',
|
|
1986
|
+
role: 'searchbox',
|
|
1987
|
+
tabindex: '0',
|
|
1988
|
+
autocorrect: 'off',
|
|
1989
|
+
autocomplete: 'off',
|
|
1990
|
+
autocapitalize: 'off',
|
|
1991
|
+
spellcheck: 'false',
|
|
1992
|
+
'aria-autocomplete': 'list',
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1995
|
+
p.headerEl.appendChild(p.searchInput);
|
|
1996
|
+
} else {
|
|
1997
|
+
if (p.searchInput.parentNode)
|
|
1998
|
+
p.searchInput.remove();
|
|
1999
|
+
p.searchInput = null;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
this.setHeaderVisible(searchable);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1892
2005
|
hasFocusedItem() {
|
|
1893
2006
|
return this._p.focusItemIndex > -1;
|
|
1894
2007
|
}
|
|
@@ -2174,7 +2287,7 @@ class DropList {
|
|
|
2174
2287
|
const p = this._p;
|
|
2175
2288
|
|
|
2176
2289
|
if (this._hasScroll()) {
|
|
2177
|
-
const
|
|
2290
|
+
const menuEl = p.menuEl, scrollTop = menuEl.scrollTop;
|
|
2178
2291
|
|
|
2179
2292
|
let itemPos, previousPos = -1;
|
|
2180
2293
|
let maxIterations = 30; // Some zoom/scroll issues can make it so that it takes almost forever
|
|
@@ -2189,12 +2302,12 @@ class DropList {
|
|
|
2189
2302
|
|
|
2190
2303
|
let itemSize = p.virtualListHelper.getItemSize(itemIndex);
|
|
2191
2304
|
|
|
2192
|
-
let listHeight =
|
|
2305
|
+
let listHeight = menuEl.clientHeight;
|
|
2193
2306
|
|
|
2194
2307
|
if (itemPos < scrollTop) {
|
|
2195
|
-
|
|
2308
|
+
menuEl.scrollTop = itemPos;
|
|
2196
2309
|
} else if (itemPos + itemSize > scrollTop + listHeight) {
|
|
2197
|
-
|
|
2310
|
+
menuEl.scrollTop = itemPos + itemSize - listHeight;
|
|
2198
2311
|
}
|
|
2199
2312
|
|
|
2200
2313
|
// force update items, until the positions and sizes are final
|
|
@@ -2479,6 +2592,8 @@ class DropList {
|
|
|
2479
2592
|
case VALUE_END:
|
|
2480
2593
|
case VALUE_UP:
|
|
2481
2594
|
case VALUE_DOWN:
|
|
2595
|
+
p.lastKeyWasChar = false;
|
|
2596
|
+
|
|
2482
2597
|
event.preventDefault();
|
|
2483
2598
|
|
|
2484
2599
|
switch (event.key) {
|
|
@@ -2505,6 +2620,7 @@ class DropList {
|
|
|
2505
2620
|
|
|
2506
2621
|
case VALUE_LEFT:
|
|
2507
2622
|
case VALUE_RIGHT:
|
|
2623
|
+
p.lastKeyWasChar = false;
|
|
2508
2624
|
if (event.key === VALUE_RIGHT && getComputedStyle(event.target).direction !== 'rtl' ||
|
|
2509
2625
|
event.key === VALUE_LEFT && getComputedStyle(event.target).direction === 'rtl') {
|
|
2510
2626
|
const items = p.filteredItems ?? p.items;
|
|
@@ -2522,22 +2638,31 @@ class DropList {
|
|
|
2522
2638
|
break;
|
|
2523
2639
|
|
|
2524
2640
|
case VALUE_ENTER:
|
|
2641
|
+
p.lastKeyWasChar = false;
|
|
2525
2642
|
this.triggerItemSelection(null, event);
|
|
2526
2643
|
event.preventDefault();
|
|
2527
2644
|
break;
|
|
2528
2645
|
|
|
2529
2646
|
case VALUE_SPACE:
|
|
2647
|
+
if (event.target.tagName === 'BUTTON' ||
|
|
2648
|
+
event.target.tagName === 'INPUT' && p.lastKeyWasChar) return;
|
|
2649
|
+
|
|
2530
2650
|
this.toggleFocusedItem();
|
|
2531
2651
|
event.preventDefault();
|
|
2532
2652
|
break;
|
|
2533
2653
|
|
|
2534
2654
|
case VALUE_ESCAPE:
|
|
2655
|
+
p.lastKeyWasChar = false;
|
|
2535
2656
|
event.preventDefault();
|
|
2536
2657
|
this.hide();
|
|
2537
2658
|
break;
|
|
2538
2659
|
|
|
2539
2660
|
default: {
|
|
2540
|
-
if (event.
|
|
2661
|
+
if (event.target.tagName === 'INPUT' ||
|
|
2662
|
+
event.target.tagName === 'TEXTAREA') {
|
|
2663
|
+
const character = event.key || String.fromCharCode(event.keyCode);
|
|
2664
|
+
p.lastKeyWasChar = character.length === 1 || event.key === VALUE_BACK_SPACE;
|
|
2665
|
+
}
|
|
2541
2666
|
|
|
2542
2667
|
// Inline search box not available, then support typing to focus by first letters
|
|
2543
2668
|
if (!p.searchable)
|
|
@@ -2552,8 +2677,10 @@ class DropList {
|
|
|
2552
2677
|
const p = this._p;
|
|
2553
2678
|
|
|
2554
2679
|
// noinspection JSDeprecatedSymbols
|
|
2555
|
-
|
|
2556
|
-
|
|
2680
|
+
const character = evt.key || String.fromCharCode(evt.keyCode);
|
|
2681
|
+
const isChar = character.length === 1;
|
|
2682
|
+
p.lastKeyWasChar = isChar || evt.key === VALUE_BACK_SPACE;
|
|
2683
|
+
if (!isChar) return;
|
|
2557
2684
|
|
|
2558
2685
|
clearTimeout(p.filterTimer);
|
|
2559
2686
|
|
|
@@ -3143,3 +3270,4 @@ class DropList {
|
|
|
3143
3270
|
}
|
|
3144
3271
|
|
|
3145
3272
|
export default DropList;
|
|
3273
|
+
export { ItemSymbol };
|
package/lib/SelectBox.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '@danielgindi/dom-utils/lib/DomCompat';
|
|
23
23
|
|
|
24
24
|
import DomEventsSink from '@danielgindi/dom-utils/lib/DomEventsSink';
|
|
25
|
-
import DropList from './DropList';
|
|
25
|
+
import DropList, { ItemSymbol } from './DropList';
|
|
26
26
|
import {
|
|
27
27
|
VALUE_BACK_SPACE,
|
|
28
28
|
VALUE_DELETE, VALUE_DOWN, VALUE_END, VALUE_ENTER,
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
} from 'keycode-js';
|
|
36
36
|
import mitt from 'mitt';
|
|
37
37
|
|
|
38
|
-
const ItemSymbol = Symbol('item');
|
|
39
38
|
const DestroyedSymbol = Symbol('destroyed');
|
|
40
39
|
const RestMultiItemsSymbol = Symbol('rest_multi_items');
|
|
41
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielgindi/selectbox",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A collection of dom utilities. So you can work natively with the dom without dom frameworks.",
|
|
5
5
|
"main": "dist/lib.cjs.min.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"sass": "^1.85.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@danielgindi/dom-utils": "^1.0.
|
|
54
|
+
"@danielgindi/dom-utils": "^1.0.11",
|
|
55
55
|
"@danielgindi/virtual-list-helper": "^1.0.13",
|
|
56
56
|
"fast-deep-equal": "^3.1.3",
|
|
57
57
|
"keycode-js": "^3.1.0",
|
package/vue/DropList.vue
CHANGED
|
@@ -53,6 +53,10 @@ export default {
|
|
|
53
53
|
type: Boolean,
|
|
54
54
|
default: false,
|
|
55
55
|
},
|
|
56
|
+
isHeaderVisible: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
default: false,
|
|
59
|
+
},
|
|
56
60
|
searchable: {
|
|
57
61
|
type: Boolean,
|
|
58
62
|
default: false,
|
|
@@ -204,6 +208,7 @@ export default {
|
|
|
204
208
|
for (let key of ['autoItemBlur', 'capturesFocus', 'multi',
|
|
205
209
|
'autoCheckGroupChildren', 'useExactTargetWidth', 'constrainToWindow',
|
|
206
210
|
'autoFlipDirection', 'estimateWidth',
|
|
211
|
+
'isHeaderVisible',
|
|
207
212
|
'searchable', 'filterOnEmptyTerm',
|
|
208
213
|
'filterGroups', 'filterEmptyGroups']) {
|
|
209
214
|
if (typeof this[key] === 'boolean') {
|
|
@@ -340,8 +345,16 @@ export default {
|
|
|
340
345
|
this._recreateList();
|
|
341
346
|
},
|
|
342
347
|
|
|
343
|
-
searchable() {
|
|
344
|
-
this.
|
|
348
|
+
searchable(v) {
|
|
349
|
+
if (this.nonReactive.instance)
|
|
350
|
+
this.nonReactive.instance.setSearchable(v);
|
|
351
|
+
this.relayout();
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
isHeaderVisible(v) {
|
|
355
|
+
if (this.nonReactive.instance)
|
|
356
|
+
this.nonReactive.instance.setHeaderVisible(v);
|
|
357
|
+
this.relayout();
|
|
345
358
|
},
|
|
346
359
|
|
|
347
360
|
positionOptions: {
|
|
@@ -437,6 +450,11 @@ export default {
|
|
|
437
450
|
list.setSingleSelectedItemByValue(modelValue === null ? undefined : modelValue);
|
|
438
451
|
}
|
|
439
452
|
|
|
453
|
+
const headerRenderer = createSlotBasedRenderFunc(this, 'header');
|
|
454
|
+
if (headerRenderer) {
|
|
455
|
+
headerRenderer({}, list.getHeaderElement());
|
|
456
|
+
}
|
|
457
|
+
|
|
440
458
|
list.show();
|
|
441
459
|
|
|
442
460
|
this._setupAutoRelayout();
|
|
@@ -506,6 +524,11 @@ export default {
|
|
|
506
524
|
this.nonReactive.instance.relayout();
|
|
507
525
|
},
|
|
508
526
|
|
|
527
|
+
getHeaderElement() {
|
|
528
|
+
if (this.nonReactive.instance)
|
|
529
|
+
this.nonReactive.instance.getHeaderElement();
|
|
530
|
+
},
|
|
531
|
+
|
|
509
532
|
elContains(other, considerSublists = true) {
|
|
510
533
|
return !!this.listRef?.elContains(other, considerSublists);
|
|
511
534
|
},
|