@vaadin/multi-select-combo-box 23.1.0-alpha3 → 23.1.0-beta2
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 +9 -9
- package/src/vaadin-multi-select-combo-box-chip.js +17 -8
- package/src/vaadin-multi-select-combo-box-container.js +4 -5
- package/src/vaadin-multi-select-combo-box-internal.js +93 -5
- package/src/vaadin-multi-select-combo-box-overlay.js +1 -1
- package/src/vaadin-multi-select-combo-box-scroller.js +24 -0
- package/src/vaadin-multi-select-combo-box.d.ts +47 -8
- package/src/vaadin-multi-select-combo-box.js +331 -71
- package/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js +20 -9
- package/theme/lumo/vaadin-multi-select-combo-box-styles.js +27 -3
- package/theme/material/vaadin-multi-select-combo-box-chip-styles.js +20 -21
- package/theme/material/vaadin-multi-select-combo-box-styles.js +22 -3
|
@@ -7,6 +7,7 @@ import './vaadin-multi-select-combo-box-chip.js';
|
|
|
7
7
|
import './vaadin-multi-select-combo-box-container.js';
|
|
8
8
|
import './vaadin-multi-select-combo-box-internal.js';
|
|
9
9
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
10
|
+
import { announce } from '@vaadin/component-base/src/a11y-announcer.js';
|
|
10
11
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
11
12
|
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
12
13
|
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
@@ -18,7 +19,6 @@ import { css, registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixi
|
|
|
18
19
|
|
|
19
20
|
const multiSelectComboBox = css`
|
|
20
21
|
:host {
|
|
21
|
-
--chip-min-width: var(--vaadin-multi-select-combo-box-chip-min-width, 4em);
|
|
22
22
|
--input-min-width: var(--vaadin-multi-select-combo-box-input-min-width, 4em);
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -26,6 +26,11 @@ const multiSelectComboBox = css`
|
|
|
26
26
|
display: none !important;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
#chips {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
:host([has-value]) ::slotted(input:placeholder-shown) {
|
|
30
35
|
color: transparent !important;
|
|
31
36
|
}
|
|
@@ -37,16 +42,17 @@ const multiSelectComboBox = css`
|
|
|
37
42
|
|
|
38
43
|
[part='chip'] {
|
|
39
44
|
flex: 0 1 auto;
|
|
40
|
-
min-width: var(--chip-min-width);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
:host([readonly]
|
|
44
|
-
|
|
47
|
+
:host(:is([readonly], [disabled])) ::slotted(input) {
|
|
48
|
+
flex-grow: 0;
|
|
49
|
+
flex-basis: 0;
|
|
50
|
+
padding: 0;
|
|
45
51
|
}
|
|
46
52
|
`;
|
|
47
53
|
|
|
48
54
|
registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectComboBox], {
|
|
49
|
-
moduleId: 'vaadin-multi-select-combo-box-styles'
|
|
55
|
+
moduleId: 'vaadin-multi-select-combo-box-styles',
|
|
50
56
|
});
|
|
51
57
|
|
|
52
58
|
/**
|
|
@@ -69,6 +75,7 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
|
|
|
69
75
|
*
|
|
70
76
|
* Part name | Description
|
|
71
77
|
* -----------------------|----------------
|
|
78
|
+
* `chips` | The element that wraps chips for selected items
|
|
72
79
|
* `chip` | Chip shown for every selected item
|
|
73
80
|
* `label` | The label element
|
|
74
81
|
* `input-field` | The element that wraps prefix, value and suffix
|
|
@@ -102,7 +109,6 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
|
|
|
102
109
|
* -----------------------------------------------------|----------------------------|--------
|
|
103
110
|
* `--vaadin-field-default-width` | Default width of the field | `12em`
|
|
104
111
|
* `--vaadin-multi-select-combo-box-overlay-max-height` | Max height of the overlay | `65vh`
|
|
105
|
-
* `--vaadin-multi-select-combo-box-chip-min-width` | Min width of the chip | `60px`
|
|
106
112
|
* `--vaadin-multi-select-combo-box-input-min-width` | Min width of the input | `4em`
|
|
107
113
|
*
|
|
108
114
|
* ### Internal components
|
|
@@ -120,7 +126,7 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
|
|
|
120
126
|
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
121
127
|
*
|
|
122
128
|
* @fires {Event} change - Fired when the user commits a value change.
|
|
123
|
-
* @fires {CustomEvent} custom-
|
|
129
|
+
* @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
|
|
124
130
|
* @fires {CustomEvent} filter-changed - Fired when the `filter` property changes.
|
|
125
131
|
* @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
|
|
126
132
|
* @fires {CustomEvent} selected-items-changed - Fired when the `selectedItems` property changes.
|
|
@@ -153,7 +159,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
153
159
|
disabled="[[disabled]]"
|
|
154
160
|
readonly="[[readonly]]"
|
|
155
161
|
auto-open-disabled="[[autoOpenDisabled]]"
|
|
156
|
-
allow-custom-value="[[
|
|
162
|
+
allow-custom-value="[[allowCustomValue]]"
|
|
157
163
|
data-provider="[[dataProvider]]"
|
|
158
164
|
filter="{{filter}}"
|
|
159
165
|
filtered-items="[[filteredItems]]"
|
|
@@ -172,14 +178,17 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
172
178
|
theme$="[[theme]]"
|
|
173
179
|
>
|
|
174
180
|
<vaadin-multi-select-combo-box-chip
|
|
181
|
+
id="overflow"
|
|
175
182
|
slot="prefix"
|
|
176
183
|
part$="[[_getOverflowPart(_overflowItems.length)]]"
|
|
177
184
|
disabled="[[disabled]]"
|
|
185
|
+
readonly="[[readonly]]"
|
|
178
186
|
label="[[_getOverflowLabel(_overflowItems.length)]]"
|
|
179
187
|
title$="[[_getOverflowTitle(_overflowItems)]]"
|
|
180
188
|
hidden$="[[_isOverflowHidden(_overflowItems.length)]]"
|
|
181
189
|
on-mousedown="_preventBlur"
|
|
182
190
|
></vaadin-multi-select-combo-box-chip>
|
|
191
|
+
<div id="chips" part="chips" slot="prefix"></div>
|
|
183
192
|
<slot name="input"></slot>
|
|
184
193
|
<div id="clearButton" part="clear-button" slot="suffix"></div>
|
|
185
194
|
<div id="toggleButton" class="toggle-button" part="toggle-button" slot="suffix"></div>
|
|
@@ -213,7 +222,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
213
222
|
type: Boolean,
|
|
214
223
|
reflectToAttribute: true,
|
|
215
224
|
observer: '_clearButtonVisibleChanged',
|
|
216
|
-
value: false
|
|
225
|
+
value: false,
|
|
217
226
|
},
|
|
218
227
|
|
|
219
228
|
/**
|
|
@@ -221,7 +230,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
221
230
|
* The items can be of either `String` or `Object` type.
|
|
222
231
|
*/
|
|
223
232
|
items: {
|
|
224
|
-
type: Array
|
|
233
|
+
type: Array,
|
|
225
234
|
},
|
|
226
235
|
|
|
227
236
|
/**
|
|
@@ -229,7 +238,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
229
238
|
* @attr {string} item-label-path
|
|
230
239
|
*/
|
|
231
240
|
itemLabelPath: {
|
|
232
|
-
type: String
|
|
241
|
+
type: String,
|
|
233
242
|
},
|
|
234
243
|
|
|
235
244
|
/**
|
|
@@ -238,7 +247,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
238
247
|
* @attr {string} item-value-path
|
|
239
248
|
*/
|
|
240
249
|
itemValuePath: {
|
|
241
|
-
type: String
|
|
250
|
+
type: String,
|
|
242
251
|
},
|
|
243
252
|
|
|
244
253
|
/**
|
|
@@ -246,7 +255,54 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
246
255
|
* @attr {string} item-id-path
|
|
247
256
|
*/
|
|
248
257
|
itemIdPath: {
|
|
249
|
-
type: String
|
|
258
|
+
type: String,
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The object used to localize this component.
|
|
263
|
+
* To change the default localization, replace the entire
|
|
264
|
+
* _i18n_ object or just the property you want to modify.
|
|
265
|
+
*
|
|
266
|
+
* The object has the following JSON structure and default values:
|
|
267
|
+
* ```
|
|
268
|
+
* {
|
|
269
|
+
* // Screen reader announcement on clear button click.
|
|
270
|
+
* cleared: 'Selection cleared',
|
|
271
|
+
* // Screen reader announcement when a chip is focused.
|
|
272
|
+
* focused: ' focused. Press Backspace to remove',
|
|
273
|
+
* // Screen reader announcement when item is selected.
|
|
274
|
+
* selected: 'added to selection',
|
|
275
|
+
* // Screen reader announcement when item is deselected.
|
|
276
|
+
* deselected: 'removed from selection',
|
|
277
|
+
* // Screen reader announcement of the selected items count.
|
|
278
|
+
* // {count} is replaced with the actual count of items.
|
|
279
|
+
* total: '{count} items selected',
|
|
280
|
+
* }
|
|
281
|
+
* ```
|
|
282
|
+
* @type {!MultiSelectComboBoxI18n}
|
|
283
|
+
* @default {English/US}
|
|
284
|
+
*/
|
|
285
|
+
i18n: {
|
|
286
|
+
type: Object,
|
|
287
|
+
value: () => {
|
|
288
|
+
return {
|
|
289
|
+
cleared: 'Selection cleared',
|
|
290
|
+
focused: 'focused. Press Backspace to remove',
|
|
291
|
+
selected: 'added to selection',
|
|
292
|
+
deselected: 'removed from selection',
|
|
293
|
+
total: '{count} items selected',
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* When present, it specifies that the field is read-only.
|
|
300
|
+
*/
|
|
301
|
+
readonly: {
|
|
302
|
+
type: Boolean,
|
|
303
|
+
value: false,
|
|
304
|
+
observer: '_readonlyChanged',
|
|
305
|
+
reflectToAttribute: true,
|
|
250
306
|
},
|
|
251
307
|
|
|
252
308
|
/**
|
|
@@ -256,7 +312,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
256
312
|
selectedItems: {
|
|
257
313
|
type: Array,
|
|
258
314
|
value: () => [],
|
|
259
|
-
notify: true
|
|
315
|
+
notify: true,
|
|
260
316
|
},
|
|
261
317
|
|
|
262
318
|
/**
|
|
@@ -266,7 +322,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
266
322
|
type: Boolean,
|
|
267
323
|
notify: true,
|
|
268
324
|
value: false,
|
|
269
|
-
reflectToAttribute: true
|
|
325
|
+
reflectToAttribute: true,
|
|
270
326
|
},
|
|
271
327
|
|
|
272
328
|
/**
|
|
@@ -276,7 +332,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
276
332
|
pageSize: {
|
|
277
333
|
type: Number,
|
|
278
334
|
value: 50,
|
|
279
|
-
observer: '_pageSizeChanged'
|
|
335
|
+
observer: '_pageSizeChanged',
|
|
280
336
|
},
|
|
281
337
|
|
|
282
338
|
/**
|
|
@@ -293,16 +349,27 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
293
349
|
*/
|
|
294
350
|
dataProvider: {
|
|
295
351
|
type: Object,
|
|
296
|
-
observer: '_dataProviderChanged'
|
|
352
|
+
observer: '_dataProviderChanged',
|
|
297
353
|
},
|
|
298
354
|
|
|
299
355
|
/**
|
|
300
356
|
* When true, the user can input a value that is not present in the items list.
|
|
301
|
-
* @attr {boolean} allow-custom-
|
|
357
|
+
* @attr {boolean} allow-custom-value
|
|
302
358
|
*/
|
|
303
|
-
|
|
359
|
+
allowCustomValue: {
|
|
304
360
|
type: Boolean,
|
|
305
|
-
value: false
|
|
361
|
+
value: false,
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A hint to the user of what can be entered in the control.
|
|
366
|
+
* The placeholder will be only displayed in the case when
|
|
367
|
+
* there is no item selected.
|
|
368
|
+
*/
|
|
369
|
+
placeholder: {
|
|
370
|
+
type: String,
|
|
371
|
+
value: '',
|
|
372
|
+
observer: '_placeholderChanged',
|
|
306
373
|
},
|
|
307
374
|
|
|
308
375
|
/**
|
|
@@ -324,7 +391,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
324
391
|
filter: {
|
|
325
392
|
type: String,
|
|
326
393
|
value: '',
|
|
327
|
-
notify: true
|
|
394
|
+
notify: true,
|
|
328
395
|
},
|
|
329
396
|
|
|
330
397
|
/**
|
|
@@ -337,14 +404,21 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
337
404
|
/** @protected */
|
|
338
405
|
_hasValue: {
|
|
339
406
|
type: Boolean,
|
|
340
|
-
value: false
|
|
407
|
+
value: false,
|
|
341
408
|
},
|
|
342
409
|
|
|
343
410
|
/** @private */
|
|
344
411
|
_overflowItems: {
|
|
345
412
|
type: Array,
|
|
346
|
-
value: () => []
|
|
347
|
-
}
|
|
413
|
+
value: () => [],
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
/** @private */
|
|
417
|
+
_focusedChipIndex: {
|
|
418
|
+
type: Number,
|
|
419
|
+
value: -1,
|
|
420
|
+
observer: '_focusedChipIndexChanged',
|
|
421
|
+
},
|
|
348
422
|
};
|
|
349
423
|
}
|
|
350
424
|
|
|
@@ -376,7 +450,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
376
450
|
this._setFocusElement(input);
|
|
377
451
|
this.stateTarget = input;
|
|
378
452
|
this.ariaTarget = input;
|
|
379
|
-
})
|
|
453
|
+
}),
|
|
380
454
|
);
|
|
381
455
|
this.addController(new LabelledInputController(this.inputElement, this._labelController));
|
|
382
456
|
|
|
@@ -391,7 +465,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
391
465
|
* @return {boolean}
|
|
392
466
|
*/
|
|
393
467
|
checkValidity() {
|
|
394
|
-
return this.required ? this._hasValue : true;
|
|
468
|
+
return this.required && !this.readonly ? this._hasValue : true;
|
|
395
469
|
}
|
|
396
470
|
|
|
397
471
|
/**
|
|
@@ -403,9 +477,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
403
477
|
super._disabledChanged(disabled, oldDisabled);
|
|
404
478
|
|
|
405
479
|
if (disabled || oldDisabled) {
|
|
406
|
-
this.
|
|
407
|
-
chip.toggleAttribute('disabled', disabled);
|
|
408
|
-
});
|
|
480
|
+
this.__updateChips();
|
|
409
481
|
}
|
|
410
482
|
}
|
|
411
483
|
|
|
@@ -431,6 +503,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
431
503
|
super._setFocused(focused);
|
|
432
504
|
|
|
433
505
|
if (!focused) {
|
|
506
|
+
this._focusedChipIndex = -1;
|
|
434
507
|
this.validate();
|
|
435
508
|
}
|
|
436
509
|
}
|
|
@@ -454,6 +527,26 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
454
527
|
this.__updateChips();
|
|
455
528
|
}
|
|
456
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Override method from `DelegateStateMixin` to set required state
|
|
532
|
+
* using `aria-required` attribute instead of `required`, in order
|
|
533
|
+
* to prevent screen readers from announcing "invalid entry".
|
|
534
|
+
* @protected
|
|
535
|
+
* @override
|
|
536
|
+
*/
|
|
537
|
+
_delegateAttribute(name, value) {
|
|
538
|
+
if (!this.stateTarget) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (name === 'required') {
|
|
543
|
+
this._delegateAttribute('aria-required', value ? 'true' : false);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
super._delegateAttribute(name, value);
|
|
548
|
+
}
|
|
549
|
+
|
|
457
550
|
/**
|
|
458
551
|
* Setting clear button visible reduces total space available
|
|
459
552
|
* for rendering chips, and making it hidden increases it.
|
|
@@ -465,6 +558,19 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
465
558
|
}
|
|
466
559
|
}
|
|
467
560
|
|
|
561
|
+
/** @private */
|
|
562
|
+
_readonlyChanged(readonly, oldReadonly) {
|
|
563
|
+
if (readonly) {
|
|
564
|
+
this.__savedItems = this.$.comboBox._getOverlayItems();
|
|
565
|
+
this.$.comboBox._setOverlayItems(Array.from(this.selectedItems));
|
|
566
|
+
this.__updateChips();
|
|
567
|
+
} else if (oldReadonly) {
|
|
568
|
+
this.$.comboBox._setOverlayItems(this.__savedItems);
|
|
569
|
+
this.__savedItems = null;
|
|
570
|
+
this.__updateChips();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
468
574
|
/** @private */
|
|
469
575
|
_pageSizeChanged(pageSize, oldPageSize) {
|
|
470
576
|
if (Math.floor(pageSize) !== pageSize || pageSize <= 0) {
|
|
@@ -475,15 +581,42 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
475
581
|
this.$.comboBox.pageSize = this.pageSize;
|
|
476
582
|
}
|
|
477
583
|
|
|
584
|
+
/** @private */
|
|
585
|
+
_placeholderChanged(placeholder) {
|
|
586
|
+
const tmpPlaceholder = this.__tmpA11yPlaceholder;
|
|
587
|
+
// Do not store temporary placeholder
|
|
588
|
+
if (tmpPlaceholder !== placeholder) {
|
|
589
|
+
this.__savedPlaceholder = placeholder;
|
|
590
|
+
|
|
591
|
+
if (tmpPlaceholder) {
|
|
592
|
+
this.placeholder = tmpPlaceholder;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
478
597
|
/** @private */
|
|
479
598
|
_selectedItemsChanged(selectedItems) {
|
|
480
599
|
this._hasValue = Boolean(selectedItems && selectedItems.length);
|
|
481
600
|
|
|
482
601
|
this._toggleHasValue();
|
|
483
602
|
|
|
603
|
+
// Use placeholder for announcing items
|
|
604
|
+
if (this._hasValue) {
|
|
605
|
+
const tmpPlaceholder = selectedItems.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', ');
|
|
606
|
+
this.__tmpA11yPlaceholder = tmpPlaceholder;
|
|
607
|
+
this.placeholder = tmpPlaceholder;
|
|
608
|
+
} else {
|
|
609
|
+
delete this.__tmpA11yPlaceholder;
|
|
610
|
+
this.placeholder = this.__savedPlaceholder;
|
|
611
|
+
}
|
|
612
|
+
|
|
484
613
|
// Re-render chips
|
|
485
614
|
this.__updateChips();
|
|
486
615
|
|
|
616
|
+
if (this.readonly) {
|
|
617
|
+
this.$.comboBox._setOverlayItems(selectedItems);
|
|
618
|
+
}
|
|
619
|
+
|
|
487
620
|
// Re-render scroller
|
|
488
621
|
this.$.comboBox.$.dropdown._scroller.requestContentUpdate();
|
|
489
622
|
|
|
@@ -546,11 +679,19 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
546
679
|
this.$.comboBox.clear();
|
|
547
680
|
}
|
|
548
681
|
|
|
682
|
+
/** @private */
|
|
683
|
+
__announceItem(itemLabel, isSelected, itemCount) {
|
|
684
|
+
const state = isSelected ? 'selected' : 'deselected';
|
|
685
|
+
const total = this.i18n.total.replace('{count}', itemCount || 0);
|
|
686
|
+
announce(`${itemLabel} ${this.i18n[state]} ${total}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
549
689
|
/** @private */
|
|
550
690
|
__removeItem(item) {
|
|
551
691
|
const itemsCopy = [...this.selectedItems];
|
|
552
692
|
itemsCopy.splice(itemsCopy.indexOf(item), 1);
|
|
553
693
|
this.__updateSelection(itemsCopy);
|
|
694
|
+
this.__announceItem(item, false, itemsCopy.length);
|
|
554
695
|
}
|
|
555
696
|
|
|
556
697
|
/** @private */
|
|
@@ -558,9 +699,13 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
558
699
|
const itemsCopy = [...this.selectedItems];
|
|
559
700
|
|
|
560
701
|
const index = this._findIndex(item, itemsCopy, this.itemIdPath);
|
|
702
|
+
const itemLabel = this._getItemLabel(item, this.itemLabelPath);
|
|
703
|
+
|
|
704
|
+
let isSelected = false;
|
|
705
|
+
|
|
561
706
|
if (index !== -1) {
|
|
562
707
|
// Do not unselect when manually typing and committing an already selected item.
|
|
563
|
-
if (this.filter.toLowerCase() ===
|
|
708
|
+
if (this.filter.toLowerCase() === itemLabel.toLowerCase()) {
|
|
564
709
|
this.__clearFilter();
|
|
565
710
|
return;
|
|
566
711
|
}
|
|
@@ -568,12 +713,15 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
568
713
|
itemsCopy.splice(index, 1);
|
|
569
714
|
} else {
|
|
570
715
|
itemsCopy.push(item);
|
|
716
|
+
isSelected = true;
|
|
571
717
|
}
|
|
572
718
|
|
|
573
719
|
this.__updateSelection(itemsCopy);
|
|
574
720
|
|
|
575
721
|
// Suppress `value-changed` event.
|
|
576
722
|
this.__clearFilter();
|
|
723
|
+
|
|
724
|
+
this.__announceItem(itemLabel, isSelected, itemsCopy.length);
|
|
577
725
|
}
|
|
578
726
|
|
|
579
727
|
/** @private */
|
|
@@ -592,7 +740,8 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
592
740
|
chip.setAttribute('slot', 'prefix');
|
|
593
741
|
|
|
594
742
|
chip.item = item;
|
|
595
|
-
chip.
|
|
743
|
+
chip.disabled = this.disabled;
|
|
744
|
+
chip.readonly = this.readonly;
|
|
596
745
|
|
|
597
746
|
const label = this._getItemLabel(item, this.itemLabelPath);
|
|
598
747
|
chip.label = label;
|
|
@@ -605,18 +754,21 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
605
754
|
}
|
|
606
755
|
|
|
607
756
|
/** @private */
|
|
608
|
-
|
|
757
|
+
__getOverflowWidth() {
|
|
758
|
+
const chip = this.$.overflow;
|
|
759
|
+
|
|
609
760
|
chip.style.visibility = 'hidden';
|
|
610
|
-
chip.
|
|
611
|
-
chip.style.minWidth = 'var(--chip-min-width)';
|
|
761
|
+
chip.removeAttribute('hidden');
|
|
612
762
|
|
|
613
|
-
|
|
763
|
+
// Detect max possible width of the overflow chip
|
|
764
|
+
chip.setAttribute('part', 'chip overflow');
|
|
765
|
+
const overflowStyle = getComputedStyle(chip);
|
|
766
|
+
const overflowWidth = chip.clientWidth + parseInt(overflowStyle.marginInlineStart);
|
|
614
767
|
|
|
615
|
-
chip.
|
|
616
|
-
chip.style.display = '';
|
|
768
|
+
chip.setAttribute('hidden', '');
|
|
617
769
|
chip.style.visibility = '';
|
|
618
770
|
|
|
619
|
-
return
|
|
771
|
+
return overflowWidth;
|
|
620
772
|
}
|
|
621
773
|
|
|
622
774
|
/** @private */
|
|
@@ -626,46 +778,36 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
626
778
|
}
|
|
627
779
|
|
|
628
780
|
// Clear all chips except the overflow
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
chip.remove();
|
|
781
|
+
Array.from(this._chips).forEach((chip) => {
|
|
782
|
+
if (chip !== this.$.overflow) {
|
|
783
|
+
chip.remove();
|
|
784
|
+
}
|
|
634
785
|
});
|
|
635
786
|
|
|
636
787
|
const items = [...this.selectedItems];
|
|
637
788
|
|
|
638
|
-
|
|
789
|
+
// Detect available remaining width for chips
|
|
790
|
+
const totalWidth = this._inputField.$.wrapper.clientWidth;
|
|
791
|
+
const inputWidth = parseInt(getComputedStyle(this.inputElement).flexBasis);
|
|
639
792
|
|
|
640
|
-
|
|
641
|
-
const chipMinWidth = this.__getMinWidth(overflow);
|
|
642
|
-
const inputMinWidth = parseInt(getComputedStyle(this.inputElement).flexBasis);
|
|
643
|
-
const containerStyle = getComputedStyle(this._inputField);
|
|
793
|
+
let remainingWidth = totalWidth - inputWidth;
|
|
644
794
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
parseInt(containerStyle.width) -
|
|
648
|
-
parseInt(containerStyle.paddingLeft) -
|
|
649
|
-
parseInt(containerStyle.paddingRight) -
|
|
650
|
-
this.$.toggleButton.clientWidth -
|
|
651
|
-
inputMinWidth;
|
|
652
|
-
|
|
653
|
-
if (this.clearButtonVisible) {
|
|
654
|
-
totalWidth -= this.$.clearButton.clientWidth;
|
|
795
|
+
if (items.length > 1) {
|
|
796
|
+
remainingWidth -= this.__getOverflowWidth();
|
|
655
797
|
}
|
|
656
798
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
799
|
+
// Add chips until remaining width is exceeded
|
|
800
|
+
for (let i = items.length - 1, refNode = null; i >= 0; i--) {
|
|
801
|
+
const chip = this.__createChip(items[i]);
|
|
802
|
+
this.$.chips.insertBefore(chip, refNode);
|
|
803
|
+
|
|
804
|
+
if (this.$.chips.clientWidth > remainingWidth) {
|
|
805
|
+
chip.remove();
|
|
660
806
|
break;
|
|
661
807
|
}
|
|
662
808
|
|
|
663
|
-
|
|
664
|
-
const chip = this.__createChip(item);
|
|
665
|
-
this._inputField.insertBefore(chip, refNode);
|
|
666
|
-
|
|
809
|
+
items.pop();
|
|
667
810
|
refNode = chip;
|
|
668
|
-
totalWidth -= chipMinWidth;
|
|
669
811
|
}
|
|
670
812
|
|
|
671
813
|
this._overflowItems = items;
|
|
@@ -680,6 +822,8 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
680
822
|
event.stopPropagation();
|
|
681
823
|
|
|
682
824
|
this.__updateSelection([]);
|
|
825
|
+
|
|
826
|
+
announce(this.i18n.cleared);
|
|
683
827
|
}
|
|
684
828
|
|
|
685
829
|
/**
|
|
@@ -690,8 +834,121 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
690
834
|
*/
|
|
691
835
|
_onKeyDown(event) {
|
|
692
836
|
const items = this.selectedItems || [];
|
|
693
|
-
|
|
694
|
-
|
|
837
|
+
|
|
838
|
+
if (event.key === 'Escape' && this.clearButtonVisible && items.length) {
|
|
839
|
+
this.selectedItems = [];
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const chips = Array.from(this._chips).slice(1);
|
|
844
|
+
|
|
845
|
+
if (!this.readonly && chips.length > 0) {
|
|
846
|
+
switch (event.key) {
|
|
847
|
+
case 'Backspace':
|
|
848
|
+
this._onBackSpace(chips);
|
|
849
|
+
break;
|
|
850
|
+
case 'ArrowLeft':
|
|
851
|
+
this._onArrowLeft(chips);
|
|
852
|
+
break;
|
|
853
|
+
case 'ArrowRight':
|
|
854
|
+
this._onArrowRight(chips);
|
|
855
|
+
break;
|
|
856
|
+
default:
|
|
857
|
+
this._focusedChipIndex = -1;
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** @private */
|
|
864
|
+
_onArrowLeft(chips) {
|
|
865
|
+
if (this.inputElement.value !== '' || this.opened) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const idx = this._focusedChipIndex;
|
|
870
|
+
let newIdx;
|
|
871
|
+
|
|
872
|
+
if (this.getAttribute('dir') !== 'rtl') {
|
|
873
|
+
if (idx === -1) {
|
|
874
|
+
// Focus last chip
|
|
875
|
+
newIdx = chips.length - 1;
|
|
876
|
+
} else if (idx > 0) {
|
|
877
|
+
// Focus prev chip
|
|
878
|
+
newIdx = idx - 1;
|
|
879
|
+
}
|
|
880
|
+
} else if (idx === chips.length - 1) {
|
|
881
|
+
// Blur last chip
|
|
882
|
+
newIdx = -1;
|
|
883
|
+
} else if (idx > -1) {
|
|
884
|
+
// Focus next chip
|
|
885
|
+
newIdx = idx + 1;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (newIdx !== undefined) {
|
|
889
|
+
this._focusedChipIndex = newIdx;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** @private */
|
|
894
|
+
_onArrowRight(chips) {
|
|
895
|
+
if (this.inputElement.value !== '' || this.opened) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const idx = this._focusedChipIndex;
|
|
900
|
+
let newIdx;
|
|
901
|
+
|
|
902
|
+
if (this.getAttribute('dir') === 'rtl') {
|
|
903
|
+
if (idx === -1) {
|
|
904
|
+
// Focus last chip
|
|
905
|
+
newIdx = chips.length - 1;
|
|
906
|
+
} else if (idx > 0) {
|
|
907
|
+
// Focus prev chip
|
|
908
|
+
newIdx = idx - 1;
|
|
909
|
+
}
|
|
910
|
+
} else if (idx === chips.length - 1) {
|
|
911
|
+
// Blur last chip
|
|
912
|
+
newIdx = -1;
|
|
913
|
+
} else if (idx > -1) {
|
|
914
|
+
// Focus next chip
|
|
915
|
+
newIdx = idx + 1;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (newIdx !== undefined) {
|
|
919
|
+
this._focusedChipIndex = newIdx;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** @private */
|
|
924
|
+
_onBackSpace(chips) {
|
|
925
|
+
if (this.inputElement.value !== '' || this.opened) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const idx = this._focusedChipIndex;
|
|
930
|
+
if (idx === -1) {
|
|
931
|
+
this._focusedChipIndex = chips.length - 1;
|
|
932
|
+
} else {
|
|
933
|
+
this.__removeItem(chips[idx].item);
|
|
934
|
+
this._focusedChipIndex = -1;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** @private */
|
|
939
|
+
_focusedChipIndexChanged(focusedIndex, oldFocusedIndex) {
|
|
940
|
+
if (focusedIndex > -1 || oldFocusedIndex > -1) {
|
|
941
|
+
const chips = Array.from(this._chips).slice(1);
|
|
942
|
+
chips.forEach((chip, index) => {
|
|
943
|
+
chip.toggleAttribute('focused', index === focusedIndex);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// Announce focused chip
|
|
947
|
+
if (focusedIndex > -1) {
|
|
948
|
+
const item = chips[focusedIndex].item;
|
|
949
|
+
const itemLabel = this._getItemLabel(item, this.itemLabelPath);
|
|
950
|
+
announce(`${itemLabel} ${this.i18n.focused}`);
|
|
951
|
+
}
|
|
695
952
|
}
|
|
696
953
|
}
|
|
697
954
|
|
|
@@ -713,14 +970,17 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
|
|
|
713
970
|
// Do not set combo-box value
|
|
714
971
|
event.preventDefault();
|
|
715
972
|
|
|
973
|
+
// Stop the original event
|
|
974
|
+
event.stopPropagation();
|
|
975
|
+
|
|
716
976
|
this.__clearFilter();
|
|
717
977
|
|
|
718
978
|
this.dispatchEvent(
|
|
719
|
-
new CustomEvent('custom-
|
|
979
|
+
new CustomEvent('custom-value-set', {
|
|
720
980
|
detail: event.detail,
|
|
721
981
|
composed: true,
|
|
722
|
-
bubbles: true
|
|
723
|
-
})
|
|
982
|
+
bubbles: true,
|
|
983
|
+
}),
|
|
724
984
|
);
|
|
725
985
|
}
|
|
726
986
|
|