@vaadin/combo-box 23.2.0-alpha2 → 23.2.0-alpha5

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-alpha2",
3
+ "version": "23.2.0-alpha5",
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-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"
41
+ "@vaadin/component-base": "23.2.0-alpha5",
42
+ "@vaadin/field-base": "23.2.0-alpha5",
43
+ "@vaadin/input-container": "23.2.0-alpha5",
44
+ "@vaadin/item": "23.2.0-alpha5",
45
+ "@vaadin/lit-renderer": "23.2.0-alpha5",
46
+ "@vaadin/vaadin-lumo-styles": "23.2.0-alpha5",
47
+ "@vaadin/vaadin-material-styles": "23.2.0-alpha5",
48
+ "@vaadin/vaadin-overlay": "23.2.0-alpha5",
49
+ "@vaadin/vaadin-themable-mixin": "23.2.0-alpha5"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@esm-bundle/chai": "^4.3.4",
51
- "@vaadin/polymer-legacy-adapter": "23.2.0-alpha2",
53
+ "@vaadin/polymer-legacy-adapter": "23.2.0-alpha5",
52
54
  "@vaadin/testing-helpers": "^0.3.2",
53
- "@vaadin/text-field": "23.2.0-alpha2",
55
+ "@vaadin/text-field": "23.2.0-alpha5",
54
56
  "lit": "^2.0.0",
55
57
  "sinon": "^13.0.2"
56
58
  },
57
- "gitHead": "c9b8113d0fa9a602f8b9cb915c1826355af2e8df"
59
+ "web-types": [
60
+ "web-types.json",
61
+ "web-types.lit.json"
62
+ ],
63
+ "gitHead": "c6247fd741d61096d75a71feda4a1faf88b6f0ce"
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
  /**
@@ -62,13 +62,17 @@ export const ComboBoxDataProviderMixin = (superClass) =>
62
62
  __placeHolder: {
63
63
  value: new ComboBoxPlaceholder(),
64
64
  },
65
+
66
+ /** @private */
67
+ __previousDataProviderFilter: {
68
+ type: String,
69
+ },
65
70
  };
66
71
  }
67
72
 
68
73
  static get observers() {
69
74
  return [
70
- '_dataProviderFilterChanged(filter, dataProvider)',
71
- '_dataProviderClearFilter(dataProvider, opened, value)',
75
+ '_dataProviderFilterChanged(filter)',
72
76
  '_warnDataProviderValue(dataProvider, value)',
73
77
  '_ensureFirstPage(opened)',
74
78
  ];
@@ -77,7 +81,6 @@ export const ComboBoxDataProviderMixin = (superClass) =>
77
81
  /** @protected */
78
82
  ready() {
79
83
  super.ready();
80
- this.clearCache();
81
84
  this._scroller.addEventListener('index-requested', (e) => {
82
85
  const index = e.detail.index;
83
86
  const currentScrollerPos = e.detail.currentScrollerPos;
@@ -101,38 +104,25 @@ export const ComboBoxDataProviderMixin = (superClass) =>
101
104
  }
102
105
 
103
106
  /** @private */
104
- _dataProviderFilterChanged() {
105
- if (!this._shouldFetchData()) {
107
+ _dataProviderFilterChanged(filter) {
108
+ if (this.__previousDataProviderFilter === undefined && filter === '') {
109
+ this.__previousDataProviderFilter = filter;
106
110
  return;
107
111
  }
108
112
 
109
- this._refreshData();
110
- }
113
+ if (this.__previousDataProviderFilter !== filter) {
114
+ this.__previousDataProviderFilter = filter;
111
115
 
112
- /** @private */
113
- _dataProviderClearFilter(dataProvider, opened, value) {
114
- // Can't depend on filter in this observer as we don't want
115
- // to clear the filter whenever it's set
116
- if (dataProvider && !this.loading && this.filter && !(opened && this.autoOpenDisabled && value === this.filter)) {
117
- this._refreshData(true);
118
- }
119
- }
116
+ this._pendingRequests = {};
117
+ // Immediately mark as loading if this refresh leads to re-fetching pages
118
+ // This prevents some issues with the properties below triggering
119
+ // observers that also rely on the loading state
120
+ this.loading = this._shouldFetchData();
121
+ // Reset size and internal loading state
122
+ this.size = undefined;
120
123
 
121
- /** @private */
122
- _refreshData(clearFilter) {
123
- // Immediately mark as loading if this refresh leads to re-fetching pages
124
- // This prevents some issues with the properties below triggering
125
- // observers that also rely on the loading state
126
- this.loading = this._shouldFetchData();
127
- // Reset size and internal loading state
128
- this.size = undefined;
129
- this._pendingRequests = {};
130
- // Clear filter if requested
131
- if (clearFilter) {
132
- this.filter = '';
124
+ this.clearCache();
133
125
  }
134
- // Clear cached pages, and reload current page if we need the data
135
- this.clearCache();
136
126
  }
137
127
 
138
128
  /** @private */
@@ -196,10 +186,13 @@ export const ComboBoxDataProviderMixin = (superClass) =>
196
186
  filteredItems.splice(params.page * params.pageSize, items.length, ...items);
197
187
  this.filteredItems = filteredItems;
198
188
 
199
- if (!this.opened && !this.hasAttribute('focused')) {
189
+ if (!this.opened && !this._isInputFocused()) {
200
190
  this._commitValue();
201
191
  }
202
- this.size = size;
192
+
193
+ if (size !== undefined) {
194
+ this.size = size;
195
+ }
203
196
 
204
197
  delete this._pendingRequests[page];
205
198
 
@@ -229,13 +222,16 @@ export const ComboBoxDataProviderMixin = (superClass) =>
229
222
  if (!this.dataProvider) {
230
223
  return;
231
224
  }
225
+
232
226
  this._pendingRequests = {};
233
227
  const filteredItems = [];
234
228
  for (let i = 0; i < (this.size || 0); i++) {
235
229
  filteredItems.push(this.__placeHolder);
236
230
  }
237
231
  this.filteredItems = filteredItems;
232
+
238
233
  if (this._shouldFetchData()) {
234
+ this._forceNextRequest = false;
239
235
  this._loadPage(0);
240
236
  } else {
241
237
  this._forceNextRequest = true;
@@ -269,6 +265,8 @@ export const ComboBoxDataProviderMixin = (superClass) =>
269
265
  this._ensureItemsOrDataProvider(() => {
270
266
  this.dataProvider = oldDataProvider;
271
267
  });
268
+
269
+ this.clearCache();
272
270
  }
273
271
 
274
272
  /** @private */
@@ -3,13 +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 { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
10
- import { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
11
- import { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
12
- 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';
13
14
  export {
14
15
  ComboBoxDataProvider,
15
16
  ComboBoxDataProviderCallback,
@@ -54,6 +55,11 @@ export type ComboBoxLightFilterChangedEvent = CustomEvent<{ value: string }>;
54
55
  */
55
56
  export type ComboBoxLightSelectedItemChangedEvent<TItem> = CustomEvent<{ value: TItem | null | undefined }>;
56
57
 
58
+ /**
59
+ * Fired whenever the field is validated.
60
+ */
61
+ export type ComboBoxLightValidatedEvent = CustomEvent<{ valid: boolean }>;
62
+
57
63
  export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
58
64
  change: ComboBoxLightChangeEvent<TItem>;
59
65
 
@@ -68,6 +74,8 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
68
74
  'value-changed': ComboBoxLightValueChangedEvent;
69
75
 
70
76
  'selected-item-changed': ComboBoxLightSelectedItemChangedEvent<TItem>;
77
+
78
+ validated: ComboBoxLightValidatedEvent;
71
79
  }
72
80
 
73
81
  /**
@@ -114,6 +122,7 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
114
122
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
115
123
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
116
124
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
125
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
117
126
  */
118
127
  declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
119
128
  /**
@@ -126,13 +135,13 @@ declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
126
135
  addEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
127
136
  type: K,
128
137
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
129
- options?: boolean | AddEventListenerOptions,
138
+ options?: AddEventListenerOptions | boolean,
130
139
  ): void;
131
140
 
132
141
  removeEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
133
142
  type: K,
134
143
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
135
- options?: boolean | EventListenerOptions,
144
+ options?: EventListenerOptions | boolean,
136
145
  ): void;
137
146
  }
138
147
 
@@ -142,7 +151,9 @@ interface ComboBoxLight<TItem = ComboBoxDefaultItem>
142
151
  KeyboardMixinClass,
143
152
  InputMixinClass,
144
153
  DisabledMixinClass,
145
- ThemableMixinClass {}
154
+ ThemableMixinClass,
155
+ ThemePropertyMixinClass,
156
+ ValidateMixinClass {}
146
157
 
147
158
  declare global {
148
159
  interface HTMLElementTagNameMap {
@@ -8,6 +8,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
10
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
11
+ import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js';
11
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
13
  import { ComboBoxDataProviderMixin } from './vaadin-combo-box-data-provider-mixin.js';
13
14
  import { ComboBoxMixin } from './vaadin-combo-box-mixin.js';
@@ -56,13 +57,15 @@ import { ComboBoxMixin } from './vaadin-combo-box-mixin.js';
56
57
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
57
58
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
58
59
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
60
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
59
61
  *
60
62
  * @extends HTMLElement
61
63
  * @mixes ComboBoxDataProviderMixin
62
64
  * @mixes ComboBoxMixin
63
65
  * @mixes ThemableMixin
66
+ * @mixes ValidateMixin
64
67
  */
65
- class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixin(PolymerElement))) {
68
+ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ValidateMixin(ThemableMixin(PolymerElement)))) {
66
69
  static get is() {
67
70
  return 'vaadin-combo-box-light';
68
71
  }
@@ -79,7 +82,6 @@ class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixi
79
82
 
80
83
  <vaadin-combo-box-overlay
81
84
  id="overlay"
82
- hidden$="[[_isOverlayHidden(filteredItems, loading)]]"
83
85
  opened="[[_overlayOpened]]"
84
86
  loading$="[[loading]]"
85
87
  theme$="[[_theme]]"
@@ -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
  /**
@@ -138,11 +138,6 @@ export declare class ComboBoxMixinClass<TItem> {
138
138
  */
139
139
  itemIdPath: string | null | undefined;
140
140
 
141
- /**
142
- * Set to true if the value is invalid.
143
- */
144
- invalid: boolean;
145
-
146
141
  protected readonly _propertyForValue: string;
147
142
 
148
143
  protected _inputElementValue: string | undefined;
@@ -170,16 +165,5 @@ export declare class ComboBoxMixinClass<TItem> {
170
165
  */
171
166
  close(): void;
172
167
 
173
- /**
174
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
175
- */
176
- validate(): boolean;
177
-
178
- /**
179
- * Returns true if the current input value satisfies all constraints (if any).
180
- * You can override this method for custom validations.
181
- */
182
- checkValidity(): boolean;
183
-
184
168
  protected _revertInputValue(): void;
185
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';
@@ -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)',
@@ -382,6 +382,25 @@ export const ComboBoxMixin = (subclass) =>
382
382
  this.opened = false;
383
383
  }
384
384
 
385
+ /**
386
+ * Override Polymer lifecycle callback to handle `filter` property change after
387
+ * the observer for `opened` property is triggered. This is needed when opening
388
+ * combo-box on user input to ensure the focused index is set correctly.
389
+ *
390
+ * @param {!Object} currentProps Current accessor values
391
+ * @param {?Object} changedProps Properties changed since the last call
392
+ * @param {?Object} oldProps Previous values for each changed property
393
+ * @protected
394
+ * @override
395
+ */
396
+ _propertiesChanged(currentProps, changedProps, oldProps) {
397
+ super._propertiesChanged(currentProps, changedProps, oldProps);
398
+
399
+ if (changedProps.filter !== undefined) {
400
+ this._filterChanged(changedProps.filter);
401
+ }
402
+ }
403
+
385
404
  /** @private */
386
405
  _initOverlay() {
387
406
  const overlay = this.$.overlay;
@@ -395,11 +414,6 @@ export const ComboBoxMixin = (subclass) =>
395
414
  // Prevent blurring the input when clicking inside the overlay
396
415
  overlay.addEventListener('mousedown', (e) => e.preventDefault());
397
416
 
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
417
  // Manual two-way binding for the overlay "opened" property
404
418
  overlay.addEventListener('opened-changed', (e) => {
405
419
  this._overlayOpened = e.detail.value;
@@ -424,9 +438,7 @@ export const ComboBoxMixin = (subclass) =>
424
438
  };
425
439
 
426
440
  // Ensure the scroller is rendered
427
- if (!this.opened) {
428
- overlay.requestContentUpdate();
429
- }
441
+ overlay.requestContentUpdate();
430
442
 
431
443
  const scroller = overlay.querySelector(scrollerTag);
432
444
 
@@ -493,6 +505,11 @@ export const ComboBoxMixin = (subclass) =>
493
505
  this._updateActiveDescendant(index);
494
506
  }
495
507
 
508
+ /** @protected */
509
+ _isInputFocused() {
510
+ return this.inputElement && isElementFocused(this.inputElement);
511
+ }
512
+
496
513
  /** @private */
497
514
  _updateActiveDescendant(index) {
498
515
  const input = this.inputElement;
@@ -519,14 +536,14 @@ export const ComboBoxMixin = (subclass) =>
519
536
  this._openedWithFocusRing = this.hasAttribute('focus-ring');
520
537
  // For touch devices, we don't want to popup virtual keyboard
521
538
  // unless input element is explicitly focused by the user.
522
- if (!this.hasAttribute('focused') && !isTouch) {
539
+ if (!this._isInputFocused() && !isTouch) {
523
540
  this.focus();
524
541
  }
525
542
 
526
543
  this.$.overlay.restoreFocusOnClose = true;
527
544
  } else {
528
545
  this._onClosed();
529
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
546
+ if (this._openedWithFocusRing && this._isInputFocused()) {
530
547
  this.setAttribute('focus-ring', '');
531
548
  }
532
549
  }
@@ -600,8 +617,6 @@ export const ComboBoxMixin = (subclass) =>
600
617
 
601
618
  /** @private */
602
619
  _onClick(e) {
603
- this._closeOnBlurIsPrevented = true;
604
-
605
620
  const path = e.composedPath();
606
621
 
607
622
  if (this._isClearButton(e)) {
@@ -611,8 +626,6 @@ export const ComboBoxMixin = (subclass) =>
611
626
  } else {
612
627
  this._onHostClick(e);
613
628
  }
614
-
615
- this._closeOnBlurIsPrevented = false;
616
629
  }
617
630
 
618
631
  /**
@@ -628,16 +641,12 @@ export const ComboBoxMixin = (subclass) =>
628
641
  if (e.key === 'Tab') {
629
642
  this.$.overlay.restoreFocusOnClose = false;
630
643
  } else if (e.key === 'ArrowDown') {
631
- this._closeOnBlurIsPrevented = true;
632
644
  this._onArrowDown();
633
- this._closeOnBlurIsPrevented = false;
634
645
 
635
646
  // Prevent caret from moving
636
647
  e.preventDefault();
637
648
  } else if (e.key === 'ArrowUp') {
638
- this._closeOnBlurIsPrevented = true;
639
649
  this._onArrowUp();
640
- this._closeOnBlurIsPrevented = false;
641
650
 
642
651
  // Prevent caret from moving
643
652
  e.preventDefault();
@@ -708,8 +717,7 @@ export const ComboBoxMixin = (subclass) =>
708
717
  // and there's no need to modify the selection range if the input isn't focused anyway.
709
718
  // This affects Safari. When the overlay is open, and then hitting tab, browser should focus
710
719
  // the next focusable element instead of the combo-box itself.
711
- // Checking the focused property here is enough instead of checking the activeElement.
712
- if (this.hasAttribute('focused')) {
720
+ if (this._isInputFocused() && this.inputElement.setSelectionRange) {
713
721
  this.inputElement.setSelectionRange(start, end);
714
722
  }
715
723
  }
@@ -819,7 +827,7 @@ export const ComboBoxMixin = (subclass) =>
819
827
  toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
820
828
  // Unfocus previously focused element if focus is not inside combo box (on touch devices)
821
829
  toggleElement.addEventListener('click', () => {
822
- if (isTouch && !this.hasAttribute('focused')) {
830
+ if (isTouch && !this._isInputFocused()) {
823
831
  document.activeElement.blur();
824
832
  }
825
833
  });
@@ -926,9 +934,7 @@ export const ComboBoxMixin = (subclass) =>
926
934
 
927
935
  this._clearSelectionRange();
928
936
 
929
- if (!this.dataProvider) {
930
- this.filter = '';
931
- }
937
+ this.filter = '';
932
938
  }
933
939
 
934
940
  /**
@@ -946,19 +952,27 @@ export const ComboBoxMixin = (subclass) =>
946
952
  * @override
947
953
  */
948
954
  _onInput(event) {
949
- if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
950
- this.open();
951
- }
955
+ const filter = this._inputElementValue;
952
956
 
953
- const value = this._inputElementValue;
954
- if (this.filter === value) {
957
+ // When opening dropdown on user input, both `opened` and `filter` properties are set.
958
+ // Perform a batched property update instead of relying on sync property observers.
959
+ // This is necessary to avoid an extra data-provider request for loading first page.
960
+ const props = {};
961
+
962
+ if (this.filter === filter) {
955
963
  // Filter and input value might get out of sync, while keyboard navigating for example.
956
964
  // Afterwards, input value might be changed to the same value as used in filtering.
957
965
  // In situation like these, we need to make sure all the filter changes handlers are run.
958
- this._filterChanged(this.filter, this.itemValuePath, this.itemLabelPath);
966
+ this._filterChanged(this.filter);
959
967
  } else {
960
- this.filter = value;
968
+ props.filter = filter;
969
+ }
970
+
971
+ if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
972
+ props.opened = true;
961
973
  }
974
+
975
+ this.setProperties(props);
962
976
  }
963
977
 
964
978
  /**
@@ -981,11 +995,7 @@ export const ComboBoxMixin = (subclass) =>
981
995
  }
982
996
 
983
997
  /** @private */
984
- _filterChanged(filter, _itemValuePath, _itemLabelPath) {
985
- if (filter === undefined) {
986
- return;
987
- }
988
-
998
+ _filterChanged(filter) {
989
999
  // Scroll to the top of the list whenever the filter changes.
990
1000
  this._scrollIntoView(0);
991
1001
 
@@ -1064,14 +1074,11 @@ export const ComboBoxMixin = (subclass) =>
1064
1074
  }
1065
1075
 
1066
1076
  if (isValidValue(value)) {
1067
- let item;
1068
1077
  if (this._getItemValue(this.selectedItem) !== value) {
1069
1078
  this._selectItemForValue(value);
1070
- } else {
1071
- item = this.selectedItem;
1072
1079
  }
1073
1080
 
1074
- if (!item && this.allowCustomValue) {
1081
+ if (!this.selectedItem && this.allowCustomValue) {
1075
1082
  this._inputElementValue = value;
1076
1083
  }
1077
1084
 
@@ -1079,6 +1086,9 @@ export const ComboBoxMixin = (subclass) =>
1079
1086
  } else {
1080
1087
  this.selectedItem = null;
1081
1088
  }
1089
+
1090
+ this.filter = '';
1091
+
1082
1092
  // In the next _detectAndDispatchChange() call, the change detection should pass
1083
1093
  this._lastCommittedValue = undefined;
1084
1094
  }
@@ -1236,9 +1246,6 @@ export const ComboBoxMixin = (subclass) =>
1236
1246
  if (this.opened) {
1237
1247
  this._focusedIndex = this.filteredItems.indexOf(e.detail.item);
1238
1248
  this.close();
1239
- } else if (this.selectedItem !== e.detail.item) {
1240
- this.selectedItem = e.detail.item;
1241
- this._detectAndDispatchChange();
1242
1249
  }
1243
1250
  }
1244
1251
 
@@ -1277,35 +1284,12 @@ export const ComboBoxMixin = (subclass) =>
1277
1284
  this._clear();
1278
1285
  }
1279
1286
 
1280
- /**
1281
- * Returns true if `value` is valid, and sets the `invalid` flag appropriately.
1282
- *
1283
- * @return {boolean} True if the value is valid and sets the `invalid` flag appropriately
1284
- */
1285
- validate() {
1286
- return !(this.invalid = !this.checkValidity());
1287
- }
1288
-
1289
- /**
1290
- * Returns true if the current input value satisfies all constraints (if any).
1291
- * You can override this method for custom validations.
1292
- *
1293
- * @return {boolean}
1294
- */
1295
- checkValidity() {
1296
- if (super.checkValidity) {
1297
- return super.checkValidity();
1298
- }
1299
-
1300
- return !this.required || !!this.value;
1301
- }
1302
-
1303
1287
  /**
1304
1288
  * Fired when the value changes.
1305
1289
  *
1306
1290
  * @event value-changed
1307
1291
  * @param {Object} detail
1308
- * @param {String} detail.value the combobox value
1292
+ * @param {String} detail.value the combobox value
1309
1293
  */
1310
1294
 
1311
1295
  /**
@@ -1313,7 +1297,7 @@ export const ComboBoxMixin = (subclass) =>
1313
1297
  *
1314
1298
  * @event selected-item-changed
1315
1299
  * @param {Object} detail
1316
- * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1300
+ * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1317
1301
  */
1318
1302
 
1319
1303
  /**