@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 +10 -10
- package/src/vaadin-multi-select-combo-box-chip.js +34 -6
- package/src/vaadin-multi-select-combo-box-container.js +4 -4
- package/src/vaadin-multi-select-combo-box-internal.js +76 -19
- package/src/vaadin-multi-select-combo-box-overlay.js +1 -1
- package/src/vaadin-multi-select-combo-box-scroller.js +24 -0
- package/src/vaadin-multi-select-combo-box.d.ts +45 -2
- package/src/vaadin-multi-select-combo-box.js +286 -36
- package/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js +53 -16
- package/theme/lumo/vaadin-multi-select-combo-box-styles.js +25 -5
- package/theme/material/vaadin-multi-select-combo-box-chip-styles.js +49 -17
- package/theme/material/vaadin-multi-select-combo-box-styles.js +20 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/multi-select-combo-box",
|
|
3
|
-
"version": "23.1.0-
|
|
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-
|
|
37
|
-
"@vaadin/component-base": "23.1.0-
|
|
38
|
-
"@vaadin/field-base": "23.1.0-
|
|
39
|
-
"@vaadin/input-container": "23.1.0-
|
|
40
|
-
"@vaadin/vaadin-lumo-styles": "23.1.0-
|
|
41
|
-
"@vaadin/vaadin-material-styles": "23.1.0-
|
|
42
|
-
"@vaadin/vaadin-themable-mixin": "23.1.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"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@esm-bundle/chai": "^4.3.4",
|
|
46
46
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
47
|
-
"sinon": "^
|
|
47
|
+
"sinon": "^13.0.2"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
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
|
-
|
|
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('
|
|
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
|
-
*
|
|
96
|
+
* Override method from `InputMixin`.
|
|
97
|
+
*
|
|
95
98
|
* @protected
|
|
99
|
+
* @override
|
|
96
100
|
*/
|
|
97
|
-
|
|
98
|
-
super.
|
|
101
|
+
clear() {
|
|
102
|
+
super.clear();
|
|
99
103
|
|
|
100
|
-
if (this.
|
|
101
|
-
this.
|
|
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
|
}
|
|
@@ -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
|
-
|
|
24
|
-
|
|
29
|
+
#chips {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
:host([has-value])
|
|
28
|
-
|
|
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
|
|
40
|
+
flex: 1 0 var(--input-min-width);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
[part
|
|
43
|
+
[part='chip'] {
|
|
37
44
|
flex: 0 1 auto;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
:host([readonly]
|
|
41
|
-
|
|
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.
|
|
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() ===
|
|
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.
|
|
505
|
-
chip.
|
|
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
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
});
|