Skip to content

Commit

Permalink
feat: add auto-expand properties instead of allChipsVisible (#6775)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Nov 13, 2023
1 parent ab1f77a commit 791894f
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 54 deletions.
3 changes: 2 additions & 1 deletion dev/multi-select-combo-box.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
required
error-message="Select at least one"
allow-custom-value
style="width: 300px"
auto-expand-horizontally
auto-expand-vertically
>
<vaadin-tooltip slot="tooltip" text="Vaadin multi-select-combo-box tooltip text"></vaadin-tooltip>
</vaadin-multi-select-combo-box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ registerStyles(
width: 100%;
}
:host([all-chips-visible]) #wrapper {
:host([auto-expand-vertically]) #wrapper {
flex-wrap: wrap;
}
`,
Expand Down Expand Up @@ -57,11 +57,12 @@ class MultiSelectComboBoxContainer extends InputContainer {
static get properties() {
return {
/**
* Set true to not collapse selected items chips into the overflow
* chip and instead always show them, causing input field to grow.
* @attr {boolean} all-chips-visible
* Set to true to not collapse selected items chips into the overflow
* chip and instead always expand vertically, causing input field to
* wrap into multiple lines when width is limited.
* @attr {boolean} auto-expand-vertically
*/
allChipsVisible: {
autoExpandVertically: {
type: Boolean,
reflectToAttribute: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,20 @@ export interface MultiSelectComboBoxEventMap<TItem> extends HTMLElementEventMap
* @fires {CustomEvent} validated - Fired whenever the field is validated.
*/
declare class MultiSelectComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
/**
* Set to true to auto expand horizontally, causing input field to
* grow until max width is reached.
* @attr {boolean} auto-expand-horizontally
*/
autoExpandHorizontally: boolean;

/**
* Set to true to not collapse selected items chips into the overflow
* chip and instead always show them all, causing input field to grow
* and wrap into multiple lines when width is limited.
* @attr {boolean} all-chips-visible
* chip and instead always expand vertically, causing input field to
* wrap into multiple lines when width is limited.
* @attr {boolean} auto-expand-vertically
*/
allChipsVisible: boolean;
autoExpandVertically: boolean;

/**
* When true, the user can input a value that is not present in the items list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ const multiSelectComboBox = css`
padding: 0;
}
:host([all-chips-visible]) #chips {
:host([auto-expand-vertically]) #chips {
display: contents;
}
:host([all-chips-visible]) [class$='container'] {
width: fit-content;
:host([auto-expand-horizontally]) [class$='container'] {
width: auto;
}
`;

Expand Down Expand Up @@ -187,7 +187,7 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
>
<vaadin-multi-select-combo-box-container
part="input-field"
all-chips-visible="[[allChipsVisible]]"
auto-expand-vertically="[[autoExpandVertically]]"
readonly="[[readonly]]"
disabled="[[disabled]]"
invalid="[[invalid]]"
Expand Down Expand Up @@ -224,17 +224,29 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El

static get properties() {
return {
/**
* Set to true to auto expand horizontally, causing input field to
* grow until max width is reached.
* @attr {boolean} auto-expand-horizontally
*/
autoExpandHorizontally: {
type: Boolean,
value: false,
reflectToAttribute: true,
observer: '_autoExpandHorizontallyChanged',
},

/**
* Set to true to not collapse selected items chips into the overflow
* chip and instead always show them all, causing input field to grow
* and wrap into multiple lines when width is limited.
* @attr {boolean} all-chips-visible
* chip and instead always expand vertically, causing input field to
* wrap into multiple lines when width is limited.
* @attr {boolean} auto-expand-vertically
*/
allChipsVisible: {
autoExpandVertically: {
type: Boolean,
value: false,
reflectToAttribute: true,
observer: '_allChipsVisibleChanged',
observer: '_autoExpandVerticallyChanged',
},

/**
Expand Down Expand Up @@ -699,8 +711,15 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El
}

/** @private */
_allChipsVisibleChanged(visible, oldVisible) {
if (visible || oldVisible) {
_autoExpandHorizontallyChanged(autoExpand, oldAutoExpand) {
if (autoExpand || oldAutoExpand) {
this.__updateChips();
}
}

/** @private */
_autoExpandVerticallyChanged(autoExpand, oldAutoExpand) {
if (autoExpand || oldAutoExpand) {
this.__updateChips();
}
}
Expand Down Expand Up @@ -950,13 +969,51 @@ class MultiSelectComboBox extends ResizeMixin(InputControlMixin(ThemableMixin(El

const chipMinWidth = parseInt(getComputedStyle(this).getPropertyValue('--_chip-min-width'));

if (this.autoExpandHorizontally) {
const chips = [];

// First, add all chips to make the field fully expand
for (let i = items.length - 1, refNode = null; i >= 0; i--) {
const chip = this.__createChip(items[i]);
this.insertBefore(chip, refNode);
refNode = chip;
chips.unshift(chip);
}

const overflowItems = [];
const availableWidth = this._inputField.$.wrapper.clientWidth - this.$.chips.clientWidth;

// When auto expanding vertically, no need to measure width
if (!this.autoExpandVertically && availableWidth < inputWidth) {
// Always show at least last item as a chip
while (chips.length > 1) {
const lastChip = chips.pop();
lastChip.remove();
overflowItems.unshift(items.pop());

// Remove chips until there is enough width for the input element to fit
const neededWidth = overflowItems.length > 0 ? inputWidth + this.__getOverflowWidth() : inputWidth;
if (this._inputField.$.wrapper.clientWidth - this.$.chips.clientWidth >= neededWidth) {
break;
}
}

if (chips.length === 1) {
chips[0].style.maxWidth = `${Math.max(chipMinWidth, remainingWidth)}px`;
}
}

this._overflowItems = overflowItems;
return;
}

// 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.insertBefore(chip, refNode);

// If all the chips are visible, no need to measure remaining width
if (!this.allChipsVisible && this.$.chips.clientWidth > remainingWidth) {
// When auto expanding vertically, no need to measure remaining width
if (!this.autoExpandVertically && this.$.chips.clientWidth > remainingWidth) {
// Always show at least last selected item as a chip
if (refNode === null) {
chip.style.maxWidth = `${Math.max(chipMinWidth, remainingWidth)}px`;
Expand Down
72 changes: 60 additions & 12 deletions packages/multi-select-combo-box/test/chips.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ describe('chips', () => {
});
});

describe('allChipsVisible', () => {
describe('autoExpandVertically', () => {
let overflow;

beforeEach(async () => {
Expand All @@ -446,32 +446,32 @@ describe('chips', () => {
overflow = getChips(comboBox)[0];
});

it('should not show overflow chip when allChipsVisible is set to true', async () => {
comboBox.allChipsVisible = true;
it('should not show overflow chip when autoExpandVertically is set to true', async () => {
comboBox.autoExpandVertically = true;
comboBox.selectedItems = ['apple', 'banana'];
await nextRender();
expect(getChips(comboBox).length).to.equal(3);
expect(overflow.hasAttribute('hidden')).to.be.true;
});

it('should show overflow chip when allChipsVisible is set to false', async () => {
comboBox.allChipsVisible = true;
it('should show overflow chip when autoExpandVertically is set to false', async () => {
comboBox.autoExpandVertically = true;
comboBox.selectedItems = ['apple', 'banana'];
await nextRender();

comboBox.allChipsVisible = false;
comboBox.autoExpandVertically = false;
await nextRender();
expect(getChips(comboBox).length).to.equal(2);
expect(overflow.hasAttribute('hidden')).to.be.false;
});

it('should update chips when allChipsVisible is set after selectedItems', async () => {
it('should update chips when autoExpandVertically is set after selectedItems', async () => {
comboBox.selectedItems = ['apple', 'banana'];
await nextRender();
expect(getChips(comboBox).length).to.equal(2);
expect(overflow.hasAttribute('hidden')).to.be.false;

comboBox.allChipsVisible = true;
comboBox.autoExpandVertically = true;
await nextRender();
expect(getChips(comboBox).length).to.equal(3);
expect(overflow.hasAttribute('hidden')).to.be.true;
Expand All @@ -480,18 +480,66 @@ describe('chips', () => {
it('should wrap chips and increase input field height if chips do not fit', async () => {
const inputField = comboBox.shadowRoot.querySelector('[part="input-field"]');
const height = inputField.clientHeight;
comboBox.allChipsVisible = true;
comboBox.autoExpandVertically = true;
comboBox.selectedItems = ['apple', 'banana', 'lemon', 'orange'];
await nextRender();
expect(inputField.clientHeight).to.be.greaterThan(height);
});
});

it('should adapt overlay width to the input field width while opened', async () => {
comboBox.allChipsVisible = true;
comboBox.style.width = 'auto';
describe('autoExpandHorizontally', () => {
let overflow;

beforeEach(async () => {
comboBox.autoExpandHorizontally = true;
await nextResize(comboBox);
comboBox.selectedItems = ['apple', 'banana', 'lemon', 'orange'];
overflow = getChips(comboBox)[0];
await nextRender();
});

it('should show all chips when there is enough space by default', () => {
expect(getChips(comboBox).length).to.equal(5);
expect(overflow.hasAttribute('hidden')).to.be.true;
});

it('should collapse chips to overflow when max-width is set on the host', async () => {
comboBox.style.maxWidth = '300px';
await nextResize(comboBox);
expect(getChips(comboBox).length).to.equal(3);
expect(overflow.hasAttribute('hidden')).to.be.false;
});

it('should collapse chips to overflow when width is set on the host', async () => {
comboBox.style.width = '300px';
await nextResize(comboBox);
expect(getChips(comboBox).length).to.equal(3);
expect(overflow.hasAttribute('hidden')).to.be.false;
});

it('should collapse chips to overflow when max-width is set on the parent', async () => {
comboBox.parentElement.style.maxWidth = '300px';
await nextResize(comboBox);
expect(getChips(comboBox).length).to.equal(3);
expect(overflow.hasAttribute('hidden')).to.be.false;
});

it('should set max-width on the chip when the host width does not allow to fit', async () => {
comboBox.style.maxWidth = '180px';
await nextResize(comboBox);
const chips = getChips(comboBox);
expect(chips.length).to.equal(2);
expect(getComputedStyle(chips[1]).maxWidth).to.be.ok;
});

it('should collapse chips when autoExpandHorizontally is set to false', async () => {
comboBox.autoExpandHorizontally = false;
await nextRender();
expect(getChips(comboBox).length).to.equal(2);
expect(overflow.hasAttribute('hidden')).to.be.false;
});

it('should adapt overlay width to the input field width while opened', async () => {
comboBox.opened = true;

const overlay = document.querySelector('vaadin-multi-select-combo-box-overlay');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ assertType<string | null | undefined>(narrowedComboBox.label);
assertType<boolean>(narrowedComboBox.required);
assertType<string>(narrowedComboBox.overlayClass);
assertType<boolean>(narrowedComboBox.selectedItemsOnTop);
assertType<boolean>(narrowedComboBox.allChipsVisible);
assertType<boolean>(narrowedComboBox.autoExpandVertically);

// Mixins
assertType<ControllerMixinClass>(narrowedComboBox);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,30 +120,31 @@ describe('multi-select-combo-box', () => {
});
});

describe('all chips visible', () => {
describe('auto expand', () => {
beforeEach(() => {
element.selectedItems = [...element.items];
element.allChipsVisible = true;
element.autoExpandHorizontally = true;
element.autoExpandVertically = true;
});

it('all chips visible', async () => {
await visualDiff(div, 'all-chips-visible');
it('auto expand', async () => {
await visualDiff(div, 'auto-expand');
});

it('all chips visible max width', async () => {
it('auto expand max width', async () => {
element.style.maxWidth = '250px';
await visualDiff(div, 'all-chips-visible-max-width');
await visualDiff(div, 'auto-expand-max-width');
});

it('all chips visible height', async () => {
it('auto expand visible height', async () => {
element.label = 'Label';
element.style.width = '300px';
element.style.height = '200px';
div.style.height = '240px';
const items = Array.from({ length: 20 }).map((_, i) => `Item ${i}`);
element.items = items;
element.selectedItems = items;
await visualDiff(div, 'all-chips-visible-height');
await visualDiff(div, 'auto-expand-height');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,20 @@ describe('multi-select-combo-box', () => {
});
});

describe('all chips visible', () => {
describe('auto expand', () => {
beforeEach(() => {
element.selectedItems = [...element.items];
element.allChipsVisible = true;
element.autoExpandHorizontally = true;
element.autoExpandVertically = true;
});

it('all chips visible', async () => {
await visualDiff(div, 'all-chips-visible');
it('auto expand', async () => {
await visualDiff(div, 'auto-expand');
});

it('all chips visible max width', async () => {
it('auto expand max width', async () => {
element.style.maxWidth = '250px';
await visualDiff(div, 'all-chips-visible-max-width');
await visualDiff(div, 'auto-expand-max-width');
});
});

Expand Down
Loading

0 comments on commit 791894f

Please sign in to comment.