@vaadin/combo-box 25.0.0-alpha3 → 25.0.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 +13 -13
- package/src/styles/vaadin-combo-box-base-styles.js +7 -5
- package/src/styles/vaadin-combo-box-overlay-base-styles.js +45 -43
- package/src/styles/vaadin-combo-box-scroller-base-styles.js +19 -17
- package/src/vaadin-combo-box-base-mixin.d.ts +56 -0
- package/src/vaadin-combo-box-base-mixin.js +786 -0
- package/src/vaadin-combo-box-data-provider-mixin.js +16 -11
- package/src/vaadin-combo-box-item-mixin.js +6 -1
- package/src/vaadin-combo-box-item.js +5 -2
- package/src/vaadin-combo-box-mixin.d.ts +3 -32
- package/src/vaadin-combo-box-mixin.js +91 -661
- package/src/vaadin-combo-box-overlay-mixin.js +6 -3
- package/src/vaadin-combo-box-overlay.js +3 -2
- package/src/vaadin-combo-box-scroller.js +2 -1
- package/src/vaadin-combo-box.d.ts +2 -0
- package/src/vaadin-combo-box.js +4 -1
- package/web-types.json +2 -2
- package/web-types.lit.json +2 -2
|
@@ -3,16 +3,9 @@
|
|
|
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 { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
|
|
12
6
|
import { get } from '@vaadin/component-base/src/path-utils.js';
|
|
13
|
-
import { InputMixin } from '@vaadin/field-base/src/input-mixin.js';
|
|
14
7
|
import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js';
|
|
15
|
-
import {
|
|
8
|
+
import { ComboBoxBaseMixin } from './vaadin-combo-box-base-mixin.js';
|
|
16
9
|
import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
|
|
17
10
|
|
|
18
11
|
/**
|
|
@@ -45,52 +38,14 @@ function findItemIndex(items, callback) {
|
|
|
45
38
|
|
|
46
39
|
/**
|
|
47
40
|
* @polymerMixin
|
|
41
|
+
* @mixes ComboBoxBaseMixin
|
|
48
42
|
* @mixes ValidateMixin
|
|
49
|
-
* @
|
|
50
|
-
* @mixes InputMixin
|
|
51
|
-
* @mixes KeyboardMixin
|
|
52
|
-
* @mixes FocusMixin
|
|
53
|
-
* @mixes OverlayClassMixin
|
|
54
|
-
* @param {function(new:HTMLElement)} subclass
|
|
43
|
+
* @param {function(new:HTMLElement)} superClass
|
|
55
44
|
*/
|
|
56
|
-
export const ComboBoxMixin = (
|
|
57
|
-
class ComboBoxMixinClass extends
|
|
58
|
-
ValidateMixin(FocusMixin(KeyboardMixin(InputMixin(DisabledMixin(subclass))))),
|
|
59
|
-
) {
|
|
45
|
+
export const ComboBoxMixin = (superClass) =>
|
|
46
|
+
class ComboBoxMixinClass extends ValidateMixin(ComboBoxBaseMixin(superClass)) {
|
|
60
47
|
static get properties() {
|
|
61
48
|
return {
|
|
62
|
-
/**
|
|
63
|
-
* True if the dropdown is open, false otherwise.
|
|
64
|
-
* @type {boolean}
|
|
65
|
-
*/
|
|
66
|
-
opened: {
|
|
67
|
-
type: Boolean,
|
|
68
|
-
notify: true,
|
|
69
|
-
value: false,
|
|
70
|
-
reflectToAttribute: true,
|
|
71
|
-
sync: true,
|
|
72
|
-
observer: '_openedChanged',
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Set true to prevent the overlay from opening automatically.
|
|
77
|
-
* @attr {boolean} auto-open-disabled
|
|
78
|
-
*/
|
|
79
|
-
autoOpenDisabled: {
|
|
80
|
-
type: Boolean,
|
|
81
|
-
sync: true,
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* When present, it specifies that the field is read-only.
|
|
86
|
-
* @type {boolean}
|
|
87
|
-
*/
|
|
88
|
-
readonly: {
|
|
89
|
-
type: Boolean,
|
|
90
|
-
value: false,
|
|
91
|
-
reflectToAttribute: true,
|
|
92
|
-
},
|
|
93
|
-
|
|
94
49
|
/**
|
|
95
50
|
* Custom function for rendering the content of every item.
|
|
96
51
|
* Receives three arguments:
|
|
@@ -144,12 +99,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
144
99
|
sync: true,
|
|
145
100
|
},
|
|
146
101
|
|
|
147
|
-
/**
|
|
148
|
-
* Used to detect user value changes and fire `change` events.
|
|
149
|
-
* @private
|
|
150
|
-
*/
|
|
151
|
-
_lastCommittedValue: String,
|
|
152
|
-
|
|
153
102
|
/**
|
|
154
103
|
* When set to `true`, "loading" attribute is added to host and the overlay element.
|
|
155
104
|
* @type {boolean}
|
|
@@ -161,17 +110,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
161
110
|
sync: true,
|
|
162
111
|
},
|
|
163
112
|
|
|
164
|
-
/**
|
|
165
|
-
* @type {number}
|
|
166
|
-
* @protected
|
|
167
|
-
*/
|
|
168
|
-
_focusedIndex: {
|
|
169
|
-
type: Number,
|
|
170
|
-
observer: '_focusedIndexChanged',
|
|
171
|
-
value: -1,
|
|
172
|
-
sync: true,
|
|
173
|
-
},
|
|
174
|
-
|
|
175
113
|
/**
|
|
176
114
|
* Filtering string the user has typed into the input field.
|
|
177
115
|
* @type {string}
|
|
@@ -248,40 +186,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
248
186
|
sync: true,
|
|
249
187
|
},
|
|
250
188
|
|
|
251
|
-
/**
|
|
252
|
-
* @type {!HTMLElement | undefined}
|
|
253
|
-
* @protected
|
|
254
|
-
*/
|
|
255
|
-
_toggleElement: {
|
|
256
|
-
type: Object,
|
|
257
|
-
observer: '_toggleElementChanged',
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Set of items to be rendered in the dropdown.
|
|
262
|
-
* @protected
|
|
263
|
-
*/
|
|
264
|
-
_dropdownItems: {
|
|
265
|
-
type: Array,
|
|
266
|
-
sync: true,
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
/** @private */
|
|
270
|
-
_closeOnBlurIsPrevented: Boolean,
|
|
271
|
-
|
|
272
|
-
/** @private */
|
|
273
|
-
_scroller: {
|
|
274
|
-
type: Object,
|
|
275
|
-
sync: true,
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
/** @private */
|
|
279
|
-
_overlayOpened: {
|
|
280
|
-
type: Boolean,
|
|
281
|
-
sync: true,
|
|
282
|
-
observer: '_overlayOpenedChanged',
|
|
283
|
-
},
|
|
284
|
-
|
|
285
189
|
/** @private */
|
|
286
190
|
__keepOverlayOpened: {
|
|
287
191
|
type: Boolean,
|
|
@@ -292,91 +196,23 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
292
196
|
|
|
293
197
|
static get observers() {
|
|
294
198
|
return [
|
|
295
|
-
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
|
|
296
199
|
'_openedOrItemsChanged(opened, _dropdownItems, loading, __keepOverlayOpened)',
|
|
297
|
-
'
|
|
200
|
+
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
|
|
201
|
+
'_updateScroller(opened, _dropdownItems, _focusedIndex, _theme)',
|
|
298
202
|
];
|
|
299
203
|
}
|
|
300
204
|
|
|
301
|
-
constructor() {
|
|
302
|
-
super();
|
|
303
|
-
this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this);
|
|
304
|
-
this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this);
|
|
305
|
-
this._boundOnClick = this._onClick.bind(this);
|
|
306
|
-
this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this);
|
|
307
|
-
this._boundOnTouchend = this._onTouchend.bind(this);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Tag name prefix used by scroller and items.
|
|
312
|
-
* @protected
|
|
313
|
-
* @return {string}
|
|
314
|
-
*/
|
|
315
|
-
get _tagNamePrefix() {
|
|
316
|
-
return 'vaadin-combo-box';
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Override method inherited from `InputMixin`
|
|
321
|
-
* to customize the input element.
|
|
322
|
-
* @protected
|
|
323
|
-
* @override
|
|
324
|
-
*/
|
|
325
|
-
_inputElementChanged(input) {
|
|
326
|
-
super._inputElementChanged(input);
|
|
327
|
-
|
|
328
|
-
if (input) {
|
|
329
|
-
input.autocomplete = 'off';
|
|
330
|
-
input.autocapitalize = 'off';
|
|
331
|
-
|
|
332
|
-
input.setAttribute('role', 'combobox');
|
|
333
|
-
input.setAttribute('aria-autocomplete', 'list');
|
|
334
|
-
input.setAttribute('aria-expanded', !!this.opened);
|
|
335
|
-
|
|
336
|
-
// Disable the macOS Safari spell check auto corrections.
|
|
337
|
-
input.setAttribute('spellcheck', 'false');
|
|
338
|
-
|
|
339
|
-
// Disable iOS autocorrect suggestions.
|
|
340
|
-
input.setAttribute('autocorrect', 'off');
|
|
341
|
-
|
|
342
|
-
this._revertInputValueToValue();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
205
|
/** @protected */
|
|
347
206
|
ready() {
|
|
348
207
|
super.ready();
|
|
349
208
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Used to detect user value changes and fire `change` events.
|
|
211
|
+
* Do not define in `properties` to avoid triggering updates.
|
|
212
|
+
* @type {string}
|
|
213
|
+
* @protected
|
|
214
|
+
*/
|
|
353
215
|
this._lastCommittedValue = this.value;
|
|
354
|
-
|
|
355
|
-
this.addEventListener('click', this._boundOnClick);
|
|
356
|
-
this.addEventListener('touchend', this._boundOnTouchend);
|
|
357
|
-
|
|
358
|
-
if (this.clearElement) {
|
|
359
|
-
this.clearElement.addEventListener('mousedown', this._boundOnClearButtonMouseDown);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const bringToFrontListener = () => {
|
|
363
|
-
requestAnimationFrame(() => {
|
|
364
|
-
this._overlayElement.bringToFront();
|
|
365
|
-
});
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
this.addEventListener('mousedown', bringToFrontListener);
|
|
369
|
-
this.addEventListener('touchstart', bringToFrontListener);
|
|
370
|
-
|
|
371
|
-
this.addController(new VirtualKeyboardController(this));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** @protected */
|
|
375
|
-
disconnectedCallback() {
|
|
376
|
-
super.disconnectedCallback();
|
|
377
|
-
|
|
378
|
-
// Close the overlay on detach
|
|
379
|
-
this.close();
|
|
380
216
|
}
|
|
381
217
|
|
|
382
218
|
/**
|
|
@@ -398,116 +234,36 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
398
234
|
}
|
|
399
235
|
|
|
400
236
|
/**
|
|
401
|
-
* Opens the dropdown list.
|
|
402
|
-
*/
|
|
403
|
-
open() {
|
|
404
|
-
// Prevent _open() being called when input is disabled or read-only
|
|
405
|
-
if (!this.disabled && !this.readonly) {
|
|
406
|
-
this.opened = true;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Closes the dropdown list.
|
|
412
|
-
*/
|
|
413
|
-
close() {
|
|
414
|
-
this.opened = false;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Override LitElement lifecycle callback to handle filter property change.
|
|
419
237
|
* @param {Object} props
|
|
420
238
|
* @protected
|
|
421
239
|
*/
|
|
422
240
|
updated(props) {
|
|
423
241
|
super.updated(props);
|
|
424
242
|
|
|
243
|
+
['loading', 'itemIdPath', 'itemClassNameGenerator', 'renderer', 'selectedItem'].forEach((prop) => {
|
|
244
|
+
if (props.has(prop)) {
|
|
245
|
+
this._scroller[prop] = this[prop];
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
425
249
|
if (props.has('filter')) {
|
|
426
250
|
this._filterChanged(this.filter);
|
|
427
251
|
}
|
|
428
252
|
}
|
|
429
253
|
|
|
430
254
|
/** @private */
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
overlay.addEventListener('touchend', this._boundOnOverlayTouchAction);
|
|
438
|
-
overlay.addEventListener('touchmove', this._boundOnOverlayTouchAction);
|
|
439
|
-
|
|
440
|
-
// Prevent blurring the input when clicking inside the overlay
|
|
441
|
-
overlay.addEventListener('mousedown', (e) => e.preventDefault());
|
|
255
|
+
_updateScroller(opened, items, focusedIndex, theme) {
|
|
256
|
+
if (opened) {
|
|
257
|
+
this._scroller.style.maxHeight =
|
|
258
|
+
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
|
|
259
|
+
}
|
|
442
260
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
261
|
+
this._scroller.setProperties({
|
|
262
|
+
items: opened ? items : [],
|
|
263
|
+
opened,
|
|
264
|
+
focusedIndex,
|
|
265
|
+
theme,
|
|
446
266
|
});
|
|
447
|
-
|
|
448
|
-
this._overlayElement = overlay;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Create and initialize the scroller element.
|
|
453
|
-
* Override to provide custom host reference.
|
|
454
|
-
*
|
|
455
|
-
* @protected
|
|
456
|
-
*/
|
|
457
|
-
_initScroller(host) {
|
|
458
|
-
const scroller = document.createElement(`${this._tagNamePrefix}-scroller`);
|
|
459
|
-
|
|
460
|
-
scroller.owner = host || this;
|
|
461
|
-
scroller.getItemLabel = this._getItemLabel.bind(this);
|
|
462
|
-
scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
|
|
463
|
-
|
|
464
|
-
const overlay = this._overlayElement;
|
|
465
|
-
|
|
466
|
-
overlay.renderer = (root) => {
|
|
467
|
-
if (!root.innerHTML) {
|
|
468
|
-
root.appendChild(scroller);
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
// Ensure the scroller is rendered
|
|
473
|
-
overlay.requestContentUpdate();
|
|
474
|
-
|
|
475
|
-
// Trigger the observer to set properties
|
|
476
|
-
this._scroller = scroller;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/** @private */
|
|
480
|
-
// eslint-disable-next-line @typescript-eslint/max-params
|
|
481
|
-
_updateScroller(
|
|
482
|
-
scroller,
|
|
483
|
-
items,
|
|
484
|
-
opened,
|
|
485
|
-
loading,
|
|
486
|
-
selectedItem,
|
|
487
|
-
itemIdPath,
|
|
488
|
-
focusedIndex,
|
|
489
|
-
renderer,
|
|
490
|
-
theme,
|
|
491
|
-
itemClassNameGenerator,
|
|
492
|
-
) {
|
|
493
|
-
if (scroller) {
|
|
494
|
-
if (opened) {
|
|
495
|
-
scroller.style.maxHeight =
|
|
496
|
-
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
scroller.setProperties({
|
|
500
|
-
items: opened ? items : [],
|
|
501
|
-
opened,
|
|
502
|
-
loading,
|
|
503
|
-
selectedItem,
|
|
504
|
-
itemIdPath,
|
|
505
|
-
focusedIndex,
|
|
506
|
-
renderer,
|
|
507
|
-
theme,
|
|
508
|
-
itemClassNameGenerator,
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
267
|
}
|
|
512
268
|
|
|
513
269
|
/** @private */
|
|
@@ -517,174 +273,39 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
517
273
|
this._overlayOpened = opened && (keepOverlayOpened || loading || !!(items && items.length));
|
|
518
274
|
}
|
|
519
275
|
|
|
520
|
-
/** @private */
|
|
521
|
-
_overlayOpenedChanged(opened, wasOpened) {
|
|
522
|
-
if (opened) {
|
|
523
|
-
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
|
|
524
|
-
|
|
525
|
-
this._onOpened();
|
|
526
|
-
} else if (wasOpened && this._dropdownItems && this._dropdownItems.length) {
|
|
527
|
-
this.close();
|
|
528
|
-
|
|
529
|
-
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/** @private */
|
|
534
|
-
_focusedIndexChanged(index, oldIndex) {
|
|
535
|
-
if (oldIndex === undefined) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
this._updateActiveDescendant(index);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/** @protected */
|
|
542
|
-
_isInputFocused() {
|
|
543
|
-
return this.inputElement && isElementFocused(this.inputElement);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/** @private */
|
|
547
|
-
_updateActiveDescendant(index) {
|
|
548
|
-
const input = this.inputElement;
|
|
549
|
-
if (!input) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const item = this._getItemElements().find((el) => el.index === index);
|
|
554
|
-
if (item) {
|
|
555
|
-
input.setAttribute('aria-activedescendant', item.id);
|
|
556
|
-
} else {
|
|
557
|
-
input.removeAttribute('aria-activedescendant');
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/** @private */
|
|
562
|
-
_openedChanged(opened, wasOpened) {
|
|
563
|
-
// Prevent _close() being called when opened is set to its default value (false).
|
|
564
|
-
if (wasOpened === undefined) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
if (opened) {
|
|
569
|
-
// For touch devices, we don't want to popup virtual keyboard
|
|
570
|
-
// unless input element is explicitly focused by the user.
|
|
571
|
-
if (!this._isInputFocused() && !isTouch) {
|
|
572
|
-
if (this.inputElement) {
|
|
573
|
-
this.inputElement.focus();
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
} else {
|
|
577
|
-
this._onClosed();
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const input = this.inputElement;
|
|
581
|
-
if (input) {
|
|
582
|
-
input.setAttribute('aria-expanded', !!opened);
|
|
583
|
-
|
|
584
|
-
if (opened) {
|
|
585
|
-
input.setAttribute('aria-controls', this._scroller.id);
|
|
586
|
-
} else {
|
|
587
|
-
input.removeAttribute('aria-controls');
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** @private */
|
|
593
|
-
_onOverlayTouchAction() {
|
|
594
|
-
// On touch devices, blur the input on touch start inside the overlay, in order to hide
|
|
595
|
-
// the virtual keyboard. But don't close the overlay on this blur.
|
|
596
|
-
this._closeOnBlurIsPrevented = true;
|
|
597
|
-
this.inputElement.blur();
|
|
598
|
-
this._closeOnBlurIsPrevented = false;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/** @protected */
|
|
602
|
-
_isClearButton(event) {
|
|
603
|
-
return event.composedPath()[0] === this.clearElement;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/** @private */
|
|
607
|
-
__onClearButtonMouseDown(event) {
|
|
608
|
-
event.preventDefault(); // Prevent native focusout event
|
|
609
|
-
this.inputElement.focus();
|
|
610
|
-
}
|
|
611
|
-
|
|
612
276
|
/**
|
|
277
|
+
* Override method from `ComboBoxBaseMixin` to deselect
|
|
278
|
+
* dropdown item by requesting content update on clear.
|
|
613
279
|
* @param {Event} event
|
|
614
280
|
* @protected
|
|
615
281
|
*/
|
|
616
282
|
_onClearButtonClick(event) {
|
|
617
|
-
|
|
618
|
-
this._onClearAction();
|
|
283
|
+
super._onClearButtonClick(event);
|
|
619
284
|
|
|
620
|
-
// De-select dropdown item
|
|
621
285
|
if (this.opened) {
|
|
622
286
|
this.requestContentUpdate();
|
|
623
287
|
}
|
|
624
288
|
}
|
|
625
289
|
|
|
626
290
|
/**
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*/
|
|
630
|
-
_onToggleButtonClick(event) {
|
|
631
|
-
// Prevent parent components such as `vaadin-grid`
|
|
632
|
-
// from handling the click event after it bubbles.
|
|
633
|
-
event.preventDefault();
|
|
634
|
-
|
|
635
|
-
if (this.opened) {
|
|
636
|
-
this.close();
|
|
637
|
-
} else {
|
|
638
|
-
this.open();
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* @param {Event} event
|
|
291
|
+
* Override method inherited from `InputMixin`
|
|
292
|
+
* to revert the input value to value.
|
|
644
293
|
* @protected
|
|
294
|
+
* @override
|
|
645
295
|
*/
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
event.preventDefault();
|
|
649
|
-
this.open();
|
|
650
|
-
}
|
|
651
|
-
}
|
|
296
|
+
_inputElementChanged(input) {
|
|
297
|
+
super._inputElementChanged(input);
|
|
652
298
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (this._isClearButton(event)) {
|
|
656
|
-
this._onClearButtonClick(event);
|
|
657
|
-
} else if (event.composedPath().includes(this._toggleElement)) {
|
|
658
|
-
this._onToggleButtonClick(event);
|
|
659
|
-
} else {
|
|
660
|
-
this._onHostClick(event);
|
|
299
|
+
if (input) {
|
|
300
|
+
this._revertInputValueToValue();
|
|
661
301
|
}
|
|
662
302
|
}
|
|
663
303
|
|
|
664
304
|
/**
|
|
665
|
-
* Override
|
|
666
|
-
*
|
|
667
|
-
* @param {KeyboardEvent} e
|
|
305
|
+
* Override method from `ComboBoxBaseMixin` to handle item label path.
|
|
668
306
|
* @protected
|
|
669
307
|
* @override
|
|
670
308
|
*/
|
|
671
|
-
_onKeyDown(e) {
|
|
672
|
-
super._onKeyDown(e);
|
|
673
|
-
|
|
674
|
-
if (e.key === 'ArrowDown') {
|
|
675
|
-
this._onArrowDown();
|
|
676
|
-
|
|
677
|
-
// Prevent caret from moving
|
|
678
|
-
e.preventDefault();
|
|
679
|
-
} else if (e.key === 'ArrowUp') {
|
|
680
|
-
this._onArrowUp();
|
|
681
|
-
|
|
682
|
-
// Prevent caret from moving
|
|
683
|
-
e.preventDefault();
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/** @private */
|
|
688
309
|
_getItemLabel(item) {
|
|
689
310
|
let label = item && this.itemLabelPath ? get(this.itemLabelPath, item) : undefined;
|
|
690
311
|
if (label === undefined || label === null) {
|
|
@@ -702,73 +323,11 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
702
323
|
return value;
|
|
703
324
|
}
|
|
704
325
|
|
|
705
|
-
/**
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this._focusedIndex = Math.min(items.length - 1, this._focusedIndex + 1);
|
|
711
|
-
this._prefillFocusedItemLabel();
|
|
712
|
-
}
|
|
713
|
-
} else {
|
|
714
|
-
this.open();
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/** @private */
|
|
719
|
-
_onArrowUp() {
|
|
720
|
-
if (this.opened) {
|
|
721
|
-
if (this._focusedIndex > -1) {
|
|
722
|
-
this._focusedIndex = Math.max(0, this._focusedIndex - 1);
|
|
723
|
-
} else {
|
|
724
|
-
const items = this._dropdownItems;
|
|
725
|
-
if (items) {
|
|
726
|
-
this._focusedIndex = items.length - 1;
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
this._prefillFocusedItemLabel();
|
|
731
|
-
} else {
|
|
732
|
-
this.open();
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/** @private */
|
|
737
|
-
_prefillFocusedItemLabel() {
|
|
738
|
-
if (this._focusedIndex > -1) {
|
|
739
|
-
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
740
|
-
this._inputElementValue = this._getItemLabel(focusedItem);
|
|
741
|
-
this._markAllSelectionRange();
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/** @private */
|
|
746
|
-
_setSelectionRange(start, end) {
|
|
747
|
-
// Setting selection range focuses and/or moves the caret in some browsers,
|
|
748
|
-
// and there's no need to modify the selection range if the input isn't focused anyway.
|
|
749
|
-
// This affects Safari. When the overlay is open, and then hitting tab, browser should focus
|
|
750
|
-
// the next focusable element instead of the combo-box itself.
|
|
751
|
-
if (this._isInputFocused() && this.inputElement.setSelectionRange) {
|
|
752
|
-
this.inputElement.setSelectionRange(start, end);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/** @private */
|
|
757
|
-
_markAllSelectionRange() {
|
|
758
|
-
if (this._inputElementValue !== undefined) {
|
|
759
|
-
this._setSelectionRange(0, this._inputElementValue.length);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/** @private */
|
|
764
|
-
_clearSelectionRange() {
|
|
765
|
-
if (this._inputElementValue !== undefined) {
|
|
766
|
-
const pos = this._inputElementValue ? this._inputElementValue.length : 0;
|
|
767
|
-
this._setSelectionRange(pos, pos);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/** @private */
|
|
326
|
+
/**
|
|
327
|
+
* Override method from `ComboBoxBaseMixin` to handle loading.
|
|
328
|
+
* @protected
|
|
329
|
+
* @override
|
|
330
|
+
*/
|
|
772
331
|
_closeOrCommit() {
|
|
773
332
|
if (!this.opened && !this.loading) {
|
|
774
333
|
this._commitValue();
|
|
@@ -778,39 +337,10 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
778
337
|
}
|
|
779
338
|
|
|
780
339
|
/**
|
|
781
|
-
* Override
|
|
782
|
-
*
|
|
783
|
-
* @param {KeyboardEvent} e
|
|
340
|
+
* Override method from `ComboBoxBaseMixin` to handle valid value.
|
|
784
341
|
* @protected
|
|
785
342
|
* @override
|
|
786
343
|
*/
|
|
787
|
-
_onEnter(e) {
|
|
788
|
-
// Do not commit value when custom values are disallowed and input value is not a valid option
|
|
789
|
-
// also stop propagation of the event, otherwise the user could submit a form while the input
|
|
790
|
-
// still contains an invalid value
|
|
791
|
-
if (!this._hasValidInputValue()) {
|
|
792
|
-
// Do not submit the surrounding form.
|
|
793
|
-
e.preventDefault();
|
|
794
|
-
// Do not trigger global listeners
|
|
795
|
-
e.stopPropagation();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Stop propagation of the enter event only if the dropdown is opened, this
|
|
800
|
-
// "consumes" the enter event for the action of closing the dropdown
|
|
801
|
-
if (this.opened) {
|
|
802
|
-
// Do not submit the surrounding form.
|
|
803
|
-
e.preventDefault();
|
|
804
|
-
// Do not trigger global listeners
|
|
805
|
-
e.stopPropagation();
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
this._closeOrCommit();
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* @protected
|
|
813
|
-
*/
|
|
814
344
|
_hasValidInputValue() {
|
|
815
345
|
const hasInvalidOption =
|
|
816
346
|
this._focusedIndex < 0 &&
|
|
@@ -821,62 +351,18 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
821
351
|
}
|
|
822
352
|
|
|
823
353
|
/**
|
|
824
|
-
* Override
|
|
825
|
-
* Do not call `super` in order to override clear
|
|
826
|
-
* button logic defined in `InputControlMixin`.
|
|
827
|
-
*
|
|
828
|
-
* @param {!KeyboardEvent} e
|
|
354
|
+
* Override method from `ComboBoxBaseMixin`.
|
|
829
355
|
* @protected
|
|
830
356
|
* @override
|
|
831
357
|
*/
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
this.autoOpenDisabled &&
|
|
835
|
-
(this.opened || (this.value !== this._inputElementValue && this._inputElementValue.length > 0))
|
|
836
|
-
) {
|
|
837
|
-
// Auto-open is disabled
|
|
838
|
-
// The overlay is open or
|
|
839
|
-
// The input value has changed but the change hasn't been committed, so cancel it.
|
|
840
|
-
e.stopPropagation();
|
|
841
|
-
this._focusedIndex = -1;
|
|
842
|
-
this.cancel();
|
|
843
|
-
} else if (this.opened) {
|
|
844
|
-
// Auto-open is enabled
|
|
845
|
-
// The overlay is open
|
|
846
|
-
e.stopPropagation();
|
|
847
|
-
|
|
848
|
-
if (this._focusedIndex > -1) {
|
|
849
|
-
// An item is focused, revert the input to the filtered value
|
|
850
|
-
this._focusedIndex = -1;
|
|
851
|
-
this._revertInputValue();
|
|
852
|
-
} else {
|
|
853
|
-
// No item is focused, cancel the change and close the overlay
|
|
854
|
-
this.cancel();
|
|
855
|
-
}
|
|
856
|
-
} else if (this.clearButtonVisible && !!this.value && !this.readonly) {
|
|
857
|
-
e.stopPropagation();
|
|
858
|
-
// The clear button is visible and the overlay is closed, so clear the value.
|
|
859
|
-
this._onClearAction();
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/** @private */
|
|
864
|
-
_toggleElementChanged(toggleElement) {
|
|
865
|
-
if (toggleElement) {
|
|
866
|
-
// Don't blur the input on toggle mousedown
|
|
867
|
-
toggleElement.addEventListener('mousedown', (e) => e.preventDefault());
|
|
868
|
-
// Unfocus previously focused element if focus is not inside combo box (on touch devices)
|
|
869
|
-
toggleElement.addEventListener('click', () => {
|
|
870
|
-
if (isTouch && !this._isInputFocused()) {
|
|
871
|
-
document.activeElement.blur();
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
}
|
|
358
|
+
_onEscapeCancel() {
|
|
359
|
+
this.cancel();
|
|
875
360
|
}
|
|
876
361
|
|
|
877
362
|
/**
|
|
878
|
-
*
|
|
363
|
+
* Override method from `ComboBoxBaseMixin` to reset selected item.
|
|
879
364
|
* @protected
|
|
365
|
+
* @override
|
|
880
366
|
*/
|
|
881
367
|
_onClearAction() {
|
|
882
368
|
this.selectedItem = null;
|
|
@@ -907,20 +393,43 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
907
393
|
this._closeOrCommit();
|
|
908
394
|
}
|
|
909
395
|
|
|
910
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* Override method from `ComboBoxBaseMixin` to store last committed value.
|
|
398
|
+
* @protected
|
|
399
|
+
* @override
|
|
400
|
+
*/
|
|
911
401
|
_onOpened() {
|
|
402
|
+
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
|
|
403
|
+
|
|
912
404
|
// _detectAndDispatchChange() should not consider value changes done before opening
|
|
913
405
|
this._lastCommittedValue = this.value;
|
|
914
406
|
}
|
|
915
407
|
|
|
916
|
-
/**
|
|
408
|
+
/**
|
|
409
|
+
* Override method from `ComboBoxBaseMixin` to dispatch an event.
|
|
410
|
+
* @protected
|
|
411
|
+
* @override
|
|
412
|
+
*/
|
|
413
|
+
_onOverlayClosed() {
|
|
414
|
+
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Override method from `ComboBoxBaseMixin` to commit value on overlay closing.
|
|
419
|
+
* @protected
|
|
420
|
+
* @override
|
|
421
|
+
*/
|
|
917
422
|
_onClosed() {
|
|
918
423
|
if (!this.loading || this.allowCustomValue) {
|
|
919
424
|
this._commitValue();
|
|
920
425
|
}
|
|
921
426
|
}
|
|
922
427
|
|
|
923
|
-
/**
|
|
428
|
+
/**
|
|
429
|
+
* Override method from `ComboBoxBaseMixin` to implement value commit logic.
|
|
430
|
+
* @protected
|
|
431
|
+
* @override
|
|
432
|
+
*/
|
|
924
433
|
_commitValue() {
|
|
925
434
|
if (this._focusedIndex > -1) {
|
|
926
435
|
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
@@ -980,7 +489,8 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
980
489
|
}
|
|
981
490
|
|
|
982
491
|
/**
|
|
983
|
-
* Override an event listener from `
|
|
492
|
+
* Override an event listener from `ComboBoxBaseMixin` to handle
|
|
493
|
+
* batched setting of both `opened` and `filter` properties.
|
|
984
494
|
* @param {!Event} event
|
|
985
495
|
* @protected
|
|
986
496
|
* @override
|
|
@@ -1045,7 +555,11 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1045
555
|
}
|
|
1046
556
|
}
|
|
1047
557
|
|
|
1048
|
-
/**
|
|
558
|
+
/**
|
|
559
|
+
* Override method from `ComboBoxBaseMixin` to handle reverting value.
|
|
560
|
+
* @protected
|
|
561
|
+
* @override
|
|
562
|
+
*/
|
|
1049
563
|
_revertInputValue() {
|
|
1050
564
|
if (this.filter !== '') {
|
|
1051
565
|
this._inputElementValue = this.filter;
|
|
@@ -1226,19 +740,6 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1226
740
|
}
|
|
1227
741
|
}
|
|
1228
742
|
|
|
1229
|
-
/** @private */
|
|
1230
|
-
_getItemElements() {
|
|
1231
|
-
return Array.from(this._scroller.querySelectorAll(`${this._tagNamePrefix}-item`));
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
/** @private */
|
|
1235
|
-
_scrollIntoView(index) {
|
|
1236
|
-
if (!this._scroller) {
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1239
|
-
this._scroller.scrollIntoView(index);
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
743
|
/**
|
|
1243
744
|
* Returns the first item that matches the provided value.
|
|
1244
745
|
*
|
|
@@ -1270,91 +771,20 @@ export const ComboBoxMixin = (subclass) =>
|
|
|
1270
771
|
});
|
|
1271
772
|
}
|
|
1272
773
|
|
|
1273
|
-
/** @private */
|
|
1274
|
-
_overlaySelectedItemChanged(e) {
|
|
1275
|
-
// Stop this private event from leaking outside.
|
|
1276
|
-
e.stopPropagation();
|
|
1277
|
-
|
|
1278
|
-
if (e.detail.item instanceof ComboBoxPlaceholder) {
|
|
1279
|
-
// Placeholder items should not be selectable.
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
if (this.opened) {
|
|
1284
|
-
this._focusedIndex = this.filteredItems.indexOf(e.detail.item);
|
|
1285
|
-
this.close();
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
774
|
/**
|
|
1290
|
-
* Override method
|
|
1291
|
-
* to close the overlay on blur and commit the value.
|
|
1292
|
-
*
|
|
1293
|
-
* @param {boolean} focused
|
|
775
|
+
* Override method from `ComboBoxBaseMixin`.
|
|
1294
776
|
* @protected
|
|
1295
777
|
* @override
|
|
1296
778
|
*/
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
if (!
|
|
1301
|
-
|
|
1302
|
-
// which will result in attempting to commit the same custom value once again.
|
|
1303
|
-
if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
|
|
1304
|
-
delete this._lastCustomValue;
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
if (isKeyboardActive()) {
|
|
1309
|
-
// Close on Tab key causing blur. With mouse, close on outside click instead.
|
|
1310
|
-
this._closeOrCommit();
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
if (!this.opened) {
|
|
1315
|
-
this._commitValue();
|
|
1316
|
-
} else if (!this._overlayOpened) {
|
|
1317
|
-
// Combo-box is opened, but overlay is not visible -> custom value was entered.
|
|
1318
|
-
// Make sure we close here as there won't be an "outside click" in this case.
|
|
1319
|
-
this.close();
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
/**
|
|
1325
|
-
* Override method inherited from `FocusMixin` to not remove focused
|
|
1326
|
-
* state when focus moves to the overlay.
|
|
1327
|
-
*
|
|
1328
|
-
* @param {FocusEvent} event
|
|
1329
|
-
* @return {boolean}
|
|
1330
|
-
* @protected
|
|
1331
|
-
* @override
|
|
1332
|
-
*/
|
|
1333
|
-
_shouldRemoveFocus(event) {
|
|
1334
|
-
// VoiceOver on iOS fires `focusout` event when moving focus to the item in the dropdown.
|
|
1335
|
-
// Do not focus the input in this case, because it would break announcement for the item.
|
|
1336
|
-
if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) {
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// Do not blur when focus moves to the overlay
|
|
1341
|
-
// Also, fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
|
|
1342
|
-
if (event.relatedTarget === this._overlayElement) {
|
|
1343
|
-
event.composedPath()[0].focus();
|
|
1344
|
-
return false;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
return true;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
/** @private */
|
|
1351
|
-
_onTouchend(event) {
|
|
1352
|
-
if (!this.clearElement || event.composedPath()[0] !== this.clearElement) {
|
|
779
|
+
_handleFocusOut() {
|
|
780
|
+
// User's logic in `custom-value-set` event listener might cause input to blur,
|
|
781
|
+
// which will result in attempting to commit the same custom value once again.
|
|
782
|
+
if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
|
|
783
|
+
delete this._lastCustomValue;
|
|
1353
784
|
return;
|
|
1354
785
|
}
|
|
1355
786
|
|
|
1356
|
-
|
|
1357
|
-
this._onClearAction();
|
|
787
|
+
super._handleFocusOut();
|
|
1358
788
|
}
|
|
1359
789
|
|
|
1360
790
|
/**
|