@vaadin/combo-box 23.2.0-dev.48e5e3967 → 23.2.0-rc1

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.2.0-dev.48e5e3967",
3
+ "version": "23.2.0-rc1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,7 +24,9 @@
24
24
  "src",
25
25
  "theme",
26
26
  "vaadin-*.d.ts",
27
- "vaadin-*.js"
27
+ "vaadin-*.js",
28
+ "web-types.json",
29
+ "web-types.lit.json"
28
30
  ],
29
31
  "keywords": [
30
32
  "Vaadin",
@@ -36,23 +38,27 @@
36
38
  "dependencies": {
37
39
  "@open-wc/dedupe-mixin": "^1.3.0",
38
40
  "@polymer/polymer": "^3.0.0",
39
- "@vaadin/component-base": "23.2.0-dev.48e5e3967",
40
- "@vaadin/field-base": "23.2.0-dev.48e5e3967",
41
- "@vaadin/input-container": "23.2.0-dev.48e5e3967",
42
- "@vaadin/item": "23.2.0-dev.48e5e3967",
43
- "@vaadin/lit-renderer": "23.2.0-dev.48e5e3967",
44
- "@vaadin/vaadin-lumo-styles": "23.2.0-dev.48e5e3967",
45
- "@vaadin/vaadin-material-styles": "23.2.0-dev.48e5e3967",
46
- "@vaadin/vaadin-overlay": "23.2.0-dev.48e5e3967",
47
- "@vaadin/vaadin-themable-mixin": "23.2.0-dev.48e5e3967"
41
+ "@vaadin/component-base": "23.2.0-rc1",
42
+ "@vaadin/field-base": "23.2.0-rc1",
43
+ "@vaadin/input-container": "23.2.0-rc1",
44
+ "@vaadin/item": "23.2.0-rc1",
45
+ "@vaadin/lit-renderer": "23.2.0-rc1",
46
+ "@vaadin/vaadin-lumo-styles": "23.2.0-rc1",
47
+ "@vaadin/vaadin-material-styles": "23.2.0-rc1",
48
+ "@vaadin/vaadin-overlay": "23.2.0-rc1",
49
+ "@vaadin/vaadin-themable-mixin": "23.2.0-rc1"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@esm-bundle/chai": "^4.3.4",
51
- "@vaadin/polymer-legacy-adapter": "23.2.0-dev.48e5e3967",
53
+ "@vaadin/polymer-legacy-adapter": "23.2.0-rc1",
52
54
  "@vaadin/testing-helpers": "^0.3.2",
53
- "@vaadin/text-field": "23.2.0-dev.48e5e3967",
55
+ "@vaadin/text-field": "23.2.0-rc1",
54
56
  "lit": "^2.0.0",
55
57
  "sinon": "^13.0.2"
56
58
  },
57
- "gitHead": "961bc4ae5b707c3c02f12b99819b3c12c9b478aa"
59
+ "web-types": [
60
+ "web-types.json",
61
+ "web-types.lit.json"
62
+ ],
63
+ "gitHead": "e78a1f2fe6f42d78cefa3f48085b09a3033c9588"
58
64
  }
@@ -3,10 +3,10 @@
3
3
  * Copyright (c) 2017 - 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 { TemplateResult } from 'lit';
7
- import { DirectiveResult } from 'lit/directive.js';
6
+ import type { TemplateResult } from 'lit';
7
+ import type { DirectiveResult } from 'lit/directive.js';
8
8
  import { LitRendererDirective } from '@vaadin/lit-renderer';
9
- import { ComboBox, ComboBoxItemModel } from '../vaadin-combo-box.js';
9
+ import type { ComboBox, ComboBoxItemModel } from '../vaadin-combo-box.js';
10
10
 
11
11
  export type ComboBoxLitRenderer<TItem> = (
12
12
  item: TItem,
@@ -3,9 +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 { Constructor } from '@open-wc/dedupe-mixin';
6
+ import type { Constructor } from '@open-wc/dedupe-mixin';
7
7
 
8
- export type ComboBoxDataProviderCallback<TItem> = (items: TItem[], size: number) => void;
8
+ export type ComboBoxDataProviderCallback<TItem> = (items: TItem[], size?: number) => void;
9
9
 
10
10
  export interface ComboBoxDataProviderParams {
11
11
  page: number;
@@ -20,7 +20,7 @@ export type ComboBoxDataProvider<TItem> = (
20
20
 
21
21
  export declare function ComboBoxDataProviderMixin<TItem, T extends Constructor<HTMLElement>>(
22
22
  base: T,
23
- ): T & Constructor<ComboBoxDataProviderMixinClass<TItem>>;
23
+ ): Constructor<ComboBoxDataProviderMixinClass<TItem>> & T;
24
24
 
25
25
  export declare class ComboBoxDataProviderMixinClass<TItem> {
26
26
  /**
@@ -186,10 +186,13 @@ export const ComboBoxDataProviderMixin = (superClass) =>
186
186
  filteredItems.splice(params.page * params.pageSize, items.length, ...items);
187
187
  this.filteredItems = filteredItems;
188
188
 
189
- if (!this.opened && !this.hasAttribute('focused')) {
189
+ if (!this.opened && !this._isInputFocused()) {
190
190
  this._commitValue();
191
191
  }
192
- this.size = size;
192
+
193
+ if (size !== undefined) {
194
+ this.size = size;
195
+ }
193
196
 
194
197
  delete this._pendingRequests[page];
195
198
 
@@ -3,14 +3,14 @@
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 { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
7
- import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
8
- import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
9
- import { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
10
- import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
- import { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
12
- import { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
13
- import { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js';
6
+ import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
7
+ import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
8
+ import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
9
+ import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
10
+ import type { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
+ import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
12
+ import type { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
13
+ import type { ComboBoxDefaultItem, ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
14
14
  export {
15
15
  ComboBoxDataProvider,
16
16
  ComboBoxDataProviderCallback,
@@ -55,6 +55,11 @@ export type ComboBoxLightFilterChangedEvent = CustomEvent<{ value: string }>;
55
55
  */
56
56
  export type ComboBoxLightSelectedItemChangedEvent<TItem> = CustomEvent<{ value: TItem | null | undefined }>;
57
57
 
58
+ /**
59
+ * Fired whenever the field is validated.
60
+ */
61
+ export type ComboBoxLightValidatedEvent = CustomEvent<{ valid: boolean }>;
62
+
58
63
  export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
59
64
  change: ComboBoxLightChangeEvent<TItem>;
60
65
 
@@ -69,6 +74,8 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
69
74
  'value-changed': ComboBoxLightValueChangedEvent;
70
75
 
71
76
  'selected-item-changed': ComboBoxLightSelectedItemChangedEvent<TItem>;
77
+
78
+ validated: ComboBoxLightValidatedEvent;
72
79
  }
73
80
 
74
81
  /**
@@ -115,6 +122,7 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
115
122
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
116
123
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
117
124
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
125
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
118
126
  */
119
127
  declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
120
128
  /**
@@ -127,13 +135,13 @@ declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
127
135
  addEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
128
136
  type: K,
129
137
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
130
- options?: boolean | AddEventListenerOptions,
138
+ options?: AddEventListenerOptions | boolean,
131
139
  ): void;
132
140
 
133
141
  removeEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
134
142
  type: K,
135
143
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
136
- options?: boolean | EventListenerOptions,
144
+ options?: EventListenerOptions | boolean,
137
145
  ): void;
138
146
  }
139
147
 
@@ -144,6 +152,7 @@ interface ComboBoxLight<TItem = ComboBoxDefaultItem>
144
152
  InputMixinClass,
145
153
  DisabledMixinClass,
146
154
  ThemableMixinClass,
155
+ ThemePropertyMixinClass,
147
156
  ValidateMixinClass {}
148
157
 
149
158
  declare global {
@@ -7,6 +7,7 @@ import './vaadin-combo-box-item.js';
7
7
  import './vaadin-combo-box-overlay.js';
8
8
  import './vaadin-combo-box-scroller.js';
9
9
  import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js';
10
+ import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
10
11
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
11
12
  import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js';
12
13
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -57,6 +58,7 @@ import { ComboBoxMixin } from './vaadin-combo-box-mixin.js';
57
58
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
58
59
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
59
60
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
61
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
60
62
  *
61
63
  * @extends HTMLElement
62
64
  * @mixes ComboBoxDataProviderMixin
@@ -81,7 +83,6 @@ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ValidateMixi
81
83
 
82
84
  <vaadin-combo-box-overlay
83
85
  id="overlay"
84
- hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
85
86
  opened="[[_overlayOpened]]"
86
87
  loading$="[[loading]]"
87
88
  theme$="[[_theme]]"
@@ -119,14 +120,14 @@ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ValidateMixi
119
120
  /** @protected */
120
121
  ready() {
121
122
  super.ready();
123
+
122
124
  this._toggleElement = this.querySelector('.toggle-button');
123
- }
124
125
 
125
- /** @protected */
126
- connectedCallback() {
127
- super.connectedCallback();
128
- this._setInputElement(this.querySelector('vaadin-text-field,.input'));
129
- this._revertInputValue();
126
+ // Wait until the slotted input DOM is ready
127
+ afterNextRender(this, () => {
128
+ this._setInputElement(this.querySelector('vaadin-text-field,.input'));
129
+ this._revertInputValue();
130
+ });
130
131
  }
131
132
 
132
133
  /**
@@ -148,6 +149,38 @@ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ValidateMixi
148
149
  return dashToCamelCase(this.attrForValue);
149
150
  }
150
151
 
152
+ /**
153
+ * @protected
154
+ * @override
155
+ * @return {HTMLInputElement | undefined}
156
+ */
157
+ get _nativeInput() {
158
+ const input = this.inputElement;
159
+
160
+ if (input) {
161
+ // Support `<input class="input">`
162
+ if (input instanceof HTMLInputElement) {
163
+ return input;
164
+ }
165
+
166
+ // Support `<input>` in light DOM (e.g. `vaadin-text-field`)
167
+ const slottedInput = input.querySelector('input');
168
+ if (slottedInput) {
169
+ return slottedInput;
170
+ }
171
+
172
+ if (input.shadowRoot) {
173
+ // Support `<input>` in Shadow DOM (e.g. `mwc-textfield`)
174
+ const shadowInput = input.shadowRoot.querySelector('input');
175
+ if (shadowInput) {
176
+ return shadowInput;
177
+ }
178
+ }
179
+ }
180
+
181
+ return undefined;
182
+ }
183
+
151
184
  /** @protected */
152
185
  _isClearButton(event) {
153
186
  return (
@@ -3,11 +3,11 @@
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 { Constructor } from '@open-wc/dedupe-mixin';
7
- import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
- import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
9
- import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
10
- import { ComboBox } from './vaadin-combo-box.js';
6
+ import type { Constructor } from '@open-wc/dedupe-mixin';
7
+ import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
+ import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
9
+ import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
10
+ import type { ComboBox } from './vaadin-combo-box.js';
11
11
 
12
12
  export type ComboBoxDefaultItem = any;
13
13
 
@@ -24,11 +24,11 @@ export type ComboBoxRenderer<TItem> = (
24
24
 
25
25
  export declare function ComboBoxMixin<TItem, T extends Constructor<HTMLElement>>(
26
26
  base: T,
27
- ): T &
28
- Constructor<ComboBoxMixinClass<TItem>> &
27
+ ): Constructor<ComboBoxMixinClass<TItem>> &
29
28
  Constructor<DisabledMixinClass> &
30
29
  Constructor<InputMixinClass> &
31
- Constructor<KeyboardMixinClass>;
30
+ Constructor<KeyboardMixinClass> &
31
+ T;
32
32
 
33
33
  export declare class ComboBoxMixinClass<TItem> {
34
34
  /**
@@ -165,16 +165,5 @@ export declare class ComboBoxMixinClass<TItem> {
165
165
  */
166
166
  close(): void;
167
167
 
168
- /**
169
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
170
- */
171
- validate(): boolean;
172
-
173
- /**
174
- * Returns true if the current input value satisfies all constraints (if any).
175
- * You can override this method for custom validations.
176
- */
177
- checkValidity(): boolean;
178
-
179
168
  protected _revertInputValue(): void;
180
169
  }
@@ -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';
@@ -279,14 +280,26 @@ export const ComboBoxMixin = (subclass) =>
279
280
  }
280
281
  }
281
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
+
282
293
  /**
283
294
  * Override method inherited from `InputMixin`
284
295
  * to customize the input element.
285
296
  * @protected
286
297
  * @override
287
298
  */
288
- _inputElementChanged(input) {
289
- super._inputElementChanged(input);
299
+ _inputElementChanged(inputElement) {
300
+ super._inputElementChanged(inputElement);
301
+
302
+ const input = this._nativeInput;
290
303
 
291
304
  if (input) {
292
305
  input.autocomplete = 'off';
@@ -413,11 +426,6 @@ export const ComboBoxMixin = (subclass) =>
413
426
  // Prevent blurring the input when clicking inside the overlay
414
427
  overlay.addEventListener('mousedown', (e) => e.preventDefault());
415
428
 
416
- // Preventing the default modal behavior of the overlay on input click
417
- overlay.addEventListener('vaadin-overlay-outside-click', (e) => {
418
- e.preventDefault();
419
- });
420
-
421
429
  // Manual two-way binding for the overlay "opened" property
422
430
  overlay.addEventListener('opened-changed', (e) => {
423
431
  this._overlayOpened = e.detail.value;
@@ -442,9 +450,7 @@ export const ComboBoxMixin = (subclass) =>
442
450
  };
443
451
 
444
452
  // Ensure the scroller is rendered
445
- if (!this.opened) {
446
- overlay.requestContentUpdate();
447
- }
453
+ overlay.requestContentUpdate();
448
454
 
449
455
  const scroller = overlay.querySelector(scrollerTag);
450
456
 
@@ -478,11 +484,6 @@ export const ComboBoxMixin = (subclass) =>
478
484
  }
479
485
  }
480
486
 
481
- /** @protected */
482
- _isOverlayHidden(items, loading) {
483
- return !loading && !(items && items.length);
484
- }
485
-
486
487
  /** @private */
487
488
  _openedOrItemsChanged(opened, items, loading) {
488
489
  // Close the overlay if there are no items to display.
@@ -511,9 +512,14 @@ export const ComboBoxMixin = (subclass) =>
511
512
  this._updateActiveDescendant(index);
512
513
  }
513
514
 
515
+ /** @protected */
516
+ _isInputFocused() {
517
+ return this.inputElement && isElementFocused(this.inputElement);
518
+ }
519
+
514
520
  /** @private */
515
521
  _updateActiveDescendant(index) {
516
- const input = this.inputElement;
522
+ const input = this._nativeInput;
517
523
  if (!input) {
518
524
  return;
519
525
  }
@@ -537,19 +543,19 @@ export const ComboBoxMixin = (subclass) =>
537
543
  this._openedWithFocusRing = this.hasAttribute('focus-ring');
538
544
  // For touch devices, we don't want to popup virtual keyboard
539
545
  // unless input element is explicitly focused by the user.
540
- if (!this.hasAttribute('focused') && !isTouch) {
546
+ if (!this._isInputFocused() && !isTouch) {
541
547
  this.focus();
542
548
  }
543
549
 
544
550
  this.$.overlay.restoreFocusOnClose = true;
545
551
  } else {
546
552
  this._onClosed();
547
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
553
+ if (this._openedWithFocusRing && this._isInputFocused()) {
548
554
  this.setAttribute('focus-ring', '');
549
555
  }
550
556
  }
551
557
 
552
- const input = this.inputElement;
558
+ const input = this._nativeInput;
553
559
  if (input) {
554
560
  input.setAttribute('aria-expanded', !!opened);
555
561
 
@@ -618,8 +624,6 @@ export const ComboBoxMixin = (subclass) =>
618
624
 
619
625
  /** @private */
620
626
  _onClick(e) {
621
- this._closeOnBlurIsPrevented = true;
622
-
623
627
  const path = e.composedPath();
624
628
 
625
629
  if (this._isClearButton(e)) {
@@ -629,8 +633,6 @@ export const ComboBoxMixin = (subclass) =>
629
633
  } else {
630
634
  this._onHostClick(e);
631
635
  }
632
-
633
- this._closeOnBlurIsPrevented = false;
634
636
  }
635
637
 
636
638
  /**
@@ -646,16 +648,12 @@ export const ComboBoxMixin = (subclass) =>
646
648
  if (e.key === 'Tab') {
647
649
  this.$.overlay.restoreFocusOnClose = false;
648
650
  } else if (e.key === 'ArrowDown') {
649
- this._closeOnBlurIsPrevented = true;
650
651
  this._onArrowDown();
651
- this._closeOnBlurIsPrevented = false;
652
652
 
653
653
  // Prevent caret from moving
654
654
  e.preventDefault();
655
655
  } else if (e.key === 'ArrowUp') {
656
- this._closeOnBlurIsPrevented = true;
657
656
  this._onArrowUp();
658
- this._closeOnBlurIsPrevented = false;
659
657
 
660
658
  // Prevent caret from moving
661
659
  e.preventDefault();
@@ -726,8 +724,7 @@ export const ComboBoxMixin = (subclass) =>
726
724
  // and there's no need to modify the selection range if the input isn't focused anyway.
727
725
  // This affects Safari. When the overlay is open, and then hitting tab, browser should focus
728
726
  // the next focusable element instead of the combo-box itself.
729
- // Checking the focused property here is enough instead of checking the activeElement.
730
- if (this.hasAttribute('focused')) {
727
+ if (this._isInputFocused() && this.inputElement.setSelectionRange) {
731
728
  this.inputElement.setSelectionRange(start, end);
732
729
  }
733
730
  }
@@ -837,7 +834,7 @@ export const ComboBoxMixin = (subclass) =>
837
834
  toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
838
835
  // Unfocus previously focused element if focus is not inside combo box (on touch devices)
839
836
  toggleElement.addEventListener('click', () => {
840
- if (isTouch && !this.hasAttribute('focused')) {
837
+ if (isTouch && !this._isInputFocused()) {
841
838
  document.activeElement.blur();
842
839
  }
843
840
  });
@@ -1048,7 +1045,7 @@ export const ComboBoxMixin = (subclass) =>
1048
1045
  this.value = '';
1049
1046
  }
1050
1047
 
1051
- this._toggleHasValue(this.value !== '');
1048
+ this._toggleHasValue(this._hasValue);
1052
1049
  this._inputElementValue = this.value;
1053
1050
  }
1054
1051
  } else {
@@ -1092,7 +1089,7 @@ export const ComboBoxMixin = (subclass) =>
1092
1089
  this._inputElementValue = value;
1093
1090
  }
1094
1091
 
1095
- this._toggleHasValue(this.value !== '');
1092
+ this._toggleHasValue(this._hasValue);
1096
1093
  } else {
1097
1094
  this.selectedItem = null;
1098
1095
  }
@@ -1256,9 +1253,6 @@ export const ComboBoxMixin = (subclass) =>
1256
1253
  if (this.opened) {
1257
1254
  this._focusedIndex = this.filteredItems.indexOf(e.detail.item);
1258
1255
  this.close();
1259
- } else if (this.selectedItem !== e.detail.item) {
1260
- this.selectedItem = e.detail.item;
1261
- this._detectAndDispatchChange();
1262
1256
  }
1263
1257
  }
1264
1258
 
@@ -1270,6 +1264,12 @@ export const ComboBoxMixin = (subclass) =>
1270
1264
 
1271
1265
  /** @private */
1272
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
+
1273
1273
  // Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
1274
1274
  if (event.relatedTarget === this.$.overlay) {
1275
1275
  event.composedPath()[0].focus();
@@ -1302,7 +1302,7 @@ export const ComboBoxMixin = (subclass) =>
1302
1302
  *
1303
1303
  * @event value-changed
1304
1304
  * @param {Object} detail
1305
- * @param {String} detail.value the combobox value
1305
+ * @param {String} detail.value the combobox value
1306
1306
  */
1307
1307
 
1308
1308
  /**
@@ -1310,7 +1310,7 @@ export const ComboBoxMixin = (subclass) =>
1310
1310
  *
1311
1311
  * @event selected-item-changed
1312
1312
  * @param {Object} detail
1313
- * @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`.
1314
1314
  */
1315
1315
 
1316
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
  /**
@@ -208,18 +216,19 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
208
216
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
209
217
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
210
218
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
219
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
211
220
  */
212
221
  declare class ComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
213
222
  addEventListener<K extends keyof ComboBoxEventMap<TItem>>(
214
223
  type: K,
215
224
  listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void,
216
- options?: boolean | AddEventListenerOptions,
225
+ options?: AddEventListenerOptions | boolean,
217
226
  ): void;
218
227
 
219
228
  removeEventListener<K extends keyof ComboBoxEventMap<TItem>>(
220
229
  type: K,
221
230
  listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void,
222
- options?: boolean | EventListenerOptions,
231
+ options?: EventListenerOptions | boolean,
223
232
  ): void;
224
233
  }
225
234
 
@@ -239,6 +248,7 @@ interface ComboBox<TItem = ComboBoxDefaultItem>
239
248
  DelegateStateMixinClass,
240
249
  DelegateFocusMixinClass,
241
250
  ThemableMixinClass,
251
+ ThemePropertyMixinClass,
242
252
  ElementMixinClass,
243
253
  ControllerMixinClass {}
244
254
 
@@ -147,6 +147,7 @@ registerStyles('vaadin-combo-box', inputFieldShared, { moduleId: 'vaadin-combo-b
147
147
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
148
148
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
149
149
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
150
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
150
151
  *
151
152
  * @extends HTMLElement
152
153
  * @mixes ElementMixin
@@ -201,7 +202,6 @@ class ComboBox extends ComboBoxDataProviderMixin(
201
202
 
202
203
  <vaadin-combo-box-overlay
203
204
  id="overlay"
204
- hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
205
205
  opened="[[_overlayOpened]]"
206
206
  loading$="[[loading]]"
207
207
  theme$="[[_theme]]"