@vaadin/multi-select-combo-box 23.1.0-alpha2 → 23.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/multi-select-combo-box",
3
- "version": "23.1.0-alpha2",
3
+ "version": "23.1.0-beta1",
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-alpha2",
37
- "@vaadin/component-base": "23.1.0-alpha2",
38
- "@vaadin/field-base": "23.1.0-alpha2",
39
- "@vaadin/input-container": "23.1.0-alpha2",
40
- "@vaadin/vaadin-lumo-styles": "23.1.0-alpha2",
41
- "@vaadin/vaadin-material-styles": "23.1.0-alpha2",
42
- "@vaadin/vaadin-themable-mixin": "23.1.0-alpha2"
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"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@esm-bundle/chai": "^4.3.4",
46
46
  "@vaadin/testing-helpers": "^0.3.2",
47
- "sinon": "^9.2.0"
47
+ "sinon": "^13.0.2"
48
48
  },
49
- "gitHead": "6842dcb8b163d4512fae8d3d12a6559077a4aee6"
49
+ "gitHead": "8be43cf83102a6b9ccf309687446e590ce0164e8"
50
50
  }
@@ -30,18 +30,46 @@ class MultiSelectComboBoxChip extends ThemableMixin(PolymerElement) {
30
30
 
31
31
  static get properties() {
32
32
  return {
33
+ disabled: {
34
+ type: Boolean,
35
+ reflectToAttribute: true,
36
+ },
37
+
38
+ readonly: {
39
+ type: Boolean,
40
+ reflectToAttribute: true,
41
+ },
42
+
33
43
  label: {
34
- type: String
44
+ type: String,
35
45
  },
36
46
 
37
47
  item: {
38
- type: Object
39
- }
48
+ type: Object,
49
+ },
40
50
  };
41
51
  }
42
52
 
43
53
  static get template() {
44
54
  return html`
55
+ <style>
56
+ :host {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ align-self: center;
60
+ white-space: nowrap;
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ [part='label'] {
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ }
68
+
69
+ :host(:is([readonly], [disabled], [part~='overflow'])) [part='remove-button'] {
70
+ display: none !important;
71
+ }
72
+ </style>
45
73
  <div part="label">[[label]]</div>
46
74
  <div part="remove-button" role="button" on-click="_onRemoveClick"></div>
47
75
  `;
@@ -54,11 +82,11 @@ class MultiSelectComboBoxChip extends ThemableMixin(PolymerElement) {
54
82
  this.dispatchEvent(
55
83
  new CustomEvent('item-removed', {
56
84
  detail: {
57
- item: this.item
85
+ item: this.item,
58
86
  },
59
87
  bubbles: true,
60
- composed: true
61
- })
88
+ composed: true,
89
+ }),
62
90
  );
63
91
  }
64
92
  }
@@ -9,14 +9,14 @@ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themab
9
9
  registerStyles(
10
10
  'vaadin-multi-select-combo-box-container',
11
11
  css`
12
- [part='wrapper'] {
12
+ #wrapper {
13
13
  display: flex;
14
14
  width: 100%;
15
15
  }
16
16
  `,
17
17
  {
18
- moduleId: 'vaadin-multi-select-combo-box-container-styles'
19
- }
18
+ moduleId: 'vaadin-multi-select-combo-box-container-styles',
19
+ },
20
20
  );
21
21
 
22
22
  let memoizedTemplate;
@@ -39,7 +39,7 @@ class MultiSelectComboBoxContainer extends InputContainer {
39
39
  const slots = content.querySelectorAll('slot');
40
40
 
41
41
  const wrapper = document.createElement('div');
42
- wrapper.setAttribute('part', 'wrapper');
42
+ wrapper.setAttribute('id', 'wrapper');
43
43
  content.insertBefore(wrapper, slots[2]);
44
44
 
45
45
  wrapper.appendChild(slots[0]);
@@ -51,8 +51,8 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
51
51
  static get properties() {
52
52
  return {
53
53
  _target: {
54
- type: Object
55
- }
54
+ type: Object,
55
+ },
56
56
  };
57
57
  }
58
58
 
@@ -65,6 +65,17 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
65
65
  return this.querySelector('[part="clear-button"]');
66
66
  }
67
67
 
68
+ /**
69
+ * Override method inherited from the combo-box
70
+ * to allow opening dropdown when readonly.
71
+ * @override
72
+ */
73
+ open() {
74
+ if (!this.disabled && !(this.readonly && this._getOverlayItems().length === 0)) {
75
+ this.opened = true;
76
+ }
77
+ }
78
+
68
79
  /**
69
80
  * @protected
70
81
  * @override
@@ -81,24 +92,17 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
81
92
  this._toggleElement = this.querySelector('.toggle-button');
82
93
  }
83
94
 
84
- /** @protected */
85
- _isClearButton(event) {
86
- return (
87
- super._isClearButton(event) ||
88
- (event.type === 'input' && !event.isTrusted) || // fake input event dispatched by clear button
89
- event.composedPath()[0].getAttribute('part') === 'clear-button'
90
- );
91
- }
92
-
93
95
  /**
94
- * @param {!Event} event
96
+ * Override method from `InputMixin`.
97
+ *
95
98
  * @protected
99
+ * @override
96
100
  */
97
- _onChange(event) {
98
- super._onChange(event);
101
+ clear() {
102
+ super.clear();
99
103
 
100
- if (this._isClearButton(event)) {
101
- this._clear();
104
+ if (this.inputElement) {
105
+ this.inputElement.value = '';
102
106
  }
103
107
  }
104
108
 
@@ -120,6 +124,11 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
120
124
  * @override
121
125
  */
122
126
  _closeOrCommit() {
127
+ if (this.readonly) {
128
+ this.close();
129
+ return;
130
+ }
131
+
123
132
  if (this.__enterPressed) {
124
133
  this.__enterPressed = null;
125
134
 
@@ -134,6 +143,49 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
134
143
  super._closeOrCommit();
135
144
  }
136
145
 
146
+ /**
147
+ * Override method inherited from the combo-box
148
+ * to not update focused item when readonly.
149
+ * @protected
150
+ * @override
151
+ */
152
+ _onArrowDown() {
153
+ if (!this.readonly) {
154
+ super._onArrowDown();
155
+ } else if (!this.opened) {
156
+ this.open();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Override method inherited from the combo-box
162
+ * to not update focused item when readonly.
163
+ * @protected
164
+ * @override
165
+ */
166
+ _onArrowUp() {
167
+ if (!this.readonly) {
168
+ super._onArrowUp();
169
+ } else if (!this.opened) {
170
+ this.open();
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Override method inherited from the combo-box
176
+ * to close dropdown on blur when readonly.
177
+ * @param {FocusEvent} event
178
+ * @protected
179
+ * @override
180
+ */
181
+ _onFocusout(event) {
182
+ super._onFocusout(event);
183
+
184
+ if (this.readonly && !this._closeOnBlurIsPrevented) {
185
+ this.close();
186
+ }
187
+ }
188
+
137
189
  /**
138
190
  * @param {CustomEvent} event
139
191
  * @protected
@@ -142,6 +194,11 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
142
194
  _overlaySelectedItemChanged(event) {
143
195
  event.stopPropagation();
144
196
 
197
+ // Do not un-select on click when readonly
198
+ if (this.readonly) {
199
+ return;
200
+ }
201
+
145
202
  if (event.detail.item instanceof ComboBoxPlaceholder) {
146
203
  return;
147
204
  }
@@ -150,9 +207,9 @@ class MultiSelectComboBoxInternal extends ComboBoxDataProviderMixin(ComboBoxMixi
150
207
  this.dispatchEvent(
151
208
  new CustomEvent('combo-box-item-selected', {
152
209
  detail: {
153
- item: event.detail.item
154
- }
155
- })
210
+ item: event.detail.item,
211
+ },
212
+ }),
156
213
  );
157
214
  }
158
215
  }
@@ -16,7 +16,7 @@ registerStyles(
16
16
  );
17
17
  }
18
18
  `,
19
- { moduleId: 'vaadin-multi-select-combo-box-overlay-styles' }
19
+ { moduleId: 'vaadin-multi-select-combo-box-overlay-styles' },
20
20
  );
21
21
 
22
22
  /**
@@ -17,15 +17,39 @@ class MultiSelectComboBoxScroller extends ComboBoxScroller {
17
17
  return 'vaadin-multi-select-combo-box-scroller';
18
18
  }
19
19
 
20
+ /** @protected */
21
+ ready() {
22
+ super.ready();
23
+
24
+ this.setAttribute('aria-multiselectable', 'true');
25
+ }
26
+
27
+ /** @private */
28
+ __getAriaSelected(_focusedIndex, itemIndex) {
29
+ const item = this.items[itemIndex];
30
+ return this.__isItemSelected(item, null, this.itemIdPath).toString();
31
+ }
32
+
20
33
  /** @private */
21
34
  __isItemSelected(item, _selectedItem, itemIdPath) {
22
35
  if (item instanceof ComboBoxPlaceholder) {
23
36
  return false;
24
37
  }
25
38
 
39
+ if (this.comboBox.readonly) {
40
+ return false;
41
+ }
42
+
26
43
  const host = this.comboBox.getRootNode().host;
27
44
  return host._findIndex(item, host.selectedItems, itemIdPath) > -1;
28
45
  }
46
+
47
+ /** @private */
48
+ __updateElement(el, index) {
49
+ super.__updateElement(el, index);
50
+
51
+ el.toggleAttribute('readonly', this.comboBox.readonly);
52
+ }
29
53
  }
30
54
 
31
55
  customElements.define(MultiSelectComboBoxScroller.is, MultiSelectComboBoxScroller);
@@ -9,6 +9,7 @@ import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js
9
9
  import { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
10
10
  import { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
11
11
  import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
12
+ import { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';
12
13
  import { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js';
13
14
  import { DelegateStateMixinClass } from '@vaadin/field-base/src/delegate-state-mixin.js';
14
15
  import { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
@@ -19,6 +20,13 @@ import { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
19
20
  import { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
20
21
  import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
21
22
 
23
+ export interface MultiSelectComboBoxI18n {
24
+ cleared: string;
25
+ selected: string;
26
+ deselected: string;
27
+ total: string;
28
+ }
29
+
22
30
  /**
23
31
  * Fired when the user commits a value change.
24
32
  */
@@ -78,6 +86,7 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
78
86
  *
79
87
  * Part name | Description
80
88
  * -----------------------|----------------
89
+ * `chips` | The element that wraps chips for selected items
81
90
  * `chip` | Chip shown for every selected item
82
91
  * `label` | The label element
83
92
  * `input-field` | The element that wraps prefix, value and suffix
@@ -85,6 +94,9 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
85
94
  * `error-message` | The error message element
86
95
  * `helper-text` | The helper text element wrapper
87
96
  * `required-indicator` | The `required` state indicator element
97
+ * `overflow` | The chip shown when component width is not enough to fit all chips
98
+ * `overflow-one` | Set on the overflow chip when only one chip does not fit
99
+ * `overflow-two` | Set on the overflow chip when two chips do not fit
88
100
  * `toggle-button` | The toggle button
89
101
  *
90
102
  * The following state attributes are available for styling:
@@ -102,6 +114,14 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
102
114
  * `opened` | Set when the dropdown is open
103
115
  * `readonly` | Set to a readonly element
104
116
  *
117
+ * The following custom CSS properties are available for styling:
118
+ *
119
+ * Custom property | Description | Default
120
+ * -----------------------------------------------------|----------------------------|--------
121
+ * `--vaadin-field-default-width` | Default width of the field | `12em`
122
+ * `--vaadin-multi-select-combo-box-overlay-max-height` | Max height of the overlay | `65vh`
123
+ * `--vaadin-multi-select-combo-box-input-min-width` | Min width of the input | `4em`
124
+ *
105
125
  * ### Internal components
106
126
  *
107
127
  * In addition to `<vaadin-multi-select-combo-box>` itself, the following internal
@@ -186,6 +206,28 @@ declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLEleme
186
206
  */
187
207
  itemValuePath: string;
188
208
 
209
+ /**
210
+ * The object used to localize this component.
211
+ * To change the default localization, replace the entire
212
+ * _i18n_ object or just the property you want to modify.
213
+ *
214
+ * The object has the following JSON structure and default values:
215
+ * ```
216
+ * {
217
+ * // Screen reader announcement on clear button click.
218
+ * cleared: 'Selection cleared',
219
+ * // Screen reader announcement when item is selected.
220
+ * selected: 'added to selection',
221
+ * // Screen reader announcement when item is deselected.
222
+ * deselected: 'removed from selection',
223
+ * // Screen reader announcement of the selected items count.
224
+ * // {count} is replaced with the actual count of items.
225
+ * total: '{count} items selected',
226
+ * }
227
+ * ```
228
+ */
229
+ i18n: MultiSelectComboBoxI18n;
230
+
189
231
  /**
190
232
  * True if the dropdown is open, false otherwise.
191
233
  */
@@ -219,13 +261,13 @@ declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLEleme
219
261
  addEventListener<K extends keyof MultiSelectComboBoxEventMap<TItem>>(
220
262
  type: K,
221
263
  listener: (this: MultiSelectComboBox<TItem>, ev: MultiSelectComboBoxEventMap<TItem>[K]) => void,
222
- options?: boolean | AddEventListenerOptions
264
+ options?: boolean | AddEventListenerOptions,
223
265
  ): void;
224
266
 
225
267
  removeEventListener<K extends keyof MultiSelectComboBoxEventMap<TItem>>(
226
268
  type: K,
227
269
  listener: (this: MultiSelectComboBox<TItem>, ev: MultiSelectComboBoxEventMap<TItem>[K]) => void,
228
- options?: boolean | EventListenerOptions
270
+ options?: boolean | EventListenerOptions,
229
271
  ): void;
230
272
  }
231
273
 
@@ -241,6 +283,7 @@ interface MultiSelectComboBox
241
283
  DisabledMixinClass,
242
284
  DelegateStateMixinClass,
243
285
  DelegateFocusMixinClass,
286
+ ResizeMixinClass,
244
287
  ThemableMixinClass,
245
288
  ElementMixinClass,
246
289
  ControllerMixinClass {}
@@ -7,7 +7,9 @@ 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';
12
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
11
13
  import { processTemplates } from '@vaadin/component-base/src/templates.js';
12
14
  import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js';
13
15
  import { InputController } from '@vaadin/field-base/src/input-controller.js';
@@ -16,34 +18,41 @@ import { inputFieldShared } from '@vaadin/field-base/src/styles/input-field-shar
16
18
  import { css, registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
17
19
 
18
20
  const multiSelectComboBox = css`
21
+ :host {
22
+ --input-min-width: var(--vaadin-multi-select-combo-box-input-min-width, 4em);
23
+ }
24
+
19
25
  [hidden] {
20
26
  display: none !important;
21
27
  }
22
28
 
23
- :host([has-value]) ::slotted(input:placeholder-shown) {
24
- color: transparent !important;
29
+ #chips {
30
+ display: flex;
31
+ align-items: center;
25
32
  }
26
33
 
27
- :host([has-value]) [class$='container'] {
28
- width: auto;
34
+ :host([has-value]) ::slotted(input:placeholder-shown) {
35
+ color: transparent !important;
29
36
  }
30
37
 
31
38
  ::slotted(input) {
32
39
  box-sizing: border-box;
33
- flex: 1 0 4em;
40
+ flex: 1 0 var(--input-min-width);
34
41
  }
35
42
 
36
- [part~='chip'] {
43
+ [part='chip'] {
37
44
  flex: 0 1 auto;
38
45
  }
39
46
 
40
- :host([readonly]) [part~='chip'] {
41
- pointer-events: none;
47
+ :host(:is([readonly], [disabled])) ::slotted(input) {
48
+ flex-grow: 0;
49
+ flex-basis: 0;
50
+ padding: 0;
42
51
  }
43
52
  `;
44
53
 
45
54
  registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectComboBox], {
46
- moduleId: 'vaadin-multi-select-combo-box-styles'
55
+ moduleId: 'vaadin-multi-select-combo-box-styles',
47
56
  });
48
57
 
49
58
  /**
@@ -66,6 +75,7 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
66
75
  *
67
76
  * Part name | Description
68
77
  * -----------------------|----------------
78
+ * `chips` | The element that wraps chips for selected items
69
79
  * `chip` | Chip shown for every selected item
70
80
  * `label` | The label element
71
81
  * `input-field` | The element that wraps prefix, value and suffix
@@ -73,6 +83,9 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
73
83
  * `error-message` | The error message element
74
84
  * `helper-text` | The helper text element wrapper
75
85
  * `required-indicator` | The `required` state indicator element
86
+ * `overflow` | The chip shown when component width is not enough to fit all chips
87
+ * `overflow-one` | Set on the overflow chip when only one chip does not fit
88
+ * `overflow-two` | Set on the overflow chip when two chips do not fit
76
89
  * `toggle-button` | The toggle button
77
90
  *
78
91
  * The following state attributes are available for styling:
@@ -90,6 +103,14 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
90
103
  * `opened` | Set when the dropdown is open
91
104
  * `readonly` | Set to a readonly element
92
105
  *
106
+ * The following custom CSS properties are available for styling:
107
+ *
108
+ * Custom property | Description | Default
109
+ * -----------------------------------------------------|----------------------------|--------
110
+ * `--vaadin-field-default-width` | Default width of the field | `12em`
111
+ * `--vaadin-multi-select-combo-box-overlay-max-height` | Max height of the overlay | `65vh`
112
+ * `--vaadin-multi-select-combo-box-input-min-width` | Min width of the input | `4em`
113
+ *
93
114
  * ### Internal components
94
115
  *
95
116
  * In addition to `<vaadin-multi-select-combo-box>` itself, the following internal
@@ -114,8 +135,9 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo
114
135
  * @mixes ElementMixin
115
136
  * @mixes ThemableMixin
116
137
  * @mixes InputControlMixin
138
+ * @mixes ResizeMixin
117
139
  */
118
- class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(PolymerElement))) {
140
+ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(ElementMixin(PolymerElement)))) {
119
141
  static get is() {
120
142
  return 'vaadin-multi-select-combo-box';
121
143
  }
@@ -155,6 +177,18 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
155
177
  invalid="[[invalid]]"
156
178
  theme$="[[theme]]"
157
179
  >
180
+ <vaadin-multi-select-combo-box-chip
181
+ id="overflow"
182
+ slot="prefix"
183
+ part$="[[_getOverflowPart(_overflowItems.length)]]"
184
+ disabled="[[disabled]]"
185
+ readonly="[[readonly]]"
186
+ label="[[_getOverflowLabel(_overflowItems.length)]]"
187
+ title$="[[_getOverflowTitle(_overflowItems)]]"
188
+ hidden$="[[_isOverflowHidden(_overflowItems.length)]]"
189
+ on-mousedown="_preventBlur"
190
+ ></vaadin-multi-select-combo-box-chip>
191
+ <div id="chips" part="chips" slot="prefix"></div>
158
192
  <slot name="input"></slot>
159
193
  <div id="clearButton" part="clear-button" slot="suffix"></div>
160
194
  <div id="toggleButton" class="toggle-button" part="toggle-button" slot="suffix"></div>
@@ -180,12 +214,23 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
180
214
  */
181
215
  autoOpenDisabled: Boolean,
182
216
 
217
+ /**
218
+ * Set to true to display the clear icon which clears the input.
219
+ * @attr {boolean} clear-button-visible
220
+ */
221
+ clearButtonVisible: {
222
+ type: Boolean,
223
+ reflectToAttribute: true,
224
+ observer: '_clearButtonVisibleChanged',
225
+ value: false,
226
+ },
227
+
183
228
  /**
184
229
  * A full set of items to filter the visible options from.
185
230
  * The items can be of either `String` or `Object` type.
186
231
  */
187
232
  items: {
188
- type: Array
233
+ type: Array,
189
234
  },
190
235
 
191
236
  /**
@@ -193,7 +238,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
193
238
  * @attr {string} item-label-path
194
239
  */
195
240
  itemLabelPath: {
196
- type: String
241
+ type: String,
197
242
  },
198
243
 
199
244
  /**
@@ -202,7 +247,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
202
247
  * @attr {string} item-value-path
203
248
  */
204
249
  itemValuePath: {
205
- type: String
250
+ type: String,
206
251
  },
207
252
 
208
253
  /**
@@ -210,7 +255,51 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
210
255
  * @attr {string} item-id-path
211
256
  */
212
257
  itemIdPath: {
213
- 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 item is selected.
272
+ * selected: 'added to selection',
273
+ * // Screen reader announcement when item is deselected.
274
+ * deselected: 'removed from selection',
275
+ * // Screen reader announcement of the selected items count.
276
+ * // {count} is replaced with the actual count of items.
277
+ * total: '{count} items selected',
278
+ * }
279
+ * ```
280
+ * @type {!MultiSelectComboBoxI18n}
281
+ * @default {English/US}
282
+ */
283
+ i18n: {
284
+ type: Object,
285
+ value: () => {
286
+ return {
287
+ cleared: 'Selection cleared',
288
+ selected: 'added to selection',
289
+ deselected: 'removed from selection',
290
+ total: '{count} items selected',
291
+ };
292
+ },
293
+ },
294
+
295
+ /**
296
+ * When present, it specifies that the field is read-only.
297
+ */
298
+ readonly: {
299
+ type: Boolean,
300
+ value: false,
301
+ observer: '_readonlyChanged',
302
+ reflectToAttribute: true,
214
303
  },
215
304
 
216
305
  /**
@@ -220,7 +309,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
220
309
  selectedItems: {
221
310
  type: Array,
222
311
  value: () => [],
223
- notify: true
312
+ notify: true,
224
313
  },
225
314
 
226
315
  /**
@@ -230,7 +319,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
230
319
  type: Boolean,
231
320
  notify: true,
232
321
  value: false,
233
- reflectToAttribute: true
322
+ reflectToAttribute: true,
234
323
  },
235
324
 
236
325
  /**
@@ -240,7 +329,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
240
329
  pageSize: {
241
330
  type: Number,
242
331
  value: 50,
243
- observer: '_pageSizeChanged'
332
+ observer: '_pageSizeChanged',
244
333
  },
245
334
 
246
335
  /**
@@ -257,7 +346,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
257
346
  */
258
347
  dataProvider: {
259
348
  type: Object,
260
- observer: '_dataProviderChanged'
349
+ observer: '_dataProviderChanged',
261
350
  },
262
351
 
263
352
  /**
@@ -266,7 +355,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
266
355
  */
267
356
  allowCustomValues: {
268
357
  type: Boolean,
269
- value: false
358
+ value: false,
270
359
  },
271
360
 
272
361
  /**
@@ -288,7 +377,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
288
377
  filter: {
289
378
  type: String,
290
379
  value: '',
291
- notify: true
380
+ notify: true,
292
381
  },
293
382
 
294
383
  /**
@@ -301,8 +390,14 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
301
390
  /** @protected */
302
391
  _hasValue: {
303
392
  type: Boolean,
304
- value: false
305
- }
393
+ value: false,
394
+ },
395
+
396
+ /** @private */
397
+ _overflowItems: {
398
+ type: Array,
399
+ value: () => [],
400
+ },
306
401
  };
307
402
  }
308
403
 
@@ -334,7 +429,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
334
429
  this._setFocusElement(input);
335
430
  this.stateTarget = input;
336
431
  this.ariaTarget = input;
337
- })
432
+ }),
338
433
  );
339
434
  this.addController(new LabelledInputController(this.inputElement, this._labelController));
340
435
 
@@ -349,7 +444,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
349
444
  * @return {boolean}
350
445
  */
351
446
  checkValidity() {
352
- return this.required ? this._hasValue : true;
447
+ return this.required && !this.readonly ? this._hasValue : true;
353
448
  }
354
449
 
355
450
  /**
@@ -361,9 +456,7 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
361
456
  super._disabledChanged(disabled, oldDisabled);
362
457
 
363
458
  if (disabled || oldDisabled) {
364
- this._chips.forEach((chip) => {
365
- chip.toggleAttribute('disabled', disabled);
366
- });
459
+ this.__updateChips();
367
460
  }
368
461
  }
369
462
 
@@ -403,6 +496,59 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
403
496
  super._toggleHasValue(this._hasValue);
404
497
  }
405
498
 
499
+ /**
500
+ * Implement callback from `ResizeMixin` to update chips.
501
+ * @protected
502
+ * @override
503
+ */
504
+ _onResize() {
505
+ this.__updateChips();
506
+ }
507
+
508
+ /**
509
+ * Override method from `DelegateStateMixin` to set required state
510
+ * using `aria-required` attribute instead of `required`, in order
511
+ * to prevent screen readers from announcing "invalid entry".
512
+ * @protected
513
+ * @override
514
+ */
515
+ _delegateAttribute(name, value) {
516
+ if (!this.stateTarget) {
517
+ return;
518
+ }
519
+
520
+ if (name === 'required') {
521
+ this._delegateAttribute('aria-required', value ? 'true' : false);
522
+ return;
523
+ }
524
+
525
+ super._delegateAttribute(name, value);
526
+ }
527
+
528
+ /**
529
+ * Setting clear button visible reduces total space available
530
+ * for rendering chips, and making it hidden increases it.
531
+ * @private
532
+ */
533
+ _clearButtonVisibleChanged(visible, oldVisible) {
534
+ if (visible || oldVisible) {
535
+ this.__updateChips();
536
+ }
537
+ }
538
+
539
+ /** @private */
540
+ _readonlyChanged(readonly, oldReadonly) {
541
+ if (readonly) {
542
+ this.__savedItems = this.$.comboBox._getOverlayItems();
543
+ this.$.comboBox._setOverlayItems(Array.from(this.selectedItems));
544
+ this.__updateChips();
545
+ } else if (oldReadonly) {
546
+ this.$.comboBox._setOverlayItems(this.__savedItems);
547
+ this.__savedItems = null;
548
+ this.__updateChips();
549
+ }
550
+ }
551
+
406
552
  /** @private */
407
553
  _pageSizeChanged(pageSize, oldPageSize) {
408
554
  if (Math.floor(pageSize) !== pageSize || pageSize <= 0) {
@@ -419,9 +565,21 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
419
565
 
420
566
  this._toggleHasValue();
421
567
 
568
+ // Use placeholder for announcing items
569
+ if (this._hasValue) {
570
+ this.__savedPlaceholder = this.placeholder;
571
+ this.placeholder = selectedItems.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', ');
572
+ } else {
573
+ this.placeholder = this.__savedPlaceholder;
574
+ }
575
+
422
576
  // Re-render chips
423
577
  this.__updateChips();
424
578
 
579
+ if (this.readonly) {
580
+ this.$.comboBox._setOverlayItems(selectedItems);
581
+ }
582
+
425
583
  // Re-render scroller
426
584
  this.$.comboBox.$.dropdown._scroller.requestContentUpdate();
427
585
 
@@ -436,6 +594,34 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
436
594
  return item && Object.prototype.hasOwnProperty.call(item, itemLabelPath) ? item[itemLabelPath] : item;
437
595
  }
438
596
 
597
+ /** @private */
598
+ _getOverflowLabel(length) {
599
+ return length;
600
+ }
601
+
602
+ /** @private */
603
+ _getOverflowPart(length) {
604
+ let part = `chip overflow`;
605
+
606
+ if (length === 1) {
607
+ part += ' overflow-one';
608
+ } else if (length === 2) {
609
+ part += ' overflow-two';
610
+ }
611
+
612
+ return part;
613
+ }
614
+
615
+ /** @private */
616
+ _getOverflowTitle(items) {
617
+ return items.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', ');
618
+ }
619
+
620
+ /** @private */
621
+ _isOverflowHidden(length) {
622
+ return length === 0;
623
+ }
624
+
439
625
  /** @private */
440
626
  _findIndex(item, selectedItems, itemIdPath) {
441
627
  if (itemIdPath && item) {
@@ -452,14 +638,23 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
452
638
 
453
639
  /** @private */
454
640
  __clearFilter() {
641
+ this.filter = '';
455
642
  this.$.comboBox.clear();
456
643
  }
457
644
 
645
+ /** @private */
646
+ __announceItem(itemLabel, isSelected, itemCount) {
647
+ const state = isSelected ? 'selected' : 'deselected';
648
+ const total = this.i18n.total.replace('{count}', itemCount || 0);
649
+ announce(`${itemLabel} ${this.i18n[state]} ${total}`);
650
+ }
651
+
458
652
  /** @private */
459
653
  __removeItem(item) {
460
654
  const itemsCopy = [...this.selectedItems];
461
655
  itemsCopy.splice(itemsCopy.indexOf(item), 1);
462
656
  this.__updateSelection(itemsCopy);
657
+ this.__announceItem(item, false, itemsCopy.length);
463
658
  }
464
659
 
465
660
  /** @private */
@@ -467,9 +662,13 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
467
662
  const itemsCopy = [...this.selectedItems];
468
663
 
469
664
  const index = this._findIndex(item, itemsCopy, this.itemIdPath);
665
+ const itemLabel = this._getItemLabel(item, this.itemLabelPath);
666
+
667
+ let isSelected = false;
668
+
470
669
  if (index !== -1) {
471
670
  // Do not unselect when manually typing and committing an already selected item.
472
- if (this.filter.toLowerCase() === this._getItemLabel(item, this.itemLabelPath).toLowerCase()) {
671
+ if (this.filter.toLowerCase() === itemLabel.toLowerCase()) {
473
672
  this.__clearFilter();
474
673
  return;
475
674
  }
@@ -477,12 +676,15 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
477
676
  itemsCopy.splice(index, 1);
478
677
  } else {
479
678
  itemsCopy.push(item);
679
+ isSelected = true;
480
680
  }
481
681
 
482
682
  this.__updateSelection(itemsCopy);
483
683
 
484
684
  // Suppress `value-changed` event.
485
685
  this.__clearFilter();
686
+
687
+ this.__announceItem(itemLabel, isSelected, itemsCopy.length);
486
688
  }
487
689
 
488
690
  /** @private */
@@ -501,8 +703,12 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
501
703
  chip.setAttribute('slot', 'prefix');
502
704
 
503
705
  chip.item = item;
504
- chip.label = this._getItemLabel(item, this.itemLabelPath);
505
- chip.toggleAttribute('disabled', this.disabled);
706
+ chip.disabled = this.disabled;
707
+ chip.readonly = this.readonly;
708
+
709
+ const label = this._getItemLabel(item, this.itemLabelPath);
710
+ chip.label = label;
711
+ chip.setAttribute('title', label);
506
712
 
507
713
  chip.addEventListener('item-removed', (e) => this._onItemRemoved(e));
508
714
  chip.addEventListener('mousedown', (e) => this._preventBlur(e));
@@ -510,22 +716,64 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
510
716
  return chip;
511
717
  }
512
718
 
719
+ /** @private */
720
+ __getOverflowWidth() {
721
+ const chip = this.$.overflow;
722
+
723
+ chip.style.visibility = 'hidden';
724
+ chip.removeAttribute('hidden');
725
+
726
+ // Detect max possible width of the overflow chip
727
+ chip.setAttribute('part', 'chip overflow');
728
+ const overflowStyle = getComputedStyle(chip);
729
+ const overflowWidth = chip.clientWidth + parseInt(overflowStyle.marginInlineStart);
730
+
731
+ chip.setAttribute('hidden', '');
732
+ chip.style.visibility = '';
733
+
734
+ return overflowWidth;
735
+ }
736
+
513
737
  /** @private */
514
738
  __updateChips() {
515
739
  if (!this._inputField) {
516
740
  return;
517
741
  }
518
742
 
519
- this._chips.forEach((chip) => {
520
- chip.remove();
743
+ // Clear all chips except the overflow
744
+ Array.from(this._chips).forEach((chip) => {
745
+ if (chip !== this.$.overflow) {
746
+ chip.remove();
747
+ }
521
748
  });
522
749
 
523
750
  const items = [...this.selectedItems];
524
751
 
525
- for (let i = items.length - 1; i >= 0; i--) {
752
+ // Detect available remaining width for chips
753
+ const totalWidth = this._inputField.$.wrapper.clientWidth;
754
+ const inputWidth = parseInt(getComputedStyle(this.inputElement).flexBasis);
755
+
756
+ let remainingWidth = totalWidth - inputWidth;
757
+
758
+ if (items.length > 1) {
759
+ remainingWidth -= this.__getOverflowWidth();
760
+ }
761
+
762
+ // Add chips until remaining width is exceeded
763
+ for (let i = items.length - 1, refNode = null; i >= 0; i--) {
526
764
  const chip = this.__createChip(items[i]);
527
- this._inputField.insertBefore(chip, this._inputField.firstElementChild);
765
+ this.$.chips.insertBefore(chip, refNode);
766
+
767
+ if (this.$.chips.clientWidth > remainingWidth) {
768
+ chip.remove();
769
+ break;
770
+ }
771
+
772
+ items.pop();
773
+ refNode = chip;
528
774
  }
775
+
776
+ this._overflowItems = items;
529
777
  }
530
778
 
531
779
  /**
@@ -537,6 +785,8 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
537
785
  event.stopPropagation();
538
786
 
539
787
  this.__updateSelection([]);
788
+
789
+ announce(this.i18n.cleared);
540
790
  }
541
791
 
542
792
  /**
@@ -576,8 +826,8 @@ class MultiSelectComboBox extends InputControlMixin(ThemableMixin(ElementMixin(P
576
826
  new CustomEvent('custom-values-set', {
577
827
  detail: event.detail,
578
828
  composed: true,
579
- bubbles: true
580
- })
829
+ bubbles: true,
830
+ }),
581
831
  );
582
832
  }
583
833
 
@@ -13,27 +13,63 @@ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themab
13
13
 
14
14
  const chip = css`
15
15
  :host {
16
- display: inline-flex;
17
- align-items: center;
18
- align-self: center;
19
- font-family: var(--lumo-font-family);
20
16
  font-size: var(--lumo-font-size-xxs);
21
17
  line-height: 1;
22
- padding: 0.3125em 0 0.3125em calc(0.5em + var(--lumo-border-radius-s) / 4);
18
+ padding: 0.3125em calc(0.5em + var(--lumo-border-radius-s) / 4);
19
+ color: var(--lumo-body-text-color);
23
20
  border-radius: var(--lumo-border-radius-s);
24
- border-radius: var(--lumo-border-radius);
25
21
  background-color: var(--lumo-contrast-20pct);
26
22
  cursor: var(--lumo-clickable-cursor);
27
- white-space: nowrap;
28
- box-sizing: border-box;
29
- min-width: 0;
23
+ }
24
+
25
+ :host(:not([part~='overflow']):not([readonly]):not([disabled])) {
26
+ padding-inline-end: 0;
27
+ }
28
+
29
+ :host([part~='overflow']) {
30
+ position: relative;
31
+ min-width: var(--lumo-size-xxs);
32
+ margin-inline-start: var(--lumo-space-s);
33
+ }
34
+
35
+ :host([part~='overflow'])::before,
36
+ :host([part~='overflow'])::after {
37
+ position: absolute;
38
+ content: '';
39
+ width: 3px;
40
+ height: calc(1.875em - 1px);
41
+ border-left: 2px solid;
42
+ border-radius: 4px 0 0 4px;
43
+ border-color: var(--lumo-contrast-30pct);
44
+ }
45
+
46
+ :host([part~='overflow'])::before {
47
+ left: -4px;
48
+ }
49
+
50
+ :host([part~='overflow'])::after {
51
+ left: -8px;
52
+ }
53
+
54
+ :host([part~='overflow-two']) {
55
+ margin-inline-start: calc(var(--lumo-space-s) / 2);
56
+ }
57
+
58
+ :host([part~='overflow-two'])::after {
59
+ display: none;
60
+ }
61
+
62
+ :host([part~='overflow-one']) {
63
+ margin-inline-start: 0;
64
+ }
65
+
66
+ :host([part~='overflow-one'])::before,
67
+ :host([part~='overflow-one'])::after {
68
+ display: none;
30
69
  }
31
70
 
32
71
  [part='label'] {
33
- color: var(--lumo-body-text-color);
34
72
  font-weight: 500;
35
- overflow: hidden;
36
- text-overflow: ellipsis;
37
73
  line-height: 1.25;
38
74
  }
39
75
 
@@ -43,8 +79,9 @@ const chip = css`
43
79
  justify-content: center;
44
80
  margin-top: -0.3125em;
45
81
  margin-bottom: -0.3125em;
46
- width: var(--lumo-icon-size-s);
47
- height: var(--lumo-icon-size-s);
82
+ margin-inline-start: auto;
83
+ width: 1.25em;
84
+ height: 1.25em;
48
85
  font-size: 1.5em;
49
86
  }
50
87
 
@@ -52,7 +89,7 @@ const chip = css`
52
89
  content: var(--lumo-icons-cross);
53
90
  }
54
91
 
55
- :host([disabled]) [part] {
92
+ :host([disabled]) [part='label'] {
56
93
  color: var(--lumo-disabled-text-color);
57
94
  -webkit-text-fill-color: var(--lumo-disabled-text-color);
58
95
  pointer-events: none;
@@ -60,5 +97,5 @@ const chip = css`
60
97
  `;
61
98
 
62
99
  registerStyles('vaadin-multi-select-combo-box-chip', [fieldButton, chip], {
63
- moduleId: 'lumo-multi-select-combo-box-chip'
100
+ moduleId: 'lumo-multi-select-combo-box-chip',
64
101
  });
@@ -10,24 +10,44 @@ import '@vaadin/vaadin-lumo-styles/typography.js';
10
10
  import { inputFieldShared } from '@vaadin/vaadin-lumo-styles/mixins/input-field-shared.js';
11
11
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
12
 
13
+ registerStyles(
14
+ 'vaadin-multi-select-combo-box-item',
15
+ css`
16
+ @media (any-hover: hover) {
17
+ :host(:hover[readonly]) {
18
+ background-color: transparent;
19
+ cursor: default;
20
+ }
21
+ }
22
+ `,
23
+ {
24
+ moduleId: 'lumo-multi-select-combo-box-item',
25
+ },
26
+ );
27
+
13
28
  const multiSelectComboBox = css`
14
29
  :host([has-value]) {
15
30
  padding-inline-start: 0;
16
31
  }
17
32
 
18
- :host([readonly]) [part~='chip'] {
19
- opacity: 0.7;
20
- }
21
-
22
33
  [part~='chip']:not(:last-of-type) {
23
34
  margin-inline-end: var(--lumo-space-xs);
24
35
  }
25
36
 
37
+ [part~='overflow']:not([hidden]) + :not(:empty) {
38
+ margin-inline-start: var(--lumo-space-xs);
39
+ }
40
+
26
41
  [part='toggle-button']::before {
27
42
  content: var(--lumo-icons-dropdown);
28
43
  }
44
+
45
+ :host([readonly][has-value]) [part='toggle-button'] {
46
+ color: var(--lumo-contrast-60pct);
47
+ cursor: var(--lumo-clickable-cursor);
48
+ }
29
49
  `;
30
50
 
31
51
  registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectComboBox], {
32
- moduleId: 'lumo-multi-select-combo-box'
52
+ moduleId: 'lumo-multi-select-combo-box',
33
53
  });
@@ -11,20 +11,60 @@ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themab
11
11
 
12
12
  const chip = css`
13
13
  :host {
14
- display: flex;
15
- align-items: center;
16
- align-self: center;
17
- box-sizing: border-box;
18
14
  height: 1.25rem;
19
15
  margin-inline-end: 0.25rem;
20
- padding-inline-start: 0.5rem;
16
+ padding: 0 0.5rem;
21
17
  border-radius: 4px;
22
18
  background-color: hsla(214, 53%, 23%, 0.1);
23
19
  cursor: default;
24
- white-space: nowrap;
25
20
  font-family: var(--material-font-family);
26
21
  }
27
22
 
23
+ :host(:not([part~='overflow']):not([readonly]):not([disabled])) {
24
+ padding-inline-end: 0;
25
+ }
26
+
27
+ :host([part~='overflow']) {
28
+ position: relative;
29
+ margin-inline-start: 0.5rem;
30
+ }
31
+
32
+ :host([part~='overflow'])::before,
33
+ :host([part~='overflow'])::after {
34
+ position: absolute;
35
+ 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);
41
+ }
42
+
43
+ :host([part~='overflow'])::before {
44
+ left: -4px;
45
+ }
46
+
47
+ :host([part~='overflow'])::after {
48
+ left: -8px;
49
+ }
50
+
51
+ :host([part~='overflow-two']) {
52
+ margin-inline-start: 0.25rem;
53
+ }
54
+
55
+ :host([part~='overflow-two'])::after {
56
+ display: none;
57
+ }
58
+
59
+ :host([part~='overflow-one']) {
60
+ margin-inline-start: 0;
61
+ }
62
+
63
+ :host([part~='overflow-one'])::before,
64
+ :host([part~='overflow-one'])::after {
65
+ display: none;
66
+ }
67
+
28
68
  [part='label'] {
29
69
  font-size: var(--material-caption-font-size);
30
70
  line-height: 1;
@@ -39,6 +79,7 @@ const chip = css`
39
79
  box-sizing: border-box;
40
80
  width: 20px;
41
81
  height: 20px;
82
+ margin-inline-start: auto;
42
83
  line-height: 20px;
43
84
  padding: 0;
44
85
  font-size: 0.75em;
@@ -48,22 +89,13 @@ const chip = css`
48
89
  content: var(--material-icons-clear);
49
90
  }
50
91
 
51
- /* Disabled */
52
- :host([disabled]) [part] {
53
- pointer-events: none;
54
- }
55
-
56
92
  :host([disabled]) [part='label'] {
57
93
  color: var(--material-disabled-text-color);
58
94
  -webkit-text-fill-color: var(--material-disabled-text-color);
59
- }
60
-
61
- :host([disabled]) [part='remove-button'] {
62
- color: hsla(0, 0%, 100%, 0.75);
63
- -webkit-text-fill-color: hsla(0, 0%, 100%, 0.75);
95
+ pointer-events: none;
64
96
  }
65
97
  `;
66
98
 
67
99
  registerStyles('vaadin-multi-select-combo-box-chip', [fieldButton, chip], {
68
- moduleId: 'material-multi-select-combo-box-chip'
100
+ moduleId: 'material-multi-select-combo-box-chip',
69
101
  });
@@ -9,11 +9,22 @@ import '@vaadin/vaadin-material-styles/typography.js';
9
9
  import { inputFieldShared } from '@vaadin/vaadin-material-styles/mixins/input-field-shared.js';
10
10
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
11
 
12
- const multiSelectComboBox = css`
13
- :host([readonly]) [part~='chip'] {
14
- opacity: 0.5;
15
- }
12
+ registerStyles(
13
+ 'vaadin-multi-select-combo-box-item',
14
+ css`
15
+ @media (any-hover: hover) {
16
+ :host(:hover[readonly]) {
17
+ background-color: transparent;
18
+ cursor: default;
19
+ }
20
+ }
21
+ `,
22
+ {
23
+ moduleId: 'material-multi-select-combo-box-item',
24
+ },
25
+ );
16
26
 
27
+ const multiSelectComboBox = css`
17
28
  [part='input-field'] {
18
29
  height: auto;
19
30
  min-height: 32px;
@@ -30,8 +41,12 @@ const multiSelectComboBox = css`
30
41
  :host([opened]) [part='toggle-button'] {
31
42
  transform: rotate(180deg);
32
43
  }
44
+
45
+ :host([readonly][has-value]) [part='toggle-button'] {
46
+ color: var(--material-secondary-text-color);
47
+ }
33
48
  `;
34
49
 
35
50
  registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectComboBox], {
36
- moduleId: 'material-multi-select-combo-box'
51
+ moduleId: 'material-multi-select-combo-box',
37
52
  });