diff --git a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box-chip.js b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box-chip.js index eb060bcc6aa..39f0eb18bdb 100644 --- a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box-chip.js +++ b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box-chip.js @@ -66,7 +66,8 @@ class MultiSelectComboBoxChip extends ThemableMixin(PolymerElement) { text-overflow: ellipsis; } - :host(:is([readonly], [disabled], [part~='overflow'])) [part='remove-button'] { + :host([hidden]), + :host(:is([readonly], [disabled], [slot='overflow'])) [part='remove-button'] { display: none !important; } diff --git a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.d.ts b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.d.ts index 51d3b3e52c4..48721058eed 100644 --- a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.d.ts +++ b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.d.ts @@ -106,17 +106,13 @@ export interface MultiSelectComboBoxEventMap extends HTMLElementEventMap * * Part name | Description * -----------------------|---------------- - * `chips` | The element that wraps chips for selected items - * `chip` | Chip shown for every selected item + * `chips` | The element that wraps slotted chips for selected items * `label` | The label element * `input-field` | The element that wraps prefix, value and suffix * `clear-button` | The clear button * `error-message` | The error message element * `helper-text` | The helper text element wrapper * `required-indicator` | The `required` state indicator element - * `overflow` | The chip shown when component width is not enough to fit all chips - * `overflow-one` | Set on the overflow chip when only one chip does not fit - * `overflow-two` | Set on the overflow chip when two chips do not fit * `toggle-button` | The toggle button * * The following state attributes are available for styling: diff --git a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.js b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.js index 3851f822163..e0471d8f62b 100644 --- a/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.js +++ b/packages/multi-select-combo-box/src/vaadin-multi-select-combo-box.js @@ -10,6 +10,7 @@ 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'; import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js'; +import { SlotController } from '@vaadin/component-base/src/slot-controller.js'; import { processTemplates } from '@vaadin/component-base/src/templates.js'; import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; import { InputControlMixin } from '@vaadin/field-base/src/input-control-mixin.js'; @@ -23,10 +24,6 @@ const multiSelectComboBox = css` --input-min-width: var(--vaadin-multi-select-combo-box-input-min-width, 4em); } - [hidden] { - display: none !important; - } - #chips { display: flex; align-items: center; @@ -37,7 +34,8 @@ const multiSelectComboBox = css` flex: 1 0 var(--input-min-width); } - [part='chip'] { + ::slotted([slot='chip']), + ::slotted([slot='overflow']) { flex: 0 1 auto; } @@ -72,17 +70,13 @@ registerStyles('vaadin-multi-select-combo-box', [inputFieldShared, multiSelectCo * * Part name | Description * -----------------------|---------------- - * `chips` | The element that wraps chips for selected items - * `chip` | Chip shown for every selected item + * `chips` | The element that wraps slotted chips for selected items * `label` | The label element * `input-field` | The element that wraps prefix, value and suffix * `clear-button` | The clear button * `error-message` | The error message element * `helper-text` | The helper text element wrapper * `required-indicator` | The `required` state indicator element - * `overflow` | The chip shown when component width is not enough to fit all chips - * `overflow-one` | Set on the overflow chip when only one chip does not fit - * `overflow-two` | Set on the overflow chip when two chips do not fit * `toggle-button` | The toggle button * * The following state attributes are available for styling: @@ -182,18 +176,10 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El invalid="[[invalid]]" theme$="[[_theme]]" > - -
+ +
+ +
!target.opened); this._inputField = this.shadowRoot.querySelector('[part="input-field"]'); + + this._overflowController = new SlotController( + this, + 'overflow', + () => document.createElement('vaadin-multi-select-combo-box-chip'), + (_, chip) => { + chip.addEventListener('mousedown', (e) => this._preventBlur(e)); + this._overflow = chip; + }, + ); + this.addController(this._overflowController); + this.__updateChips(); processTemplates(this); @@ -716,34 +717,6 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El return this.$.comboBox._getItemLabel(item); } - /** @private */ - _getOverflowLabel(length) { - return length; - } - - /** @private */ - _getOverflowPart(length) { - let part = `chip overflow`; - - if (length === 1) { - part += ' overflow-one'; - } else if (length === 2) { - part += ' overflow-two'; - } - - return part; - } - - /** @private */ - _getOverflowTitle(items) { - return this._mergeItemLabels(items); - } - - /** @private */ - _isOverflowHidden(length) { - return length === 0; - } - /** @private */ _mergeItemLabels(items) { return items.map((item) => this._getItemLabel(item)).join(', '); @@ -828,8 +801,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El /** @private */ __createChip(item) { const chip = document.createElement('vaadin-multi-select-combo-box-chip'); - chip.setAttribute('part', 'chip'); - chip.setAttribute('slot', 'prefix'); + chip.setAttribute('slot', 'chip'); chip.item = item; chip.disabled = this.disabled; @@ -847,16 +819,20 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El /** @private */ __getOverflowWidth() { - const chip = this.$.overflow; + const chip = this._overflow; chip.style.visibility = 'hidden'; chip.removeAttribute('hidden'); + const count = chip.getAttribute('count'); + // Detect max possible width of the overflow chip - chip.setAttribute('part', 'chip overflow'); + // by measuring it with widest number (2 digits) + chip.setAttribute('count', '99'); const overflowStyle = getComputedStyle(chip); const overflowWidth = chip.clientWidth + parseInt(overflowStyle.marginInlineStart); + chip.setAttribute('count', count); chip.setAttribute('hidden', ''); chip.style.visibility = ''; @@ -870,10 +846,8 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El } // Clear all chips except the overflow - Array.from(this._chips).forEach((chip) => { - if (chip !== this.$.overflow) { - chip.remove(); - } + this._chips.forEach((chip) => { + chip.remove(); }); const items = [...this.selectedItems]; @@ -891,7 +865,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El // 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); + this.insertBefore(chip, refNode); if (this.$.chips.clientWidth > remainingWidth) { chip.remove(); @@ -905,6 +879,21 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El this._overflowItems = items; } + /** @private */ + __updateOverflowChip(overflow, items, disabled, readonly) { + if (overflow) { + const count = items.length; + + overflow.label = `${count}`; + overflow.setAttribute('count', `${count}`); + overflow.setAttribute('title', this._mergeItemLabels(items)); + overflow.toggleAttribute('hidden', count === 0); + + overflow.disabled = disabled; + overflow.readonly = readonly; + } + } + /** @private */ _onClearButtonTouchend(event) { // Cancel the following click and focus events @@ -961,7 +950,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El _onKeyDown(event) { super._onKeyDown(event); - const chips = Array.from(this._chips).slice(1); + const chips = this._chips; if (!this.readonly && chips.length > 0) { switch (event.key) { @@ -1059,7 +1048,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El /** @private */ _focusedChipIndexChanged(focusedIndex, oldFocusedIndex) { if (focusedIndex > -1 || oldFocusedIndex > -1) { - const chips = Array.from(this._chips).slice(1); + const chips = this._chips; chips.forEach((chip, index) => { chip.toggleAttribute('focused', index === focusedIndex); }); diff --git a/packages/multi-select-combo-box/test/basic.test.js b/packages/multi-select-combo-box/test/basic.test.js index 4d09af1d26b..b4a63c698b6 100644 --- a/packages/multi-select-combo-box/test/basic.test.js +++ b/packages/multi-select-combo-box/test/basic.test.js @@ -226,7 +226,7 @@ describe('basic', () => { }); describe('chips', () => { - const getChips = (combo) => combo.shadowRoot.querySelectorAll('[part~="chip"]'); + const getChips = (combo) => combo.querySelectorAll('vaadin-multi-select-combo-box-chip'); const getChipContent = (chip) => chip.shadowRoot.querySelector('[part="label"]').textContent; @@ -366,28 +366,20 @@ describe('basic', () => { it('should set overflow chip label as not fitting chips count', async () => { comboBox.selectedItems = ['apple', 'banana', 'orange']; await nextRender(); - expect(overflow.label).to.equal(2); + expect(overflow.label).to.equal('2'); }); - it('should set overflow chip title as not fitting chips labels', async () => { + it('should set overflow chip count as not fitting chips count', async () => { comboBox.selectedItems = ['apple', 'banana', 'orange']; await nextRender(); - const title = overflow.getAttribute('title'); - expect(title).to.equal('apple, banana'); - }); - - it('should set overflow chip part if only one chip does not fit', async () => { - comboBox.selectedItems = ['apple', 'banana']; - await nextRender(); - const part = overflow.getAttribute('part'); - expect(part).to.contain('overflow-one'); + expect(overflow.getAttribute('count')).to.equal('2'); }); - it('should set overflow chip part if two chips do not fit', async () => { + it('should set overflow chip title as not fitting chips labels', async () => { comboBox.selectedItems = ['apple', 'banana', 'orange']; await nextRender(); - const part = overflow.getAttribute('part'); - expect(part).to.contain('overflow-two'); + const title = overflow.getAttribute('title'); + expect(title).to.equal('apple, banana'); }); describe('resize', () => { @@ -629,7 +621,7 @@ describe('basic', () => { }); it('should fire change when chip is removed', () => { - const chip = comboBox.shadowRoot.querySelector('[part="chip"]'); + const chip = comboBox.querySelector('[slot="chip"]'); chip.shadowRoot.querySelector('[part="remove-button"]').click(); expect(spy.calledOnce).to.be.true; }); diff --git a/packages/multi-select-combo-box/test/dom/__snapshots__/multi-select-combo-box.test.snap.js b/packages/multi-select-combo-box/test/dom/__snapshots__/multi-select-combo-box.test.snap.js index 3731d5a55c1..f2900994447 100644 --- a/packages/multi-select-combo-box/test/dom/__snapshots__/multi-select-combo-box.test.snap.js +++ b/packages/multi-select-combo-box/test/dom/__snapshots__/multi-select-combo-box.test.snap.js @@ -1,7 +1,43 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["vaadin-multi-select-combo-box default"] = +snapshots["vaadin-multi-select-combo-box host default"] = +` + + + + + +`; +/* end snapshot vaadin-multi-select-combo-box host default */ + +snapshots["vaadin-multi-select-combo-box shadow default"] = `
@@ -14,19 +50,18 @@ snapshots["vaadin-multi-select-combo-box default"] =
- +
+ +
@@ -59,5 +94,5 @@ snapshots["vaadin-multi-select-combo-box default"] = `; -/* end snapshot vaadin-multi-select-combo-box default */ +/* end snapshot vaadin-multi-select-combo-box shadow default */ diff --git a/packages/multi-select-combo-box/test/dom/multi-select-combo-box.test.js b/packages/multi-select-combo-box/test/dom/multi-select-combo-box.test.js index f3a13a64c3c..72991a8143e 100644 --- a/packages/multi-select-combo-box/test/dom/multi-select-combo-box.test.js +++ b/packages/multi-select-combo-box/test/dom/multi-select-combo-box.test.js @@ -9,7 +9,15 @@ describe('vaadin-multi-select-combo-box', () => { multiSelectComboBox = fixtureSync(''); }); - it('default', async () => { - await expect(multiSelectComboBox).shadowDom.to.equalSnapshot(); + describe('host', () => { + it('default', async () => { + await expect(multiSelectComboBox).dom.to.equalSnapshot(); + }); + }); + + describe('shadow', () => { + it('default', async () => { + await expect(multiSelectComboBox).shadowDom.to.equalSnapshot(); + }); }); }); diff --git a/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js b/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js index ebf855426b4..44740a5929f 100644 --- a/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js +++ b/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-chip-styles.js @@ -15,34 +15,26 @@ const chip = css` :host { font-size: var(--lumo-font-size-xxs); line-height: 1; - padding: 0.3125em calc(0.5em + var(--lumo-border-radius-s) / 4); color: var(--lumo-body-text-color); border-radius: var(--lumo-border-radius-s); background-color: var(--lumo-contrast-20pct); cursor: var(--lumo-clickable-cursor); - } - - :host([focused]) { - background-color: var(--lumo-primary-color); - color: var(--lumo-primary-contrast-color); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } :host([focused]) [part='remove-button'] { color: inherit; } - :host(:not([part~='overflow']):not([readonly]):not([disabled])) { - padding-inline-end: 0; - } - - :host([part~='overflow']) { + :host([slot='overflow']) { position: relative; min-width: var(--lumo-size-xxs); margin-inline-start: var(--lumo-space-s); } - :host([part~='overflow'])::before, - :host([part~='overflow'])::after { + :host([slot='overflow'])::before, + :host([slot='overflow'])::after { position: absolute; content: ''; width: 100%; @@ -52,28 +44,28 @@ const chip = css` border-color: var(--lumo-contrast-30pct); } - :host([part~='overflow'])::before { + :host([slot='overflow'])::before { left: calc(-1 * var(--lumo-space-s) / 2); } - :host([part~='overflow'])::after { + :host([slot='overflow'])::after { left: calc(-1 * var(--lumo-space-s)); } - :host([part~='overflow-two']) { + :host([count='2']) { margin-inline-start: calc(var(--lumo-space-s) / 2); } - :host([part~='overflow-two'])::after { + :host([count='2'])::after { display: none; } - :host([part~='overflow-one']) { + :host([count='1']) { margin-inline-start: 0; } - :host([part~='overflow-one'])::before, - :host([part~='overflow-one'])::after { + :host([count='1'])::before, + :host([count='1'])::after { display: none; } diff --git a/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-styles.js b/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-styles.js index ee28edc4095..4f767f3c501 100644 --- a/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-styles.js +++ b/packages/multi-select-combo-box/theme/lumo/vaadin-multi-select-combo-box-styles.js @@ -34,12 +34,28 @@ const multiSelectComboBox = css` caret-color: var(--lumo-body-text-color) !important; } - [part~='chip']:not(:last-of-type) { + /* Override input-container styles */ + ::slotted([slot='chip']), + ::slotted([slot='overflow']) { + min-height: auto; + padding: 0.3125em calc(0.5em + var(--lumo-border-radius-s) / 4); + color: var(--lumo-body-text-color); + -webkit-mask-image: none; + mask-image: none; + } + + ::slotted([slot='chip']:not([readonly]):not([disabled])) { + padding-inline-end: 0; + } + + ::slotted([slot='chip']:not(:last-of-type)), + ::slotted([slot='overflow']:not(:last-of-type)) { margin-inline-end: var(--lumo-space-xs); } - [part~='overflow']:not([hidden]) + :not(:empty) { - margin-inline-start: var(--lumo-space-xs); + ::slotted([slot='chip'][focused]) { + background-color: var(--lumo-primary-color); + color: var(--lumo-primary-contrast-color); } [part='toggle-button']::before { diff --git a/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-chip-styles.js b/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-chip-styles.js index 4d079d84294..8859478d7dd 100644 --- a/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-chip-styles.js +++ b/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-chip-styles.js @@ -13,28 +13,25 @@ const chip = css` :host { height: 1.25rem; margin-inline-end: 0.25rem; - padding: 0 0.5rem; border-radius: 4px; background-color: rgba(0, 0, 0, 0.08); cursor: default; font-family: var(--material-font-family); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } :host([focused]) { background-color: rgba(0, 0, 0, 0.16); } - :host(:not([part~='overflow']):not([readonly]):not([disabled])) { - padding-inline-end: 0; - } - - :host([part~='overflow']) { + :host([slot='overflow']) { position: relative; margin-inline-start: 0.5rem; } - :host([part~='overflow'])::before, - :host([part~='overflow'])::after { + :host([slot='overflow'])::before, + :host([slot='overflow'])::after { position: absolute; content: ''; width: 100%; @@ -44,28 +41,28 @@ const chip = css` border-color: rgba(0, 0, 0, 0.08); } - :host([part~='overflow'])::before { + :host([slot='overflow'])::before { left: -0.25rem; } - :host([part~='overflow'])::after { + :host([slot='overflow'])::after { left: -0.5rem; } - :host([part~='overflow-two']) { + :host([count='2']) { margin-inline-start: 0.25rem; } - :host([part~='overflow-two'])::after { + :host([count='2'])::after { display: none; } - :host([part~='overflow-one']) { + :host([count='1']) { margin-inline-start: 0; } - :host([part~='overflow-one'])::before, - :host([part~='overflow-one'])::after { + :host([count='1'])::before, + :host([count='1'])::after { display: none; } diff --git a/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-styles.js b/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-styles.js index 6b6d1521390..7898c2f8c06 100644 --- a/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-styles.js +++ b/packages/multi-select-combo-box/theme/material/vaadin-multi-select-combo-box-styles.js @@ -29,6 +29,16 @@ const multiSelectComboBox = css` caret-color: var(--material-body-text-color) !important; } + /* Override input-container styles */ + ::slotted([slot='chip']), + ::slotted([slot='overflow']) { + padding: 0 0.5rem; + } + + ::slotted([slot='chip']:not([readonly]):not([disabled])) { + padding-inline-end: 0; + } + [part='input-field'] { height: auto; min-height: 32px;