@vaadin/multi-select-combo-box 23.3.29 → 23.4.0-alpha2

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.3.29",
3
+ "version": "23.4.0-alpha2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -37,14 +37,14 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@polymer/polymer": "^3.0.0",
40
- "@vaadin/combo-box": "~23.3.29",
41
- "@vaadin/component-base": "~23.3.29",
42
- "@vaadin/field-base": "~23.3.29",
43
- "@vaadin/input-container": "~23.3.29",
44
- "@vaadin/lit-renderer": "~23.3.29",
45
- "@vaadin/vaadin-lumo-styles": "~23.3.29",
46
- "@vaadin/vaadin-material-styles": "~23.3.29",
47
- "@vaadin/vaadin-themable-mixin": "~23.3.29"
40
+ "@vaadin/combo-box": "23.4.0-alpha2",
41
+ "@vaadin/component-base": "23.4.0-alpha2",
42
+ "@vaadin/field-base": "23.4.0-alpha2",
43
+ "@vaadin/input-container": "23.4.0-alpha2",
44
+ "@vaadin/lit-renderer": "23.4.0-alpha2",
45
+ "@vaadin/vaadin-lumo-styles": "23.4.0-alpha2",
46
+ "@vaadin/vaadin-material-styles": "23.4.0-alpha2",
47
+ "@vaadin/vaadin-themable-mixin": "23.4.0-alpha2"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@esm-bundle/chai": "^4.3.4",
@@ -56,5 +56,5 @@
56
56
  "web-types.json",
57
57
  "web-types.lit.json"
58
58
  ],
59
- "gitHead": "5e34a34170af02e934a759c9e2f4b100202504c7"
59
+ "gitHead": "3fa9a5d13db6baf0307f863669ab2f3f5114c6a0"
60
60
  }
@@ -13,6 +13,10 @@ registerStyles(
13
13
  display: flex;
14
14
  width: 100%;
15
15
  }
16
+
17
+ :host([auto-expand-vertically]) #wrapper {
18
+ flex-wrap: wrap;
19
+ }
16
20
  `,
17
21
  {
18
22
  moduleId: 'vaadin-multi-select-combo-box-container-styles',
@@ -47,6 +51,21 @@ class MultiSelectComboBoxContainer extends InputContainer {
47
51
  }
48
52
  return memoizedTemplate;
49
53
  }
54
+
55
+ static get properties() {
56
+ return {
57
+ /**
58
+ * Set to true to not collapse selected items chips into the overflow
59
+ * chip and instead always expand vertically, causing input field to
60
+ * wrap into multiple lines when width is limited.
61
+ * @attr {boolean} auto-expand-vertically
62
+ */
63
+ autoExpandVertically: {
64
+ type: Boolean,
65
+ reflectToAttribute: true,
66
+ },
67
+ };
68
+ }
50
69
  }
51
70
 
52
71
  customElements.define(MultiSelectComboBoxContainer.is, MultiSelectComboBoxContainer);
@@ -86,6 +86,15 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
86
86
  value: () => [],
87
87
  },
88
88
 
89
+ /**
90
+ * Set to true to group selected items at the top of the overlay.
91
+ * @attr {boolean} selected-items-on-top
92
+ */
93
+ selectedItemsOnTop: {
94
+ type: Boolean,
95
+ value: false,
96
+ },
97
+
89
98
  /**
90
99
  * Last input value entered by the user before value is updated.
91
100
  * Used to store `filter` property value before clearing it.
@@ -95,6 +104,14 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
95
104
  notify: true,
96
105
  },
97
106
 
107
+ /**
108
+ * A subset of items to be shown at the top of the overlay.
109
+ */
110
+ topGroup: {
111
+ type: Array,
112
+ observer: '_topGroupChanged',
113
+ },
114
+
98
115
  _target: {
99
116
  type: Object,
100
117
  },
@@ -138,6 +155,39 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
138
155
  this._toggleElement = this.querySelector('.toggle-button');
139
156
  }
140
157
 
158
+ /**
159
+ * Override combo-box method to group selected
160
+ * items at the top of the overlay.
161
+ *
162
+ * @protected
163
+ * @override
164
+ */
165
+ _setDropdownItems(items) {
166
+ if (this.filter || this.readonly || !this.selectedItemsOnTop) {
167
+ this._dropdownItems = items;
168
+ return;
169
+ }
170
+
171
+ if (items && items.length && this.topGroup && this.topGroup.length) {
172
+ // Filter out items included to the top group.
173
+ const filteredItems = items.filter(
174
+ (item) => this._comboBox._findIndex(item, this.topGroup, this.itemIdPath) === -1,
175
+ );
176
+
177
+ this._dropdownItems = this.topGroup.concat(filteredItems);
178
+ return;
179
+ }
180
+
181
+ this._dropdownItems = items;
182
+ }
183
+
184
+ /** @private */
185
+ _topGroupChanged(topGroup) {
186
+ if (topGroup) {
187
+ this._setDropdownItems(this.filteredItems);
188
+ }
189
+ }
190
+
141
191
  /**
142
192
  * Override combo-box method to set correct owner for using by item renderers.
143
193
  * This needs to be done before the scroller gets added to the DOM to ensure
@@ -149,6 +199,8 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
149
199
  _initScroller() {
150
200
  const comboBox = this.getRootNode().host;
151
201
 
202
+ this._comboBox = comboBox;
203
+
152
204
  super._initScroller(comboBox);
153
205
  }
154
206
 
@@ -193,9 +245,9 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
193
245
  this.__enterPressed = null;
194
246
 
195
247
  // Keep selected item focused after committing on Enter.
196
- const focusedItem = this.filteredItems[this._focusedIndex];
248
+ const focusedItem = this._dropdownItems[this._focusedIndex];
197
249
  this._commitValue();
198
- this._focusedIndex = this.filteredItems.indexOf(focusedItem);
250
+ this._focusedIndex = this._dropdownItems.indexOf(focusedItem);
199
251
 
200
252
  return;
201
253
  }
@@ -166,6 +166,21 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
166
166
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
167
167
  */
168
168
  declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
169
+ /**
170
+ * Set to true to auto expand horizontally, causing input field to
171
+ * grow until max width is reached.
172
+ * @attr {boolean} auto-expand-horizontally
173
+ */
174
+ autoExpandHorizontally: boolean;
175
+
176
+ /**
177
+ * Set to true to not collapse selected items chips into the overflow
178
+ * chip and instead always expand vertically, causing input field to
179
+ * wrap into multiple lines when width is limited.
180
+ * @attr {boolean} auto-expand-vertically
181
+ */
182
+ autoExpandVertically: boolean;
183
+
169
184
  /**
170
185
  * When true, the user can input a value that is not present in the items list.
171
186
  * @attr {boolean} allow-custom-value
@@ -295,6 +310,12 @@ declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLEleme
295
310
  */
296
311
  selectedItems: TItem[];
297
312
 
313
+ /**
314
+ * Set to true to group selected items at the top of the overlay.
315
+ * @attr {boolean} selected-items-on-top
316
+ */
317
+ selectedItemsOnTop: boolean;
318
+
298
319
  /**
299
320
  * Total number of items.
300
321
  */
@@ -46,6 +46,14 @@ const multiSelectComboBox = css`
46
46
  flex-basis: 0;
47
47
  padding: 0;
48
48
  }
49
+
50
+ :host([auto-expand-vertically]) #chips {
51
+ display: contents;
52
+ }
53
+
54
+ :host([auto-expand-horizontally]) [class$='container'] {
55
+ width: auto;
56
+ }
49
57
  `;
50
58
 
51
59
  registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectComboBox], {
@@ -167,6 +175,8 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
167
175
  size="{{size}}"
168
176
  filtered-items="[[__effectiveFilteredItems]]"
169
177
  selected-items="[[selectedItems]]"
178
+ selected-items-on-top="[[selectedItemsOnTop]]"
179
+ top-group="[[_topGroup]]"
170
180
  opened="{{opened}}"
171
181
  renderer="[[renderer]]"
172
182
  theme$="[[_theme]]"
@@ -177,6 +187,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
177
187
  >
178
188
  <vaadin-multi-select-combo-box-container
179
189
  part="input-field"
190
+ auto-expand-vertically="[[autoExpandVertically]]"
180
191
  readonly="[[readonly]]"
181
192
  disabled="[[disabled]]"
182
193
  invalid="[[invalid]]"
@@ -221,6 +232,31 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
221
232
 
222
233
  static get properties() {
223
234
  return {
235
+ /**
236
+ * Set to true to auto expand horizontally, causing input field to
237
+ * grow until max width is reached.
238
+ * @attr {boolean} auto-expand-horizontally
239
+ */
240
+ autoExpandHorizontally: {
241
+ type: Boolean,
242
+ value: false,
243
+ reflectToAttribute: true,
244
+ observer: '_autoExpandHorizontallyChanged',
245
+ },
246
+
247
+ /**
248
+ * Set to true to not collapse selected items chips into the overflow
249
+ * chip and instead always expand vertically, causing input field to
250
+ * wrap into multiple lines when width is limited.
251
+ * @attr {boolean} auto-expand-vertically
252
+ */
253
+ autoExpandVertically: {
254
+ type: Boolean,
255
+ value: false,
256
+ reflectToAttribute: true,
257
+ observer: '_autoExpandVerticallyChanged',
258
+ },
259
+
224
260
  /**
225
261
  * Set true to prevent the overlay from opening automatically.
226
262
  * @attr {boolean} auto-open-disabled
@@ -431,6 +467,15 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
431
467
  */
432
468
  filteredItems: Array,
433
469
 
470
+ /**
471
+ * Set to true to group selected items at the top of the overlay.
472
+ * @attr {boolean} selected-items-on-top
473
+ */
474
+ selectedItemsOnTop: {
475
+ type: Boolean,
476
+ value: false,
477
+ },
478
+
434
479
  /** @private */
435
480
  value: {
436
481
  type: String,
@@ -465,11 +510,19 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
465
510
  _lastFilter: {
466
511
  type: String,
467
512
  },
513
+
514
+ /** @private */
515
+ _topGroup: {
516
+ type: Array,
517
+ },
468
518
  };
469
519
  }
470
520
 
471
521
  static get observers() {
472
- return ['_selectedItemsChanged(selectedItems, selectedItems.*)'];
522
+ return [
523
+ '_selectedItemsChanged(selectedItems, selectedItems.*)',
524
+ '__updateTopGroup(selectedItemsOnTop, selectedItems, opened)',
525
+ ];
473
526
  }
474
527
 
475
528
  /** @protected */
@@ -631,6 +684,20 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
631
684
  super._delegateAttribute(name, value);
632
685
  }
633
686
 
687
+ /** @private */
688
+ _autoExpandHorizontallyChanged(autoExpand, oldAutoExpand) {
689
+ if (autoExpand || oldAutoExpand) {
690
+ this.__updateChips();
691
+ }
692
+ }
693
+
694
+ /** @private */
695
+ _autoExpandVerticallyChanged(autoExpand, oldAutoExpand) {
696
+ if (autoExpand || oldAutoExpand) {
697
+ this.__updateChips();
698
+ }
699
+ }
700
+
634
701
  /**
635
702
  * Setting clear button visible reduces total space available
636
703
  * for rendering chips, and making it hidden increases it.
@@ -709,6 +776,10 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
709
776
 
710
777
  // Update selected for dropdown items
711
778
  this.requestContentUpdate();
779
+
780
+ if (this.opened) {
781
+ this.$.comboBox.$.overlay._updateOverlayWidth();
782
+ }
712
783
  }
713
784
 
714
785
  /** @private */
@@ -825,6 +896,15 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
825
896
  this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
826
897
  }
827
898
 
899
+ /** @private */
900
+ __updateTopGroup(selectedItemsOnTop, selectedItems, opened) {
901
+ if (!selectedItemsOnTop) {
902
+ this._topGroup = [];
903
+ } else if (!opened) {
904
+ this._topGroup = [...selectedItems];
905
+ }
906
+ }
907
+
828
908
  /** @private */
829
909
  __createChip(item) {
830
910
  const chip = document.createElement('vaadin-multi-select-combo-box-chip');
@@ -888,14 +968,60 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
888
968
  remainingWidth -= this.__getOverflowWidth();
889
969
  }
890
970
 
971
+ const chipMinWidth = parseInt(getComputedStyle(this).getPropertyValue('--_chip-min-width'));
972
+
973
+ if (this.autoExpandHorizontally) {
974
+ const chips = [];
975
+
976
+ // First, add all chips to make the field fully expand
977
+ for (let i = items.length - 1, refNode = null; i >= 0; i--) {
978
+ const chip = this.__createChip(items[i]);
979
+ this.$.chips.insertBefore(chip, refNode);
980
+ refNode = chip;
981
+ chips.unshift(chip);
982
+ }
983
+
984
+ const overflowItems = [];
985
+ const availableWidth = this._inputField.$.wrapper.clientWidth - this.$.chips.clientWidth;
986
+
987
+ // When auto expanding vertically, no need to measure width
988
+ if (!this.autoExpandVertically && availableWidth < inputWidth) {
989
+ // Always show at least last item as a chip
990
+ while (chips.length > 1) {
991
+ const lastChip = chips.pop();
992
+ lastChip.remove();
993
+ overflowItems.unshift(items.pop());
994
+
995
+ // Remove chips until there is enough width for the input element to fit
996
+ const neededWidth = overflowItems.length > 0 ? inputWidth + this.__getOverflowWidth() : inputWidth;
997
+ if (this._inputField.$.wrapper.clientWidth - this.$.chips.clientWidth >= neededWidth) {
998
+ break;
999
+ }
1000
+ }
1001
+
1002
+ if (chips.length === 1) {
1003
+ chips[0].style.maxWidth = `${Math.max(chipMinWidth, remainingWidth)}px`;
1004
+ }
1005
+ }
1006
+
1007
+ this._overflowItems = overflowItems;
1008
+ return;
1009
+ }
1010
+
891
1011
  // Add chips until remaining width is exceeded
892
1012
  for (let i = items.length - 1, refNode = null; i >= 0; i--) {
893
1013
  const chip = this.__createChip(items[i]);
894
1014
  this.$.chips.insertBefore(chip, refNode);
895
1015
 
896
- if (this.$.chips.clientWidth > remainingWidth) {
897
- chip.remove();
898
- break;
1016
+ // When auto expanding vertically, no need to measure remaining width
1017
+ if (!this.autoExpandVertically && this.$.chips.clientWidth > remainingWidth) {
1018
+ // Always show at least last selected item as a chip
1019
+ if (refNode === null) {
1020
+ chip.style.maxWidth = `${Math.max(chipMinWidth, remainingWidth)}px`;
1021
+ } else {
1022
+ chip.remove();
1023
+ break;
1024
+ }
899
1025
  }
900
1026
 
901
1027
  items.pop();
@@ -25,6 +25,16 @@ registerStyles(
25
25
  },
26
26
  );
27
27
 
28
+ registerStyles(
29
+ 'vaadin-multi-select-combo-box-container',
30
+ css`
31
+ :host([auto-expand-vertically]) {
32
+ padding-block: var(--lumo-space-xs);
33
+ }
34
+ `,
35
+ { moduleId: 'lumo-multi-select-combo-box-container' },
36
+ );
37
+
28
38
  const multiSelectComboBox = css`
29
39
  :host([has-value]) {
30
40
  padding-inline-start: 0;
@@ -34,10 +44,22 @@ const multiSelectComboBox = css`
34
44
  caret-color: var(--lumo-body-text-color) !important;
35
45
  }
36
46
 
47
+ [part='label'] {
48
+ flex-shrink: 0;
49
+ }
50
+
37
51
  [part~='chip']:not(:last-of-type) {
38
52
  margin-inline-end: var(--lumo-space-xs);
39
53
  }
40
54
 
55
+ :host([auto-expand-vertically]) [part~='chip'] {
56
+ margin-block: calc(var(--lumo-space-xs) / 2);
57
+ }
58
+
59
+ :host([auto-expand-vertically]) ::slotted([slot='input']) {
60
+ min-height: calc(var(--lumo-text-field-size, var(--lumo-size-m)) - 2 * var(--lumo-space-xs));
61
+ }
62
+
41
63
  [part~='overflow']:not([hidden]) + :not(:empty) {
42
64
  margin-inline-start: var(--lumo-space-xs);
43
65
  }
@@ -34,6 +34,11 @@ const multiSelectComboBox = css`
34
34
  min-height: 32px;
35
35
  }
36
36
 
37
+ :host([auto-expand-vertically]) [part~='chip'] {
38
+ margin-top: 0.25rem;
39
+ align-self: flex-start;
40
+ }
41
+
37
42
  [part='input-field'] ::slotted(input) {
38
43
  padding: 6px 0;
39
44
  }
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/multi-select-combo-box",
4
- "version": "23.3.29",
4
+ "version": "23.4.0-alpha2",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -175,6 +175,28 @@
175
175
  ]
176
176
  }
177
177
  },
178
+ {
179
+ "name": "auto-expand-horizontally",
180
+ "description": "Set to true to auto expand horizontally, causing input field to\ngrow until max width is reached.",
181
+ "value": {
182
+ "type": [
183
+ "boolean",
184
+ "null",
185
+ "undefined"
186
+ ]
187
+ }
188
+ },
189
+ {
190
+ "name": "auto-expand-vertically",
191
+ "description": "Set to true to not collapse selected items chips into the overflow\nchip and instead always expand vertically, causing input field to\nwrap into multiple lines when width is limited.",
192
+ "value": {
193
+ "type": [
194
+ "boolean",
195
+ "null",
196
+ "undefined"
197
+ ]
198
+ }
199
+ },
178
200
  {
179
201
  "name": "auto-open-disabled",
180
202
  "description": "Set true to prevent the overlay from opening automatically.",
@@ -285,6 +307,17 @@
285
307
  ]
286
308
  }
287
309
  },
310
+ {
311
+ "name": "selected-items-on-top",
312
+ "description": "Set to true to group selected items at the top of the overlay.",
313
+ "value": {
314
+ "type": [
315
+ "boolean",
316
+ "null",
317
+ "undefined"
318
+ ]
319
+ }
320
+ },
288
321
  {
289
322
  "name": "theme",
290
323
  "description": "The theme variants to apply to the component.",
@@ -453,6 +486,28 @@
453
486
  ]
454
487
  }
455
488
  },
489
+ {
490
+ "name": "autoExpandHorizontally",
491
+ "description": "Set to true to auto expand horizontally, causing input field to\ngrow until max width is reached.",
492
+ "value": {
493
+ "type": [
494
+ "boolean",
495
+ "null",
496
+ "undefined"
497
+ ]
498
+ }
499
+ },
500
+ {
501
+ "name": "autoExpandVertically",
502
+ "description": "Set to true to not collapse selected items chips into the overflow\nchip and instead always expand vertically, causing input field to\nwrap into multiple lines when width is limited.",
503
+ "value": {
504
+ "type": [
505
+ "boolean",
506
+ "null",
507
+ "undefined"
508
+ ]
509
+ }
510
+ },
456
511
  {
457
512
  "name": "autoOpenDisabled",
458
513
  "description": "Set true to prevent the overlay from opening automatically.",
@@ -626,6 +681,17 @@
626
681
  "undefined"
627
682
  ]
628
683
  }
684
+ },
685
+ {
686
+ "name": "selectedItemsOnTop",
687
+ "description": "Set to true to group selected items at the top of the overlay.",
688
+ "value": {
689
+ "type": [
690
+ "boolean",
691
+ "null",
692
+ "undefined"
693
+ ]
694
+ }
629
695
  }
630
696
  ],
631
697
  "events": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/multi-select-combo-box",
4
- "version": "23.3.29",
4
+ "version": "23.4.0-alpha2",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -68,6 +68,20 @@
68
68
  "kind": "expression"
69
69
  }
70
70
  },
71
+ {
72
+ "name": "?autoExpandHorizontally",
73
+ "description": "Set to true to auto expand horizontally, causing input field to\ngrow until max width is reached.",
74
+ "value": {
75
+ "kind": "expression"
76
+ }
77
+ },
78
+ {
79
+ "name": "?autoExpandVertically",
80
+ "description": "Set to true to not collapse selected items chips into the overflow\nchip and instead always expand vertically, causing input field to\nwrap into multiple lines when width is limited.",
81
+ "value": {
82
+ "kind": "expression"
83
+ }
84
+ },
71
85
  {
72
86
  "name": "?autoOpenDisabled",
73
87
  "description": "Set true to prevent the overlay from opening automatically.",
@@ -96,6 +110,13 @@
96
110
  "kind": "expression"
97
111
  }
98
112
  },
113
+ {
114
+ "name": "?selectedItemsOnTop",
115
+ "description": "Set to true to group selected items at the top of the overlay.",
116
+ "value": {
117
+ "kind": "expression"
118
+ }
119
+ },
99
120
  {
100
121
  "name": ".label",
101
122
  "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",