@vaadin/combo-box 23.2.0-dev.53560527d → 23.2.0

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.
@@ -6,6 +6,7 @@
6
6
  import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
7
7
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
8
8
  import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js';
9
+ import { isElementFocused } from '@vaadin/component-base/src/focus-utils.js';
9
10
  import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
10
11
  import { processTemplates } from '@vaadin/component-base/src/templates.js';
11
12
  import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
@@ -236,7 +237,6 @@ export const ComboBoxMixin = (subclass) =>
236
237
 
237
238
  static get observers() {
238
239
  return [
239
- '_filterChanged(filter, itemValuePath, itemLabelPath)',
240
240
  '_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
241
241
  '_openedOrItemsChanged(opened, filteredItems, loading)',
242
242
  '_updateScroller(_scroller, filteredItems, opened, loading, selectedItem, itemIdPath, _focusedIndex, renderer, theme)',
@@ -280,14 +280,26 @@ export const ComboBoxMixin = (subclass) =>
280
280
  }
281
281
  }
282
282
 
283
+ /**
284
+ * Get a reference to the native `<input>` element.
285
+ * Override to provide a custom input.
286
+ * @protected
287
+ * @return {HTMLInputElement | undefined}
288
+ */
289
+ get _nativeInput() {
290
+ return this.inputElement;
291
+ }
292
+
283
293
  /**
284
294
  * Override method inherited from `InputMixin`
285
295
  * to customize the input element.
286
296
  * @protected
287
297
  * @override
288
298
  */
289
- _inputElementChanged(input) {
290
- super._inputElementChanged(input);
299
+ _inputElementChanged(inputElement) {
300
+ super._inputElementChanged(inputElement);
301
+
302
+ const input = this._nativeInput;
291
303
 
292
304
  if (input) {
293
305
  input.autocomplete = 'off';
@@ -382,6 +394,25 @@ export const ComboBoxMixin = (subclass) =>
382
394
  this.opened = false;
383
395
  }
384
396
 
397
+ /**
398
+ * Override Polymer lifecycle callback to handle `filter` property change after
399
+ * the observer for `opened` property is triggered. This is needed when opening
400
+ * combo-box on user input to ensure the focused index is set correctly.
401
+ *
402
+ * @param {!Object} currentProps Current accessor values
403
+ * @param {?Object} changedProps Properties changed since the last call
404
+ * @param {?Object} oldProps Previous values for each changed property
405
+ * @protected
406
+ * @override
407
+ */
408
+ _propertiesChanged(currentProps, changedProps, oldProps) {
409
+ super._propertiesChanged(currentProps, changedProps, oldProps);
410
+
411
+ if (changedProps.filter !== undefined) {
412
+ this._filterChanged(changedProps.filter);
413
+ }
414
+ }
415
+
385
416
  /** @private */
386
417
  _initOverlay() {
387
418
  const overlay = this.$.overlay;
@@ -395,11 +426,6 @@ export const ComboBoxMixin = (subclass) =>
395
426
  // Prevent blurring the input when clicking inside the overlay
396
427
  overlay.addEventListener('mousedown', (e) => e.preventDefault());
397
428
 
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
429
  // Manual two-way binding for the overlay "opened" property
404
430
  overlay.addEventListener('opened-changed', (e) => {
405
431
  this._overlayOpened = e.detail.value;
@@ -424,9 +450,7 @@ export const ComboBoxMixin = (subclass) =>
424
450
  };
425
451
 
426
452
  // Ensure the scroller is rendered
427
- if (!this.opened) {
428
- overlay.requestContentUpdate();
429
- }
453
+ overlay.requestContentUpdate();
430
454
 
431
455
  const scroller = overlay.querySelector(scrollerTag);
432
456
 
@@ -442,6 +466,11 @@ export const ComboBoxMixin = (subclass) =>
442
466
  // eslint-disable-next-line max-params
443
467
  _updateScroller(scroller, items, opened, loading, selectedItem, itemIdPath, focusedIndex, renderer, theme) {
444
468
  if (scroller) {
469
+ if (opened) {
470
+ scroller.style.maxHeight =
471
+ getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
472
+ }
473
+
445
474
  scroller.setProperties({
446
475
  items: opened ? items : [],
447
476
  opened,
@@ -455,11 +484,6 @@ export const ComboBoxMixin = (subclass) =>
455
484
  }
456
485
  }
457
486
 
458
- /** @protected */
459
- _isOverlayHidden(items, loading) {
460
- return !loading && !(items && items.length);
461
- }
462
-
463
487
  /** @private */
464
488
  _openedOrItemsChanged(opened, items, loading) {
465
489
  // Close the overlay if there are no items to display.
@@ -488,9 +512,14 @@ export const ComboBoxMixin = (subclass) =>
488
512
  this._updateActiveDescendant(index);
489
513
  }
490
514
 
515
+ /** @protected */
516
+ _isInputFocused() {
517
+ return this.inputElement && isElementFocused(this.inputElement);
518
+ }
519
+
491
520
  /** @private */
492
521
  _updateActiveDescendant(index) {
493
- const input = this.inputElement;
522
+ const input = this._nativeInput;
494
523
  if (!input) {
495
524
  return;
496
525
  }
@@ -514,19 +543,19 @@ export const ComboBoxMixin = (subclass) =>
514
543
  this._openedWithFocusRing = this.hasAttribute('focus-ring');
515
544
  // For touch devices, we don't want to popup virtual keyboard
516
545
  // unless input element is explicitly focused by the user.
517
- if (!this.hasAttribute('focused') && !isTouch) {
546
+ if (!this._isInputFocused() && !isTouch) {
518
547
  this.focus();
519
548
  }
520
549
 
521
550
  this.$.overlay.restoreFocusOnClose = true;
522
551
  } else {
523
552
  this._onClosed();
524
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
553
+ if (this._openedWithFocusRing && this._isInputFocused()) {
525
554
  this.setAttribute('focus-ring', '');
526
555
  }
527
556
  }
528
557
 
529
- const input = this.inputElement;
558
+ const input = this._nativeInput;
530
559
  if (input) {
531
560
  input.setAttribute('aria-expanded', !!opened);
532
561
 
@@ -595,8 +624,6 @@ export const ComboBoxMixin = (subclass) =>
595
624
 
596
625
  /** @private */
597
626
  _onClick(e) {
598
- this._closeOnBlurIsPrevented = true;
599
-
600
627
  const path = e.composedPath();
601
628
 
602
629
  if (this._isClearButton(e)) {
@@ -606,8 +633,6 @@ export const ComboBoxMixin = (subclass) =>
606
633
  } else {
607
634
  this._onHostClick(e);
608
635
  }
609
-
610
- this._closeOnBlurIsPrevented = false;
611
636
  }
612
637
 
613
638
  /**
@@ -623,16 +648,12 @@ export const ComboBoxMixin = (subclass) =>
623
648
  if (e.key === 'Tab') {
624
649
  this.$.overlay.restoreFocusOnClose = false;
625
650
  } else if (e.key === 'ArrowDown') {
626
- this._closeOnBlurIsPrevented = true;
627
651
  this._onArrowDown();
628
- this._closeOnBlurIsPrevented = false;
629
652
 
630
653
  // Prevent caret from moving
631
654
  e.preventDefault();
632
655
  } else if (e.key === 'ArrowUp') {
633
- this._closeOnBlurIsPrevented = true;
634
656
  this._onArrowUp();
635
- this._closeOnBlurIsPrevented = false;
636
657
 
637
658
  // Prevent caret from moving
638
659
  e.preventDefault();
@@ -703,8 +724,7 @@ export const ComboBoxMixin = (subclass) =>
703
724
  // and there's no need to modify the selection range if the input isn't focused anyway.
704
725
  // This affects Safari. When the overlay is open, and then hitting tab, browser should focus
705
726
  // the next focusable element instead of the combo-box itself.
706
- // Checking the focused property here is enough instead of checking the activeElement.
707
- if (this.hasAttribute('focused')) {
727
+ if (this._isInputFocused() && this.inputElement.setSelectionRange) {
708
728
  this.inputElement.setSelectionRange(start, end);
709
729
  }
710
730
  }
@@ -814,7 +834,7 @@ export const ComboBoxMixin = (subclass) =>
814
834
  toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
815
835
  // Unfocus previously focused element if focus is not inside combo box (on touch devices)
816
836
  toggleElement.addEventListener('click', () => {
817
- if (isTouch && !this.hasAttribute('focused')) {
837
+ if (isTouch && !this._isInputFocused()) {
818
838
  document.activeElement.blur();
819
839
  }
820
840
  });
@@ -849,10 +869,6 @@ export const ComboBoxMixin = (subclass) =>
849
869
  _onOpened() {
850
870
  // Defer scroll position adjustment to improve performance.
851
871
  requestAnimationFrame(() => {
852
- // When opened is set as attribute, this logic needs to be delayed until scroller is created.
853
- this._scroller.style.maxHeight =
854
- getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
855
-
856
872
  this._scrollIntoView(this._focusedIndex);
857
873
 
858
874
  // Set attribute after the items are rendered when overlay is opened for the first time.
@@ -925,9 +941,7 @@ export const ComboBoxMixin = (subclass) =>
925
941
 
926
942
  this._clearSelectionRange();
927
943
 
928
- if (!this.dataProvider) {
929
- this.filter = '';
930
- }
944
+ this.filter = '';
931
945
  }
932
946
 
933
947
  /**
@@ -945,19 +959,27 @@ export const ComboBoxMixin = (subclass) =>
945
959
  * @override
946
960
  */
947
961
  _onInput(event) {
948
- if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
949
- this.open();
950
- }
962
+ const filter = this._inputElementValue;
963
+
964
+ // When opening dropdown on user input, both `opened` and `filter` properties are set.
965
+ // Perform a batched property update instead of relying on sync property observers.
966
+ // This is necessary to avoid an extra data-provider request for loading first page.
967
+ const props = {};
951
968
 
952
- const value = this._inputElementValue;
953
- if (this.filter === value) {
969
+ if (this.filter === filter) {
954
970
  // Filter and input value might get out of sync, while keyboard navigating for example.
955
971
  // Afterwards, input value might be changed to the same value as used in filtering.
956
972
  // In situation like these, we need to make sure all the filter changes handlers are run.
957
- this._filterChanged(this.filter, this.itemValuePath, this.itemLabelPath);
973
+ this._filterChanged(this.filter);
958
974
  } else {
959
- this.filter = value;
975
+ props.filter = filter;
976
+ }
977
+
978
+ if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
979
+ props.opened = true;
960
980
  }
981
+
982
+ this.setProperties(props);
961
983
  }
962
984
 
963
985
  /**
@@ -980,11 +1002,7 @@ export const ComboBoxMixin = (subclass) =>
980
1002
  }
981
1003
 
982
1004
  /** @private */
983
- _filterChanged(filter, _itemValuePath, _itemLabelPath) {
984
- if (filter === undefined) {
985
- return;
986
- }
987
-
1005
+ _filterChanged(filter) {
988
1006
  // Scroll to the top of the list whenever the filter changes.
989
1007
  this._scrollIntoView(0);
990
1008
 
@@ -1027,7 +1045,7 @@ export const ComboBoxMixin = (subclass) =>
1027
1045
  this.value = '';
1028
1046
  }
1029
1047
 
1030
- this._toggleHasValue(this.value !== '');
1048
+ this._toggleHasValue(this._hasValue);
1031
1049
  this._inputElementValue = this.value;
1032
1050
  }
1033
1051
  } else {
@@ -1063,21 +1081,21 @@ export const ComboBoxMixin = (subclass) =>
1063
1081
  }
1064
1082
 
1065
1083
  if (isValidValue(value)) {
1066
- let item;
1067
1084
  if (this._getItemValue(this.selectedItem) !== value) {
1068
1085
  this._selectItemForValue(value);
1069
- } else {
1070
- item = this.selectedItem;
1071
1086
  }
1072
1087
 
1073
- if (!item && this.allowCustomValue) {
1088
+ if (!this.selectedItem && this.allowCustomValue) {
1074
1089
  this._inputElementValue = value;
1075
1090
  }
1076
1091
 
1077
- this._toggleHasValue(this.value !== '');
1092
+ this._toggleHasValue(this._hasValue);
1078
1093
  } else {
1079
1094
  this.selectedItem = null;
1080
1095
  }
1096
+
1097
+ this.filter = '';
1098
+
1081
1099
  // In the next _detectAndDispatchChange() call, the change detection should pass
1082
1100
  this._lastCommittedValue = undefined;
1083
1101
  }
@@ -1235,9 +1253,6 @@ export const ComboBoxMixin = (subclass) =>
1235
1253
  if (this.opened) {
1236
1254
  this._focusedIndex = this.filteredItems.indexOf(e.detail.item);
1237
1255
  this.close();
1238
- } else if (this.selectedItem !== e.detail.item) {
1239
- this.selectedItem = e.detail.item;
1240
- this._detectAndDispatchChange();
1241
1256
  }
1242
1257
  }
1243
1258
 
@@ -1249,6 +1264,12 @@ export const ComboBoxMixin = (subclass) =>
1249
1264
 
1250
1265
  /** @private */
1251
1266
  _onFocusout(event) {
1267
+ // VoiceOver on iOS fires `focusout` event when moving focus to the item in the dropdown.
1268
+ // Do not focus the input in this case, because it would break announcement for the item.
1269
+ if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) {
1270
+ return;
1271
+ }
1272
+
1252
1273
  // Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
1253
1274
  if (event.relatedTarget === this.$.overlay) {
1254
1275
  event.composedPath()[0].focus();
@@ -1276,35 +1297,12 @@ export const ComboBoxMixin = (subclass) =>
1276
1297
  this._clear();
1277
1298
  }
1278
1299
 
1279
- /**
1280
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
1281
- *
1282
- * @return {boolean} True if the value is valid and sets the `invalid` flag appropriately
1283
- */
1284
- validate() {
1285
- return !(this.invalid = !this.checkValidity());
1286
- }
1287
-
1288
- /**
1289
- * Returns true if the current input value satisfies all constraints (if any).
1290
- * You can override this method for custom validations.
1291
- *
1292
- * @return {boolean}
1293
- */
1294
- checkValidity() {
1295
- if (super.checkValidity) {
1296
- return super.checkValidity();
1297
- }
1298
-
1299
- return !this.required || !!this.value;
1300
- }
1301
-
1302
1300
  /**
1303
1301
  * Fired when the value changes.
1304
1302
  *
1305
1303
  * @event value-changed
1306
1304
  * @param {Object} detail
1307
- * @param {String} detail.value the combobox value
1305
+ * @param {String} detail.value the combobox value
1308
1306
  */
1309
1307
 
1310
1308
  /**
@@ -1312,7 +1310,7 @@ export const ComboBoxMixin = (subclass) =>
1312
1310
  *
1313
1311
  * @event selected-item-changed
1314
1312
  * @param {Object} detail
1315
- * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1313
+ * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1316
1314
  */
1317
1315
 
1318
1316
  /**
@@ -3,24 +3,25 @@
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 { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
7
- import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
- import { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
9
- import { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
10
- import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
11
- import { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js';
12
- import { DelegateStateMixinClass } from '@vaadin/field-base/src/delegate-state-mixin.js';
13
- import { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
14
- import { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js';
15
- import { InputControlMixinClass } from '@vaadin/field-base/src/input-control-mixin.js';
16
- import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
17
- import { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
18
- import { PatternMixinClass } from '@vaadin/field-base/src/pattern-mixin.js';
19
- import { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
20
- import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
21
- import { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
22
- import { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
23
- import { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js';
6
+ import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
7
+ import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
+ import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
9
+ import type { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
10
+ import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
11
+ import type { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js';
12
+ import type { DelegateStateMixinClass } from '@vaadin/field-base/src/delegate-state-mixin.js';
13
+ import type { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
14
+ import type { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js';
15
+ import type { InputControlMixinClass } from '@vaadin/field-base/src/input-control-mixin.js';
16
+ import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
17
+ import type { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
18
+ import type { PatternMixinClass } from '@vaadin/field-base/src/pattern-mixin.js';
19
+ import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
20
+ import type { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
21
+ import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
22
+ import type { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
23
+ import type { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
24
+ import type { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js';
24
25
  export {
25
26
  ComboBoxDataProvider,
26
27
  ComboBoxDataProviderCallback,
@@ -65,6 +66,11 @@ export type ComboBoxFilterChangedEvent = CustomEvent<{ value: string }>;
65
66
  */
66
67
  export type ComboBoxSelectedItemChangedEvent<TItem> = CustomEvent<{ value: TItem | null | undefined }>;
67
68
 
69
+ /**
70
+ * Fired whenever the field is validated.
71
+ */
72
+ export type ComboBoxValidatedEvent = CustomEvent<{ valid: boolean }>;
73
+
68
74
  export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
69
75
  change: ComboBoxChangeEvent<TItem>;
70
76
 
@@ -79,6 +85,8 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
79
85
  'value-changed': ComboBoxValueChangedEvent;
80
86
 
81
87
  'selected-item-changed': ComboBoxSelectedItemChangedEvent<TItem>;
88
+
89
+ validated: ComboBoxValidatedEvent;
82
90
  }
83
91
 
84
92
  /**
@@ -165,6 +173,7 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
165
173
  * Custom property | Description | Default
166
174
  * ----------------------------------------|----------------------------|---------
167
175
  * `--vaadin-field-default-width` | Default width of the field | `12em`
176
+ * `--vaadin-combo-box-overlay-width` | Width of the overlay | `auto`
168
177
  * `--vaadin-combo-box-overlay-max-height` | Max height of the overlay | `65vh`
169
178
  *
170
179
  * `<vaadin-combo-box>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.
@@ -198,7 +207,7 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
198
207
  * Note: the `theme` attribute value set on `<vaadin-combo-box>` is
199
208
  * propagated to the internal components listed above.
200
209
  *
201
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
210
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
202
211
  *
203
212
  * @fires {Event} change - Fired when the user commits a value change.
204
213
  * @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
@@ -207,18 +216,19 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
207
216
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
208
217
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
209
218
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
219
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
210
220
  */
211
221
  declare class ComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
212
222
  addEventListener<K extends keyof ComboBoxEventMap<TItem>>(
213
223
  type: K,
214
224
  listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void,
215
- options?: boolean | AddEventListenerOptions,
225
+ options?: AddEventListenerOptions | boolean,
216
226
  ): void;
217
227
 
218
228
  removeEventListener<K extends keyof ComboBoxEventMap<TItem>>(
219
229
  type: K,
220
230
  listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void,
221
- options?: boolean | EventListenerOptions,
231
+ options?: EventListenerOptions | boolean,
222
232
  ): void;
223
233
  }
224
234
 
@@ -238,6 +248,7 @@ interface ComboBox<TItem = ComboBoxDefaultItem>
238
248
  DelegateStateMixinClass,
239
249
  DelegateFocusMixinClass,
240
250
  ThemableMixinClass,
251
+ ThemePropertyMixinClass,
241
252
  ElementMixinClass,
242
253
  ControllerMixinClass {}
243
254
 
@@ -104,6 +104,7 @@ registerStyles('vaadin-combo-box', inputFieldShared, { moduleId: 'vaadin-combo-b
104
104
  * Custom property | Description | Default
105
105
  * ----------------------------------------|----------------------------|---------
106
106
  * `--vaadin-field-default-width` | Default width of the field | `12em`
107
+ * `--vaadin-combo-box-overlay-width` | Width of the overlay | `auto`
107
108
  * `--vaadin-combo-box-overlay-max-height` | Max height of the overlay | `65vh`
108
109
  *
109
110
  * `<vaadin-combo-box>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.
@@ -137,7 +138,7 @@ registerStyles('vaadin-combo-box', inputFieldShared, { moduleId: 'vaadin-combo-b
137
138
  * Note: the `theme` attribute value set on `<vaadin-combo-box>` is
138
139
  * propagated to the internal components listed above.
139
140
  *
140
- * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
141
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
141
142
  *
142
143
  * @fires {Event} change - Fired when the user commits a value change.
143
144
  * @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
@@ -146,6 +147,7 @@ registerStyles('vaadin-combo-box', inputFieldShared, { moduleId: 'vaadin-combo-b
146
147
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
147
148
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
148
149
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
150
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
149
151
  *
150
152
  * @extends HTMLElement
151
153
  * @mixes ElementMixin
@@ -200,7 +202,6 @@ class ComboBox extends ComboBoxDataProviderMixin(
200
202
 
201
203
  <vaadin-combo-box-overlay
202
204
  id="overlay"
203
- hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
204
205
  opened="[[_overlayOpened]]"
205
206
  loading$="[[loading]]"
206
207
  theme$="[[_theme]]"
@@ -2,6 +2,7 @@ import '@vaadin/vaadin-lumo-styles/color.js';
2
2
  import '@vaadin/vaadin-lumo-styles/spacing.js';
3
3
  import '@vaadin/vaadin-lumo-styles/style.js';
4
4
  import '@vaadin/vaadin-overlay/theme/lumo/vaadin-overlay.js';
5
+ import { loader } from '@vaadin/vaadin-lumo-styles/mixins/loader.js';
5
6
  import { menuOverlayCore } from '@vaadin/vaadin-lumo-styles/mixins/menu-overlay.js';
6
7
  import { overlay } from '@vaadin/vaadin-lumo-styles/mixins/overlay.js';
7
8
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -36,10 +37,7 @@ const comboBoxOverlay = css`
36
37
  margin-bottom: var(--lumo-space-xs);
37
38
  }
38
39
 
39
- :host([loading]) [part~='loader'] {
40
- box-sizing: border-box;
41
- width: var(--lumo-icon-size-s);
42
- height: var(--lumo-icon-size-s);
40
+ [part~='loader'] {
43
41
  position: absolute;
44
42
  z-index: 1;
45
43
  left: var(--lumo-space-s);
@@ -48,38 +46,11 @@ const comboBoxOverlay = css`
48
46
  margin-left: auto;
49
47
  margin-inline-start: auto;
50
48
  margin-inline-end: 0;
51
- border: 2px solid transparent;
52
- border-color: var(--lumo-primary-color-50pct) var(--lumo-primary-color-50pct) var(--lumo-primary-color)
53
- var(--lumo-primary-color);
54
- border-radius: calc(0.5 * var(--lumo-icon-size-s));
55
- opacity: 0;
56
- animation: 1s linear infinite lumo-combo-box-loader-rotate, 0.3s 0.1s lumo-combo-box-loader-fade-in both;
57
- pointer-events: none;
58
- }
59
-
60
- @keyframes lumo-combo-box-loader-fade-in {
61
- 0% {
62
- opacity: 0;
63
- }
64
-
65
- 100% {
66
- opacity: 1;
67
- }
68
- }
69
-
70
- @keyframes lumo-combo-box-loader-rotate {
71
- 0% {
72
- transform: rotate(0deg);
73
- }
74
-
75
- 100% {
76
- transform: rotate(360deg);
77
- }
78
49
  }
79
50
 
80
51
  /* RTL specific styles */
81
52
 
82
- :host([loading][dir='rtl']) [part~='loader'] {
53
+ :host([dir='rtl']) [part~='loader'] {
83
54
  left: auto;
84
55
  margin-left: 0;
85
56
  margin-right: auto;
@@ -88,6 +59,6 @@ const comboBoxOverlay = css`
88
59
  }
89
60
  `;
90
61
 
91
- registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay], {
62
+ registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay, loader], {
92
63
  moduleId: 'lumo-combo-box-overlay',
93
64
  });