@vaadin/vaadin-combo-box 5.4.6 → 5.4.10
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/README.md +12 -15
- package/package.json +4 -4
- package/src/vaadin-combo-box-data-provider-mixin.js +25 -2
- package/src/vaadin-combo-box-dropdown-wrapper.js +15 -14
- package/src/vaadin-combo-box-dropdown.js +12 -0
- package/src/vaadin-combo-box-mixin.js +40 -19
- package/src/vaadin-combo-box.js +2 -6
package/README.md
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
[](https://www.npmjs.com/package/@vaadin/vaadin-combo-box)
|
|
3
|
-
[](https://github.com/vaadin/vaadin-combo-box/releases)
|
|
4
|
-
[](https://www.webcomponents.org/element/vaadin/vaadin-combo-box)
|
|
5
|
-
[](https://travis-ci.org/vaadin/vaadin-combo-box)
|
|
6
|
-
[](https://coveralls.io/github/vaadin/vaadin-combo-box?branch=master)
|
|
7
|
-
[](https://gitter.im/vaadin/web-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
1
|
+
# <vaadin-combo-box>
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
> ⚠️ Starting from Vaadin 20, the source code and issues for this component are migrated to the [`vaadin/web-components`](https://github.com/vaadin/web-components/tree/master/packages/vaadin-combo-box) monorepository.
|
|
4
|
+
> This repository contains the source code and releases of `<vaadin-combo-box>` for the Vaadin versions 10 to 19.
|
|
11
5
|
|
|
12
|
-
|
|
6
|
+
[<vaadin-combo-box>](https://vaadin.com/components/vaadin-combo-box) is a Web Component combining a dropdown list with an input field for filtering the list of items, part of the [Vaadin components](https://vaadin.com/components).
|
|
13
7
|
|
|
14
8
|
[Live Demo ↗](https://vaadin.com/components/vaadin-combo-box/html-examples)
|
|
15
9
|
|
|
|
16
10
|
[API documentation ↗](https://vaadin.com/components/vaadin-combo-box/html-api)
|
|
17
11
|
|
|
18
|
-
[
|
|
12
|
+
[](https://www.npmjs.com/package/@vaadin/vaadin-combo-box)
|
|
13
|
+
[](https://www.webcomponents.org/element/vaadin/vaadin-combo-box)
|
|
14
|
+
[](https://vaadin.com/directory/component/vaadinvaadin-combo-box)
|
|
15
|
+
[](https://discord.gg/PHmkCKC)
|
|
19
16
|
|
|
20
17
|
<!--
|
|
21
18
|
```
|
|
@@ -95,18 +92,18 @@ To use the Material theme, import the correspondent file from the `theme/materia
|
|
|
95
92
|
|
|
96
93
|
- The components with the Lumo theme:
|
|
97
94
|
|
|
98
|
-
`theme/lumo/vaadin-combo-box.html`
|
|
95
|
+
`theme/lumo/vaadin-combo-box.html`
|
|
99
96
|
`theme/lumo/vaadin-combo-box-light.html`
|
|
100
97
|
|
|
101
98
|
- The components with the Material theme:
|
|
102
99
|
|
|
103
|
-
`theme/material/vaadin-combo-box.html`
|
|
100
|
+
`theme/material/vaadin-combo-box.html`
|
|
104
101
|
`theme/material/vaadin-combo-box-light.html`
|
|
105
102
|
|
|
106
|
-
- Alias for `theme/lumo/vaadin-combo-box.html`
|
|
103
|
+
- Alias for `theme/lumo/vaadin-combo-box.html`
|
|
107
104
|
`theme/lumo/vaadin-combo-box-light.html`
|
|
108
105
|
|
|
109
|
-
`vaadin-combo-box.html`
|
|
106
|
+
`vaadin-combo-box.html`
|
|
110
107
|
`vaadin-combo-box-light.html`
|
|
111
108
|
|
|
112
109
|
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"repository": "vaadin/vaadin-combo-box",
|
|
11
11
|
"homepage": "https://vaadin.com/components",
|
|
12
12
|
"name": "@vaadin/vaadin-combo-box",
|
|
13
|
-
"version": "5.4.
|
|
13
|
+
"version": "5.4.10",
|
|
14
14
|
"main": "vaadin-combo-box.js",
|
|
15
15
|
"author": "Vaadin Ltd",
|
|
16
16
|
"license": "Apache-2.0",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"@vaadin/vaadin-element-mixin": "^2.4.1"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
"generate-typings": "gen-typescript-declarations --outDir . --verify"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
54
|
"@polymer/iron-form": "^3.0.0",
|
|
55
55
|
"@polymer/iron-component-page": "^4.0.0",
|
|
56
56
|
"@polymer/iron-test-helpers": "^3.0.0",
|
|
@@ -94,6 +94,17 @@ export const ComboBoxDataProviderMixin = superClass => class DataProviderMixin e
|
|
|
94
94
|
this.clearCache();
|
|
95
95
|
this.$.overlay.addEventListener('index-requested', e => {
|
|
96
96
|
const index = e.detail.index;
|
|
97
|
+
const currentScrollerPos = e.detail.currentScrollerPos;
|
|
98
|
+
const allowedIndexRange = Math.floor(this.pageSize * 1.5);
|
|
99
|
+
|
|
100
|
+
// Ignores the indexes, which are being re-sent during scrolling reset,
|
|
101
|
+
// if the corresponding page is around the current scroller position.
|
|
102
|
+
// Otherwise, there might be a last pages duplicates, which cause the
|
|
103
|
+
// loading indicator hanging and blank items
|
|
104
|
+
if (this._shouldSkipIndex(index, allowedIndexRange, currentScrollerPos)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
if (index !== undefined) {
|
|
98
109
|
const page = this._getPageForIndex(index);
|
|
99
110
|
if (this._shouldLoadPage(page)) {
|
|
@@ -132,6 +143,13 @@ export const ComboBoxDataProviderMixin = superClass => class DataProviderMixin e
|
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
|
|
146
|
+
/** @private */
|
|
147
|
+
_shouldSkipIndex(index, allowedIndexRange, currentScrollerPos) {
|
|
148
|
+
return currentScrollerPos !== 0 &&
|
|
149
|
+
index >= currentScrollerPos - allowedIndexRange &&
|
|
150
|
+
index <= currentScrollerPos + allowedIndexRange;
|
|
151
|
+
}
|
|
152
|
+
|
|
135
153
|
/** @private */
|
|
136
154
|
_shouldLoadPage(page) {
|
|
137
155
|
if (!this.filteredItems || this._forceNextRequest) {
|
|
@@ -188,8 +206,13 @@ export const ComboBoxDataProviderMixin = superClass => class DataProviderMixin e
|
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
};
|
|
191
|
-
|
|
192
|
-
this.
|
|
209
|
+
|
|
210
|
+
if (!this._pendingRequests[page]) {
|
|
211
|
+
// Don't request page if it's already being requested
|
|
212
|
+
this._pendingRequests[page] = callback;
|
|
213
|
+
this.dataProvider(params, callback);
|
|
214
|
+
}
|
|
215
|
+
|
|
193
216
|
}
|
|
194
217
|
}
|
|
195
218
|
|
|
@@ -48,7 +48,7 @@ class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) {
|
|
|
48
48
|
<div id="scroller" on-click="_stopPropagation">
|
|
49
49
|
<iron-list id="selector" role="listbox" items="[[_getItems(opened, _items)]]" scroll-target="[[_scroller]]">
|
|
50
50
|
<template>
|
|
51
|
-
<vaadin-combo-box-item on-click="_onItemClick" index="[[__requestItemByIndex(item, index)]]" item="[[item]]" label="[[getItemLabel(item, _itemLabelPath)]]" selected="[[_isItemSelected(item, _selectedItem, _itemIdPath)]]" renderer="[[renderer]]" role\$="[[_getAriaRole(index)]]" aria-selected\$="[[_getAriaSelected(_focusedIndex,index)]]" focused="[[_isItemFocused(_focusedIndex,index)]]" tabindex="-1" theme\$="[[theme]]">
|
|
51
|
+
<vaadin-combo-box-item on-click="_onItemClick" index="[[__requestItemByIndex(item, index, _resetScrolling)]]" item="[[item]]" label="[[getItemLabel(item, _itemLabelPath)]]" selected="[[_isItemSelected(item, _selectedItem, _itemIdPath)]]" renderer="[[renderer]]" role\$="[[_getAriaRole(index)]]" aria-selected\$="[[_getAriaSelected(_focusedIndex,index)]]" focused="[[_isItemFocused(_focusedIndex,index)]]" tabindex="-1" theme\$="[[theme]]">
|
|
52
52
|
</vaadin-combo-box-item>
|
|
53
53
|
</template>
|
|
54
54
|
</iron-list>
|
|
@@ -102,21 +102,21 @@ class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) {
|
|
|
102
102
|
theme: String,
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Used to recognize
|
|
106
|
-
*
|
|
107
|
-
*
|
|
105
|
+
* Used to recognize if the filter changed, so to skip the
|
|
106
|
+
* scrolling restore. If true, then scroll to 0 position. Restore
|
|
107
|
+
* the previous position otherwise.
|
|
108
108
|
*/
|
|
109
|
-
|
|
109
|
+
filterChanged: {
|
|
110
110
|
type: Boolean,
|
|
111
111
|
value: false
|
|
112
112
|
},
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
* Used to recognize
|
|
116
|
-
*
|
|
117
|
-
* the
|
|
115
|
+
* Used to recognize scroller reset after new items have been set
|
|
116
|
+
* to iron-list and to ignore unwanted pages load. If 'true', then
|
|
117
|
+
* skip loading of the pages until it becomes 'false'.
|
|
118
118
|
*/
|
|
119
|
-
|
|
119
|
+
_resetScrolling: {
|
|
120
120
|
type: Boolean,
|
|
121
121
|
value: false
|
|
122
122
|
},
|
|
@@ -188,7 +188,7 @@ class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) {
|
|
|
188
188
|
const currentScrollerPosition = this._selector.firstVisibleIndex;
|
|
189
189
|
if (currentScrollerPosition !== 0) {
|
|
190
190
|
this._oldScrollerPosition = currentScrollerPosition;
|
|
191
|
-
this.
|
|
191
|
+
this._resetScrolling = true;
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
// Let the position to be restored in the future calls unless it's not
|
|
@@ -203,7 +203,7 @@ class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) {
|
|
|
203
203
|
if (this._isNotEmpty(items) && this._selector && this._oldScrollerPosition !== 0) {
|
|
204
204
|
// new items size might be less than old scrolling position
|
|
205
205
|
this._scrollIntoView(Math.min(items.length - 1, this._oldScrollerPosition));
|
|
206
|
-
this.
|
|
206
|
+
this._resetScrolling = false;
|
|
207
207
|
// reset position to 0 again in order to properly handle the filter
|
|
208
208
|
// cases (scroll to 0 after typing the filter)
|
|
209
209
|
this._oldScrollerPosition = 0;
|
|
@@ -358,10 +358,11 @@ class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) {
|
|
|
358
358
|
*
|
|
359
359
|
* @return {number}
|
|
360
360
|
*/
|
|
361
|
-
__requestItemByIndex(item, index) {
|
|
361
|
+
__requestItemByIndex(item, index, resetScrolling) {
|
|
362
362
|
if ((item instanceof ComboBoxPlaceholder) && index !==
|
|
363
|
-
undefined && !
|
|
364
|
-
this.dispatchEvent(new CustomEvent('index-requested', {detail:
|
|
363
|
+
undefined && !resetScrolling) {
|
|
364
|
+
this.dispatchEvent(new CustomEvent('index-requested', {detail:
|
|
365
|
+
{index: index, currentScrollerPos: this._oldScrollerPosition}}));
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
return index;
|
|
@@ -23,6 +23,9 @@ $_documentContainer.innerHTML = `<dom-module id="vaadin-combo-box-overlay-styles
|
|
|
23
23
|
</dom-module>`;
|
|
24
24
|
|
|
25
25
|
document.head.appendChild($_documentContainer.content);
|
|
26
|
+
let memoizedTemplate;
|
|
27
|
+
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
30
|
* The overlay element.
|
|
28
31
|
*
|
|
@@ -41,6 +44,15 @@ class ComboBoxOverlayElement extends OverlayElement {
|
|
|
41
44
|
return 'vaadin-combo-box-overlay';
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
static get template() {
|
|
48
|
+
if (!memoizedTemplate) {
|
|
49
|
+
memoizedTemplate = super.template.cloneNode(true);
|
|
50
|
+
memoizedTemplate.content.querySelector('[part~="overlay"]').removeAttribute('tabindex');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return memoizedTemplate;
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
connectedCallback() {
|
|
45
57
|
super.connectedCallback();
|
|
46
58
|
|
|
@@ -260,7 +260,7 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
260
260
|
'_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)',
|
|
261
261
|
'_templateOrRendererChanged(_itemTemplate, renderer)',
|
|
262
262
|
'_loadingChanged(loading)',
|
|
263
|
-
'_selectedItemChanged(selectedItem, itemLabelPath)',
|
|
263
|
+
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
|
|
264
264
|
'_toggleElementChanged(_toggleElement)'
|
|
265
265
|
];
|
|
266
266
|
}
|
|
@@ -576,16 +576,36 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
576
576
|
*/
|
|
577
577
|
_onEscape(e) {
|
|
578
578
|
if (this.autoOpenDisabled) {
|
|
579
|
-
|
|
580
|
-
this.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (this._focusedIndex > -1) {
|
|
579
|
+
// Auto-open is disabled
|
|
580
|
+
if (this.opened || (this.value !== this._inputElementValue && this._inputElementValue.length > 0)) {
|
|
581
|
+
// The overlay is open or
|
|
582
|
+
// the input value has changed but the change hasn't been committed, so cancel it.
|
|
583
|
+
this._stopPropagation(e);
|
|
585
584
|
this._focusedIndex = -1;
|
|
586
|
-
this._revertInputValue();
|
|
587
|
-
} else {
|
|
588
585
|
this.cancel();
|
|
586
|
+
} else if (this.clearButtonVisible && !this.opened && !!this.value) {
|
|
587
|
+
this._stopPropagation(e);
|
|
588
|
+
// The clear button is visible and the overlay is closed, so clear the value.
|
|
589
|
+
this._clear();
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
// Auto-open is enabled
|
|
593
|
+
if (this.opened) {
|
|
594
|
+
// The overlay is open
|
|
595
|
+
this._stopPropagation(e);
|
|
596
|
+
|
|
597
|
+
if (this._focusedIndex > -1) {
|
|
598
|
+
// An item is focused, revert the input to the filtered value
|
|
599
|
+
this._focusedIndex = -1;
|
|
600
|
+
this._revertInputValue();
|
|
601
|
+
} else {
|
|
602
|
+
// No item is focused, cancel the change and close the overlay
|
|
603
|
+
this.cancel();
|
|
604
|
+
}
|
|
605
|
+
} else if (this.clearButtonVisible && !!this.value) {
|
|
606
|
+
this._stopPropagation(e);
|
|
607
|
+
// The clear button is visible and the overlay is closed, so clear the value.
|
|
608
|
+
this._clear();
|
|
589
609
|
}
|
|
590
610
|
}
|
|
591
611
|
}
|
|
@@ -683,8 +703,9 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
683
703
|
this.value = '';
|
|
684
704
|
}
|
|
685
705
|
} else {
|
|
706
|
+
const toLowerCase = (item) => item && item.toLowerCase && item.toLowerCase();
|
|
686
707
|
const itemsMatchedByLabel = this.filteredItems
|
|
687
|
-
&& this.filteredItems.filter(item => this._getItemLabel(item) === this._inputElementValue)
|
|
708
|
+
&& this.filteredItems.filter(item => toLowerCase(this._getItemLabel(item)) === toLowerCase(this._inputElementValue))
|
|
688
709
|
|| [];
|
|
689
710
|
if (this.allowCustomValue
|
|
690
711
|
// to prevent a repetitive input value being saved after pressing ESC and Tab.
|
|
@@ -697,7 +718,7 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
697
718
|
this._selectItemForValue(customValue);
|
|
698
719
|
this.value = customValue;
|
|
699
720
|
}
|
|
700
|
-
} else if (!this.allowCustomValue && !this.opened && itemsMatchedByLabel.length
|
|
721
|
+
} else if (!this.allowCustomValue && !this.opened && itemsMatchedByLabel.length > 0) {
|
|
701
722
|
this.value = this._getItemValue(itemsMatchedByLabel[0]);
|
|
702
723
|
} else {
|
|
703
724
|
this._inputElementValue = this.selectedItem ? this._getItemLabel(this.selectedItem) : (this.value || '');
|
|
@@ -897,11 +918,13 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
897
918
|
|
|
898
919
|
/** @private */
|
|
899
920
|
_itemsOrPathsChanged(e, itemValuePath, itemLabelPath) {
|
|
900
|
-
if (e.value === undefined) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
921
|
if (e.path === 'items' || e.path === 'items.splices') {
|
|
904
|
-
|
|
922
|
+
if (this.items) {
|
|
923
|
+
this.filteredItems = this.items.slice(0);
|
|
924
|
+
} else if (this.__previousItems) {
|
|
925
|
+
// Only clear filteredItems if the component had items previously but got cleared
|
|
926
|
+
this.filteredItems = null;
|
|
927
|
+
}
|
|
905
928
|
|
|
906
929
|
const valueIndex = this._indexOfValue(this.value, this.items);
|
|
907
930
|
this._focusedIndex = valueIndex;
|
|
@@ -911,13 +934,11 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
911
934
|
this.selectedItem = item;
|
|
912
935
|
}
|
|
913
936
|
}
|
|
937
|
+
this.__previousItems = e.value;
|
|
914
938
|
}
|
|
915
939
|
|
|
916
940
|
/** @private */
|
|
917
941
|
_filteredItemsChanged(e, itemValuePath, itemLabelPath) {
|
|
918
|
-
if (e.value === undefined) {
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
942
|
if (e.path === 'filteredItems' || e.path === 'filteredItems.splices') {
|
|
922
943
|
this._setOverlayItems(this.filteredItems);
|
|
923
944
|
|
|
@@ -1045,7 +1066,7 @@ export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extend
|
|
|
1045
1066
|
event.composedPath()[0].focus();
|
|
1046
1067
|
return;
|
|
1047
1068
|
}
|
|
1048
|
-
if (!this._closeOnBlurIsPrevented) {
|
|
1069
|
+
if (!this.readonly && !this._closeOnBlurIsPrevented) {
|
|
1049
1070
|
this._closeOrCommit();
|
|
1050
1071
|
}
|
|
1051
1072
|
}
|
package/src/vaadin-combo-box.js
CHANGED
|
@@ -229,7 +229,7 @@ class ComboBoxElement extends
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
static get version() {
|
|
232
|
-
return '5.4.
|
|
232
|
+
return '5.4.10';
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
static get properties() {
|
|
@@ -358,12 +358,8 @@ class ComboBoxElement extends
|
|
|
358
358
|
// Stop propagation of Esc in capturing phase so that
|
|
359
359
|
// vaadin-text-field will not handle Esc as a shortcut
|
|
360
360
|
// to clear the value.
|
|
361
|
-
|
|
362
|
-
// instead of just "this", otherwise keyboard navigation behaviour
|
|
363
|
-
// breaks a bit on Safari and some related tests fail.
|
|
364
|
-
this.inputElement.addEventListener('keydown', e => {
|
|
361
|
+
this.addEventListener('keydown', e => {
|
|
365
362
|
if (this._isEventKey(e, 'esc')) {
|
|
366
|
-
this._stopPropagation(e);
|
|
367
363
|
// Trigger _onEscape method of vaadin-combo-box-mixin because
|
|
368
364
|
// bubbling phase is not reached.
|
|
369
365
|
this._onEscape(e);
|