@vaadin/multi-select-combo-box 23.1.0-beta1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/multi-select-combo-box",
3
- "version": "23.1.0-beta1",
3
+ "version": "23.1.0-beta2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,18 +33,18 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@polymer/polymer": "^3.0.0",
36
- "@vaadin/combo-box": "23.1.0-beta1",
37
- "@vaadin/component-base": "23.1.0-beta1",
38
- "@vaadin/field-base": "23.1.0-beta1",
39
- "@vaadin/input-container": "23.1.0-beta1",
40
- "@vaadin/vaadin-lumo-styles": "23.1.0-beta1",
41
- "@vaadin/vaadin-material-styles": "23.1.0-beta1",
42
- "@vaadin/vaadin-themable-mixin": "23.1.0-beta1"
36
+ "@vaadin/combo-box": "23.1.0-beta2",
37
+ "@vaadin/component-base": "23.1.0-beta2",
38
+ "@vaadin/field-base": "23.1.0-beta2",
39
+ "@vaadin/input-container": "23.1.0-beta2",
40
+ "@vaadin/vaadin-lumo-styles": "23.1.0-beta2",
41
+ "@vaadin/vaadin-material-styles": "23.1.0-beta2",
42
+ "@vaadin/vaadin-themable-mixin": "23.1.0-beta2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@esm-bundle/chai": "^4.3.4",
46
46
  "@vaadin/testing-helpers": "^0.3.2",
47
47
  "sinon": "^13.0.2"
48
48
  },
49
- "gitHead": "8be43cf83102a6b9ccf309687446e590ce0164e8"
49
+ "gitHead": "f11f9245a0b5e6bf912725a501c27c24b74e7c8d"
50
50
  }
@@ -179,6 +179,10 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
179
179
  * @override
180
180
  */
181
181
  _onFocusout(event) {
182
+ // Disable combo-box logic that updates selectedItem
183
+ // based on the overlay focused index on input blur
184
+ this._ignoreCommitValue = true;
185
+
182
186
  super._onFocusout(event);
183
187
 
184
188
  if (this.readonly && !this._closeOnBlurIsPrevented) {
@@ -186,6 +190,26 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
186
190
  }
187
191
  }
188
192
 
193
+ /**
194
+ * Override method inherited from the combo-box
195
+ * to not commit an already selected item again
196
+ * on blur, which would result in un-selecting.
197
+ * @protected
198
+ * @override
199
+ */
200
+ _detectAndDispatchChange() {
201
+ if (this._ignoreCommitValue) {
202
+ this._ignoreCommitValue = false;
203
+
204
+ // Reset internal combo-box state
205
+ this.selectedItem = null;
206
+ this._inputElementValue = '';
207
+ return;
208
+ }
209
+
210
+ super._detectAndDispatchChange();
211
+ }
212
+
189
213
  /**
190
214
  * @param {CustomEvent} event
191
215
  * @protected
@@ -22,6 +22,7 @@ import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themabl
22
22
 
23
23
  export interface MultiSelectComboBoxI18n {
24
24
  cleared: string;
25
+ focused: string;
25
26
  selected: string;
26
27
  deselected: string;
27
28
  total: string;
@@ -37,7 +38,7 @@ export type MultiSelectComboBoxChangeEvent<TItem> = Event & {
37
38
  /**
38
39
  * Fired when the user sets a custom value.
39
40
  */
40
- export type MultiSelectComboBoxCustomValuesSetEvent = CustomEvent<string>;
41
+ export type MultiSelectComboBoxCustomValueSetEvent = CustomEvent<string>;
41
42
 
42
43
  /**
43
44
  * Fired when the `filter` property changes.
@@ -57,7 +58,7 @@ export type MultiSelectComboBoxSelectedItemsChangedEvent<TItem> = CustomEvent<{
57
58
  export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap {
58
59
  change: MultiSelectComboBoxChangeEvent<TItem>;
59
60
 
60
- 'custom-values-set': MultiSelectComboBoxCustomValuesSetEvent;
61
+ 'custom-value-set': MultiSelectComboBoxCustomValueSetEvent;
61
62
 
62
63
  'filter-changed': MultiSelectComboBoxFilterChangedEvent;
63
64
 
@@ -137,7 +138,7 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
137
138
  * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
138
139
  *
139
140
  * @fires {Event} change - Fired when the user commits a value change.
140
- * @fires {CustomEvent} custom-values-set - Fired when the user sets a custom value.
141
+ * @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
141
142
  * @fires {CustomEvent} filter-changed - Fired when the `filter` property changes.
142
143
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
143
144
  * @fires {CustomEvent} selected-items-changed - Fired when the `selectedItems` property changes.
@@ -145,9 +146,9 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
145
146
  declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
146
147
  /**
147
148
  * When true, the user can input a value that is not present in the items list.
148
- * @attr {boolean} allow-custom-values
149
+ * @attr {boolean} allow-custom-value
149
150
  */
150
- allowCustomValues: boolean;
151
+ allowCustomValue: boolean;
151
152
 
152
153
  /**
153
154
  * Set true to prevent the overlay from opening automatically.
@@ -216,6 +217,8 @@ declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLEleme
216
217
  * {
217
218
  * // Screen reader announcement on clear button click.
218
219
  * cleared: 'Selection cleared',
220
+ * // Screen reader announcement when a chip is focused.
221
+ * focused: ' focused. Press Backspace to remove',
219
222
  * // Screen reader announcement when item is selected.
220
223
  * selected: 'added to selection',
221
224
  * // Screen reader announcement when item is deselected.
@@ -239,6 +242,13 @@ declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLEleme
239
242
  */
240
243
  pageSize: number;
241
244
 
245
+ /**
246
+ * A hint to the user of what can be entered in the control.
247
+ * The placeholder will be only displayed in the case when
248
+ * there is no item selected.
249
+ */
250
+ placeholder: string;
251
+
242
252
  /**
243
253
  * Custom function for rendering the content of every item.
244
254
  * Receives three arguments:
@@ -126,7 +126,7 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
126
126
  * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
127
127
  *
128
128
  * @fires {Event} change - Fired when the user commits a value change.
129
- * @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.
130
130
  * @fires {CustomEvent} filter-changed - Fired when the `filter` property changes.
131
131
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
132
132
  * @fires {CustomEvent} selected-items-changed - Fired when the `selectedItems` property changes.
@@ -159,7 +159,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
159
159
  disabled="[[disabled]]"
160
160
  readonly="[[readonly]]"
161
161
  auto-open-disabled="[[autoOpenDisabled]]"
162
- allow-custom-value="[[allowCustomValues]]"
162
+ allow-custom-value="[[allowCustomValue]]"
163
163
  data-provider="[[dataProvider]]"
164
164
  filter="{{filter}}"
165
165
  filtered-items="[[filteredItems]]"
@@ -268,6 +268,8 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
268
268
  * {
269
269
  * // Screen reader announcement on clear button click.
270
270
  * cleared: 'Selection cleared',
271
+ * // Screen reader announcement when a chip is focused.
272
+ * focused: ' focused. Press Backspace to remove',
271
273
  * // Screen reader announcement when item is selected.
272
274
  * selected: 'added to selection',
273
275
  * // Screen reader announcement when item is deselected.
@@ -285,6 +287,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
285
287
  value: () => {
286
288
  return {
287
289
  cleared: 'Selection cleared',
290
+ focused: 'focused. Press Backspace to remove',
288
291
  selected: 'added to selection',
289
292
  deselected: 'removed from selection',
290
293
  total: '{count} items selected',
@@ -351,13 +354,24 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
351
354
 
352
355
  /**
353
356
  * When true, the user can input a value that is not present in the items list.
354
- * @attr {boolean} allow-custom-values
357
+ * @attr {boolean} allow-custom-value
355
358
  */
356
- allowCustomValues: {
359
+ allowCustomValue: {
357
360
  type: Boolean,
358
361
  value: false,
359
362
  },
360
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',
373
+ },
374
+
361
375
  /**
362
376
  * Custom function for rendering the content of every item.
363
377
  * Receives three arguments:
@@ -398,6 +412,13 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
398
412
  type: Array,
399
413
  value: () => [],
400
414
  },
415
+
416
+ /** @private */
417
+ _focusedChipIndex: {
418
+ type: Number,
419
+ value: -1,
420
+ observer: '_focusedChipIndexChanged',
421
+ },
401
422
  };
402
423
  }
403
424
 
@@ -482,6 +503,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
482
503
  super._setFocused(focused);
483
504
 
484
505
  if (!focused) {
506
+ this._focusedChipIndex = -1;
485
507
  this.validate();
486
508
  }
487
509
  }
@@ -559,6 +581,19 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
559
581
  this.$.comboBox.pageSize = this.pageSize;
560
582
  }
561
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
+
562
597
  /** @private */
563
598
  _selectedItemsChanged(selectedItems) {
564
599
  this._hasValue = Boolean(selectedItems && selectedItems.length);
@@ -567,9 +602,11 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
567
602
 
568
603
  // Use placeholder for announcing items
569
604
  if (this._hasValue) {
570
- this.__savedPlaceholder = this.placeholder;
571
- this.placeholder = selectedItems.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', ');
605
+ const tmpPlaceholder = selectedItems.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', ');
606
+ this.__tmpA11yPlaceholder = tmpPlaceholder;
607
+ this.placeholder = tmpPlaceholder;
572
608
  } else {
609
+ delete this.__tmpA11yPlaceholder;
573
610
  this.placeholder = this.__savedPlaceholder;
574
611
  }
575
612
 
@@ -797,8 +834,121 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
797
834
  */
798
835
  _onKeyDown(event) {
799
836
  const items = this.selectedItems || [];
800
- if (!this.readonly && event.key === 'Backspace' && items.length && this.inputElement.value === '') {
801
- 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
+ }
802
952
  }
803
953
  }
804
954
 
@@ -820,10 +970,13 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
820
970
  // Do not set combo-box value
821
971
  event.preventDefault();
822
972
 
973
+ // Stop the original event
974
+ event.stopPropagation();
975
+
823
976
  this.__clearFilter();
824
977
 
825
978
  this.dispatchEvent(
826
- new CustomEvent('custom-values-set', {
979
+ new CustomEvent('custom-value-set', {
827
980
  detail: event.detail,
828
981
  composed: true,
829
982
  bubbles: true,
@@ -22,6 +22,15 @@ const chip = css`
22
22
  cursor: var(--lumo-clickable-cursor);
23
23
  }
24
24
 
25
+ :host([focused]) {
26
+ background-color: var(--lumo-primary-color);
27
+ color: var(--lumo-primary-contrast-color);
28
+ }
29
+
30
+ :host([focused]) [part='remove-button'] {
31
+ color: inherit;
32
+ }
33
+
25
34
  :host(:not([part~='overflow']):not([readonly]):not([disabled])) {
26
35
  padding-inline-end: 0;
27
36
  }
@@ -36,19 +45,19 @@ const chip = css`
36
45
  :host([part~='overflow'])::after {
37
46
  position: absolute;
38
47
  content: '';
39
- width: 3px;
40
- height: calc(1.875em - 1px);
41
- border-left: 2px solid;
42
- border-radius: 4px 0 0 4px;
48
+ width: 100%;
49
+ height: 100%;
50
+ border-left: calc(var(--lumo-space-s) / 4) solid;
51
+ border-radius: var(--lumo-border-radius-s);
43
52
  border-color: var(--lumo-contrast-30pct);
44
53
  }
45
54
 
46
55
  :host([part~='overflow'])::before {
47
- left: -4px;
56
+ left: calc(-1 * var(--lumo-space-s) / 2);
48
57
  }
49
58
 
50
59
  :host([part~='overflow'])::after {
51
- left: -8px;
60
+ left: calc(-1 * var(--lumo-space-s));
52
61
  }
53
62
 
54
63
  :host([part~='overflow-two']) {
@@ -83,6 +92,7 @@ const chip = css`
83
92
  width: 1.25em;
84
93
  height: 1.25em;
85
94
  font-size: 1.5em;
95
+ transition: none;
86
96
  }
87
97
 
88
98
  [part='remove-button']::before {
@@ -30,6 +30,10 @@ const multiSelectComboBox = css`
30
30
  padding-inline-start: 0;
31
31
  }
32
32
 
33
+ :host([has-value]) ::slotted(input:placeholder-shown) {
34
+ caret-color: var(--lumo-body-text-color) !important;
35
+ }
36
+
33
37
  [part~='chip']:not(:last-of-type) {
34
38
  margin-inline-end: var(--lumo-space-xs);
35
39
  }
@@ -15,11 +15,15 @@ const chip = css`
15
15
  margin-inline-end: 0.25rem;
16
16
  padding: 0 0.5rem;
17
17
  border-radius: 4px;
18
- background-color: hsla(214, 53%, 23%, 0.1);
18
+ background-color: rgba(0, 0, 0, 0.08);
19
19
  cursor: default;
20
20
  font-family: var(--material-font-family);
21
21
  }
22
22
 
23
+ :host([focused]) {
24
+ background-color: rgba(0, 0, 0, 0.16);
25
+ }
26
+
23
27
  :host(:not([part~='overflow']):not([readonly]):not([disabled])) {
24
28
  padding-inline-end: 0;
25
29
  }
@@ -33,19 +37,19 @@ const chip = css`
33
37
  :host([part~='overflow'])::after {
34
38
  position: absolute;
35
39
  content: '';
36
- width: 3px;
37
- height: 20px;
38
- border-left: 2px solid;
39
- border-radius: 4px 0 0 4px;
40
- border-color: hsla(214, 53%, 23%, 0.1);
40
+ width: 100%;
41
+ height: 100%;
42
+ border-left: 0.125rem solid;
43
+ border-radius: 0.25rem;
44
+ border-color: rgba(0, 0, 0, 0.08);
41
45
  }
42
46
 
43
47
  :host([part~='overflow'])::before {
44
- left: -4px;
48
+ left: -0.25rem;
45
49
  }
46
50
 
47
51
  :host([part~='overflow'])::after {
48
- left: -8px;
52
+ left: -0.5rem;
49
53
  }
50
54
 
51
55
  :host([part~='overflow-two']) {
@@ -25,6 +25,10 @@ registerStyles(
25
25
  );
26
26
 
27
27
  const multiSelectComboBox = css`
28
+ :host([has-value]) ::slotted(input:placeholder-shown) {
29
+ caret-color: var(--material-body-text-color) !important;
30
+ }
31
+
28
32
  [part='input-field'] {
29
33
  height: auto;
30
34
  min-height: 32px;