@vaadin/combo-box 23.2.0-alpha1 → 23.2.0-alpha4

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-alpha1",
3
+ "version": "23.2.0-alpha4",
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-alpha1",
40
- "@vaadin/field-base": "23.2.0-alpha1",
41
- "@vaadin/input-container": "23.2.0-alpha1",
42
- "@vaadin/item": "23.2.0-alpha1",
43
- "@vaadin/lit-renderer": "23.2.0-alpha1",
44
- "@vaadin/vaadin-lumo-styles": "23.2.0-alpha1",
45
- "@vaadin/vaadin-material-styles": "23.2.0-alpha1",
46
- "@vaadin/vaadin-overlay": "23.2.0-alpha1",
47
- "@vaadin/vaadin-themable-mixin": "23.2.0-alpha1"
41
+ "@vaadin/component-base": "23.2.0-alpha4",
42
+ "@vaadin/field-base": "23.2.0-alpha4",
43
+ "@vaadin/input-container": "23.2.0-alpha4",
44
+ "@vaadin/item": "23.2.0-alpha4",
45
+ "@vaadin/lit-renderer": "23.2.0-alpha4",
46
+ "@vaadin/vaadin-lumo-styles": "23.2.0-alpha4",
47
+ "@vaadin/vaadin-material-styles": "23.2.0-alpha4",
48
+ "@vaadin/vaadin-overlay": "23.2.0-alpha4",
49
+ "@vaadin/vaadin-themable-mixin": "23.2.0-alpha4"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@esm-bundle/chai": "^4.3.4",
51
- "@vaadin/polymer-legacy-adapter": "23.2.0-alpha1",
53
+ "@vaadin/polymer-legacy-adapter": "23.2.0-alpha4",
52
54
  "@vaadin/testing-helpers": "^0.3.2",
53
- "@vaadin/text-field": "23.2.0-alpha1",
55
+ "@vaadin/text-field": "23.2.0-alpha4",
54
56
  "lit": "^2.0.0",
55
57
  "sinon": "^13.0.2"
56
58
  },
57
- "gitHead": "f226a2976c270d3d53c824f6e0a740a5d3382d91"
59
+ "web-types": [
60
+ "web-types.json",
61
+ "web-types.lit.json"
62
+ ],
63
+ "gitHead": "cbf5f1d0f38ac9b81c65cf9ef5660182e176e598"
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,13 @@
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 { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
12
+ import type { ComboBoxDefaultItem, ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
13
13
  export {
14
14
  ComboBoxDataProvider,
15
15
  ComboBoxDataProviderCallback,
@@ -54,6 +54,11 @@ export type ComboBoxLightFilterChangedEvent = CustomEvent<{ value: string }>;
54
54
  */
55
55
  export type ComboBoxLightSelectedItemChangedEvent<TItem> = CustomEvent<{ value: TItem | null | undefined }>;
56
56
 
57
+ /**
58
+ * Fired whenever the field is validated.
59
+ */
60
+ export type ComboBoxLightValidatedEvent = CustomEvent<{ valid: boolean }>;
61
+
57
62
  export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
58
63
  change: ComboBoxLightChangeEvent<TItem>;
59
64
 
@@ -68,6 +73,8 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
68
73
  'value-changed': ComboBoxLightValueChangedEvent;
69
74
 
70
75
  'selected-item-changed': ComboBoxLightSelectedItemChangedEvent<TItem>;
76
+
77
+ validated: ComboBoxLightValidatedEvent;
71
78
  }
72
79
 
73
80
  /**
@@ -114,6 +121,7 @@ export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap {
114
121
  * @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
115
122
  * @fires {CustomEvent} selected-item-changed - Fired when the `selectedItem` property changes.
116
123
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
124
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
117
125
  */
118
126
  declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
119
127
  /**
@@ -126,13 +134,13 @@ declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement {
126
134
  addEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
127
135
  type: K,
128
136
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
129
- options?: boolean | AddEventListenerOptions,
137
+ options?: AddEventListenerOptions | boolean,
130
138
  ): void;
131
139
 
132
140
  removeEventListener<K extends keyof ComboBoxLightEventMap<TItem>>(
133
141
  type: K,
134
142
  listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void,
135
- options?: boolean | EventListenerOptions,
143
+ options?: EventListenerOptions | boolean,
136
144
  ): void;
137
145
  }
138
146
 
@@ -142,7 +150,8 @@ interface ComboBoxLight<TItem = ComboBoxDefaultItem>
142
150
  KeyboardMixinClass,
143
151
  InputMixinClass,
144
152
  DisabledMixinClass,
145
- ThemableMixinClass {}
153
+ ThemableMixinClass,
154
+ ValidateMixinClass {}
146
155
 
147
156
  declare global {
148
157
  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
  }
@@ -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;
@@ -442,6 +456,11 @@ export const ComboBoxMixin = (subclass) =>
442
456
  // eslint-disable-next-line max-params
443
457
  _updateScroller(scroller, items, opened, loading, selectedItem, itemIdPath, focusedIndex, renderer, theme) {
444
458
  if (scroller) {
459
+ if (opened) {
460
+ scroller.style.maxHeight =
461
+ getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
462
+ }
463
+
445
464
  scroller.setProperties({
446
465
  items: opened ? items : [],
447
466
  opened,
@@ -488,6 +507,11 @@ export const ComboBoxMixin = (subclass) =>
488
507
  this._updateActiveDescendant(index);
489
508
  }
490
509
 
510
+ /** @protected */
511
+ _isInputFocused() {
512
+ return this.inputElement && isElementFocused(this.inputElement);
513
+ }
514
+
491
515
  /** @private */
492
516
  _updateActiveDescendant(index) {
493
517
  const input = this.inputElement;
@@ -514,14 +538,14 @@ export const ComboBoxMixin = (subclass) =>
514
538
  this._openedWithFocusRing = this.hasAttribute('focus-ring');
515
539
  // For touch devices, we don't want to popup virtual keyboard
516
540
  // unless input element is explicitly focused by the user.
517
- if (!this.hasAttribute('focused') && !isTouch) {
541
+ if (!this._isInputFocused() && !isTouch) {
518
542
  this.focus();
519
543
  }
520
544
 
521
545
  this.$.overlay.restoreFocusOnClose = true;
522
546
  } else {
523
547
  this._onClosed();
524
- if (this._openedWithFocusRing && this.hasAttribute('focused')) {
548
+ if (this._openedWithFocusRing && this._isInputFocused()) {
525
549
  this.setAttribute('focus-ring', '');
526
550
  }
527
551
  }
@@ -595,8 +619,6 @@ export const ComboBoxMixin = (subclass) =>
595
619
 
596
620
  /** @private */
597
621
  _onClick(e) {
598
- this._closeOnBlurIsPrevented = true;
599
-
600
622
  const path = e.composedPath();
601
623
 
602
624
  if (this._isClearButton(e)) {
@@ -606,8 +628,6 @@ export const ComboBoxMixin = (subclass) =>
606
628
  } else {
607
629
  this._onHostClick(e);
608
630
  }
609
-
610
- this._closeOnBlurIsPrevented = false;
611
631
  }
612
632
 
613
633
  /**
@@ -623,16 +643,12 @@ export const ComboBoxMixin = (subclass) =>
623
643
  if (e.key === 'Tab') {
624
644
  this.$.overlay.restoreFocusOnClose = false;
625
645
  } else if (e.key === 'ArrowDown') {
626
- this._closeOnBlurIsPrevented = true;
627
646
  this._onArrowDown();
628
- this._closeOnBlurIsPrevented = false;
629
647
 
630
648
  // Prevent caret from moving
631
649
  e.preventDefault();
632
650
  } else if (e.key === 'ArrowUp') {
633
- this._closeOnBlurIsPrevented = true;
634
651
  this._onArrowUp();
635
- this._closeOnBlurIsPrevented = false;
636
652
 
637
653
  // Prevent caret from moving
638
654
  e.preventDefault();
@@ -703,8 +719,7 @@ export const ComboBoxMixin = (subclass) =>
703
719
  // and there's no need to modify the selection range if the input isn't focused anyway.
704
720
  // This affects Safari. When the overlay is open, and then hitting tab, browser should focus
705
721
  // 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')) {
722
+ if (this._isInputFocused() && this.inputElement.setSelectionRange) {
708
723
  this.inputElement.setSelectionRange(start, end);
709
724
  }
710
725
  }
@@ -814,7 +829,7 @@ export const ComboBoxMixin = (subclass) =>
814
829
  toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
815
830
  // Unfocus previously focused element if focus is not inside combo box (on touch devices)
816
831
  toggleElement.addEventListener('click', () => {
817
- if (isTouch && !this.hasAttribute('focused')) {
832
+ if (isTouch && !this._isInputFocused()) {
818
833
  document.activeElement.blur();
819
834
  }
820
835
  });
@@ -849,10 +864,6 @@ export const ComboBoxMixin = (subclass) =>
849
864
  _onOpened() {
850
865
  // Defer scroll position adjustment to improve performance.
851
866
  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
867
  this._scrollIntoView(this._focusedIndex);
857
868
 
858
869
  // Set attribute after the items are rendered when overlay is opened for the first time.
@@ -925,9 +936,7 @@ export const ComboBoxMixin = (subclass) =>
925
936
 
926
937
  this._clearSelectionRange();
927
938
 
928
- if (!this.dataProvider) {
929
- this.filter = '';
930
- }
939
+ this.filter = '';
931
940
  }
932
941
 
933
942
  /**
@@ -945,19 +954,27 @@ export const ComboBoxMixin = (subclass) =>
945
954
  * @override
946
955
  */
947
956
  _onInput(event) {
948
- if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
949
- this.open();
950
- }
957
+ const filter = this._inputElementValue;
951
958
 
952
- const value = this._inputElementValue;
953
- if (this.filter === value) {
959
+ // When opening dropdown on user input, both `opened` and `filter` properties are set.
960
+ // Perform a batched property update instead of relying on sync property observers.
961
+ // This is necessary to avoid an extra data-provider request for loading first page.
962
+ const props = {};
963
+
964
+ if (this.filter === filter) {
954
965
  // Filter and input value might get out of sync, while keyboard navigating for example.
955
966
  // Afterwards, input value might be changed to the same value as used in filtering.
956
967
  // 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);
968
+ this._filterChanged(this.filter);
958
969
  } else {
959
- this.filter = value;
970
+ props.filter = filter;
960
971
  }
972
+
973
+ if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
974
+ props.opened = true;
975
+ }
976
+
977
+ this.setProperties(props);
961
978
  }
962
979
 
963
980
  /**
@@ -980,11 +997,7 @@ export const ComboBoxMixin = (subclass) =>
980
997
  }
981
998
 
982
999
  /** @private */
983
- _filterChanged(filter, _itemValuePath, _itemLabelPath) {
984
- if (filter === undefined) {
985
- return;
986
- }
987
-
1000
+ _filterChanged(filter) {
988
1001
  // Scroll to the top of the list whenever the filter changes.
989
1002
  this._scrollIntoView(0);
990
1003
 
@@ -1063,14 +1076,11 @@ export const ComboBoxMixin = (subclass) =>
1063
1076
  }
1064
1077
 
1065
1078
  if (isValidValue(value)) {
1066
- let item;
1067
1079
  if (this._getItemValue(this.selectedItem) !== value) {
1068
1080
  this._selectItemForValue(value);
1069
- } else {
1070
- item = this.selectedItem;
1071
1081
  }
1072
1082
 
1073
- if (!item && this.allowCustomValue) {
1083
+ if (!this.selectedItem && this.allowCustomValue) {
1074
1084
  this._inputElementValue = value;
1075
1085
  }
1076
1086
 
@@ -1078,6 +1088,9 @@ export const ComboBoxMixin = (subclass) =>
1078
1088
  } else {
1079
1089
  this.selectedItem = null;
1080
1090
  }
1091
+
1092
+ this.filter = '';
1093
+
1081
1094
  // In the next _detectAndDispatchChange() call, the change detection should pass
1082
1095
  this._lastCommittedValue = undefined;
1083
1096
  }
@@ -1276,35 +1289,12 @@ export const ComboBoxMixin = (subclass) =>
1276
1289
  this._clear();
1277
1290
  }
1278
1291
 
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
1292
  /**
1303
1293
  * Fired when the value changes.
1304
1294
  *
1305
1295
  * @event value-changed
1306
1296
  * @param {Object} detail
1307
- * @param {String} detail.value the combobox value
1297
+ * @param {String} detail.value the combobox value
1308
1298
  */
1309
1299
 
1310
1300
  /**
@@ -1312,7 +1302,7 @@ export const ComboBoxMixin = (subclass) =>
1312
1302
  *
1313
1303
  * @event selected-item-changed
1314
1304
  * @param {Object} detail
1315
- * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1305
+ * @param {Object|String} detail.value the selected item. Type is the same as the type of `items`.
1316
1306
  */
1317
1307
 
1318
1308
  /**
@@ -250,7 +250,7 @@ export class ComboBoxScroller extends PolymerElement {
250
250
  /** @private */
251
251
  __loadingChanged() {
252
252
  if (this.__virtualizer) {
253
- this.requestContentUpdate();
253
+ setTimeout(() => this.requestContentUpdate());
254
254
  }
255
255
  }
256
256