@vaadin/multi-select-combo-box 25.0.0-alpha8 → 25.0.0-beta1
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 +15 -18
- package/src/styles/vaadin-multi-select-combo-box-base-styles.js +1 -1
- package/src/styles/vaadin-multi-select-combo-box-chip-base-styles.js +23 -9
- package/src/vaadin-multi-select-combo-box-chip.js +1 -1
- package/src/vaadin-multi-select-combo-box-container.js +1 -0
- package/src/vaadin-multi-select-combo-box-item.js +1 -1
- package/src/vaadin-multi-select-combo-box-mixin.d.ts +18 -89
- package/src/vaadin-multi-select-combo-box-mixin.js +426 -307
- package/src/vaadin-multi-select-combo-box-overlay.js +2 -2
- package/src/vaadin-multi-select-combo-box-scroller.js +1 -1
- package/src/vaadin-multi-select-combo-box.d.ts +13 -6
- package/src/vaadin-multi-select-combo-box.js +37 -88
- package/vaadin-multi-select-combo-box.js +1 -1
- package/web-types.json +205 -230
- package/web-types.lit.json +78 -78
- package/src/styles/vaadin-multi-select-combo-box-chip-core-styles.js +0 -33
- package/src/styles/vaadin-multi-select-combo-box-core-styles.d.ts +0 -8
- package/src/styles/vaadin-multi-select-combo-box-core-styles.js +0 -47
- package/src/styles/vaadin-multi-select-combo-box-overlay-core-styles.js +0 -21
- package/src/styles/vaadin-multi-select-combo-box-scroller-core-styles.js +0 -27
- package/src/vaadin-multi-select-combo-box-internal-mixin.js +0 -446
- package/src/vaadin-multi-select-combo-box-internal.js +0 -57
- package/theme/lumo/vaadin-multi-select-combo-box-chip-styles.d.ts +0 -10
- package/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js +0 -107
- package/theme/lumo/vaadin-multi-select-combo-box-styles.d.ts +0 -10
- package/theme/lumo/vaadin-multi-select-combo-box-styles.js +0 -121
- package/theme/lumo/vaadin-multi-select-combo-box.d.ts +0 -8
- package/theme/lumo/vaadin-multi-select-combo-box.js +0 -8
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { announce } from '@vaadin/a11y-base/src/announce.js';
|
|
7
|
+
import { ComboBoxDataProviderMixin } from '@vaadin/combo-box/src/vaadin-combo-box-data-provider-mixin.js';
|
|
8
|
+
import { ComboBoxItemsMixin } from '@vaadin/combo-box/src/vaadin-combo-box-items-mixin.js';
|
|
9
|
+
import { ComboBoxPlaceholder } from '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
|
10
|
+
import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
|
|
7
11
|
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
8
12
|
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
9
13
|
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
|
|
@@ -11,13 +15,27 @@ import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js
|
|
|
11
15
|
import { InputController } from '@vaadin/field-base/src/input-controller.js';
|
|
12
16
|
import { LabelledInputController } from '@vaadin/field-base/src/labelled-input-controller.js';
|
|
13
17
|
|
|
18
|
+
const DEFAULT_I18N = {
|
|
19
|
+
cleared: 'Selection cleared',
|
|
20
|
+
focused: 'focused. Press Backspace to remove',
|
|
21
|
+
selected: 'added to selection',
|
|
22
|
+
deselected: 'removed from selection',
|
|
23
|
+
total: '{count} items selected',
|
|
24
|
+
};
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* @polymerMixin
|
|
28
|
+
* @mixes ComboBoxDataProviderMixin
|
|
29
|
+
* @mixes ComboBoxItemsMixin
|
|
30
|
+
* @mixes I18nMixin
|
|
16
31
|
* @mixes InputControlMixin
|
|
17
32
|
* @mixes ResizeMixin
|
|
18
33
|
*/
|
|
19
34
|
export const MultiSelectComboBoxMixin = (superClass) =>
|
|
20
|
-
class MultiSelectComboBoxMixinClass extends
|
|
35
|
+
class MultiSelectComboBoxMixinClass extends I18nMixin(
|
|
36
|
+
DEFAULT_I18N,
|
|
37
|
+
ComboBoxDataProviderMixin(ComboBoxItemsMixin(InputControlMixin(ResizeMixin(superClass)))),
|
|
38
|
+
) {
|
|
21
39
|
static get properties() {
|
|
22
40
|
return {
|
|
23
41
|
/**
|
|
@@ -29,7 +47,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
29
47
|
type: Boolean,
|
|
30
48
|
value: false,
|
|
31
49
|
reflectToAttribute: true,
|
|
32
|
-
observer: '_autoExpandHorizontallyChanged',
|
|
33
50
|
sync: true,
|
|
34
51
|
},
|
|
35
52
|
|
|
@@ -43,37 +60,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
43
60
|
type: Boolean,
|
|
44
61
|
value: false,
|
|
45
62
|
reflectToAttribute: true,
|
|
46
|
-
observer: '_autoExpandVerticallyChanged',
|
|
47
|
-
sync: true,
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Set true to prevent the overlay from opening automatically.
|
|
52
|
-
* @attr {boolean} auto-open-disabled
|
|
53
|
-
*/
|
|
54
|
-
autoOpenDisabled: {
|
|
55
|
-
type: Boolean,
|
|
56
|
-
sync: true,
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Set to true to display the clear icon which clears the input.
|
|
61
|
-
* @attr {boolean} clear-button-visible
|
|
62
|
-
*/
|
|
63
|
-
clearButtonVisible: {
|
|
64
|
-
type: Boolean,
|
|
65
|
-
reflectToAttribute: true,
|
|
66
|
-
observer: '_clearButtonVisibleChanged',
|
|
67
|
-
value: false,
|
|
68
|
-
sync: true,
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A full set of items to filter the visible options from.
|
|
73
|
-
* The items can be of either `String` or `Object` type.
|
|
74
|
-
*/
|
|
75
|
-
items: {
|
|
76
|
-
type: Array,
|
|
77
63
|
sync: true,
|
|
78
64
|
},
|
|
79
65
|
|
|
@@ -85,28 +71,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
85
71
|
*/
|
|
86
72
|
itemClassNameGenerator: {
|
|
87
73
|
type: Object,
|
|
88
|
-
observer: '__itemClassNameGeneratorChanged',
|
|
89
|
-
sync: true,
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The item property used for a visual representation of the item.
|
|
94
|
-
* @attr {string} item-label-path
|
|
95
|
-
*/
|
|
96
|
-
itemLabelPath: {
|
|
97
|
-
type: String,
|
|
98
|
-
value: 'label',
|
|
99
|
-
sync: true,
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Path for the value of the item. If `items` is an array of objects,
|
|
104
|
-
* this property is used as a string value for the selected item.
|
|
105
|
-
* @attr {string} item-value-path
|
|
106
|
-
*/
|
|
107
|
-
itemValuePath: {
|
|
108
|
-
type: String,
|
|
109
|
-
value: 'value',
|
|
110
74
|
sync: true,
|
|
111
75
|
},
|
|
112
76
|
|
|
@@ -119,43 +83,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
119
83
|
sync: true,
|
|
120
84
|
},
|
|
121
85
|
|
|
122
|
-
/**
|
|
123
|
-
* The object used to localize this component.
|
|
124
|
-
* To change the default localization, replace the entire
|
|
125
|
-
* _i18n_ object or just the property you want to modify.
|
|
126
|
-
*
|
|
127
|
-
* The object has the following JSON structure and default values:
|
|
128
|
-
* ```
|
|
129
|
-
* {
|
|
130
|
-
* // Screen reader announcement on clear button click.
|
|
131
|
-
* cleared: 'Selection cleared',
|
|
132
|
-
* // Screen reader announcement when a chip is focused.
|
|
133
|
-
* focused: ' focused. Press Backspace to remove',
|
|
134
|
-
* // Screen reader announcement when item is selected.
|
|
135
|
-
* selected: 'added to selection',
|
|
136
|
-
* // Screen reader announcement when item is deselected.
|
|
137
|
-
* deselected: 'removed from selection',
|
|
138
|
-
* // Screen reader announcement of the selected items count.
|
|
139
|
-
* // {count} is replaced with the actual count of items.
|
|
140
|
-
* total: '{count} items selected',
|
|
141
|
-
* }
|
|
142
|
-
* ```
|
|
143
|
-
* @type {!MultiSelectComboBoxI18n}
|
|
144
|
-
* @default {English/US}
|
|
145
|
-
*/
|
|
146
|
-
i18n: {
|
|
147
|
-
type: Object,
|
|
148
|
-
value: () => {
|
|
149
|
-
return {
|
|
150
|
-
cleared: 'Selection cleared',
|
|
151
|
-
focused: 'focused. Press Backspace to remove',
|
|
152
|
-
selected: 'added to selection',
|
|
153
|
-
deselected: 'removed from selection',
|
|
154
|
-
total: '{count} items selected',
|
|
155
|
-
};
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
|
|
159
86
|
/**
|
|
160
87
|
* When true, filter string isn't cleared after selecting an item.
|
|
161
88
|
*/
|
|
@@ -174,23 +101,12 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
174
101
|
sync: true,
|
|
175
102
|
},
|
|
176
103
|
|
|
177
|
-
/**
|
|
178
|
-
* A space-delimited list of CSS class names to set on the overlay element.
|
|
179
|
-
*
|
|
180
|
-
* @attr {string} overlay-class
|
|
181
|
-
*/
|
|
182
|
-
overlayClass: {
|
|
183
|
-
type: String,
|
|
184
|
-
sync: true,
|
|
185
|
-
},
|
|
186
|
-
|
|
187
104
|
/**
|
|
188
105
|
* When present, it specifies that the field is read-only.
|
|
189
106
|
*/
|
|
190
107
|
readonly: {
|
|
191
108
|
type: Boolean,
|
|
192
109
|
value: false,
|
|
193
|
-
observer: '_readonlyChanged',
|
|
194
110
|
reflectToAttribute: true,
|
|
195
111
|
sync: true,
|
|
196
112
|
},
|
|
@@ -206,53 +122,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
206
122
|
sync: true,
|
|
207
123
|
},
|
|
208
124
|
|
|
209
|
-
/**
|
|
210
|
-
* True if the dropdown is open, false otherwise.
|
|
211
|
-
*/
|
|
212
|
-
opened: {
|
|
213
|
-
type: Boolean,
|
|
214
|
-
notify: true,
|
|
215
|
-
value: false,
|
|
216
|
-
reflectToAttribute: true,
|
|
217
|
-
sync: true,
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Total number of items.
|
|
222
|
-
*/
|
|
223
|
-
size: {
|
|
224
|
-
type: Number,
|
|
225
|
-
sync: true,
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Number of items fetched at a time from the data provider.
|
|
230
|
-
* @attr {number} page-size
|
|
231
|
-
*/
|
|
232
|
-
pageSize: {
|
|
233
|
-
type: Number,
|
|
234
|
-
value: 50,
|
|
235
|
-
observer: '_pageSizeChanged',
|
|
236
|
-
sync: true,
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Function that provides items lazily. Receives two arguments:
|
|
241
|
-
*
|
|
242
|
-
* - `params` - Object with the following properties:
|
|
243
|
-
* - `params.page` Requested page index
|
|
244
|
-
* - `params.pageSize` Current page size
|
|
245
|
-
* - `params.filter` Currently applied filter
|
|
246
|
-
*
|
|
247
|
-
* - `callback(items, size)` - Callback function with arguments:
|
|
248
|
-
* - `items` Current page of items
|
|
249
|
-
* - `size` Total number of items.
|
|
250
|
-
*/
|
|
251
|
-
dataProvider: {
|
|
252
|
-
type: Object,
|
|
253
|
-
sync: true,
|
|
254
|
-
},
|
|
255
|
-
|
|
256
125
|
/**
|
|
257
126
|
* When true, the user can input a value that is not present in the items list.
|
|
258
127
|
* @attr {boolean} allow-custom-value
|
|
@@ -290,26 +159,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
290
159
|
sync: true,
|
|
291
160
|
},
|
|
292
161
|
|
|
293
|
-
/**
|
|
294
|
-
* Filtering string the user has typed into the input field.
|
|
295
|
-
*/
|
|
296
|
-
filter: {
|
|
297
|
-
type: String,
|
|
298
|
-
value: '',
|
|
299
|
-
notify: true,
|
|
300
|
-
sync: true,
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* A subset of items, filtered based on the user input. Filtered items
|
|
305
|
-
* can be assigned directly to omit the internal filtering functionality.
|
|
306
|
-
* The items can be of either `String` or `Object` type.
|
|
307
|
-
*/
|
|
308
|
-
filteredItems: {
|
|
309
|
-
type: Array,
|
|
310
|
-
sync: true,
|
|
311
|
-
},
|
|
312
|
-
|
|
313
162
|
/**
|
|
314
163
|
* Set to true to group selected items at the top of the overlay.
|
|
315
164
|
* @attr {boolean} selected-items-on-top
|
|
@@ -348,6 +197,13 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
348
197
|
/** @private */
|
|
349
198
|
_topGroup: {
|
|
350
199
|
type: Array,
|
|
200
|
+
observer: '_topGroupChanged',
|
|
201
|
+
sync: true,
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/** @private */
|
|
205
|
+
_inputField: {
|
|
206
|
+
type: Object,
|
|
351
207
|
},
|
|
352
208
|
};
|
|
353
209
|
}
|
|
@@ -355,11 +211,44 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
355
211
|
static get observers() {
|
|
356
212
|
return [
|
|
357
213
|
'_selectedItemsChanged(selectedItems)',
|
|
214
|
+
'__openedOrItemsChanged(opened, _dropdownItems, loading, __keepOverlayOpened)',
|
|
358
215
|
'__updateOverflowChip(_overflow, _overflowItems, disabled, readonly)',
|
|
216
|
+
'__updateScroller(opened, _dropdownItems, _focusedIndex, _theme)',
|
|
359
217
|
'__updateTopGroup(selectedItemsOnTop, selectedItems, opened)',
|
|
360
218
|
];
|
|
361
219
|
}
|
|
362
220
|
|
|
221
|
+
/**
|
|
222
|
+
* The object used to localize this component. To change the default
|
|
223
|
+
* localization, replace this with an object that provides all properties, or
|
|
224
|
+
* just the individual properties you want to change.
|
|
225
|
+
*
|
|
226
|
+
* The object has the following JSON structure and default values:
|
|
227
|
+
* ```js
|
|
228
|
+
* {
|
|
229
|
+
* // Screen reader announcement on clear button click.
|
|
230
|
+
* cleared: 'Selection cleared',
|
|
231
|
+
* // Screen reader announcement when a chip is focused.
|
|
232
|
+
* focused: ' focused. Press Backspace to remove',
|
|
233
|
+
* // Screen reader announcement when item is selected.
|
|
234
|
+
* selected: 'added to selection',
|
|
235
|
+
* // Screen reader announcement when item is deselected.
|
|
236
|
+
* deselected: 'removed from selection',
|
|
237
|
+
* // Screen reader announcement of the selected items count.
|
|
238
|
+
* // {count} is replaced with the actual count of items.
|
|
239
|
+
* total: '{count} items selected',
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
* @return {!MultiSelectComboBoxI18n}
|
|
243
|
+
*/
|
|
244
|
+
get i18n() {
|
|
245
|
+
return super.i18n;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
set i18n(value) {
|
|
249
|
+
super.i18n = value;
|
|
250
|
+
}
|
|
251
|
+
|
|
363
252
|
/** @protected */
|
|
364
253
|
get slotStyles() {
|
|
365
254
|
const tag = this.localName;
|
|
@@ -399,6 +288,15 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
399
288
|
return this.selectedItems && this.selectedItems.length > 0;
|
|
400
289
|
}
|
|
401
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Tag name prefix used by scroller and items.
|
|
293
|
+
* @protected
|
|
294
|
+
* @return {string}
|
|
295
|
+
*/
|
|
296
|
+
get _tagNamePrefix() {
|
|
297
|
+
return 'vaadin-multi-select-combo-box';
|
|
298
|
+
}
|
|
299
|
+
|
|
402
300
|
/** @protected */
|
|
403
301
|
ready() {
|
|
404
302
|
super.ready();
|
|
@@ -419,6 +317,7 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
419
317
|
this._tooltipController.setAriaTarget(this.inputElement);
|
|
420
318
|
this._tooltipController.setShouldShow((target) => !target.opened);
|
|
421
319
|
|
|
320
|
+
this._toggleElement = this.$.toggleButton;
|
|
422
321
|
this._inputField = this.shadowRoot.querySelector('[part="input-field"]');
|
|
423
322
|
|
|
424
323
|
this._overflowController = new SlotController(this, 'overflow', 'vaadin-multi-select-combo-box-chip', {
|
|
@@ -428,8 +327,41 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
428
327
|
},
|
|
429
328
|
});
|
|
430
329
|
this.addController(this._overflowController);
|
|
330
|
+
}
|
|
431
331
|
|
|
432
|
-
|
|
332
|
+
/** @protected */
|
|
333
|
+
updated(props) {
|
|
334
|
+
super.updated(props);
|
|
335
|
+
|
|
336
|
+
['loading', 'itemIdPath', 'itemClassNameGenerator', 'renderer'].forEach((prop) => {
|
|
337
|
+
if (props.has(prop)) {
|
|
338
|
+
this._scroller[prop] = this[prop];
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (props.has('selectedItems') && this.opened) {
|
|
343
|
+
this.$.overlay._updateOverlayWidth();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const chipProps = [
|
|
347
|
+
'autoExpandHorizontally',
|
|
348
|
+
'autoExpandVertically',
|
|
349
|
+
'disabled',
|
|
350
|
+
'readonly',
|
|
351
|
+
'clearButtonVisible',
|
|
352
|
+
'itemClassNameGenerator',
|
|
353
|
+
];
|
|
354
|
+
if (chipProps.some((prop) => props.has(prop))) {
|
|
355
|
+
this.__updateChips();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (props.has('readonly')) {
|
|
359
|
+
this._setDropdownItems(this.filteredItems);
|
|
360
|
+
|
|
361
|
+
if (this.dataProvider) {
|
|
362
|
+
this.clearCache();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
433
365
|
}
|
|
434
366
|
|
|
435
367
|
/**
|
|
@@ -440,22 +372,37 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
440
372
|
return this.required && !this.readonly ? this._hasValue : true;
|
|
441
373
|
}
|
|
442
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Opens the dropdown list.
|
|
377
|
+
* @override
|
|
378
|
+
*/
|
|
379
|
+
open() {
|
|
380
|
+
// Allow opening dropdown when readonly.
|
|
381
|
+
if (!this.disabled && !(this.readonly && this.selectedItems.length === 0)) {
|
|
382
|
+
this.opened = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
443
386
|
/**
|
|
444
387
|
* Clears the selected items.
|
|
445
388
|
*/
|
|
446
389
|
clear() {
|
|
447
390
|
this.__updateSelection([]);
|
|
448
391
|
|
|
449
|
-
announce(this.
|
|
392
|
+
announce(this.__effectiveI18n.cleared);
|
|
450
393
|
}
|
|
451
394
|
|
|
452
395
|
/**
|
|
453
396
|
* Clears the cached pages and reloads data from data provider when needed.
|
|
397
|
+
* @override
|
|
454
398
|
*/
|
|
455
399
|
clearCache() {
|
|
456
|
-
|
|
457
|
-
|
|
400
|
+
// Do not clear the data provider cache when read-only.
|
|
401
|
+
if (this.readonly) {
|
|
402
|
+
return;
|
|
458
403
|
}
|
|
404
|
+
|
|
405
|
+
super.clearCache();
|
|
459
406
|
}
|
|
460
407
|
|
|
461
408
|
/**
|
|
@@ -465,34 +412,130 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
465
412
|
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
466
413
|
*/
|
|
467
414
|
requestContentUpdate() {
|
|
468
|
-
if (this
|
|
469
|
-
|
|
415
|
+
if (!this._scroller) {
|
|
416
|
+
return;
|
|
470
417
|
}
|
|
418
|
+
|
|
419
|
+
this._scroller.requestContentUpdate();
|
|
420
|
+
|
|
421
|
+
this._getItemElements().forEach((item) => {
|
|
422
|
+
item.requestContentUpdate();
|
|
423
|
+
});
|
|
471
424
|
}
|
|
472
425
|
|
|
473
426
|
/**
|
|
474
|
-
* Override method
|
|
427
|
+
* Override method from `ComboBoxBaseMixin` to implement clearing logic.
|
|
475
428
|
* @protected
|
|
476
429
|
* @override
|
|
477
430
|
*/
|
|
478
|
-
|
|
479
|
-
|
|
431
|
+
_onClearAction() {
|
|
432
|
+
this.clear();
|
|
433
|
+
}
|
|
480
434
|
|
|
481
|
-
|
|
482
|
-
|
|
435
|
+
/**
|
|
436
|
+
* Override method from `ComboBoxBaseMixin`
|
|
437
|
+
* to commit value on overlay closing.
|
|
438
|
+
* @protected
|
|
439
|
+
* @override
|
|
440
|
+
*/
|
|
441
|
+
_onClosed() {
|
|
442
|
+
// Do not commit selected item again on outside click
|
|
443
|
+
this._ignoreCommitValue = true;
|
|
444
|
+
|
|
445
|
+
if (!this.loading || this.allowCustomValue) {
|
|
446
|
+
this._commitValue();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** @private */
|
|
451
|
+
__updateScroller(opened, items, focusedIndex, theme) {
|
|
452
|
+
if (opened) {
|
|
453
|
+
this._scroller.style.maxHeight =
|
|
454
|
+
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this._scroller.setProperties({
|
|
458
|
+
items: opened ? items : [],
|
|
459
|
+
opened,
|
|
460
|
+
focusedIndex,
|
|
461
|
+
theme,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** @private */
|
|
466
|
+
__openedOrItemsChanged(opened, items, loading, keepOverlayOpened) {
|
|
467
|
+
// Close the overlay if there are no items to display.
|
|
468
|
+
// See https://github.com/vaadin/vaadin-combo-box/pull/964
|
|
469
|
+
this._overlayOpened = opened && (keepOverlayOpened || loading || !!(items && items.length));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @protected
|
|
474
|
+
*/
|
|
475
|
+
_closeOrCommit() {
|
|
476
|
+
if (!this.opened) {
|
|
477
|
+
this._commitValue();
|
|
478
|
+
} else {
|
|
479
|
+
this.close();
|
|
483
480
|
}
|
|
484
481
|
}
|
|
485
482
|
|
|
486
483
|
/**
|
|
487
|
-
* Override method inherited from `InputMixin` to forward the input to combo-box.
|
|
488
484
|
* @protected
|
|
489
485
|
* @override
|
|
490
486
|
*/
|
|
491
|
-
|
|
492
|
-
|
|
487
|
+
_commitValue() {
|
|
488
|
+
// Store filter value for checking if user input is matching
|
|
489
|
+
// an item which is already selected, to not un-select it.
|
|
490
|
+
this._lastFilter = this.filter;
|
|
491
|
+
|
|
492
|
+
// Do not commit focused item on not blur / outside click
|
|
493
|
+
if (this._ignoreCommitValue) {
|
|
494
|
+
this._inputElementValue = '';
|
|
495
|
+
this._focusedIndex = -1;
|
|
496
|
+
this._ignoreCommitValue = false;
|
|
497
|
+
} else {
|
|
498
|
+
this.__commitUserInput();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Clear filter unless keepFilter is set
|
|
502
|
+
if (!this.keepFilter || !this.opened) {
|
|
503
|
+
this.filter = '';
|
|
504
|
+
}
|
|
505
|
+
}
|
|
493
506
|
|
|
494
|
-
|
|
495
|
-
|
|
507
|
+
/** @private */
|
|
508
|
+
__commitUserInput() {
|
|
509
|
+
if (this._focusedIndex > -1) {
|
|
510
|
+
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
511
|
+
this.__selectItem(focusedItem);
|
|
512
|
+
} else if (this._inputElementValue) {
|
|
513
|
+
// Detect if input value doesn't match an existing item
|
|
514
|
+
const items = [...this._dropdownItems];
|
|
515
|
+
const itemMatchingInputValue = items[this.__getItemIndexByLabel(items, this._inputElementValue)];
|
|
516
|
+
|
|
517
|
+
if (this.allowCustomValue && !itemMatchingInputValue) {
|
|
518
|
+
const customValue = this._inputElementValue;
|
|
519
|
+
|
|
520
|
+
// Store reference to the last custom value for checking it on focusout.
|
|
521
|
+
this._lastCustomValue = customValue;
|
|
522
|
+
|
|
523
|
+
this.__clearInternalValue(true);
|
|
524
|
+
|
|
525
|
+
this.dispatchEvent(
|
|
526
|
+
new CustomEvent('custom-value-set', {
|
|
527
|
+
detail: customValue,
|
|
528
|
+
composed: true,
|
|
529
|
+
bubbles: true,
|
|
530
|
+
}),
|
|
531
|
+
);
|
|
532
|
+
} else if (!this.allowCustomValue && !this.opened && itemMatchingInputValue) {
|
|
533
|
+
// An item matching by label was found, select it.
|
|
534
|
+
this.__selectItem(itemMatchingInputValue);
|
|
535
|
+
} else {
|
|
536
|
+
// Clear input value on Escape press while closed.
|
|
537
|
+
this._inputElementValue = '';
|
|
538
|
+
}
|
|
496
539
|
}
|
|
497
540
|
}
|
|
498
541
|
|
|
@@ -502,6 +545,10 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
502
545
|
* @protected
|
|
503
546
|
*/
|
|
504
547
|
_setFocused(focused) {
|
|
548
|
+
if (!focused) {
|
|
549
|
+
this._ignoreCommitValue = true;
|
|
550
|
+
}
|
|
551
|
+
|
|
505
552
|
super._setFocused(focused);
|
|
506
553
|
|
|
507
554
|
// Do not validate when focusout is caused by document
|
|
@@ -510,6 +557,10 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
510
557
|
this._focusedChipIndex = -1;
|
|
511
558
|
this._requestValidation();
|
|
512
559
|
}
|
|
560
|
+
|
|
561
|
+
if (!focused && this.readonly && !this._closeOnBlurIsPrevented) {
|
|
562
|
+
this.close();
|
|
563
|
+
}
|
|
513
564
|
}
|
|
514
565
|
|
|
515
566
|
/**
|
|
@@ -541,73 +592,6 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
541
592
|
super._delegateAttribute(name, value);
|
|
542
593
|
}
|
|
543
594
|
|
|
544
|
-
/** @private */
|
|
545
|
-
_autoExpandHorizontallyChanged(autoExpand, oldAutoExpand) {
|
|
546
|
-
if (autoExpand || oldAutoExpand) {
|
|
547
|
-
this.__updateChips();
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/** @private */
|
|
552
|
-
_autoExpandVerticallyChanged(autoExpand, oldAutoExpand) {
|
|
553
|
-
if (autoExpand || oldAutoExpand) {
|
|
554
|
-
this.__updateChips();
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Setting clear button visible reduces total space available
|
|
560
|
-
* for rendering chips, and making it hidden increases it.
|
|
561
|
-
* @private
|
|
562
|
-
*/
|
|
563
|
-
_clearButtonVisibleChanged(visible, oldVisible) {
|
|
564
|
-
if (visible || oldVisible) {
|
|
565
|
-
this.__updateChips();
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Implement two-way binding for the `filteredItems` property
|
|
571
|
-
* that can be set on the internal combo-box element.
|
|
572
|
-
*
|
|
573
|
-
* @param {CustomEvent} event
|
|
574
|
-
* @private
|
|
575
|
-
*/
|
|
576
|
-
_onFilteredItemsChanged(event) {
|
|
577
|
-
const { value } = event.detail;
|
|
578
|
-
if (Array.isArray(value) || value == null) {
|
|
579
|
-
this.filteredItems = value;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/** @private */
|
|
584
|
-
_readonlyChanged(readonly, oldReadonly) {
|
|
585
|
-
if (readonly || oldReadonly) {
|
|
586
|
-
this.__updateChips();
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (this.dataProvider) {
|
|
590
|
-
this.clearCache();
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/** @private */
|
|
595
|
-
__itemClassNameGeneratorChanged(generator, oldGenerator) {
|
|
596
|
-
if (generator || oldGenerator) {
|
|
597
|
-
this.__updateChips();
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/** @private */
|
|
602
|
-
_pageSizeChanged(pageSize, oldPageSize) {
|
|
603
|
-
if (Math.floor(pageSize) !== pageSize || pageSize <= 0) {
|
|
604
|
-
this.pageSize = oldPageSize;
|
|
605
|
-
console.error('"pageSize" value must be an integer > 0');
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
this.$.comboBox.pageSize = this.pageSize;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
595
|
/** @private */
|
|
612
596
|
_placeholderChanged(placeholder) {
|
|
613
597
|
const tmpPlaceholder = this.__tmpA11yPlaceholder;
|
|
@@ -643,15 +627,90 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
643
627
|
|
|
644
628
|
// Update selected for dropdown items
|
|
645
629
|
this.requestContentUpdate();
|
|
630
|
+
}
|
|
646
631
|
|
|
647
|
-
|
|
648
|
-
|
|
632
|
+
/** @private */
|
|
633
|
+
_topGroupChanged(topGroup) {
|
|
634
|
+
if (topGroup) {
|
|
635
|
+
this._setDropdownItems(this.filteredItems);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Override method from `ComboBoxBaseMixin` to handle valid value.
|
|
641
|
+
* @protected
|
|
642
|
+
* @override
|
|
643
|
+
*/
|
|
644
|
+
_hasValidInputValue() {
|
|
645
|
+
const hasInvalidOption = this._focusedIndex < 0 && this._inputElementValue !== '';
|
|
646
|
+
return this.allowCustomValue || !hasInvalidOption;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Override method inherited from the combo-box
|
|
651
|
+
* to not request data provider when read-only.
|
|
652
|
+
*
|
|
653
|
+
* @protected
|
|
654
|
+
* @override
|
|
655
|
+
*/
|
|
656
|
+
_shouldFetchData() {
|
|
657
|
+
if (this.readonly) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return super._shouldFetchData();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Override combo-box method to group selected
|
|
666
|
+
* items at the top of the overlay.
|
|
667
|
+
*
|
|
668
|
+
* @protected
|
|
669
|
+
* @override
|
|
670
|
+
*/
|
|
671
|
+
_setDropdownItems(items) {
|
|
672
|
+
if (this.readonly) {
|
|
673
|
+
this.__setDropdownItems(this.selectedItems);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (this.filter || !this.selectedItemsOnTop) {
|
|
678
|
+
this.__setDropdownItems(items);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (items && items.length && this._topGroup && this._topGroup.length) {
|
|
683
|
+
// Filter out items included to the top group.
|
|
684
|
+
const filteredItems = items.filter((item) => this._findIndex(item, this._topGroup, this.itemIdPath) === -1);
|
|
685
|
+
|
|
686
|
+
this.__setDropdownItems(this._topGroup.concat(filteredItems));
|
|
687
|
+
return;
|
|
649
688
|
}
|
|
689
|
+
|
|
690
|
+
this.__setDropdownItems(items);
|
|
650
691
|
}
|
|
651
692
|
|
|
652
693
|
/** @private */
|
|
653
|
-
|
|
654
|
-
|
|
694
|
+
__setDropdownItems(newItems) {
|
|
695
|
+
const oldItems = this._dropdownItems;
|
|
696
|
+
this._dropdownItems = newItems;
|
|
697
|
+
|
|
698
|
+
// Store the currently focused item if any. The focused index preserves
|
|
699
|
+
// in the case when more filtered items are loading but it is reset
|
|
700
|
+
// when the user types in a filter query.
|
|
701
|
+
const focusedItem = oldItems ? oldItems[this._focusedIndex] : null;
|
|
702
|
+
|
|
703
|
+
// Try to first set focus on the item that had been focused before `newItems` were updated
|
|
704
|
+
// if it is still present in the `newItems` array. Otherwise, set the focused index
|
|
705
|
+
// depending on the selected item or the filter query.
|
|
706
|
+
const focusedItemIndex = this.__getItemIndexByValue(newItems, this._getItemValue(focusedItem));
|
|
707
|
+
if (focusedItemIndex > -1) {
|
|
708
|
+
this._focusedIndex = focusedItemIndex;
|
|
709
|
+
} else {
|
|
710
|
+
// When the user filled in something that is different from the current value = filtering is enabled,
|
|
711
|
+
// set the focused index to the item that matches the filter query.
|
|
712
|
+
this._focusedIndex = this.__getItemIndexByLabel(newItems, this.filter);
|
|
713
|
+
}
|
|
655
714
|
}
|
|
656
715
|
|
|
657
716
|
/** @private */
|
|
@@ -682,13 +741,10 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
682
741
|
*/
|
|
683
742
|
__clearInternalValue(force = false) {
|
|
684
743
|
if (!this.keepFilter || force) {
|
|
685
|
-
// Clear both
|
|
744
|
+
// Clear both input value and filter.
|
|
686
745
|
this.filter = '';
|
|
687
|
-
this
|
|
746
|
+
this._inputElementValue = '';
|
|
688
747
|
} else {
|
|
689
|
-
// Only clear combo box value. This effectively resets _lastCommittedValue
|
|
690
|
-
// which allows toggling the same item multiple times via keyboard.
|
|
691
|
-
this.$.comboBox.clear();
|
|
692
748
|
// Restore input to the filter value. Needed when items are
|
|
693
749
|
// navigated with keyboard, which overrides the input value
|
|
694
750
|
// with the item label.
|
|
@@ -699,8 +755,8 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
699
755
|
/** @private */
|
|
700
756
|
__announceItem(itemLabel, isSelected, itemCount) {
|
|
701
757
|
const state = isSelected ? 'selected' : 'deselected';
|
|
702
|
-
const total = this.
|
|
703
|
-
announce(`${itemLabel} ${this.
|
|
758
|
+
const total = this.__effectiveI18n.total.replace('{count}', itemCount || 0);
|
|
759
|
+
announce(`${itemLabel} ${this.__effectiveI18n[state]} ${total}`);
|
|
704
760
|
}
|
|
705
761
|
|
|
706
762
|
/** @private */
|
|
@@ -925,26 +981,20 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
925
981
|
}
|
|
926
982
|
}
|
|
927
983
|
|
|
928
|
-
/** @private */
|
|
929
|
-
_onClearButtonTouchend(event) {
|
|
930
|
-
// Cancel the following click and focus events
|
|
931
|
-
event.preventDefault();
|
|
932
|
-
// Prevent default combo box behavior which can otherwise unnecessarily
|
|
933
|
-
// clear the input and filter
|
|
934
|
-
event.stopPropagation();
|
|
935
|
-
|
|
936
|
-
this.clear();
|
|
937
|
-
}
|
|
938
|
-
|
|
939
984
|
/**
|
|
940
|
-
* Override method
|
|
985
|
+
* Override method from `ComboBoxBaseMixin` to deselect
|
|
986
|
+
* dropdown item by requesting content update on clear.
|
|
987
|
+
* @param {Event} event
|
|
941
988
|
* @protected
|
|
942
|
-
* @override
|
|
943
989
|
*/
|
|
944
990
|
_onClearButtonClick(event) {
|
|
945
991
|
event.stopPropagation();
|
|
946
992
|
|
|
947
|
-
|
|
993
|
+
super._onClearButtonClick(event);
|
|
994
|
+
|
|
995
|
+
if (this.opened) {
|
|
996
|
+
this.requestContentUpdate();
|
|
997
|
+
}
|
|
948
998
|
}
|
|
949
999
|
|
|
950
1000
|
/**
|
|
@@ -969,10 +1019,86 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
969
1019
|
* @override
|
|
970
1020
|
*/
|
|
971
1021
|
_onEscape(event) {
|
|
972
|
-
if (this.
|
|
1022
|
+
if (this.readonly) {
|
|
1023
|
+
event.stopPropagation();
|
|
1024
|
+
if (this.opened) {
|
|
1025
|
+
this.close();
|
|
1026
|
+
}
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (this.clearButtonVisible && !this.opened && this.selectedItems && this.selectedItems.length) {
|
|
973
1031
|
event.stopPropagation();
|
|
974
1032
|
this.selectedItems = [];
|
|
975
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
super._onEscape(event);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Override method from `ComboBoxBaseMixin` to handle Escape pres.
|
|
1040
|
+
* @protected
|
|
1041
|
+
* @override
|
|
1042
|
+
*/
|
|
1043
|
+
_onEscapeCancel() {
|
|
1044
|
+
this._closeOrCommit();
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Override an event listener from `KeyboardMixin` to keep
|
|
1049
|
+
* overlay open when item is selected or unselected.
|
|
1050
|
+
* @param {!Event} event
|
|
1051
|
+
* @protected
|
|
1052
|
+
* @override
|
|
1053
|
+
*/
|
|
1054
|
+
_onEnter(event) {
|
|
1055
|
+
if (this.opened) {
|
|
1056
|
+
// Do not submit the surrounding form.
|
|
1057
|
+
event.preventDefault();
|
|
1058
|
+
// Do not trigger global listeners.
|
|
1059
|
+
event.stopPropagation();
|
|
1060
|
+
|
|
1061
|
+
if (this.readonly) {
|
|
1062
|
+
this.close();
|
|
1063
|
+
} else if (this._hasValidInputValue()) {
|
|
1064
|
+
// Keep selected item focused after committing on Enter.
|
|
1065
|
+
const focusedItem = this._dropdownItems[this._focusedIndex];
|
|
1066
|
+
this._commitValue();
|
|
1067
|
+
this._focusedIndex = this._dropdownItems.indexOf(focusedItem);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
super._onEnter(event);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Override method inherited from the combo-box
|
|
1078
|
+
* to not update focused item when readonly.
|
|
1079
|
+
* @protected
|
|
1080
|
+
* @override
|
|
1081
|
+
*/
|
|
1082
|
+
_onArrowDown() {
|
|
1083
|
+
if (!this.readonly) {
|
|
1084
|
+
super._onArrowDown();
|
|
1085
|
+
} else if (!this.opened) {
|
|
1086
|
+
this.open();
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Override method inherited from the combo-box
|
|
1092
|
+
* to not update focused item when readonly.
|
|
1093
|
+
* @protected
|
|
1094
|
+
* @override
|
|
1095
|
+
*/
|
|
1096
|
+
_onArrowUp() {
|
|
1097
|
+
if (!this.readonly) {
|
|
1098
|
+
super._onArrowUp();
|
|
1099
|
+
} else if (!this.opened) {
|
|
1100
|
+
this.open();
|
|
1101
|
+
}
|
|
976
1102
|
}
|
|
977
1103
|
|
|
978
1104
|
/**
|
|
@@ -1097,41 +1223,34 @@ export const MultiSelectComboBoxMixin = (superClass) =>
|
|
|
1097
1223
|
if (focusedIndex > -1) {
|
|
1098
1224
|
const item = chips[focusedIndex].item;
|
|
1099
1225
|
const itemLabel = this._getItemLabel(item);
|
|
1100
|
-
announce(`${itemLabel} ${this.
|
|
1226
|
+
announce(`${itemLabel} ${this.__effectiveI18n.focused}`);
|
|
1101
1227
|
}
|
|
1102
1228
|
}
|
|
1103
1229
|
}
|
|
1104
1230
|
|
|
1105
|
-
/**
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
/** @private */
|
|
1114
|
-
_onComboBoxItemSelected(event) {
|
|
1115
|
-
this.__selectItem(event.detail.item);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
/** @private */
|
|
1119
|
-
_onCustomValueSet(event) {
|
|
1120
|
-
// Do not set combo-box value
|
|
1121
|
-
event.preventDefault();
|
|
1122
|
-
|
|
1123
|
-
// Stop the original event
|
|
1231
|
+
/**
|
|
1232
|
+
* @param {CustomEvent} event
|
|
1233
|
+
* @protected
|
|
1234
|
+
* @override
|
|
1235
|
+
*/
|
|
1236
|
+
_overlaySelectedItemChanged(event) {
|
|
1124
1237
|
event.stopPropagation();
|
|
1125
1238
|
|
|
1126
|
-
|
|
1239
|
+
// Do not un-select on click when readonly
|
|
1240
|
+
if (this.readonly) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1127
1243
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1244
|
+
if (event.detail.item instanceof ComboBoxPlaceholder) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (this.opened) {
|
|
1249
|
+
// Store filter value for checking if user input is matching
|
|
1250
|
+
// an item which is already selected, to not un-select it.
|
|
1251
|
+
this._lastFilter = this._inputElementValue;
|
|
1252
|
+
this.__selectItem(event.detail.item);
|
|
1253
|
+
}
|
|
1135
1254
|
}
|
|
1136
1255
|
|
|
1137
1256
|
/** @private */
|