@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 CHANGED
@@ -1,21 +1,18 @@
1
- [![npm latest version](https://badgen.net/npm/v/@vaadin/vaadin-combo-box/latest)](https://www.npmjs.com/package/@vaadin/vaadin-combo-box)
2
- [![npm next version](https://badgen.net/npm/v/@vaadin/vaadin-combo-box/next)](https://www.npmjs.com/package/@vaadin/vaadin-combo-box)
3
- [![Bower version](https://badgen.net/github/release/vaadin/vaadin-combo-box)](https://github.com/vaadin/vaadin-combo-box/releases)
4
- [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-combo-box)
5
- [![Build Status](https://travis-ci.org/vaadin/vaadin-combo-box.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-combo-box)
6
- [![Coverage Status](https://coveralls.io/repos/github/vaadin/vaadin-combo-box/badge.svg?branch=master)](https://coveralls.io/github/vaadin/vaadin-combo-box?branch=master)
7
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vaadin/web-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
1
+ # <vaadin-combo-box>
8
2
 
9
- [![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/vaadinvaadin-combo-box)
10
- [![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/vaadinvaadin-combo-box.svg)](https://vaadin.com/directory/component/vaadinvaadin-combo-box)
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
- # &lt;vaadin-combo-box&gt;
6
+ [&lt;vaadin-combo-box&gt;](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
- [&lt;vaadin-combo-box&gt;](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).
12
+ [![npm latest version](https://badgen.net/npm/v/@vaadin/vaadin-combo-box/latest)](https://www.npmjs.com/package/@vaadin/vaadin-combo-box)
13
+ [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-combo-box)
14
+ [![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/vaadinvaadin-combo-box)
15
+ [![Discord](https://img.shields.io/discord/732335336448852018?label=discord)](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.6",
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
- "generate-typings": "gen-typescript-declarations --outDir . --verify"
52
- },
53
- "devDependencies": {
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
- this._pendingRequests[page] = callback;
192
- this.dataProvider(params, callback);
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 scroller reset after new items have been set
106
- * to iron-list and to ignore unwanted pages load. If 'true', then
107
- * skip loading of the pages until it becomes 'false'.
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
- resetScrolling: {
109
+ filterChanged: {
110
110
  type: Boolean,
111
111
  value: false
112
112
  },
113
113
 
114
114
  /**
115
- * Used to recognize if the filter changed, so to skip the
116
- * scrolling restore. If true, then scroll to 0 position. Restore
117
- * the previous position otherwise.
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
- filterChanged: {
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.resetScrolling = true;
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.resetScrolling = false;
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 && !this.resetScrolling) {
364
- this.dispatchEvent(new CustomEvent('index-requested', {detail: {index}}));
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
- this._focusedIndex = -1;
580
- this.cancel();
581
- } else if (this.opened) {
582
- this._stopPropagation(e);
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 == 1) {
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
- this.filteredItems = this.items ? this.items.slice(0) : this.items;
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
  }
@@ -229,7 +229,7 @@ class ComboBoxElement extends
229
229
  }
230
230
 
231
231
  static get version() {
232
- return '5.4.6';
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
- // We need to set this listener for "this.inputElement"
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);