@vaadin/combo-box 24.8.4 → 25.0.0-alpha10
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/README.md +0 -23
- package/package.json +17 -19
- package/src/styles/vaadin-combo-box-base-styles.d.ts +8 -0
- package/src/styles/vaadin-combo-box-base-styles.js +17 -0
- package/src/styles/vaadin-combo-box-core-styles.d.ts +8 -0
- package/src/styles/vaadin-combo-box-core-styles.js +12 -0
- package/src/styles/vaadin-combo-box-overlay-base-styles.js +46 -0
- package/src/styles/vaadin-combo-box-overlay-core-styles.js +18 -0
- package/src/styles/vaadin-combo-box-scroller-base-styles.js +29 -0
- package/src/styles/vaadin-combo-box-scroller-core-styles.js +27 -0
- package/src/vaadin-combo-box-base-mixin.d.ts +56 -0
- package/src/vaadin-combo-box-base-mixin.js +776 -0
- package/src/vaadin-combo-box-data-provider-mixin.js +17 -32
- package/src/vaadin-combo-box-item-mixin.js +6 -1
- package/src/vaadin-combo-box-item.js +17 -16
- package/src/vaadin-combo-box-items-mixin.d.ts +53 -0
- package/src/vaadin-combo-box-items-mixin.js +275 -0
- package/src/vaadin-combo-box-mixin.d.ts +3 -72
- package/src/vaadin-combo-box-mixin.js +84 -922
- package/src/vaadin-combo-box-overlay-mixin.js +1 -22
- package/src/vaadin-combo-box-overlay.js +15 -22
- package/src/vaadin-combo-box-scroller.js +10 -26
- package/src/vaadin-combo-box.d.ts +12 -14
- package/src/vaadin-combo-box.js +81 -53
- package/web-types.json +51 -536
- package/web-types.lit.json +17 -262
- package/src/vaadin-combo-box-light-mixin.d.ts +0 -26
- package/src/vaadin-combo-box-light-mixin.js +0 -131
- package/src/vaadin-combo-box-light.d.ts +0 -161
- package/src/vaadin-combo-box-light.js +0 -94
- package/src/vaadin-lit-combo-box-item.js +0 -68
- package/src/vaadin-lit-combo-box-light.js +0 -57
- package/src/vaadin-lit-combo-box-overlay.js +0 -60
- package/src/vaadin-lit-combo-box-scroller.js +0 -59
- package/src/vaadin-lit-combo-box.js +0 -169
- package/theme/lumo/vaadin-combo-box-light.d.ts +0 -3
- package/theme/lumo/vaadin-combo-box-light.js +0 -3
- package/theme/lumo/vaadin-lit-combo-box-light.d.ts +0 -3
- package/theme/lumo/vaadin-lit-combo-box-light.js +0 -3
- package/theme/lumo/vaadin-lit-combo-box.d.ts +0 -4
- package/theme/lumo/vaadin-lit-combo-box.js +0 -4
- package/theme/material/vaadin-combo-box-item-styles.d.ts +0 -5
- package/theme/material/vaadin-combo-box-item-styles.js +0 -20
- package/theme/material/vaadin-combo-box-light.d.ts +0 -3
- package/theme/material/vaadin-combo-box-light.js +0 -3
- package/theme/material/vaadin-combo-box-overlay-styles.d.ts +0 -4
- package/theme/material/vaadin-combo-box-overlay-styles.js +0 -51
- package/theme/material/vaadin-combo-box-styles.d.ts +0 -3
- package/theme/material/vaadin-combo-box-styles.js +0 -21
- package/theme/material/vaadin-combo-box.d.ts +0 -4
- package/theme/material/vaadin-combo-box.js +0 -4
- package/theme/material/vaadin-lit-combo-box-light.d.ts +0 -3
- package/theme/material/vaadin-lit-combo-box-light.js +0 -3
- package/theme/material/vaadin-lit-combo-box.d.ts +0 -4
- package/theme/material/vaadin-lit-combo-box.js +0 -4
- package/vaadin-combo-box-light.d.ts +0 -1
- package/vaadin-combo-box-light.js +0 -2
- package/vaadin-lit-combo-box-light.d.ts +0 -1
- package/vaadin-lit-combo-box-light.js +0 -2
- package/vaadin-lit-combo-box.d.ts +0 -1
- package/vaadin-lit-combo-box.js +0 -2
|
@@ -3,19 +3,8 @@
|
|
|
3
3
|
* Copyright (c) 2015 - 2025 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
|
|
7
|
-
import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
|
|
8
|
-
import { isElementFocused, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
9
|
-
import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
|
|
10
|
-
import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
|
|
11
|
-
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
12
|
-
import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
|
|
13
|
-
import { get } from '@vaadin/component-base/src/path-utils.js';
|
|
14
|
-
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
15
|
-
import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
|
|
16
6
|
import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js';
|
|
17
|
-
import {
|
|
18
|
-
import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
|
|
7
|
+
import { ComboBoxItemsMixin } from './vaadin-combo-box-items-mixin.js';
|
|
19
8
|
|
|
20
9
|
/**
|
|
21
10
|
* Checks if the value is supported as an item value in this control.
|
|
@@ -27,73 +16,16 @@ function isValidValue(value) {
|
|
|
27
16
|
return value !== undefined && value !== null;
|
|
28
17
|
}
|
|
29
18
|
|
|
30
|
-
/**
|
|
31
|
-
* Returns the index of the first item that satisfies the provided testing function
|
|
32
|
-
* ignoring placeholder items.
|
|
33
|
-
*
|
|
34
|
-
* @param {Array<ComboBoxItem | string>} items
|
|
35
|
-
* @param {Function} callback
|
|
36
|
-
* @return {number}
|
|
37
|
-
*/
|
|
38
|
-
function findItemIndex(items, callback) {
|
|
39
|
-
return items.findIndex((item) => {
|
|
40
|
-
if (item instanceof ComboBoxPlaceholder) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return callback(item);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
19
|
/**
|
|
49
20
|
* @polymerMixin
|
|
50
|
-
* @mixes
|
|
21
|
+
* @mixes ComboBoxItemsMixin
|
|
51
22
|
* @mixes ValidateMixin
|
|
52
|
-
* @
|
|
53
|
-
* @mixes InputMixin
|
|
54
|
-
* @mixes KeyboardMixin
|
|
55
|
-
* @mixes FocusMixin
|
|
56
|
-
* @mixes OverlayClassMixin
|
|
57
|
-
* @param {function(new:HTMLElement)} subclass
|
|
23
|
+
* @param {function(new:HTMLElement)} superClass
|
|
58
24
|
*/
|
|
59
|
-
export const ComboBoxMixin = (
|
|
60
|
-
class ComboBoxMixinClass extends
|
|
61
|
-
ControllerMixin(ValidateMixin(FocusMixin(KeyboardMixin(InputMixin(DisabledMixin(subclass)))))),
|
|
62
|
-
) {
|
|
25
|
+
export const ComboBoxMixin = (superClass) =>
|
|
26
|
+
class ComboBoxMixinClass extends ValidateMixin(ComboBoxItemsMixin(superClass)) {
|
|
63
27
|
static get properties() {
|
|
64
28
|
return {
|
|
65
|
-
/**
|
|
66
|
-
* True if the dropdown is open, false otherwise.
|
|
67
|
-
* @type {boolean}
|
|
68
|
-
*/
|
|
69
|
-
opened: {
|
|
70
|
-
type: Boolean,
|
|
71
|
-
notify: true,
|
|
72
|
-
value: false,
|
|
73
|
-
reflectToAttribute: true,
|
|
74
|
-
sync: true,
|
|
75
|
-
observer: '_openedChanged',
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Set true to prevent the overlay from opening automatically.
|
|
80
|
-
* @attr {boolean} auto-open-disabled
|
|
81
|
-
*/
|
|
82
|
-
autoOpenDisabled: {
|
|
83
|
-
type: Boolean,
|
|
84
|
-
sync: true,
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* When present, it specifies that the field is read-only.
|
|
89
|
-
* @type {boolean}
|
|
90
|
-
*/
|
|
91
|
-
readonly: {
|
|
92
|
-
type: Boolean,
|
|
93
|
-
value: false,
|
|
94
|
-
reflectToAttribute: true,
|
|
95
|
-
},
|
|
96
|
-
|
|
97
29
|
/**
|
|
98
30
|
* Custom function for rendering the content of every item.
|
|
99
31
|
* Receives three arguments:
|
|
@@ -111,17 +43,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
111
43
|
sync: true,
|
|
112
44
|
},
|
|
113
45
|
|
|
114
|
-
/**
|
|
115
|
-
* A full set of items to filter the visible options from.
|
|
116
|
-
* The items can be of either `String` or `Object` type.
|
|
117
|
-
* @type {!Array<!ComboBoxItem | string> | undefined}
|
|
118
|
-
*/
|
|
119
|
-
items: {
|
|
120
|
-
type: Array,
|
|
121
|
-
sync: true,
|
|
122
|
-
observer: '_itemsChanged',
|
|
123
|
-
},
|
|
124
|
-
|
|
125
46
|
/**
|
|
126
47
|
* If `true`, the user can input a value that is not present in the items list.
|
|
127
48
|
* `value` property will be set to the input value in this case.
|
|
@@ -135,24 +56,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
135
56
|
value: false,
|
|
136
57
|
},
|
|
137
58
|
|
|
138
|
-
/**
|
|
139
|
-
* A subset of items, filtered based on the user input. Filtered items
|
|
140
|
-
* can be assigned directly to omit the internal filtering functionality.
|
|
141
|
-
* The items can be of either `String` or `Object` type.
|
|
142
|
-
* @type {!Array<!ComboBoxItem | string> | undefined}
|
|
143
|
-
*/
|
|
144
|
-
filteredItems: {
|
|
145
|
-
type: Array,
|
|
146
|
-
observer: '_filteredItemsChanged',
|
|
147
|
-
sync: true,
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Used to detect user value changes and fire `change` events.
|
|
152
|
-
* @private
|
|
153
|
-
*/
|
|
154
|
-
_lastCommittedValue: String,
|
|
155
|
-
|
|
156
59
|
/**
|
|
157
60
|
* When set to `true`, "loading" attribute is added to host and the overlay element.
|
|
158
61
|
* @type {boolean}
|
|
@@ -164,28 +67,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
164
67
|
sync: true,
|
|
165
68
|
},
|
|
166
69
|
|
|
167
|
-
/**
|
|
168
|
-
* @type {number}
|
|
169
|
-
* @protected
|
|
170
|
-
*/
|
|
171
|
-
_focusedIndex: {
|
|
172
|
-
type: Number,
|
|
173
|
-
observer: '_focusedIndexChanged',
|
|
174
|
-
value: -1,
|
|
175
|
-
sync: true,
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Filtering string the user has typed into the input field.
|
|
180
|
-
* @type {string}
|
|
181
|
-
*/
|
|
182
|
-
filter: {
|
|
183
|
-
type: String,
|
|
184
|
-
value: '',
|
|
185
|
-
notify: true,
|
|
186
|
-
sync: true,
|
|
187
|
-
},
|
|
188
|
-
|
|
189
70
|
/**
|
|
190
71
|
* The selected item from the `items` array.
|
|
191
72
|
* @type {ComboBoxItem | string | undefined}
|
|
@@ -206,39 +87,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
206
87
|
type: Object,
|
|
207
88
|
},
|
|
208
89
|
|
|
209
|
-
/**
|
|
210
|
-
* Path for label of the item. If `items` is an array of objects, the
|
|
211
|
-
* `itemLabelPath` is used to fetch the displayed string label for each
|
|
212
|
-
* item.
|
|
213
|
-
*
|
|
214
|
-
* The item label is also used for matching items when processing user
|
|
215
|
-
* input, i.e., for filtering and selecting items.
|
|
216
|
-
* @attr {string} item-label-path
|
|
217
|
-
* @type {string}
|
|
218
|
-
*/
|
|
219
|
-
itemLabelPath: {
|
|
220
|
-
type: String,
|
|
221
|
-
value: 'label',
|
|
222
|
-
observer: '_itemLabelPathChanged',
|
|
223
|
-
sync: true,
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Path for the value of the item. If `items` is an array of objects, the
|
|
228
|
-
* `itemValuePath:` is used to fetch the string value for the selected
|
|
229
|
-
* item.
|
|
230
|
-
*
|
|
231
|
-
* The item value is used in the `value` property of the combo box,
|
|
232
|
-
* to provide the form value.
|
|
233
|
-
* @attr {string} item-value-path
|
|
234
|
-
* @type {string}
|
|
235
|
-
*/
|
|
236
|
-
itemValuePath: {
|
|
237
|
-
type: String,
|
|
238
|
-
value: 'value',
|
|
239
|
-
sync: true,
|
|
240
|
-
},
|
|
241
|
-
|
|
242
90
|
/**
|
|
243
91
|
* Path for the id of the item. If `items` is an array of objects,
|
|
244
92
|
* the `itemIdPath` is used to compare and identify the same item
|
|
@@ -251,40 +99,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
251
99
|
sync: true,
|
|
252
100
|
},
|
|
253
101
|
|
|
254
|
-
/**
|
|
255
|
-
* @type {!HTMLElement | undefined}
|
|
256
|
-
* @protected
|
|
257
|
-
*/
|
|
258
|
-
_toggleElement: {
|
|
259
|
-
type: Object,
|
|
260
|
-
observer: '_toggleElementChanged',
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Set of items to be rendered in the dropdown.
|
|
265
|
-
* @protected
|
|
266
|
-
*/
|
|
267
|
-
_dropdownItems: {
|
|
268
|
-
type: Array,
|
|
269
|
-
sync: true,
|
|
270
|
-
},
|
|
271
|
-
|
|
272
|
-
/** @private */
|
|
273
|
-
_closeOnBlurIsPrevented: Boolean,
|
|
274
|
-
|
|
275
|
-
/** @private */
|
|
276
|
-
_scroller: {
|
|
277
|
-
type: Object,
|
|
278
|
-
sync: true,
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
/** @private */
|
|
282
|
-
_overlayOpened: {
|
|
283
|
-
type: Boolean,
|
|
284
|
-
sync: true,
|
|
285
|
-
observer: '_overlayOpenedChanged',
|
|
286
|
-
},
|
|
287
|
-
|
|
288
102
|
/** @private */
|
|
289
103
|
__keepOverlayOpened: {
|
|
290
104
|
type: Boolean,
|
|
@@ -295,105 +109,23 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
295
109
|
|
|
296
110
|
static get observers() {
|
|
297
111
|
return [
|
|
298
|
-
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
|
|
299
112
|
'_openedOrItemsChanged(opened, _dropdownItems, loading, __keepOverlayOpened)',
|
|
300
|
-
'
|
|
113
|
+
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
|
|
114
|
+
'_updateScroller(opened, _dropdownItems, _focusedIndex, _theme)',
|
|
301
115
|
];
|
|
302
116
|
}
|
|
303
117
|
|
|
304
|
-
constructor() {
|
|
305
|
-
super();
|
|
306
|
-
this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this);
|
|
307
|
-
this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this);
|
|
308
|
-
this._boundOnClick = this._onClick.bind(this);
|
|
309
|
-
this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this);
|
|
310
|
-
this._boundOnTouchend = this._onTouchend.bind(this);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Tag name prefix used by scroller and items.
|
|
315
|
-
* @protected
|
|
316
|
-
* @return {string}
|
|
317
|
-
*/
|
|
318
|
-
get _tagNamePrefix() {
|
|
319
|
-
return 'vaadin-combo-box';
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Get a reference to the native `<input>` element.
|
|
324
|
-
* Override to provide a custom input.
|
|
325
|
-
* @protected
|
|
326
|
-
* @return {HTMLInputElement | undefined}
|
|
327
|
-
*/
|
|
328
|
-
get _nativeInput() {
|
|
329
|
-
return this.inputElement;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Override method inherited from `InputMixin`
|
|
334
|
-
* to customize the input element.
|
|
335
|
-
* @protected
|
|
336
|
-
* @override
|
|
337
|
-
*/
|
|
338
|
-
_inputElementChanged(inputElement) {
|
|
339
|
-
super._inputElementChanged(inputElement);
|
|
340
|
-
|
|
341
|
-
const input = this._nativeInput;
|
|
342
|
-
|
|
343
|
-
if (input) {
|
|
344
|
-
input.autocomplete = 'off';
|
|
345
|
-
input.autocapitalize = 'off';
|
|
346
|
-
|
|
347
|
-
input.setAttribute('role', 'combobox');
|
|
348
|
-
input.setAttribute('aria-autocomplete', 'list');
|
|
349
|
-
input.setAttribute('aria-expanded', !!this.opened);
|
|
350
|
-
|
|
351
|
-
// Disable the macOS Safari spell check auto corrections.
|
|
352
|
-
input.setAttribute('spellcheck', 'false');
|
|
353
|
-
|
|
354
|
-
// Disable iOS autocorrect suggestions.
|
|
355
|
-
input.setAttribute('autocorrect', 'off');
|
|
356
|
-
|
|
357
|
-
this._revertInputValueToValue();
|
|
358
|
-
|
|
359
|
-
if (this.clearElement) {
|
|
360
|
-
this.clearElement.addEventListener('mousedown', this._boundOnClearButtonMouseDown);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
118
|
/** @protected */
|
|
366
119
|
ready() {
|
|
367
120
|
super.ready();
|
|
368
121
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Used to detect user value changes and fire `change` events.
|
|
124
|
+
* Do not define in `properties` to avoid triggering updates.
|
|
125
|
+
* @type {string}
|
|
126
|
+
* @protected
|
|
127
|
+
*/
|
|
372
128
|
this._lastCommittedValue = this.value;
|
|
373
|
-
|
|
374
|
-
this.addEventListener('click', this._boundOnClick);
|
|
375
|
-
this.addEventListener('touchend', this._boundOnTouchend);
|
|
376
|
-
|
|
377
|
-
const bringToFrontListener = () => {
|
|
378
|
-
requestAnimationFrame(() => {
|
|
379
|
-
this._overlayElement.bringToFront();
|
|
380
|
-
});
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
this.addEventListener('mousedown', bringToFrontListener);
|
|
384
|
-
this.addEventListener('touchstart', bringToFrontListener);
|
|
385
|
-
|
|
386
|
-
processTemplates(this);
|
|
387
|
-
|
|
388
|
-
this.addController(new VirtualKeyboardController(this));
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/** @protected */
|
|
392
|
-
disconnectedCallback() {
|
|
393
|
-
super.disconnectedCallback();
|
|
394
|
-
|
|
395
|
-
// Close the overlay on detach
|
|
396
|
-
this.close();
|
|
397
129
|
}
|
|
398
130
|
|
|
399
131
|
/**
|
|
@@ -415,135 +147,32 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
415
147
|
}
|
|
416
148
|
|
|
417
149
|
/**
|
|
418
|
-
* Opens the dropdown list.
|
|
419
|
-
*/
|
|
420
|
-
open() {
|
|
421
|
-
// Prevent _open() being called when input is disabled or read-only
|
|
422
|
-
if (!this.disabled && !this.readonly) {
|
|
423
|
-
this.opened = true;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Closes the dropdown list.
|
|
429
|
-
*/
|
|
430
|
-
close() {
|
|
431
|
-
this.opened = false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Override Polymer lifecycle callback to handle `filter` property change after
|
|
436
|
-
* the observer for `opened` property is triggered. This is needed when opening
|
|
437
|
-
* combo-box on user input to ensure the focused index is set correctly.
|
|
438
|
-
*
|
|
439
|
-
* @param {!Object} currentProps Current accessor values
|
|
440
|
-
* @param {?Object} changedProps Properties changed since the last call
|
|
441
|
-
* @param {?Object} oldProps Previous values for each changed property
|
|
442
|
-
* @protected
|
|
443
|
-
* @override
|
|
444
|
-
*/
|
|
445
|
-
_propertiesChanged(currentProps, changedProps, oldProps) {
|
|
446
|
-
super._propertiesChanged(currentProps, changedProps, oldProps);
|
|
447
|
-
|
|
448
|
-
if (changedProps.filter !== undefined) {
|
|
449
|
-
this._filterChanged(changedProps.filter);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Override LitElement lifecycle callback to handle filter property change.
|
|
455
150
|
* @param {Object} props
|
|
456
151
|
* @protected
|
|
457
152
|
*/
|
|
458
153
|
updated(props) {
|
|
459
154
|
super.updated(props);
|
|
460
155
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/** @private */
|
|
467
|
-
_initOverlay() {
|
|
468
|
-
const overlay = this.$.overlay;
|
|
469
|
-
|
|
470
|
-
// Store instance for detecting "dir" attribute on opening
|
|
471
|
-
overlay._comboBox = this;
|
|
472
|
-
|
|
473
|
-
overlay.addEventListener('touchend', this._boundOnOverlayTouchAction);
|
|
474
|
-
overlay.addEventListener('touchmove', this._boundOnOverlayTouchAction);
|
|
475
|
-
|
|
476
|
-
// Prevent blurring the input when clicking inside the overlay
|
|
477
|
-
overlay.addEventListener('mousedown', (e) => e.preventDefault());
|
|
478
|
-
|
|
479
|
-
// Manual two-way binding for the overlay "opened" property
|
|
480
|
-
overlay.addEventListener('opened-changed', (e) => {
|
|
481
|
-
this._overlayOpened = e.detail.value;
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
this._overlayElement = overlay;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Create and initialize the scroller element.
|
|
489
|
-
* Override to provide custom host reference.
|
|
490
|
-
*
|
|
491
|
-
* @protected
|
|
492
|
-
*/
|
|
493
|
-
_initScroller(host) {
|
|
494
|
-
const scroller = document.createElement(`${this._tagNamePrefix}-scroller`);
|
|
495
|
-
|
|
496
|
-
scroller.owner = host || this;
|
|
497
|
-
scroller.getItemLabel = this._getItemLabel.bind(this);
|
|
498
|
-
scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
|
|
499
|
-
|
|
500
|
-
const overlay = this._overlayElement;
|
|
501
|
-
|
|
502
|
-
overlay.renderer = (root) => {
|
|
503
|
-
if (!root.innerHTML) {
|
|
504
|
-
root.appendChild(scroller);
|
|
156
|
+
['loading', 'itemIdPath', 'itemClassNameGenerator', 'renderer', 'selectedItem'].forEach((prop) => {
|
|
157
|
+
if (props.has(prop)) {
|
|
158
|
+
this._scroller[prop] = this[prop];
|
|
505
159
|
}
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
// Ensure the scroller is rendered
|
|
509
|
-
overlay.requestContentUpdate();
|
|
510
|
-
|
|
511
|
-
// Trigger the observer to set properties
|
|
512
|
-
this._scroller = scroller;
|
|
160
|
+
});
|
|
513
161
|
}
|
|
514
162
|
|
|
515
163
|
/** @private */
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
opened,
|
|
521
|
-
loading,
|
|
522
|
-
selectedItem,
|
|
523
|
-
itemIdPath,
|
|
524
|
-
focusedIndex,
|
|
525
|
-
renderer,
|
|
526
|
-
theme,
|
|
527
|
-
itemClassNameGenerator,
|
|
528
|
-
) {
|
|
529
|
-
if (scroller) {
|
|
530
|
-
if (opened) {
|
|
531
|
-
scroller.style.maxHeight =
|
|
532
|
-
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
scroller.setProperties({
|
|
536
|
-
items: opened ? items : [],
|
|
537
|
-
opened,
|
|
538
|
-
loading,
|
|
539
|
-
selectedItem,
|
|
540
|
-
itemIdPath,
|
|
541
|
-
focusedIndex,
|
|
542
|
-
renderer,
|
|
543
|
-
theme,
|
|
544
|
-
itemClassNameGenerator,
|
|
545
|
-
});
|
|
164
|
+
_updateScroller(opened, items, focusedIndex, theme) {
|
|
165
|
+
if (opened) {
|
|
166
|
+
this._scroller.style.maxHeight =
|
|
167
|
+
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
|
|
546
168
|
}
|
|
169
|
+
|
|
170
|
+
this._scroller.setProperties({
|
|
171
|
+
items: opened ? items : [],
|
|
172
|
+
opened,
|
|
173
|
+
focusedIndex,
|
|
174
|
+
theme,
|
|
175
|
+
});
|
|
547
176
|
}
|
|
548
177
|
|
|
549
178
|
/** @private */
|
|
@@ -553,258 +182,39 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
553
182
|
this._overlayOpened = opened && (keepOverlayOpened || loading || !!(items && items.length));
|
|
554
183
|
}
|
|
555
184
|
|
|
556
|
-
/** @private */
|
|
557
|
-
_overlayOpenedChanged(opened, wasOpened) {
|
|
558
|
-
if (opened) {
|
|
559
|
-
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
|
|
560
|
-
|
|
561
|
-
this._onOpened();
|
|
562
|
-
} else if (wasOpened && this._dropdownItems && this._dropdownItems.length) {
|
|
563
|
-
this.close();
|
|
564
|
-
|
|
565
|
-
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/** @private */
|
|
570
|
-
_focusedIndexChanged(index, oldIndex) {
|
|
571
|
-
if (oldIndex === undefined) {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
this._updateActiveDescendant(index);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/** @protected */
|
|
578
|
-
_isInputFocused() {
|
|
579
|
-
return this.inputElement && isElementFocused(this.inputElement);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/** @private */
|
|
583
|
-
_updateActiveDescendant(index) {
|
|
584
|
-
const input = this._nativeInput;
|
|
585
|
-
if (!input) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const item = this._getItemElements().find((el) => el.index === index);
|
|
590
|
-
if (item) {
|
|
591
|
-
input.setAttribute('aria-activedescendant', item.id);
|
|
592
|
-
} else {
|
|
593
|
-
input.removeAttribute('aria-activedescendant');
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/** @private */
|
|
598
|
-
_openedChanged(opened, wasOpened) {
|
|
599
|
-
// Prevent _close() being called when opened is set to its default value (false).
|
|
600
|
-
if (wasOpened === undefined) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (opened) {
|
|
605
|
-
// For touch devices, we don't want to popup virtual keyboard
|
|
606
|
-
// unless input element is explicitly focused by the user.
|
|
607
|
-
if (!this._isInputFocused() && !isTouch) {
|
|
608
|
-
if (this.inputElement) {
|
|
609
|
-
this.inputElement.focus();
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
} else {
|
|
613
|
-
this._onClosed();
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const input = this._nativeInput;
|
|
617
|
-
if (input) {
|
|
618
|
-
input.setAttribute('aria-expanded', !!opened);
|
|
619
|
-
|
|
620
|
-
if (opened) {
|
|
621
|
-
input.setAttribute('aria-controls', this._scroller.id);
|
|
622
|
-
} else {
|
|
623
|
-
input.removeAttribute('aria-controls');
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** @private */
|
|
629
|
-
_onOverlayTouchAction() {
|
|
630
|
-
// On touch devices, blur the input on touch start inside the overlay, in order to hide
|
|
631
|
-
// the virtual keyboard. But don't close the overlay on this blur.
|
|
632
|
-
this._closeOnBlurIsPrevented = true;
|
|
633
|
-
this.inputElement.blur();
|
|
634
|
-
this._closeOnBlurIsPrevented = false;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/** @protected */
|
|
638
|
-
_isClearButton(event) {
|
|
639
|
-
return event.composedPath()[0] === this.clearElement;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/** @private */
|
|
643
|
-
__onClearButtonMouseDown(event) {
|
|
644
|
-
event.preventDefault(); // Prevent native focusout event
|
|
645
|
-
this.inputElement.focus();
|
|
646
|
-
}
|
|
647
|
-
|
|
648
185
|
/**
|
|
186
|
+
* Override method from `ComboBoxBaseMixin` to deselect
|
|
187
|
+
* dropdown item by requesting content update on clear.
|
|
649
188
|
* @param {Event} event
|
|
650
189
|
* @protected
|
|
651
190
|
*/
|
|
652
191
|
_onClearButtonClick(event) {
|
|
653
|
-
|
|
654
|
-
this._onClearAction();
|
|
192
|
+
super._onClearButtonClick(event);
|
|
655
193
|
|
|
656
|
-
// De-select dropdown item
|
|
657
194
|
if (this.opened) {
|
|
658
195
|
this.requestContentUpdate();
|
|
659
196
|
}
|
|
660
197
|
}
|
|
661
198
|
|
|
662
199
|
/**
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*/
|
|
666
|
-
_onToggleButtonClick(event) {
|
|
667
|
-
// Prevent parent components such as `vaadin-grid`
|
|
668
|
-
// from handling the click event after it bubbles.
|
|
669
|
-
event.preventDefault();
|
|
670
|
-
|
|
671
|
-
if (this.opened) {
|
|
672
|
-
this.close();
|
|
673
|
-
} else {
|
|
674
|
-
this.open();
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* @param {Event} event
|
|
200
|
+
* Override method inherited from `InputMixin`
|
|
201
|
+
* to revert the input value to value.
|
|
680
202
|
* @protected
|
|
203
|
+
* @override
|
|
681
204
|
*/
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
event.preventDefault();
|
|
685
|
-
this.open();
|
|
686
|
-
}
|
|
687
|
-
}
|
|
205
|
+
_inputElementChanged(input) {
|
|
206
|
+
super._inputElementChanged(input);
|
|
688
207
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (this._isClearButton(event)) {
|
|
692
|
-
this._onClearButtonClick(event);
|
|
693
|
-
} else if (event.composedPath().includes(this._toggleElement)) {
|
|
694
|
-
this._onToggleButtonClick(event);
|
|
695
|
-
} else {
|
|
696
|
-
this._onHostClick(event);
|
|
208
|
+
if (input) {
|
|
209
|
+
this._revertInputValueToValue();
|
|
697
210
|
}
|
|
698
211
|
}
|
|
699
212
|
|
|
700
213
|
/**
|
|
701
|
-
* Override
|
|
702
|
-
*
|
|
703
|
-
* @param {KeyboardEvent} e
|
|
214
|
+
* Override method from `ComboBoxBaseMixin` to handle loading.
|
|
704
215
|
* @protected
|
|
705
216
|
* @override
|
|
706
217
|
*/
|
|
707
|
-
_onKeyDown(e) {
|
|
708
|
-
super._onKeyDown(e);
|
|
709
|
-
|
|
710
|
-
if (e.key === 'ArrowDown') {
|
|
711
|
-
this._onArrowDown();
|
|
712
|
-
|
|
713
|
-
// Prevent caret from moving
|
|
714
|
-
e.preventDefault();
|
|
715
|
-
} else if (e.key === 'ArrowUp') {
|
|
716
|
-
this._onArrowUp();
|
|
717
|
-
|
|
718
|
-
// Prevent caret from moving
|
|
719
|
-
e.preventDefault();
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/** @private */
|
|
724
|
-
_getItemLabel(item) {
|
|
725
|
-
let label = item && this.itemLabelPath ? get(this.itemLabelPath, item) : undefined;
|
|
726
|
-
if (label === undefined || label === null) {
|
|
727
|
-
label = item ? item.toString() : '';
|
|
728
|
-
}
|
|
729
|
-
return label;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/** @private */
|
|
733
|
-
_getItemValue(item) {
|
|
734
|
-
let value = item && this.itemValuePath ? get(this.itemValuePath, item) : undefined;
|
|
735
|
-
if (value === undefined) {
|
|
736
|
-
value = item ? item.toString() : '';
|
|
737
|
-
}
|
|
738
|
-
return value;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/** @private */
|
|
742
|
-
_onArrowDown() {
|
|
743
|
-
if (this.opened) {
|
|
744
|
-
const items = this._dropdownItems;
|
|
745
|
-
if (items) {
|
|
746
|
-
this._focusedIndex = Math.min(items.length - 1, this._focusedIndex + 1);
|
|
747
|
-
this._prefillFocusedItemLabel();
|
|
748
|
-
}
|
|
749
|
-
} else {
|
|
750
|
-
this.open();
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/** @private */
|
|
755
|
-
_onArrowUp() {
|
|
756
|
-
if (this.opened) {
|
|
757
|
-
if (this._focusedIndex > -1) {
|
|
758
|
-
this._focusedIndex = Math.max(0, this._focusedIndex - 1);
|
|
759
|
-
} else {
|
|
760
|
-
const items = this._dropdownItems;
|
|
761
|
-
if (items) {
|
|
762
|
-
this._focusedIndex = items.length - 1;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
this._prefillFocusedItemLabel();
|
|
767
|
-
} else {
|
|
768
|
-
this.open();
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/** @private */
|
|
773
|
-
_prefillFocusedItemLabel() {
|
|
774
|
-
if (this._focusedIndex > -1) {
|
|
775
|
-
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
776
|
-
this._inputElementValue = this._getItemLabel(focusedItem);
|
|
777
|
-
this._markAllSelectionRange();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/** @private */
|
|
782
|
-
_setSelectionRange(start, end) {
|
|
783
|
-
// Setting selection range focuses and/or moves the caret in some browsers,
|
|
784
|
-
// and there's no need to modify the selection range if the input isn't focused anyway.
|
|
785
|
-
// This affects Safari. When the overlay is open, and then hitting tab, browser should focus
|
|
786
|
-
// the next focusable element instead of the combo-box itself.
|
|
787
|
-
if (this._isInputFocused() && this.inputElement.setSelectionRange) {
|
|
788
|
-
this.inputElement.setSelectionRange(start, end);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/** @private */
|
|
793
|
-
_markAllSelectionRange() {
|
|
794
|
-
if (this._inputElementValue !== undefined) {
|
|
795
|
-
this._setSelectionRange(0, this._inputElementValue.length);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/** @private */
|
|
800
|
-
_clearSelectionRange() {
|
|
801
|
-
if (this._inputElementValue !== undefined) {
|
|
802
|
-
const pos = this._inputElementValue ? this._inputElementValue.length : 0;
|
|
803
|
-
this._setSelectionRange(pos, pos);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/** @private */
|
|
808
218
|
_closeOrCommit() {
|
|
809
219
|
if (!this.opened && !this.loading) {
|
|
810
220
|
this._commitValue();
|
|
@@ -814,39 +224,10 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
814
224
|
}
|
|
815
225
|
|
|
816
226
|
/**
|
|
817
|
-
* Override
|
|
818
|
-
*
|
|
819
|
-
* @param {KeyboardEvent} e
|
|
227
|
+
* Override method from `ComboBoxBaseMixin` to handle valid value.
|
|
820
228
|
* @protected
|
|
821
229
|
* @override
|
|
822
230
|
*/
|
|
823
|
-
_onEnter(e) {
|
|
824
|
-
// Do not commit value when custom values are disallowed and input value is not a valid option
|
|
825
|
-
// also stop propagation of the event, otherwise the user could submit a form while the input
|
|
826
|
-
// still contains an invalid value
|
|
827
|
-
if (!this._hasValidInputValue()) {
|
|
828
|
-
// Do not submit the surrounding form.
|
|
829
|
-
e.preventDefault();
|
|
830
|
-
// Do not trigger global listeners
|
|
831
|
-
e.stopPropagation();
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Stop propagation of the enter event only if the dropdown is opened, this
|
|
836
|
-
// "consumes" the enter event for the action of closing the dropdown
|
|
837
|
-
if (this.opened) {
|
|
838
|
-
// Do not submit the surrounding form.
|
|
839
|
-
e.preventDefault();
|
|
840
|
-
// Do not trigger global listeners
|
|
841
|
-
e.stopPropagation();
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
this._closeOrCommit();
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* @protected
|
|
849
|
-
*/
|
|
850
231
|
_hasValidInputValue() {
|
|
851
232
|
const hasInvalidOption =
|
|
852
233
|
this._focusedIndex < 0 &&
|
|
@@ -857,62 +238,18 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
857
238
|
}
|
|
858
239
|
|
|
859
240
|
/**
|
|
860
|
-
* Override
|
|
861
|
-
* Do not call `super` in order to override clear
|
|
862
|
-
* button logic defined in `InputControlMixin`.
|
|
863
|
-
*
|
|
864
|
-
* @param {!KeyboardEvent} e
|
|
241
|
+
* Override method from `ComboBoxBaseMixin`.
|
|
865
242
|
* @protected
|
|
866
243
|
* @override
|
|
867
244
|
*/
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
this.autoOpenDisabled &&
|
|
871
|
-
(this.opened || (this.value !== this._inputElementValue && this._inputElementValue.length > 0))
|
|
872
|
-
) {
|
|
873
|
-
// Auto-open is disabled
|
|
874
|
-
// The overlay is open or
|
|
875
|
-
// The input value has changed but the change hasn't been committed, so cancel it.
|
|
876
|
-
e.stopPropagation();
|
|
877
|
-
this._focusedIndex = -1;
|
|
878
|
-
this.cancel();
|
|
879
|
-
} else if (this.opened) {
|
|
880
|
-
// Auto-open is enabled
|
|
881
|
-
// The overlay is open
|
|
882
|
-
e.stopPropagation();
|
|
883
|
-
|
|
884
|
-
if (this._focusedIndex > -1) {
|
|
885
|
-
// An item is focused, revert the input to the filtered value
|
|
886
|
-
this._focusedIndex = -1;
|
|
887
|
-
this._revertInputValue();
|
|
888
|
-
} else {
|
|
889
|
-
// No item is focused, cancel the change and close the overlay
|
|
890
|
-
this.cancel();
|
|
891
|
-
}
|
|
892
|
-
} else if (this.clearButtonVisible && !!this.value && !this.readonly) {
|
|
893
|
-
e.stopPropagation();
|
|
894
|
-
// The clear button is visible and the overlay is closed, so clear the value.
|
|
895
|
-
this._onClearAction();
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
/** @private */
|
|
900
|
-
_toggleElementChanged(toggleElement) {
|
|
901
|
-
if (toggleElement) {
|
|
902
|
-
// Don't blur the input on toggle mousedown
|
|
903
|
-
toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
|
|
904
|
-
// Unfocus previously focused element if focus is not inside combo box (on touch devices)
|
|
905
|
-
toggleElement.addEventListener('click', () => {
|
|
906
|
-
if (isTouch && !this._isInputFocused()) {
|
|
907
|
-
document.activeElement.blur();
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
}
|
|
245
|
+
_onEscapeCancel() {
|
|
246
|
+
this.cancel();
|
|
911
247
|
}
|
|
912
248
|
|
|
913
249
|
/**
|
|
914
|
-
*
|
|
250
|
+
* Override method from `ComboBoxBaseMixin` to reset selected item.
|
|
915
251
|
* @protected
|
|
252
|
+
* @override
|
|
916
253
|
*/
|
|
917
254
|
_onClearAction() {
|
|
918
255
|
this.selectedItem = null;
|
|
@@ -943,20 +280,43 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
943
280
|
this._closeOrCommit();
|
|
944
281
|
}
|
|
945
282
|
|
|
946
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Override method from `ComboBoxBaseMixin` to store last committed value.
|
|
285
|
+
* @protected
|
|
286
|
+
* @override
|
|
287
|
+
*/
|
|
947
288
|
_onOpened() {
|
|
289
|
+
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
|
|
290
|
+
|
|
948
291
|
// _detectAndDispatchChange() should not consider value changes done before opening
|
|
949
292
|
this._lastCommittedValue = this.value;
|
|
950
293
|
}
|
|
951
294
|
|
|
952
|
-
/**
|
|
295
|
+
/**
|
|
296
|
+
* Override method from `ComboBoxBaseMixin` to dispatch an event.
|
|
297
|
+
* @protected
|
|
298
|
+
* @override
|
|
299
|
+
*/
|
|
300
|
+
_onOverlayClosed() {
|
|
301
|
+
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Override method from `ComboBoxBaseMixin` to commit value on overlay closing.
|
|
306
|
+
* @protected
|
|
307
|
+
* @override
|
|
308
|
+
*/
|
|
953
309
|
_onClosed() {
|
|
954
310
|
if (!this.loading || this.allowCustomValue) {
|
|
955
311
|
this._commitValue();
|
|
956
312
|
}
|
|
957
313
|
}
|
|
958
314
|
|
|
959
|
-
/**
|
|
315
|
+
/**
|
|
316
|
+
* Override method from `ComboBoxBaseMixin` to implement value commit logic.
|
|
317
|
+
* @protected
|
|
318
|
+
* @override
|
|
319
|
+
*/
|
|
960
320
|
_commitValue() {
|
|
961
321
|
if (this._focusedIndex > -1) {
|
|
962
322
|
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
@@ -1015,36 +375,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1015
375
|
this._clearFilter();
|
|
1016
376
|
}
|
|
1017
377
|
|
|
1018
|
-
/**
|
|
1019
|
-
* Override an event listener from `InputMixin`.
|
|
1020
|
-
* @param {!Event} event
|
|
1021
|
-
* @protected
|
|
1022
|
-
* @override
|
|
1023
|
-
*/
|
|
1024
|
-
_onInput(event) {
|
|
1025
|
-
const filter = this._inputElementValue;
|
|
1026
|
-
|
|
1027
|
-
// When opening dropdown on user input, both `opened` and `filter` properties are set.
|
|
1028
|
-
// Perform a batched property update instead of relying on sync property observers.
|
|
1029
|
-
// This is necessary to avoid an extra data-provider request for loading first page.
|
|
1030
|
-
const props = {};
|
|
1031
|
-
|
|
1032
|
-
if (this.filter === filter) {
|
|
1033
|
-
// Filter and input value might get out of sync, while keyboard navigating for example.
|
|
1034
|
-
// Afterwards, input value might be changed to the same value as used in filtering.
|
|
1035
|
-
// In situation like these, we need to make sure all the filter changes handlers are run.
|
|
1036
|
-
this._filterChanged(this.filter);
|
|
1037
|
-
} else {
|
|
1038
|
-
props.filter = filter;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
|
|
1042
|
-
props.opened = true;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
this.setProperties(props);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
378
|
/**
|
|
1049
379
|
* Override an event listener from `InputMixin`.
|
|
1050
380
|
* @param {!Event} event
|
|
@@ -1057,31 +387,11 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1057
387
|
event.stopPropagation();
|
|
1058
388
|
}
|
|
1059
389
|
|
|
1060
|
-
/**
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
/** @private */
|
|
1068
|
-
_filterChanged(filter) {
|
|
1069
|
-
// Scroll to the top of the list whenever the filter changes.
|
|
1070
|
-
this._scrollIntoView(0);
|
|
1071
|
-
|
|
1072
|
-
this._focusedIndex = -1;
|
|
1073
|
-
|
|
1074
|
-
if (this.items) {
|
|
1075
|
-
this.filteredItems = this._filterItems(this.items, filter);
|
|
1076
|
-
} else {
|
|
1077
|
-
// With certain use cases (e. g., external filtering), `items` are
|
|
1078
|
-
// undefined. Filtering is unnecessary per se, but the filteredItems
|
|
1079
|
-
// observer should still be invoked to update focused item.
|
|
1080
|
-
this._filteredItemsChanged(this.filteredItems);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/** @protected */
|
|
390
|
+
/**
|
|
391
|
+
* Override method from `ComboBoxBaseMixin` to handle reverting value.
|
|
392
|
+
* @protected
|
|
393
|
+
* @override
|
|
394
|
+
*/
|
|
1085
395
|
_revertInputValue() {
|
|
1086
396
|
if (this.filter !== '') {
|
|
1087
397
|
this._inputElementValue = this.filter;
|
|
@@ -1173,40 +483,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1173
483
|
}
|
|
1174
484
|
}
|
|
1175
485
|
|
|
1176
|
-
/** @private */
|
|
1177
|
-
_itemsChanged(items, oldItems) {
|
|
1178
|
-
this._ensureItemsOrDataProvider(() => {
|
|
1179
|
-
this.items = oldItems;
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
if (items) {
|
|
1183
|
-
this.filteredItems = items.slice(0);
|
|
1184
|
-
} else if (oldItems) {
|
|
1185
|
-
// Only clear filteredItems if the component had items previously but got cleared
|
|
1186
|
-
this.filteredItems = null;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
/** @private */
|
|
1191
|
-
_filteredItemsChanged(filteredItems) {
|
|
1192
|
-
this._setDropdownItems(filteredItems);
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/** @private */
|
|
1196
|
-
_filterItems(arr, filter) {
|
|
1197
|
-
if (!arr) {
|
|
1198
|
-
return arr;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
const filteredItems = arr.filter((item) => {
|
|
1202
|
-
filter = filter ? filter.toString().toLowerCase() : '';
|
|
1203
|
-
// Check if item contains input value.
|
|
1204
|
-
return this._getItemLabel(item).toString().toLowerCase().indexOf(filter) > -1;
|
|
1205
|
-
});
|
|
1206
|
-
|
|
1207
|
-
return filteredItems;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
486
|
/** @private */
|
|
1211
487
|
_selectItemForValue(value) {
|
|
1212
488
|
const valueIndex = this.__getItemIndexByValue(this.filteredItems, value);
|
|
@@ -1230,6 +506,7 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1230
506
|
* Override this method to show custom items.
|
|
1231
507
|
*
|
|
1232
508
|
* @protected
|
|
509
|
+
* @override
|
|
1233
510
|
*/
|
|
1234
511
|
_setDropdownItems(newItems) {
|
|
1235
512
|
const oldItems = this._dropdownItems;
|
|
@@ -1262,135 +539,20 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1262
539
|
}
|
|
1263
540
|
}
|
|
1264
541
|
|
|
1265
|
-
/** @private */
|
|
1266
|
-
_getItemElements() {
|
|
1267
|
-
return Array.from(this._scroller.querySelectorAll(`${this._tagNamePrefix}-item`));
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
/** @private */
|
|
1271
|
-
_scrollIntoView(index) {
|
|
1272
|
-
if (!this._scroller) {
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
this._scroller.scrollIntoView(index);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* Returns the first item that matches the provided value.
|
|
1280
|
-
*
|
|
1281
|
-
* @private
|
|
1282
|
-
*/
|
|
1283
|
-
__getItemIndexByValue(items, value) {
|
|
1284
|
-
if (!items || !isValidValue(value)) {
|
|
1285
|
-
return -1;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
return findItemIndex(items, (item) => {
|
|
1289
|
-
return this._getItemValue(item) === value;
|
|
1290
|
-
});
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
/**
|
|
1294
|
-
* Returns the first item that matches the provided label.
|
|
1295
|
-
* Labels are matched against each other case insensitively.
|
|
1296
|
-
*
|
|
1297
|
-
* @private
|
|
1298
|
-
*/
|
|
1299
|
-
__getItemIndexByLabel(items, label) {
|
|
1300
|
-
if (!items || !label) {
|
|
1301
|
-
return -1;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
return findItemIndex(items, (item) => {
|
|
1305
|
-
return this._getItemLabel(item).toString().toLowerCase() === label.toString().toLowerCase();
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
/** @private */
|
|
1310
|
-
_overlaySelectedItemChanged(e) {
|
|
1311
|
-
// Stop this private event from leaking outside.
|
|
1312
|
-
e.stopPropagation();
|
|
1313
|
-
|
|
1314
|
-
if (e.detail.item instanceof ComboBoxPlaceholder) {
|
|
1315
|
-
// Placeholder items should not be selectable.
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
if (this.opened) {
|
|
1320
|
-
this._focusedIndex = this.filteredItems.indexOf(e.detail.item);
|
|
1321
|
-
this.close();
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* Override method inherited from `FocusMixin`
|
|
1327
|
-
* to close the overlay on blur and commit the value.
|
|
1328
|
-
*
|
|
1329
|
-
* @param {boolean} focused
|
|
1330
|
-
* @protected
|
|
1331
|
-
* @override
|
|
1332
|
-
*/
|
|
1333
|
-
_setFocused(focused) {
|
|
1334
|
-
super._setFocused(focused);
|
|
1335
|
-
|
|
1336
|
-
if (!focused && !this.readonly && !this._closeOnBlurIsPrevented) {
|
|
1337
|
-
// User's logic in `custom-value-set` event listener might cause input to blur,
|
|
1338
|
-
// which will result in attempting to commit the same custom value once again.
|
|
1339
|
-
if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
|
|
1340
|
-
delete this._lastCustomValue;
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
if (isKeyboardActive()) {
|
|
1345
|
-
// Close on Tab key causing blur. With mouse, close on outside click instead.
|
|
1346
|
-
this._closeOrCommit();
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
if (!this.opened) {
|
|
1351
|
-
this._commitValue();
|
|
1352
|
-
} else if (!this._overlayOpened) {
|
|
1353
|
-
// Combo-box is opened, but overlay is not visible -> custom value was entered.
|
|
1354
|
-
// Make sure we close here as there won't be an "outside click" in this case.
|
|
1355
|
-
this.close();
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
542
|
/**
|
|
1361
|
-
* Override method
|
|
1362
|
-
* state when focus moves to the overlay.
|
|
1363
|
-
*
|
|
1364
|
-
* @param {FocusEvent} event
|
|
1365
|
-
* @return {boolean}
|
|
543
|
+
* Override method from `ComboBoxBaseMixin`.
|
|
1366
544
|
* @protected
|
|
1367
545
|
* @override
|
|
1368
546
|
*/
|
|
1369
|
-
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
if (
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// Do not blur when focus moves to the overlay
|
|
1377
|
-
// Also, fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
|
|
1378
|
-
if (event.relatedTarget === this._overlayElement) {
|
|
1379
|
-
event.composedPath()[0].focus();
|
|
1380
|
-
return false;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
return true;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
/** @private */
|
|
1387
|
-
_onTouchend(event) {
|
|
1388
|
-
if (!this.clearElement || event.composedPath()[0] !== this.clearElement) {
|
|
547
|
+
_handleFocusOut() {
|
|
548
|
+
// User's logic in `custom-value-set` event listener might cause input to blur,
|
|
549
|
+
// which will result in attempting to commit the same custom value once again.
|
|
550
|
+
if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
|
|
551
|
+
delete this._lastCustomValue;
|
|
1389
552
|
return;
|
|
1390
553
|
}
|
|
1391
554
|
|
|
1392
|
-
|
|
1393
|
-
this._onClearAction();
|
|
555
|
+
super._handleFocusOut();
|
|
1394
556
|
}
|
|
1395
557
|
|
|
1396
558
|
/**
|