@vaadin/multi-select-combo-box
Advanced tools
Comparing version 23.1.0-alpha4 to 23.1.0-beta1
{ | ||
"name": "@vaadin/multi-select-combo-box", | ||
"version": "23.1.0-alpha4", | ||
"version": "23.1.0-beta1", | ||
"publishConfig": { | ||
@@ -36,9 +36,9 @@ "access": "public" | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/combo-box": "23.1.0-alpha4", | ||
"@vaadin/component-base": "23.1.0-alpha4", | ||
"@vaadin/field-base": "23.1.0-alpha4", | ||
"@vaadin/input-container": "23.1.0-alpha4", | ||
"@vaadin/vaadin-lumo-styles": "23.1.0-alpha4", | ||
"@vaadin/vaadin-material-styles": "23.1.0-alpha4", | ||
"@vaadin/vaadin-themable-mixin": "23.1.0-alpha4" | ||
"@vaadin/combo-box": "23.1.0-beta1", | ||
"@vaadin/component-base": "23.1.0-beta1", | ||
"@vaadin/field-base": "23.1.0-beta1", | ||
"@vaadin/input-container": "23.1.0-beta1", | ||
"@vaadin/vaadin-lumo-styles": "23.1.0-beta1", | ||
"@vaadin/vaadin-material-styles": "23.1.0-beta1", | ||
"@vaadin/vaadin-themable-mixin": "23.1.0-beta1" | ||
}, | ||
@@ -50,3 +50,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "aacdb7fe09811894751f0378ff7fb66071892c71" | ||
"gitHead": "8be43cf83102a6b9ccf309687446e590ce0164e8" | ||
} |
@@ -33,2 +33,12 @@ /** | ||
return { | ||
disabled: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
}, | ||
readonly: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
}, | ||
label: { | ||
@@ -53,3 +63,2 @@ type: String, | ||
box-sizing: border-box; | ||
min-width: 0; | ||
} | ||
@@ -62,3 +71,3 @@ | ||
:host([part~='overflow']) [part='remove-button'] { | ||
:host(:is([readonly], [disabled], [part~='overflow'])) [part='remove-button'] { | ||
display: none !important; | ||
@@ -65,0 +74,0 @@ } |
@@ -12,6 +12,5 @@ /** | ||
css` | ||
.wrapper { | ||
#wrapper { | ||
display: flex; | ||
width: 100%; | ||
min-width: 0; | ||
} | ||
@@ -44,3 +43,3 @@ `, | ||
const wrapper = document.createElement('div'); | ||
wrapper.setAttribute('class', 'wrapper'); | ||
wrapper.setAttribute('id', 'wrapper'); | ||
content.insertBefore(wrapper, slots[2]); | ||
@@ -47,0 +46,0 @@ |
@@ -20,3 +20,16 @@ /** | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.setAttribute('aria-multiselectable', 'true'); | ||
} | ||
/** @private */ | ||
__getAriaSelected(_focusedIndex, itemIndex) { | ||
const item = this.items[itemIndex]; | ||
return this.__isItemSelected(item, null, this.itemIdPath).toString(); | ||
} | ||
/** @private */ | ||
__isItemSelected(item, _selectedItem, itemIdPath) { | ||
@@ -23,0 +36,0 @@ if (item instanceof ComboBoxPlaceholder) { |
@@ -23,2 +23,9 @@ /** | ||
export interface MultiSelectComboBoxI18n { | ||
cleared: string; | ||
selected: string; | ||
deselected: string; | ||
total: string; | ||
} | ||
/** | ||
@@ -83,2 +90,3 @@ * Fired when the user commits a value change. | ||
* -----------------------|---------------- | ||
* `chips` | The element that wraps chips for selected items | ||
* `chip` | Chip shown for every selected item | ||
@@ -117,3 +125,2 @@ * `label` | The label element | ||
* `--vaadin-multi-select-combo-box-overlay-max-height` | Max height of the overlay | `65vh` | ||
* `--vaadin-multi-select-combo-box-chip-min-width` | Min width of the chip | `60px` | ||
* `--vaadin-multi-select-combo-box-input-min-width` | Min width of the input | `4em` | ||
@@ -206,2 +213,24 @@ * | ||
/** | ||
* The object used to localize this component. | ||
* To change the default localization, replace the entire | ||
* _i18n_ object or just the property you want to modify. | ||
* | ||
* The object has the following JSON structure and default values: | ||
* ``` | ||
* { | ||
* // Screen reader announcement on clear button click. | ||
* cleared: 'Selection cleared', | ||
* // Screen reader announcement when item is selected. | ||
* selected: 'added to selection', | ||
* // Screen reader announcement when item is deselected. | ||
* deselected: 'removed from selection', | ||
* // Screen reader announcement of the selected items count. | ||
* // {count} is replaced with the actual count of items. | ||
* total: '{count} items selected', | ||
* } | ||
* ``` | ||
*/ | ||
i18n: MultiSelectComboBoxI18n; | ||
/** | ||
* True if the dropdown is open, false otherwise. | ||
@@ -208,0 +237,0 @@ */ |
@@ -10,2 +10,3 @@ /** | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { announce } from '@vaadin/component-base/src/a11y-announcer.js'; | ||
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; | ||
@@ -22,3 +23,2 @@ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js'; | ||
:host { | ||
--chip-min-width: var(--vaadin-multi-select-combo-box-chip-min-width, 4em); | ||
--input-min-width: var(--vaadin-multi-select-combo-box-input-min-width, 4em); | ||
@@ -31,2 +31,7 @@ } | ||
#chips { | ||
display: flex; | ||
align-items: center; | ||
} | ||
:host([has-value]) ::slotted(input:placeholder-shown) { | ||
@@ -43,7 +48,8 @@ color: transparent !important; | ||
flex: 0 1 auto; | ||
min-width: var(--chip-min-width); | ||
} | ||
:host([readonly]) [part~='chip'] { | ||
pointer-events: none; | ||
:host(:is([readonly], [disabled])) ::slotted(input) { | ||
flex-grow: 0; | ||
flex-basis: 0; | ||
padding: 0; | ||
} | ||
@@ -76,2 +82,3 @@ `; | ||
* -----------------------|---------------- | ||
* `chips` | The element that wraps chips for selected items | ||
* `chip` | Chip shown for every selected item | ||
@@ -110,3 +117,2 @@ * `label` | The label element | ||
* `--vaadin-multi-select-combo-box-overlay-max-height` | Max height of the overlay | `65vh` | ||
* `--vaadin-multi-select-combo-box-chip-min-width` | Min width of the chip | `60px` | ||
* `--vaadin-multi-select-combo-box-input-min-width` | Min width of the input | `4em` | ||
@@ -181,5 +187,7 @@ * | ||
<vaadin-multi-select-combo-box-chip | ||
id="overflow" | ||
slot="prefix" | ||
part$="[[_getOverflowPart(_overflowItems.length)]]" | ||
disabled="[[disabled]]" | ||
readonly="[[readonly]]" | ||
label="[[_getOverflowLabel(_overflowItems.length)]]" | ||
@@ -190,2 +198,3 @@ title$="[[_getOverflowTitle(_overflowItems)]]" | ||
></vaadin-multi-select-combo-box-chip> | ||
<div id="chips" part="chips" slot="prefix"></div> | ||
<slot name="input"></slot> | ||
@@ -261,2 +270,36 @@ <div id="clearButton" part="clear-button" slot="suffix"></div> | ||
/** | ||
* The object used to localize this component. | ||
* To change the default localization, replace the entire | ||
* _i18n_ object or just the property you want to modify. | ||
* | ||
* The object has the following JSON structure and default values: | ||
* ``` | ||
* { | ||
* // Screen reader announcement on clear button click. | ||
* cleared: 'Selection cleared', | ||
* // Screen reader announcement when item is selected. | ||
* selected: 'added to selection', | ||
* // Screen reader announcement when item is deselected. | ||
* deselected: 'removed from selection', | ||
* // Screen reader announcement of the selected items count. | ||
* // {count} is replaced with the actual count of items. | ||
* total: '{count} items selected', | ||
* } | ||
* ``` | ||
* @type {!MultiSelectComboBoxI18n} | ||
* @default {English/US} | ||
*/ | ||
i18n: { | ||
type: Object, | ||
value: () => { | ||
return { | ||
cleared: 'Selection cleared', | ||
selected: 'added to selection', | ||
deselected: 'removed from selection', | ||
total: '{count} items selected', | ||
}; | ||
}, | ||
}, | ||
/** | ||
* When present, it specifies that the field is read-only. | ||
@@ -413,3 +456,3 @@ */ | ||
checkValidity() { | ||
return this.required ? this._hasValue : true; | ||
return this.required && !this.readonly ? this._hasValue : true; | ||
} | ||
@@ -426,5 +469,3 @@ | ||
if (disabled || oldDisabled) { | ||
this._chips.forEach((chip) => { | ||
chip.toggleAttribute('disabled', disabled); | ||
}); | ||
this.__updateChips(); | ||
} | ||
@@ -479,2 +520,22 @@ } | ||
/** | ||
* Override method from `DelegateStateMixin` to set required state | ||
* using `aria-required` attribute instead of `required`, in order | ||
* to prevent screen readers from announcing "invalid entry". | ||
* @protected | ||
* @override | ||
*/ | ||
_delegateAttribute(name, value) { | ||
if (!this.stateTarget) { | ||
return; | ||
} | ||
if (name === 'required') { | ||
this._delegateAttribute('aria-required', value ? 'true' : false); | ||
return; | ||
} | ||
super._delegateAttribute(name, value); | ||
} | ||
/** | ||
* Setting clear button visible reduces total space available | ||
@@ -495,14 +556,7 @@ * for rendering chips, and making it hidden increases it. | ||
this.$.comboBox._setOverlayItems(Array.from(this.selectedItems)); | ||
// Update chips to hide remove button | ||
this._chips.forEach((chip) => { | ||
chip.setAttribute('readonly', ''); | ||
}); | ||
this.__updateChips(); | ||
} else if (oldReadonly) { | ||
this.$.comboBox._setOverlayItems(this.__savedItems); | ||
this.__savedItems = null; | ||
this._chips.forEach((chip) => { | ||
chip.removeAttribute('readonly'); | ||
}); | ||
this.__updateChips(); | ||
} | ||
@@ -527,2 +581,10 @@ } | ||
// Use placeholder for announcing items | ||
if (this._hasValue) { | ||
this.__savedPlaceholder = this.placeholder; | ||
this.placeholder = selectedItems.map((item) => this._getItemLabel(item, this.itemLabelPath)).join(', '); | ||
} else { | ||
this.placeholder = this.__savedPlaceholder; | ||
} | ||
// Re-render chips | ||
@@ -598,2 +660,9 @@ this.__updateChips(); | ||
/** @private */ | ||
__announceItem(itemLabel, isSelected, itemCount) { | ||
const state = isSelected ? 'selected' : 'deselected'; | ||
const total = this.i18n.total.replace('{count}', itemCount || 0); | ||
announce(`${itemLabel} ${this.i18n[state]} ${total}`); | ||
} | ||
/** @private */ | ||
__removeItem(item) { | ||
@@ -603,2 +672,3 @@ const itemsCopy = [...this.selectedItems]; | ||
this.__updateSelection(itemsCopy); | ||
this.__announceItem(item, false, itemsCopy.length); | ||
} | ||
@@ -611,5 +681,9 @@ | ||
const index = this._findIndex(item, itemsCopy, this.itemIdPath); | ||
const itemLabel = this._getItemLabel(item, this.itemLabelPath); | ||
let isSelected = false; | ||
if (index !== -1) { | ||
// Do not unselect when manually typing and committing an already selected item. | ||
if (this.filter.toLowerCase() === this._getItemLabel(item, this.itemLabelPath).toLowerCase()) { | ||
if (this.filter.toLowerCase() === itemLabel.toLowerCase()) { | ||
this.__clearFilter(); | ||
@@ -622,2 +696,3 @@ return; | ||
itemsCopy.push(item); | ||
isSelected = true; | ||
} | ||
@@ -629,2 +704,4 @@ | ||
this.__clearFilter(); | ||
this.__announceItem(itemLabel, isSelected, itemsCopy.length); | ||
} | ||
@@ -648,4 +725,4 @@ | ||
chip.item = item; | ||
chip.toggleAttribute('disabled', this.disabled); | ||
chip.toggleAttribute('readonly', this.readonly); | ||
chip.disabled = this.disabled; | ||
chip.readonly = this.readonly; | ||
@@ -663,14 +740,17 @@ const label = this._getItemLabel(item, this.itemLabelPath); | ||
/** @private */ | ||
__getMinWidth(chip) { | ||
__getOverflowWidth() { | ||
const chip = this.$.overflow; | ||
chip.style.visibility = 'hidden'; | ||
chip.style.display = 'block'; | ||
chip.style.minWidth = 'var(--chip-min-width)'; | ||
chip.removeAttribute('hidden'); | ||
const result = parseInt(getComputedStyle(chip).minWidth); | ||
// Detect max possible width of the overflow chip | ||
chip.setAttribute('part', 'chip overflow'); | ||
const overflowStyle = getComputedStyle(chip); | ||
const overflowWidth = chip.clientWidth + parseInt(overflowStyle.marginInlineStart); | ||
chip.style.minWidth = ''; | ||
chip.style.display = ''; | ||
chip.setAttribute('hidden', ''); | ||
chip.style.visibility = ''; | ||
return result; | ||
return overflowWidth; | ||
} | ||
@@ -685,7 +765,6 @@ | ||
// Clear all chips except the overflow | ||
const chips = Array.from(this._chips).reverse(); | ||
const overflow = chips.pop(); | ||
chips.forEach((chip) => { | ||
chip.remove(); | ||
Array.from(this._chips).forEach((chip) => { | ||
if (chip !== this.$.overflow) { | ||
chip.remove(); | ||
} | ||
}); | ||
@@ -695,33 +774,24 @@ | ||
let refNode = overflow.nextElementSibling; | ||
// Detect available remaining width for chips | ||
const totalWidth = this._inputField.$.wrapper.clientWidth; | ||
const inputWidth = parseInt(getComputedStyle(this.inputElement).flexBasis); | ||
// Use overflow chip to measure min-width | ||
const chipMinWidth = this.__getMinWidth(overflow); | ||
const inputMinWidth = parseInt(getComputedStyle(this.inputElement).flexBasis); | ||
const containerStyle = getComputedStyle(this._inputField); | ||
let remainingWidth = totalWidth - inputWidth; | ||
// Detect available width for chips | ||
let totalWidth = | ||
parseInt(containerStyle.width) - | ||
parseInt(containerStyle.paddingLeft) - | ||
parseInt(containerStyle.paddingRight) - | ||
this.$.toggleButton.clientWidth - | ||
inputMinWidth; | ||
if (this.clearButtonVisible) { | ||
totalWidth -= this.$.clearButton.clientWidth; | ||
if (items.length > 1) { | ||
remainingWidth -= this.__getOverflowWidth(); | ||
} | ||
for (let i = items.length - 1; i >= 0; i--) { | ||
// Ensure there is enough space for another chip | ||
if (totalWidth < chipMinWidth) { | ||
// Add chips until remaining width is exceeded | ||
for (let i = items.length - 1, refNode = null; i >= 0; i--) { | ||
const chip = this.__createChip(items[i]); | ||
this.$.chips.insertBefore(chip, refNode); | ||
if (this.$.chips.clientWidth > remainingWidth) { | ||
chip.remove(); | ||
break; | ||
} | ||
const item = items.pop(); | ||
const chip = this.__createChip(item); | ||
this._inputField.insertBefore(chip, refNode); | ||
items.pop(); | ||
refNode = chip; | ||
totalWidth -= chipMinWidth; | ||
} | ||
@@ -741,2 +811,4 @@ | ||
this.__updateSelection([]); | ||
announce(this.i18n.cleared); | ||
} | ||
@@ -743,0 +815,0 @@ |
@@ -82,2 +82,3 @@ /** | ||
margin-bottom: -0.3125em; | ||
margin-inline-start: auto; | ||
width: 1.25em; | ||
@@ -97,7 +98,2 @@ height: 1.25em; | ||
} | ||
:host([readonly]) [part='remove-button'], | ||
:host([disabled]) [part='remove-button'] { | ||
display: none; | ||
} | ||
`; | ||
@@ -104,0 +100,0 @@ |
@@ -33,6 +33,2 @@ /** | ||
:host([readonly]) [part~='chip'] { | ||
opacity: 0.7; | ||
} | ||
[part~='chip']:not(:last-of-type) { | ||
@@ -42,2 +38,6 @@ margin-inline-end: var(--lumo-space-xs); | ||
[part~='overflow']:not([hidden]) + :not(:empty) { | ||
margin-inline-start: var(--lumo-space-xs); | ||
} | ||
[part='toggle-button']::before { | ||
@@ -44,0 +44,0 @@ content: var(--lumo-icons-dropdown); |
@@ -82,2 +82,3 @@ /** | ||
height: 20px; | ||
margin-inline-start: auto; | ||
line-height: 20px; | ||
@@ -97,7 +98,2 @@ padding: 0; | ||
} | ||
:host([readonly]) [part='remove-button'], | ||
:host([disabled]) [part='remove-button'] { | ||
display: none; | ||
} | ||
`; | ||
@@ -104,0 +100,0 @@ |
@@ -28,6 +28,2 @@ /** | ||
const multiSelectComboBox = css` | ||
:host([readonly]) [part~='chip'] { | ||
opacity: 0.5; | ||
} | ||
[part='input-field'] { | ||
@@ -34,0 +30,0 @@ height: auto; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72768
1751
+ Added@vaadin/combo-box@23.1.0-beta1(transitive)
+ Added@vaadin/component-base@23.1.0-beta1(transitive)
+ Added@vaadin/field-base@23.1.0-beta1(transitive)
+ Added@vaadin/icon@23.1.0-beta1(transitive)
+ Added@vaadin/input-container@23.1.0-beta1(transitive)
+ Added@vaadin/item@23.1.0-beta1(transitive)
+ Added@vaadin/vaadin-lumo-styles@23.1.0-beta1(transitive)
+ Added@vaadin/vaadin-material-styles@23.1.0-beta1(transitive)
+ Added@vaadin/vaadin-overlay@23.1.0-beta1(transitive)
+ Added@vaadin/vaadin-themable-mixin@23.1.0-beta1(transitive)
- Removed@vaadin/combo-box@23.1.0-alpha4(transitive)
- Removed@vaadin/component-base@23.1.0-alpha4(transitive)
- Removed@vaadin/field-base@23.1.0-alpha4(transitive)
- Removed@vaadin/icon@23.1.0-alpha4(transitive)
- Removed@vaadin/input-container@23.1.0-alpha4(transitive)
- Removed@vaadin/item@23.1.0-alpha4(transitive)
- Removed@vaadin/vaadin-lumo-styles@23.1.0-alpha4(transitive)
- Removed@vaadin/vaadin-material-styles@23.1.0-alpha4(transitive)
- Removed@vaadin/vaadin-overlay@23.1.0-alpha4(transitive)
- Removed@vaadin/vaadin-themable-mixin@23.1.0-alpha4(transitive)