@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.
@@ -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]) [part~='chip'] {
44
- pointer-events: none;
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-values-set - Fired when the user sets a custom value.
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="[[allowCustomValues]]"
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-values
357
+ * @attr {boolean} allow-custom-value
302
358
  */
303
- allowCustomValues: {
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._chips.forEach((chip) => {
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() === this._getItemLabel(item, this.itemLabelPath).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.toggleAttribute('disabled', this.disabled);
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
- __getMinWidth(chip) {
757
+ __getOverflowWidth() {
758
+ const chip = this.$.overflow;
759
+
609
760
  chip.style.visibility = 'hidden';
610
- chip.style.display = 'block';
611
- chip.style.minWidth = 'var(--chip-min-width)';
761
+ chip.removeAttribute('hidden');
612
762
 
613
- const result = parseInt(getComputedStyle(chip).minWidth);
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.style.minWidth = '';
616
- chip.style.display = '';
768
+ chip.setAttribute('hidden', '');
617
769
  chip.style.visibility = '';
618
770
 
619
- return result;
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
- const chips = Array.from(this._chips).reverse();
630
- const overflow = chips.pop();
631
-
632
- chips.forEach((chip) => {
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
- let refNode = overflow.nextElementSibling;
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
- // Use overflow chip to measure min-width
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
- // Detect available width for chips
646
- let totalWidth =
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
- for (let i = items.length - 1; i >= 0; i--) {
658
- // Ensure there is enough space for another chip
659
- if (totalWidth < chipMinWidth) {
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
- const item = items.pop();
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
- if (!this.readonly && event.key === 'Backspace' && items.length && this.inputElement.value === '') {
694
- this.__removeItem(items[items.length - 1]);
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-values-set', {
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