@vaadin/combo-box 23.1.0 → 23.2.0-alpha2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/combo-box",
3
- "version": "23.1.0",
3
+ "version": "23.2.0-alpha2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -19,10 +19,10 @@
19
19
  "main": "vaadin-combo-box.js",
20
20
  "type": "module",
21
21
  "files": [
22
+ "lit.d.ts",
23
+ "lit.js",
22
24
  "src",
23
25
  "theme",
24
- "lit.js",
25
- "lit.d.ts",
26
26
  "vaadin-*.d.ts",
27
27
  "vaadin-*.js"
28
28
  ],
@@ -36,23 +36,23 @@
36
36
  "dependencies": {
37
37
  "@open-wc/dedupe-mixin": "^1.3.0",
38
38
  "@polymer/polymer": "^3.0.0",
39
- "@vaadin/component-base": "^23.1.0",
40
- "@vaadin/field-base": "^23.1.0",
41
- "@vaadin/input-container": "^23.1.0",
42
- "@vaadin/item": "^23.1.0",
43
- "@vaadin/lit-renderer": "^23.1.0",
44
- "@vaadin/vaadin-lumo-styles": "^23.1.0",
45
- "@vaadin/vaadin-material-styles": "^23.1.0",
46
- "@vaadin/vaadin-overlay": "^23.1.0",
47
- "@vaadin/vaadin-themable-mixin": "^23.1.0"
39
+ "@vaadin/component-base": "23.2.0-alpha2",
40
+ "@vaadin/field-base": "23.2.0-alpha2",
41
+ "@vaadin/input-container": "23.2.0-alpha2",
42
+ "@vaadin/item": "23.2.0-alpha2",
43
+ "@vaadin/lit-renderer": "23.2.0-alpha2",
44
+ "@vaadin/vaadin-lumo-styles": "23.2.0-alpha2",
45
+ "@vaadin/vaadin-material-styles": "23.2.0-alpha2",
46
+ "@vaadin/vaadin-overlay": "23.2.0-alpha2",
47
+ "@vaadin/vaadin-themable-mixin": "23.2.0-alpha2"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@esm-bundle/chai": "^4.3.4",
51
- "@vaadin/polymer-legacy-adapter": "^23.1.0",
51
+ "@vaadin/polymer-legacy-adapter": "23.2.0-alpha2",
52
52
  "@vaadin/testing-helpers": "^0.3.2",
53
- "@vaadin/text-field": "^23.1.0",
53
+ "@vaadin/text-field": "23.2.0-alpha2",
54
54
  "lit": "^2.0.0",
55
55
  "sinon": "^13.0.2"
56
56
  },
57
- "gitHead": "322bba42b83f908a78cd972b06acadc5da95a69d"
57
+ "gitHead": "c9b8113d0fa9a602f8b9cb915c1826355af2e8df"
58
58
  }
@@ -78,7 +78,7 @@ export const ComboBoxDataProviderMixin = (superClass) =>
78
78
  ready() {
79
79
  super.ready();
80
80
  this.clearCache();
81
- this.$.dropdown.addEventListener('index-requested', (e) => {
81
+ this._scroller.addEventListener('index-requested', (e) => {
82
82
  const index = e.detail.index;
83
83
  const currentScrollerPos = e.detail.currentScrollerPos;
84
84
  const allowedIndexRange = Math.floor(this.pageSize * 1.5);
@@ -177,47 +177,44 @@ export const ComboBoxDataProviderMixin = (superClass) =>
177
177
  /** @private */
178
178
  _loadPage(page) {
179
179
  // Make sure same page isn't requested multiple times.
180
- if (!this._pendingRequests[page] && this.dataProvider) {
181
- this.loading = true;
182
-
183
- const params = {
184
- page,
185
- pageSize: this.pageSize,
186
- filter: this.filter,
187
- };
188
-
189
- const callback = (items, size) => {
190
- if (this._pendingRequests[page] === callback) {
191
- if (!this.filteredItems) {
192
- const filteredItems = [];
193
- filteredItems.splice(params.page * params.pageSize, items.length, ...items);
194
- this.filteredItems = filteredItems;
195
- } else {
196
- this.splice('filteredItems', params.page * params.pageSize, items.length, ...items);
197
- }
198
- // Update selectedItem from filteredItems if value is set
199
- if (this._isValidValue(this.value) && this._getItemValue(this.selectedItem) !== this.value) {
200
- this._selectItemForValue(this.value);
201
- }
202
- if (!this.opened && !this.hasAttribute('focused')) {
203
- this._commitValue();
204
- }
205
- this.size = size;
206
-
207
- delete this._pendingRequests[page];
208
-
209
- if (Object.keys(this._pendingRequests).length === 0) {
210
- this.loading = false;
211
- }
212
- }
213
- };
180
+ if (this._pendingRequests[page] || !this.dataProvider) {
181
+ return;
182
+ }
183
+
184
+ const params = {
185
+ page,
186
+ pageSize: this.pageSize,
187
+ filter: this.filter,
188
+ };
214
189
 
215
- if (!this._pendingRequests[page]) {
216
- // Don't request page if it's already being requested
217
- this._pendingRequests[page] = callback;
218
- this.dataProvider(params, callback);
190
+ const callback = (items, size) => {
191
+ if (this._pendingRequests[page] !== callback) {
192
+ return;
219
193
  }
220
- }
194
+
195
+ const filteredItems = this.filteredItems ? [...this.filteredItems] : [];
196
+ filteredItems.splice(params.page * params.pageSize, items.length, ...items);
197
+ this.filteredItems = filteredItems;
198
+
199
+ if (!this.opened && !this.hasAttribute('focused')) {
200
+ this._commitValue();
201
+ }
202
+ this.size = size;
203
+
204
+ delete this._pendingRequests[page];
205
+
206
+ if (Object.keys(this._pendingRequests).length === 0) {
207
+ this.loading = false;
208
+ }
209
+ };
210
+
211
+ this._pendingRequests[page] = callback;
212
+ // Set the `loading` flag only after marking the request as pending
213
+ // to prevent the same page from getting requested multiple times
214
+ // as a result of `__loadingChanged` in the scroller which requests
215
+ // a virtualizer update which in turn may trigger a data provider page request.
216
+ this.loading = true;
217
+ this.dataProvider(params, callback);
221
218
  }
222
219
 
223
220
  /** @private */
@@ -287,7 +284,7 @@ export const ComboBoxDataProviderMixin = (superClass) =>
287
284
  /** @private */
288
285
  _warnDataProviderValue(dataProvider, value) {
289
286
  if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) {
290
- const valueIndex = this._indexOfValue(value, this.filteredItems);
287
+ const valueIndex = this.__getItemIndexByValue(this.filteredItems, value);
291
288
  if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) {
292
289
  console.warn(
293
290
  'Warning: unable to determine the label for the provided `value`. ' +
@@ -3,7 +3,9 @@
3
3
  * Copyright (c) 2015 - 2022 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import './vaadin-combo-box-dropdown.js';
6
+ import './vaadin-combo-box-item.js';
7
+ import './vaadin-combo-box-overlay.js';
8
+ import './vaadin-combo-box-scroller.js';
7
9
  import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js';
8
10
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
9
11
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -75,19 +77,16 @@ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixi
75
77
 
76
78
  <slot></slot>
77
79
 
78
- <vaadin-combo-box-dropdown
79
- id="dropdown"
80
- opened="[[opened]]"
80
+ <vaadin-combo-box-overlay
81
+ id="overlay"
82
+ hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
83
+ opened="[[_overlayOpened]]"
84
+ loading$="[[loading]]"
85
+ theme$="[[_theme]]"
81
86
  position-target="[[inputElement]]"
82
- restore-focus-on-close="[[__restoreFocusOnClose]]"
87
+ no-vertical-overlap
83
88
  restore-focus-node="[[inputElement]]"
84
- renderer="[[renderer]]"
85
- _focused-index="[[_focusedIndex]]"
86
- _item-id-path="[[itemIdPath]]"
87
- _item-label-path="[[itemLabelPath]]"
88
- loading="[[loading]]"
89
- theme="[[_theme]]"
90
- ></vaadin-combo-box-dropdown>
89
+ ></vaadin-combo-box-overlay>
91
90
  `;
92
91
  }
93
92
 
@@ -147,6 +147,11 @@ export declare class ComboBoxMixinClass<TItem> {
147
147
 
148
148
  protected _inputElementValue: string | undefined;
149
149
 
150
+ /**
151
+ * Tag name prefix used by scroller and items.
152
+ */
153
+ protected readonly _tagNamePrefix: string;
154
+
150
155
  /**
151
156
  * Requests an update for the content of items.
152
157
  * While performing the update, it invokes the renderer (passed in the `renderer` property) once an item.
@@ -177,6 +182,4 @@ export declare class ComboBoxMixinClass<TItem> {
177
182
  checkValidity(): boolean;
178
183
 
179
184
  protected _revertInputValue(): void;
180
-
181
- protected _getItemElements(): HTMLElement[];
182
185
  }
@@ -12,6 +12,34 @@ import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
12
12
  import { VirtualKeyboardController } from '@vaadin/field-base/src/virtual-keyboard-controller.js';
13
13
  import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
14
14
 
15
+ /**
16
+ * Checks if the value is supported as an item value in this control.
17
+ *
18
+ * @param {unknown} value
19
+ * @return {boolean}
20
+ */
21
+ function isValidValue(value) {
22
+ return value !== undefined && value !== null;
23
+ }
24
+
25
+ /**
26
+ * Returns the index of the first item that satisfies the provided testing function
27
+ * ignoring placeholder items.
28
+ *
29
+ * @param {Array<ComboBoxItem | string>} items
30
+ * @param {Function} callback
31
+ * @return {number}
32
+ */
33
+ function findItemIndex(items, callback) {
34
+ return items.findIndex((item) => {
35
+ if (item instanceof ComboBoxPlaceholder) {
36
+ return false;
37
+ }
38
+
39
+ return callback(item);
40
+ });
41
+ }
42
+
15
43
  /**
16
44
  * @polymerMixin
17
45
  * @param {function(new:HTMLElement)} subclass
@@ -95,6 +123,7 @@ export const ComboBoxMixin = (subclass) =>
95
123
  */
96
124
  filteredItems: {
97
125
  type: Array,
126
+ observer: '_filteredItemsChanged',
98
127
  },
99
128
 
100
129
  /**
@@ -111,7 +140,6 @@ export const ComboBoxMixin = (subclass) =>
111
140
  type: Boolean,
112
141
  value: false,
113
142
  reflectToAttribute: true,
114
- observer: '_loadingChanged',
115
143
  },
116
144
 
117
145
  /**
@@ -196,16 +224,22 @@ export const ComboBoxMixin = (subclass) =>
196
224
  _closeOnBlurIsPrevented: Boolean,
197
225
 
198
226
  /** @private */
199
- __restoreFocusOnClose: Boolean,
227
+ _scroller: Object,
228
+
229
+ /** @private */
230
+ _overlayOpened: {
231
+ type: Boolean,
232
+ observer: '_overlayOpenedChanged',
233
+ },
200
234
  };
201
235
  }
202
236
 
203
237
  static get observers() {
204
238
  return [
205
239
  '_filterChanged(filter, itemValuePath, itemLabelPath)',
206
- '_itemsOrPathsChanged(items.*, itemValuePath, itemLabelPath)',
207
- '_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)',
208
240
  '_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
241
+ '_openedOrItemsChanged(opened, filteredItems, loading)',
242
+ '_updateScroller(_scroller, filteredItems, opened, loading, selectedItem, itemIdPath, _focusedIndex, renderer, theme)',
209
243
  ];
210
244
  }
211
245
 
@@ -214,13 +248,20 @@ export const ComboBoxMixin = (subclass) =>
214
248
  this._boundOnFocusout = this._onFocusout.bind(this);
215
249
  this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this);
216
250
  this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this);
217
- this._boundClose = this.close.bind(this);
218
- this._boundOnOpened = this._onOpened.bind(this);
219
251
  this._boundOnClick = this._onClick.bind(this);
220
252
  this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this);
221
253
  this._boundOnTouchend = this._onTouchend.bind(this);
222
254
  }
223
255
 
256
+ /**
257
+ * Tag name prefix used by scroller and items.
258
+ * @protected
259
+ * @return {string}
260
+ */
261
+ get _tagNamePrefix() {
262
+ return 'vaadin-combo-box';
263
+ }
264
+
224
265
  /**
225
266
  * @return {string | undefined}
226
267
  * @protected
@@ -274,23 +315,19 @@ export const ComboBoxMixin = (subclass) =>
274
315
  ready() {
275
316
  super.ready();
276
317
 
318
+ this._initOverlay();
319
+ this._initScroller();
320
+
277
321
  this.addEventListener('focusout', this._boundOnFocusout);
278
322
 
279
323
  this._lastCommittedValue = this.value;
280
324
 
281
- this.$.dropdown.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
282
-
283
- this.addEventListener('vaadin-combo-box-dropdown-closed', this._boundClose);
284
- this.addEventListener('vaadin-combo-box-dropdown-opened', this._boundOnOpened);
285
325
  this.addEventListener('click', this._boundOnClick);
286
-
287
- this.$.dropdown.addEventListener('vaadin-overlay-touch-action', this._boundOnOverlayTouchAction);
288
-
289
326
  this.addEventListener('touchend', this._boundOnTouchend);
290
327
 
291
328
  const bringToFrontListener = () => {
292
329
  requestAnimationFrame(() => {
293
- this.$.dropdown.$.overlay.bringToFront();
330
+ this.$.overlay.bringToFront();
294
331
  });
295
332
  };
296
333
 
@@ -302,6 +339,14 @@ export const ComboBoxMixin = (subclass) =>
302
339
  this.addController(new VirtualKeyboardController(this));
303
340
  }
304
341
 
342
+ /** @protected */
343
+ disconnectedCallback() {
344
+ super.disconnectedCallback();
345
+
346
+ // Close the overlay on detach
347
+ this.close();
348
+ }
349
+
305
350
  /**
306
351
  * Requests an update for the content of items.
307
352
  * While performing the update, it invokes the renderer (passed in the `renderer` property) once an item.
@@ -309,11 +354,11 @@ export const ComboBoxMixin = (subclass) =>
309
354
  * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
310
355
  */
311
356
  requestContentUpdate() {
312
- if (!this.$.dropdown._scroller) {
357
+ if (!this._scroller) {
313
358
  return;
314
359
  }
315
360
 
316
- this.$.dropdown._scroller.requestContentUpdate();
361
+ this._scroller.requestContentUpdate();
317
362
 
318
363
  this._getItemElements().forEach((item) => {
319
364
  item.requestContentUpdate();
@@ -337,6 +382,109 @@ export const ComboBoxMixin = (subclass) =>
337
382
  this.opened = false;
338
383
  }
339
384
 
385
+ /** @private */
386
+ _initOverlay() {
387
+ const overlay = this.$.overlay;
388
+
389
+ // Store instance for detecting "dir" attribute on opening
390
+ overlay._comboBox = this;
391
+
392
+ overlay.addEventListener('touchend', this._boundOnOverlayTouchAction);
393
+ overlay.addEventListener('touchmove', this._boundOnOverlayTouchAction);
394
+
395
+ // Prevent blurring the input when clicking inside the overlay
396
+ overlay.addEventListener('mousedown', (e) => e.preventDefault());
397
+
398
+ // Preventing the default modal behavior of the overlay on input click
399
+ overlay.addEventListener('vaadin-overlay-outside-click', (e) => {
400
+ e.preventDefault();
401
+ });
402
+
403
+ // Manual two-way binding for the overlay "opened" property
404
+ overlay.addEventListener('opened-changed', (e) => {
405
+ this._overlayOpened = e.detail.value;
406
+ });
407
+ }
408
+
409
+ /**
410
+ * Create and initialize the scroller element.
411
+ * Override to provide custom host reference.
412
+ *
413
+ * @protected
414
+ */
415
+ _initScroller(host) {
416
+ const scrollerTag = `${this._tagNamePrefix}-scroller`;
417
+
418
+ const overlay = this.$.overlay;
419
+
420
+ overlay.renderer = (root) => {
421
+ if (!root.firstChild) {
422
+ root.appendChild(document.createElement(scrollerTag));
423
+ }
424
+ };
425
+
426
+ // Ensure the scroller is rendered
427
+ if (!this.opened) {
428
+ overlay.requestContentUpdate();
429
+ }
430
+
431
+ const scroller = overlay.querySelector(scrollerTag);
432
+
433
+ scroller.comboBox = host || this;
434
+ scroller.getItemLabel = this._getItemLabel.bind(this);
435
+ scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
436
+
437
+ // Trigger the observer to set properties
438
+ this._scroller = scroller;
439
+ }
440
+
441
+ /** @private */
442
+ // eslint-disable-next-line max-params
443
+ _updateScroller(scroller, items, opened, loading, selectedItem, itemIdPath, focusedIndex, renderer, theme) {
444
+ if (scroller) {
445
+ if (opened) {
446
+ scroller.style.maxHeight =
447
+ getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
448
+ }
449
+
450
+ scroller.setProperties({
451
+ items: opened ? items : [],
452
+ opened,
453
+ loading,
454
+ selectedItem,
455
+ itemIdPath,
456
+ focusedIndex,
457
+ renderer,
458
+ theme,
459
+ });
460
+ }
461
+ }
462
+
463
+ /** @protected */
464
+ _isOverlayHidden(items, loading) {
465
+ return !loading && !(items && items.length);
466
+ }
467
+
468
+ /** @private */
469
+ _openedOrItemsChanged(opened, items, loading) {
470
+ // Close the overlay if there are no items to display.
471
+ // See https://github.com/vaadin/vaadin-combo-box/pull/964
472
+ this._overlayOpened = !!(opened && (loading || (items && items.length)));
473
+ }
474
+
475
+ /** @private */
476
+ _overlayOpenedChanged(opened, wasOpened) {
477
+ if (opened) {
478
+ this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
479
+
480
+ this._onOpened();
481
+ } else if (wasOpened && this.filteredItems && this.filteredItems.length) {
482
+ this.close();
483
+
484
+ this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
485
+ }
486
+ }
487
+
340
488
  /** @private */
341
489
  _focusedIndexChanged(index, oldIndex) {
342
490
  if (oldIndex === undefined) {
@@ -375,7 +523,7 @@ export const ComboBoxMixin = (subclass) =>
375
523
  this.focus();
376
524
  }
377
525
 
378
- this.__restoreFocusOnClose = true;
526
+ this.$.overlay.restoreFocusOnClose = true;
379
527
  } else {
380
528
  this._onClosed();
381
529
  if (this._openedWithFocusRing && this.hasAttribute('focused')) {
@@ -388,7 +536,7 @@ export const ComboBoxMixin = (subclass) =>
388
536
  input.setAttribute('aria-expanded', !!opened);
389
537
 
390
538
  if (opened) {
391
- input.setAttribute('aria-controls', this.$.dropdown.scrollerId);
539
+ input.setAttribute('aria-controls', this._scroller.id);
392
540
  } else {
393
541
  input.removeAttribute('aria-controls');
394
542
  }
@@ -478,7 +626,7 @@ export const ComboBoxMixin = (subclass) =>
478
626
  super._onKeyDown(e);
479
627
 
480
628
  if (e.key === 'Tab') {
481
- this.__restoreFocusOnClose = false;
629
+ this.$.overlay.restoreFocusOnClose = false;
482
630
  } else if (e.key === 'ArrowDown') {
483
631
  this._closeOnBlurIsPrevented = true;
484
632
  this._onArrowDown();
@@ -498,7 +646,11 @@ export const ComboBoxMixin = (subclass) =>
498
646
 
499
647
  /** @private */
500
648
  _getItemLabel(item) {
501
- return this.$.dropdown.getItemLabel(item);
649
+ let label = item && this.itemLabelPath ? this.get(this.itemLabelPath, item) : undefined;
650
+ if (label === undefined || label === null) {
651
+ label = item ? item.toString() : '';
652
+ }
653
+ return label;
502
654
  }
503
655
 
504
656
  /** @private */
@@ -513,7 +665,7 @@ export const ComboBoxMixin = (subclass) =>
513
665
  /** @private */
514
666
  _onArrowDown() {
515
667
  if (this.opened) {
516
- const items = this._getOverlayItems();
668
+ const items = this.filteredItems;
517
669
  if (items) {
518
670
  this._focusedIndex = Math.min(items.length - 1, this._focusedIndex + 1);
519
671
  this._prefillFocusedItemLabel();
@@ -529,7 +681,7 @@ export const ComboBoxMixin = (subclass) =>
529
681
  if (this._focusedIndex > -1) {
530
682
  this._focusedIndex = Math.max(0, this._focusedIndex - 1);
531
683
  } else {
532
- const items = this._getOverlayItems();
684
+ const items = this.filteredItems;
533
685
  if (items) {
534
686
  this._focusedIndex = items.length - 1;
535
687
  }
@@ -544,7 +696,8 @@ export const ComboBoxMixin = (subclass) =>
544
696
  /** @private */
545
697
  _prefillFocusedItemLabel() {
546
698
  if (this._focusedIndex > -1) {
547
- this._inputElementValue = this._getItemLabel(this.$.dropdown.focusedItem);
699
+ const focusedItem = this.filteredItems[this._focusedIndex];
700
+ this._inputElementValue = this._getItemLabel(focusedItem);
548
701
  this._markAllSelectionRange();
549
702
  }
550
703
  }
@@ -701,7 +854,7 @@ export const ComboBoxMixin = (subclass) =>
701
854
  _onOpened() {
702
855
  // Defer scroll position adjustment to improve performance.
703
856
  requestAnimationFrame(() => {
704
- this.$.dropdown.adjustScrollPosition();
857
+ this._scrollIntoView(this._focusedIndex);
705
858
 
706
859
  // Set attribute after the items are rendered when overlay is opened for the first time.
707
860
  this._updateActiveDescendant(this._focusedIndex);
@@ -720,9 +873,8 @@ export const ComboBoxMixin = (subclass) =>
720
873
 
721
874
  /** @private */
722
875
  _commitValue() {
723
- const items = this._getOverlayItems();
724
- if (items && this._focusedIndex > -1) {
725
- const focusedItem = items[this._focusedIndex];
876
+ if (this._focusedIndex > -1) {
877
+ const focusedItem = this.filteredItems[this._focusedIndex];
726
878
  if (this.selectedItem !== focusedItem) {
727
879
  this.selectedItem = focusedItem;
728
880
  }
@@ -735,18 +887,14 @@ export const ComboBoxMixin = (subclass) =>
735
887
  this.value = '';
736
888
  }
737
889
  } else {
738
- const toLowerCase = (item) => item && item.toLowerCase && item.toLowerCase();
739
-
740
- // Try to find an item whose label matches the input value. A matching item is searched from
741
- // the filteredItems array (if available) and the selectedItem (if available).
742
- const itemMatchingByLabel = [...(this.filteredItems || []), this.selectedItem].find((item) => {
743
- return toLowerCase(this._getItemLabel(item)) === toLowerCase(this._inputElementValue);
744
- });
890
+ // Try to find an item which label matches the input value.
891
+ const items = [...(this.filteredItems || []), this.selectedItem];
892
+ const itemMatchingInputValue = items[this.__getItemIndexByLabel(items, this._inputElementValue)];
745
893
 
746
894
  if (
747
895
  this.allowCustomValue &&
748
896
  // To prevent a repetitive input value being saved after pressing ESC and Tab.
749
- !itemMatchingByLabel
897
+ !itemMatchingInputValue
750
898
  ) {
751
899
  const customValue = this._inputElementValue;
752
900
 
@@ -763,12 +911,11 @@ export const ComboBoxMixin = (subclass) =>
763
911
  });
764
912
  this.dispatchEvent(e);
765
913
  if (!e.defaultPrevented) {
766
- this._selectItemForValue(customValue);
767
914
  this.value = customValue;
768
915
  }
769
- } else if (!this.allowCustomValue && !this.opened && itemMatchingByLabel) {
916
+ } else if (!this.allowCustomValue && !this.opened && itemMatchingInputValue) {
770
917
  // An item matching by label was found, select it.
771
- this.value = this._getItemValue(itemMatchingByLabel);
918
+ this.value = this._getItemValue(itemMatchingInputValue);
772
919
  } else {
773
920
  // Revert the input value
774
921
  this._inputElementValue = this.selectedItem ? this._getItemLabel(this.selectedItem) : this.value || '';
@@ -834,13 +981,15 @@ export const ComboBoxMixin = (subclass) =>
834
981
  }
835
982
 
836
983
  /** @private */
837
- _filterChanged(filter, itemValuePath, itemLabelPath) {
984
+ _filterChanged(filter, _itemValuePath, _itemLabelPath) {
838
985
  if (filter === undefined) {
839
986
  return;
840
987
  }
841
988
 
842
989
  // Scroll to the top of the list whenever the filter changes.
843
- this.$.dropdown._scrollIntoView(0);
990
+ this._scrollIntoView(0);
991
+
992
+ this._focusedIndex = -1;
844
993
 
845
994
  if (this.items) {
846
995
  this.filteredItems = this._filterItems(this.items, filter);
@@ -848,14 +997,7 @@ export const ComboBoxMixin = (subclass) =>
848
997
  // With certain use cases (e. g., external filtering), `items` are
849
998
  // undefined. Filtering is unnecessary per se, but the filteredItems
850
999
  // observer should still be invoked to update focused item.
851
- this._filteredItemsChanged({ path: 'filteredItems', value: this.filteredItems }, itemValuePath, itemLabelPath);
852
- }
853
- }
854
-
855
- /** @private */
856
- _loadingChanged(loading) {
857
- if (loading) {
858
- this._focusedIndex = -1;
1000
+ this._filteredItemsChanged(this.filteredItems);
859
1001
  }
860
1002
  }
861
1003
 
@@ -904,9 +1046,7 @@ export const ComboBoxMixin = (subclass) =>
904
1046
  this._inputElementValue = this._getItemLabel(selectedItem);
905
1047
  }
906
1048
 
907
- this.$.dropdown._selectedItem = selectedItem;
908
- const items = this._getOverlayItems();
909
- if (this.filteredItems && items) {
1049
+ if (this.filteredItems) {
910
1050
  this._focusedIndex = this.filteredItems.indexOf(selectedItem);
911
1051
  }
912
1052
  }
@@ -923,7 +1063,7 @@ export const ComboBoxMixin = (subclass) =>
923
1063
  return;
924
1064
  }
925
1065
 
926
- if (this._isValidValue(value)) {
1066
+ if (isValidValue(value)) {
927
1067
  let item;
928
1068
  if (this._getItemValue(this.selectedItem) !== value) {
929
1069
  this._selectItemForValue(value);
@@ -956,53 +1096,53 @@ export const ComboBoxMixin = (subclass) =>
956
1096
  this._ensureItemsOrDataProvider(() => {
957
1097
  this.items = oldItems;
958
1098
  });
1099
+
1100
+ if (items) {
1101
+ this.filteredItems = items.slice(0);
1102
+ } else if (oldItems) {
1103
+ // Only clear filteredItems if the component had items previously but got cleared
1104
+ this.filteredItems = null;
1105
+ }
959
1106
  }
960
1107
 
961
1108
  /** @private */
962
- _itemsOrPathsChanged(e) {
963
- if (e.path === 'items' || e.path === 'items.splices') {
964
- if (this.items) {
965
- this.filteredItems = this.items.slice(0);
966
- } else if (this.__previousItems) {
967
- // Only clear filteredItems if the component had items previously but got cleared
968
- this.filteredItems = null;
969
- }
970
-
971
- const valueIndex = this._indexOfValue(this.value, this.items);
972
- this._focusedIndex = valueIndex;
1109
+ _filteredItemsChanged(filteredItems, oldFilteredItems) {
1110
+ // Store the currently focused item if any. The focused index preserves
1111
+ // in the case when more filtered items are loading but it is reset
1112
+ // when the user types in a filter query.
1113
+ const focusedItem = oldFilteredItems ? oldFilteredItems[this._focusedIndex] : null;
1114
+
1115
+ // Try to sync `selectedItem` based on `value` once a new set of `filteredItems` is available
1116
+ // (as a result of external filtering or when they have been loaded by the data provider).
1117
+ // When `value` is specified but `selectedItem` is not, it means that there was no item
1118
+ // matching `value` at the moment `value` was set, so `selectedItem` has remained unsynced.
1119
+ const valueIndex = this.__getItemIndexByValue(filteredItems, this.value);
1120
+ if ((this.selectedItem === null || this.selectedItem === undefined) && valueIndex >= 0) {
1121
+ this.selectedItem = filteredItems[valueIndex];
1122
+ }
973
1123
 
974
- const item = valueIndex > -1 && this.items[valueIndex];
975
- if (item) {
976
- this.selectedItem = item;
977
- }
1124
+ // Try to first set focus on the item that had been focused before `filteredItems` were updated
1125
+ // if it is still present in the `filteredItems` array. Otherwise, set the focused index
1126
+ // depending on the selected item or the filter query.
1127
+ const focusedItemIndex = this.__getItemIndexByValue(filteredItems, this._getItemValue(focusedItem));
1128
+ if (focusedItemIndex > -1) {
1129
+ this._focusedIndex = focusedItemIndex;
1130
+ } else {
1131
+ this.__setInitialFocusedIndex();
978
1132
  }
979
- this.__previousItems = e.value;
980
1133
  }
981
1134
 
982
1135
  /** @private */
983
- _filteredItemsChanged(e) {
984
- if (e.path === 'filteredItems' || e.path === 'filteredItems.splices') {
985
- this._setOverlayItems(this.filteredItems);
986
-
987
- // When the external filtering is used and `value` was provided before `filteredItems`,
988
- // initialize the selected item with the current value here. This will also cause
989
- // the input element value to sync. In other cases, the selected item is already initialized
990
- // in other observers such as `valueChanged`, `_itemsOrPathsChanged`.
991
- const valueIndex = this._indexOfValue(this.value, this.filteredItems);
992
- if (this.selectedItem === null && valueIndex >= 0) {
993
- this._selectItemForValue(this.value);
994
- }
995
-
996
- const inputValue = this._inputElementValue;
997
- if (inputValue === undefined || inputValue === this._getItemLabel(this.selectedItem)) {
998
- // When the input element value is the same as the current value or not defined,
999
- // set the focused index to the item that matches the value.
1000
- this._focusedIndex = this.$.dropdown.indexOfLabel(this._getItemLabel(this.selectedItem));
1001
- } else {
1002
- // When the user filled in something that is different from the current value = filtering is enabled,
1003
- // set the focused index to the item that matches the filter query.
1004
- this._focusedIndex = this.$.dropdown.indexOfLabel(this.filter);
1005
- }
1136
+ __setInitialFocusedIndex() {
1137
+ const inputValue = this._inputElementValue;
1138
+ if (inputValue === undefined || inputValue === this._getItemLabel(this.selectedItem)) {
1139
+ // When the input element value is the same as the current value or not defined,
1140
+ // set the focused index to the item that matches the value.
1141
+ this._focusedIndex = this.__getItemIndexByLabel(this.filteredItems, this._getItemLabel(this.selectedItem));
1142
+ } else {
1143
+ // When the user filled in something that is different from the current value = filtering is enabled,
1144
+ // set the focused index to the item that matches the filter query.
1145
+ this._focusedIndex = this.__getItemIndexByLabel(this.filteredItems, this.filter);
1006
1146
  }
1007
1147
  }
1008
1148
 
@@ -1023,7 +1163,7 @@ export const ComboBoxMixin = (subclass) =>
1023
1163
 
1024
1164
  /** @private */
1025
1165
  _selectItemForValue(value) {
1026
- const valueIndex = this._indexOfValue(value, this.filteredItems);
1166
+ const valueIndex = this.__getItemIndexByValue(this.filteredItems, value);
1027
1167
  const previouslySelectedItem = this.selectedItem;
1028
1168
 
1029
1169
  if (valueIndex >= 0) {
@@ -1039,42 +1179,48 @@ export const ComboBoxMixin = (subclass) =>
1039
1179
  }
1040
1180
  }
1041
1181
 
1042
- /** @protected */
1043
- _getItemElements() {
1044
- return Array.from(this.$.dropdown._scroller.querySelectorAll('vaadin-combo-box-item'));
1045
- }
1046
-
1047
1182
  /** @private */
1048
- _getOverlayItems() {
1049
- return this.$.dropdown._items;
1183
+ _getItemElements() {
1184
+ return Array.from(this._scroller.querySelectorAll(`${this._tagNamePrefix}-item`));
1050
1185
  }
1051
1186
 
1052
1187
  /** @private */
1053
- _setOverlayItems(items) {
1054
- this.$.dropdown.set('_items', items);
1188
+ _scrollIntoView(index) {
1189
+ if (!this._scroller) {
1190
+ return;
1191
+ }
1192
+ this._scroller.scrollIntoView(index);
1055
1193
  }
1056
1194
 
1057
- /** @private */
1058
- _indexOfValue(value, items) {
1059
- if (!items || !this._isValidValue(value)) {
1195
+ /**
1196
+ * Returns the first item that matches the provided value.
1197
+ *
1198
+ * @private
1199
+ */
1200
+ __getItemIndexByValue(items, value) {
1201
+ if (!items || !isValidValue(value)) {
1060
1202
  return -1;
1061
1203
  }
1062
1204
 
1063
- return items.findIndex((item) => {
1064
- if (item instanceof ComboBoxPlaceholder) {
1065
- return false;
1066
- }
1067
-
1205
+ return findItemIndex(items, (item) => {
1068
1206
  return this._getItemValue(item) === value;
1069
1207
  });
1070
1208
  }
1071
1209
 
1072
1210
  /**
1073
- * Checks if the value is supported as an item value in this control.
1211
+ * Returns the first item that matches the provided label.
1212
+ * Labels are matched against each other case insensitively.
1213
+ *
1074
1214
  * @private
1075
1215
  */
1076
- _isValidValue(value) {
1077
- return value !== undefined && value !== null;
1216
+ __getItemIndexByLabel(items, label) {
1217
+ if (!items || !label) {
1218
+ return -1;
1219
+ }
1220
+
1221
+ return findItemIndex(items, (item) => {
1222
+ return this._getItemLabel(item).toString().toLowerCase() === label.toString().toLowerCase();
1223
+ });
1078
1224
  }
1079
1225
 
1080
1226
  /** @private */
@@ -1105,7 +1251,7 @@ export const ComboBoxMixin = (subclass) =>
1105
1251
  /** @private */
1106
1252
  _onFocusout(event) {
1107
1253
  // Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
1108
- if (event.relatedTarget === this.$.dropdown.$.overlay) {
1254
+ if (event.relatedTarget === this.$.overlay) {
1109
1255
  event.composedPath()[0].focus();
1110
1256
  return;
1111
1257
  }
@@ -1181,4 +1327,16 @@ export const ComboBoxMixin = (subclass) =>
1181
1327
  * To comply with https://developer.mozilla.org/en-US/docs/Web/Events/change
1182
1328
  * @event change
1183
1329
  */
1330
+
1331
+ /**
1332
+ * Fired after the `vaadin-combo-box-overlay` opens.
1333
+ *
1334
+ * @event vaadin-combo-box-dropdown-opened
1335
+ */
1336
+
1337
+ /**
1338
+ * Fired after the `vaadin-combo-box-overlay` closes.
1339
+ *
1340
+ * @event vaadin-combo-box-dropdown-closed
1341
+ */
1184
1342
  };
@@ -52,9 +52,7 @@ export class ComboBoxOverlay extends PositionMixin(OverlayElement) {
52
52
  connectedCallback() {
53
53
  super.connectedCallback();
54
54
 
55
- const dropdown = this.__dataHost;
56
- const comboBox = dropdown && dropdown.getRootNode().host;
57
- this._comboBox = comboBox;
55
+ const comboBox = this._comboBox;
58
56
 
59
57
  const hostDir = comboBox && comboBox.getAttribute('dir');
60
58
  if (hostDir) {
@@ -4,6 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
7
+ import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
7
8
  import { Virtualizer } from '@vaadin/component-base/src/virtualizer.js';
8
9
  import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
9
10
 
@@ -90,6 +91,7 @@ export class ComboBoxScroller extends PolymerElement {
90
91
  */
91
92
  selectedItem: {
92
93
  type: Object,
94
+ observer: '__selectedItemChanged',
93
95
  },
94
96
 
95
97
  /**
@@ -145,6 +147,9 @@ export class ComboBoxScroller extends PolymerElement {
145
147
  ready() {
146
148
  super.ready();
147
149
 
150
+ // Ensure every instance has unique ID
151
+ this.id = `${this.localName}-${generateUniqueId()}`;
152
+
148
153
  // Allow extensions to customize tag name for the items
149
154
  this.__hostTagName = this.constructor.is.replace('-scroller', '');
150
155
 
@@ -218,7 +223,7 @@ export class ComboBoxScroller extends PolymerElement {
218
223
 
219
224
  /** @private */
220
225
  __isItemFocused(focusedIndex, itemIndex) {
221
- return focusedIndex === itemIndex;
226
+ return !this.loading && focusedIndex === itemIndex;
222
227
  }
223
228
 
224
229
  /** @private */
@@ -243,12 +248,19 @@ export class ComboBoxScroller extends PolymerElement {
243
248
  }
244
249
 
245
250
  /** @private */
246
- __loadingChanged(loading) {
247
- if (this.__virtualizer && !loading) {
251
+ __loadingChanged() {
252
+ if (this.__virtualizer) {
248
253
  setTimeout(() => this.requestContentUpdate());
249
254
  }
250
255
  }
251
256
 
257
+ /** @private */
258
+ __selectedItemChanged() {
259
+ if (this.__virtualizer) {
260
+ this.requestContentUpdate();
261
+ }
262
+ }
263
+
252
264
  /** @private */
253
265
  __focusedIndexChanged(index, oldIndex) {
254
266
  if (!this.__virtualizer) {
@@ -4,7 +4,9 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import '@vaadin/input-container/src/vaadin-input-container.js';
7
- import './vaadin-combo-box-dropdown.js';
7
+ import './vaadin-combo-box-item.js';
8
+ import './vaadin-combo-box-overlay.js';
9
+ import './vaadin-combo-box-scroller.js';
8
10
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
9
11
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
12
  import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js';
@@ -196,19 +198,16 @@ class ComboBox extends ComboBoxDataProviderMixin(
196
198
  </div>
197
199
  </div>
198
200
 
199
- <vaadin-combo-box-dropdown
200
- id="dropdown"
201
- opened="[[opened]]"
202
- renderer="[[renderer]]"
201
+ <vaadin-combo-box-overlay
202
+ id="overlay"
203
+ hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
204
+ opened="[[_overlayOpened]]"
205
+ loading$="[[loading]]"
206
+ theme$="[[_theme]]"
203
207
  position-target="[[_positionTarget]]"
204
- restore-focus-on-close="[[__restoreFocusOnClose]]"
208
+ no-vertical-overlap
205
209
  restore-focus-node="[[inputElement]]"
206
- _focused-index="[[_focusedIndex]]"
207
- _item-id-path="[[itemIdPath]]"
208
- _item-label-path="[[itemLabelPath]]"
209
- loading="[[loading]]"
210
- theme="[[_theme]]"
211
- ></vaadin-combo-box-dropdown>
210
+ ></vaadin-combo-box-overlay>
212
211
  `;
213
212
  }
214
213
 
@@ -273,7 +272,7 @@ class ComboBox extends ComboBoxDataProviderMixin(
273
272
  */
274
273
  _shouldRemoveFocus(event) {
275
274
  // Do not blur when focus moves to the overlay
276
- if (event.relatedTarget === this.$.dropdown.$.overlay) {
275
+ if (event.relatedTarget === this.$.overlay) {
277
276
  event.composedPath()[0].focus();
278
277
  return false;
279
278
  }
@@ -1,287 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2015 - 2022 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import './vaadin-combo-box-item.js';
7
- import './vaadin-combo-box-overlay.js';
8
- import './vaadin-combo-box-scroller.js';
9
- import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
10
-
11
- /**
12
- * Element for internal use only.
13
- *
14
- * @extends HTMLElement
15
- * @private
16
- */
17
- export class ComboBoxDropdown extends PolymerElement {
18
- static get is() {
19
- return 'vaadin-combo-box-dropdown';
20
- }
21
-
22
- static get template() {
23
- return html`
24
- <vaadin-combo-box-overlay
25
- id="overlay"
26
- hidden$="[[_isOverlayHidden(_items.*, loading)]]"
27
- loading$="[[loading]]"
28
- opened="{{_overlayOpened}}"
29
- theme$="[[theme]]"
30
- position-target="[[positionTarget]]"
31
- no-vertical-overlap
32
- restore-focus-on-close="[[restoreFocusOnClose]]"
33
- restore-focus-node="[[restoreFocusNode]]"
34
- ></vaadin-combo-box-overlay>
35
- `;
36
- }
37
-
38
- static get properties() {
39
- return {
40
- /**
41
- * True if the combo-box has been activate by the user.
42
- * The actual opened state depends on whether the dropdown has items.
43
- */
44
- opened: Boolean,
45
-
46
- /**
47
- * The element to position/align the dropdown by.
48
- */
49
- positionTarget: {
50
- type: Object,
51
- },
52
-
53
- /**
54
- * Custom function for rendering the content of the `<vaadin-combo-box-item>` propagated from the combo box element.
55
- */
56
- renderer: Function,
57
-
58
- /**
59
- * `true` when new items are being loaded.
60
- */
61
- loading: {
62
- type: Boolean,
63
- value: false,
64
- reflectToAttribute: true,
65
- },
66
-
67
- /**
68
- * Used to propagate the `theme` attribute from the host element.
69
- */
70
- theme: String,
71
-
72
- _selectedItem: {
73
- type: Object,
74
- },
75
-
76
- _items: {
77
- type: Array,
78
- },
79
-
80
- _focusedIndex: {
81
- type: Number,
82
- value: -1,
83
- },
84
-
85
- focusedItem: {
86
- type: String,
87
- computed: '_getFocusedItem(_focusedIndex)',
88
- },
89
-
90
- _itemLabelPath: {
91
- type: String,
92
- value: 'label',
93
- },
94
-
95
- _itemValuePath: {
96
- type: String,
97
- value: 'value',
98
- },
99
-
100
- _scroller: Object,
101
-
102
- _itemIdPath: String,
103
-
104
- _overlayOpened: {
105
- type: Boolean,
106
- observer: '_openedChanged',
107
- },
108
- };
109
- }
110
-
111
- static get observers() {
112
- return [
113
- '_openedOrItemsChanged(opened, _items, loading)',
114
- '__updateScroller(_scroller, _items, opened, loading, _selectedItem, _itemIdPath, _focusedIndex, renderer, theme)',
115
- ];
116
- }
117
-
118
- constructor() {
119
- super();
120
-
121
- // Ensure every instance has unique ID
122
- const uniqueId = (ComboBoxDropdown._uniqueId = 1 + ComboBoxDropdown._uniqueId || 0);
123
- this.scrollerId = `${this.localName}-scroller-${uniqueId}`;
124
- }
125
-
126
- ready() {
127
- super.ready();
128
-
129
- // Allow extensions to customize tag name for the items
130
- this.__hostTagName = this.constructor.is.replace('-dropdown', '');
131
-
132
- const overlay = this.$.overlay;
133
- const scrollerTag = `${this.__hostTagName}-scroller`;
134
-
135
- overlay.renderer = (root) => {
136
- if (!root.firstChild) {
137
- const scroller = document.createElement(scrollerTag);
138
- root.appendChild(scroller);
139
- }
140
- };
141
-
142
- // Ensure the scroller is rendered
143
- overlay.requestContentUpdate();
144
-
145
- this._scroller = overlay.content.querySelector(scrollerTag);
146
- this._scroller.id = this.scrollerId;
147
-
148
- this._scroller.getItemLabel = this.getItemLabel.bind(this);
149
- this._scroller.comboBox = this.getRootNode().host;
150
-
151
- this._scroller.addEventListener('selection-changed', (e) => this._forwardScrollerEvent(e));
152
- this._scroller.addEventListener('index-requested', (e) => this._forwardScrollerEvent(e));
153
-
154
- overlay.addEventListener('touchend', (e) => this._fireTouchAction(e));
155
- overlay.addEventListener('touchmove', (e) => this._fireTouchAction(e));
156
-
157
- // Prevent blurring the input when clicking inside the overlay.
158
- overlay.addEventListener('mousedown', (e) => e.preventDefault());
159
-
160
- // Preventing the default modal behaviour of the overlay on input clicking
161
- overlay.addEventListener('vaadin-overlay-outside-click', (e) => {
162
- e.preventDefault();
163
- });
164
- }
165
-
166
- disconnectedCallback() {
167
- super.disconnectedCallback();
168
-
169
- // Making sure the overlay is closed and removed from DOM after detaching the dropdown.
170
- this._overlayOpened = false;
171
- }
172
-
173
- _fireTouchAction(sourceEvent) {
174
- this.dispatchEvent(
175
- new CustomEvent('vaadin-overlay-touch-action', {
176
- detail: { sourceEvent },
177
- }),
178
- );
179
- }
180
-
181
- _forwardScrollerEvent(event) {
182
- this.dispatchEvent(new CustomEvent(event.type, { detail: event.detail }));
183
- }
184
-
185
- _openedChanged(opened, wasOpened) {
186
- if (opened) {
187
- this._scroller.style.maxHeight =
188
- getComputedStyle(this).getPropertyValue(`--${this.__hostTagName}-overlay-max-height`) || '65vh';
189
-
190
- this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
191
- } else if (wasOpened && !this.__emptyItems) {
192
- this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
193
- }
194
- }
195
-
196
- _openedOrItemsChanged(opened, items, loading) {
197
- // See https://github.com/vaadin/vaadin-combo-box/pull/964
198
- const hasItems = items && items.length;
199
- if (!hasItems) {
200
- this.__emptyItems = true;
201
- }
202
- this._overlayOpened = !!(opened && (loading || hasItems));
203
- this.__emptyItems = false;
204
- }
205
-
206
- _getFocusedItem(focusedIndex) {
207
- if (focusedIndex >= 0) {
208
- return this._items[focusedIndex];
209
- }
210
- }
211
-
212
- /**
213
- * Gets the index of the item with the provided label.
214
- * @return {number}
215
- */
216
- indexOfLabel(label) {
217
- if (this._items && label) {
218
- for (let i = 0; i < this._items.length; i++) {
219
- if (this.getItemLabel(this._items[i]).toString().toLowerCase() === label.toString().toLowerCase()) {
220
- return i;
221
- }
222
- }
223
- }
224
-
225
- return -1;
226
- }
227
-
228
- /**
229
- * Gets the label string for the item based on the `_itemLabelPath`.
230
- * @return {string}
231
- */
232
- getItemLabel(item, itemLabelPath) {
233
- itemLabelPath = itemLabelPath || this._itemLabelPath;
234
- let label = item && itemLabelPath ? this.get(itemLabelPath, item) : undefined;
235
- if (label === undefined || label === null) {
236
- label = item ? item.toString() : '';
237
- }
238
- return label;
239
- }
240
-
241
- _scrollIntoView(index) {
242
- if (!this._scroller) {
243
- return;
244
- }
245
- this._scroller.scrollIntoView(index);
246
- }
247
-
248
- adjustScrollPosition() {
249
- if (this.opened && this._items) {
250
- this._scrollIntoView(this._focusedIndex);
251
- }
252
- }
253
-
254
- // eslint-disable-next-line max-params
255
- __updateScroller(scroller, items, opened, loading, selectedItem, itemIdPath, focusedIndex, renderer, theme) {
256
- if (scroller) {
257
- scroller.setProperties({
258
- items: opened ? items : [],
259
- opened,
260
- loading,
261
- selectedItem,
262
- itemIdPath,
263
- focusedIndex,
264
- renderer,
265
- theme,
266
- });
267
- }
268
- }
269
-
270
- _isOverlayHidden() {
271
- return !this.loading && !(this._items && this._items.length);
272
- }
273
-
274
- /**
275
- * Fired after the `vaadin-combo-box-dropdown` opens.
276
- *
277
- * @event vaadin-combo-box-dropdown-opened
278
- */
279
-
280
- /**
281
- * Fired after the `vaadin-combo-box-dropdown` closes.
282
- *
283
- * @event vaadin-combo-box-dropdown-closed
284
- */
285
- }
286
-
287
- customElements.define(ComboBoxDropdown.is, ComboBoxDropdown);