diff --git a/packages/avatar-group/src/vaadin-avatar-group-list-box.js b/packages/avatar-group/src/vaadin-avatar-group-list-box.js deleted file mode 100644 index 5a19e53925d..00000000000 --- a/packages/avatar-group/src/vaadin-avatar-group-list-box.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright (c) 2020 - 2022 Vaadin Ltd. - * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ - */ -import { ListBox } from '@vaadin/list-box/src/vaadin-list-box.js'; - -/** - * An element used internally by ``. Not intended to be used separately. - * - * @extends ListBox - * @private - */ -class AvatarGroupListBox extends ListBox { - static get is() { - return 'vaadin-avatar-group-list-box'; - } -} - -customElements.define(AvatarGroupListBox.is, AvatarGroupListBox); diff --git a/packages/avatar-group/src/vaadin-avatar-group.d.ts b/packages/avatar-group/src/vaadin-avatar-group.d.ts index 1c12933e19a..4e11a536609 100644 --- a/packages/avatar-group/src/vaadin-avatar-group.d.ts +++ b/packages/avatar-group/src/vaadin-avatar-group.d.ts @@ -60,7 +60,6 @@ export interface AvatarGroupItem { * In addition to `` itself, the following internal * components are themable: * - * - `` - has the same API as [``](#/elements/vaadin-list-box). * - `` - has the same API as [``](#/elements/vaadin-overlay). */ declare class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(HTMLElement))) { diff --git a/packages/avatar-group/src/vaadin-avatar-group.js b/packages/avatar-group/src/vaadin-avatar-group.js index 23702aebf21..6d7bacf6e51 100644 --- a/packages/avatar-group/src/vaadin-avatar-group.js +++ b/packages/avatar-group/src/vaadin-avatar-group.js @@ -6,7 +6,7 @@ import '@polymer/polymer/lib/elements/dom-repeat.js'; import '@vaadin/avatar/src/vaadin-avatar.js'; import '@vaadin/item/src/vaadin-item.js'; -import './vaadin-avatar-group-list-box.js'; +import '@vaadin/list-box/src/vaadin-list-box.js'; import './vaadin-avatar-group-overlay.js'; import { calculateSplices } from '@polymer/polymer/lib/utils/array-splice.js'; import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; @@ -52,7 +52,6 @@ const MINIMUM_DISPLAYED_AVATARS = 2; * In addition to `` itself, the following internal * components are themable: * - * - `` - has the same API as [``](#/elements/vaadin-list-box). * - `` - has the same API as [``](#/elements/vaadin-overlay). * * @extends HTMLElement @@ -140,28 +139,7 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) opened="{{_opened}}" no-vertical-overlap on-vaadin-overlay-close="_onVaadinOverlayClose" - > - - + > `; } @@ -266,6 +244,13 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) value: null, }, + /** @private */ + _overflowItems: { + type: Array, + observer: '__overflowItemsChanged', + computed: '__computeOverflowItems(items.*, __itemsInView, maxItemsVisible)', + }, + /** @private */ _opened: { type: Boolean, @@ -293,6 +278,8 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) this._overlayElement = this.shadowRoot.querySelector('vaadin-avatar-group-overlay'); this._overlayElement.positionTarget = this.$.overflow; + this.$.overlay.renderer = this.__overlayRenderer.bind(this); + afterNextRender(this, () => { this.__setItemsInView(); }); @@ -311,6 +298,61 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) return action.replace('{user}', user.name || user.abbr || this.i18n.anonymous); } + /** + * Renders items when they are provided by the `items` property and clears the content otherwise. + * @param {!HTMLElement} root + * @param {!Select} _select + * @private + */ + __overlayRenderer(root) { + let listBox = root.firstElementChild; + if (!listBox) { + listBox = document.createElement('vaadin-list-box'); + listBox.addEventListener('keydown', (event) => this._onListKeyDown(event)); + root.appendChild(listBox); + } + + listBox.textContent = ''; + + if (!this._overflowItems) { + return; + } + + this._overflowItems.forEach((item) => { + listBox.appendChild(this.__createItemElement(item)); + }); + } + + /** @private */ + __createItemElement(item) { + const itemElement = document.createElement('vaadin-item'); + itemElement.setAttribute('theme', 'avatar-group-item'); + itemElement.setAttribute('role', 'option'); + + const avatar = document.createElement('vaadin-avatar'); + itemElement.appendChild(avatar); + + avatar.setAttribute('aria-hidden', 'true'); + avatar.setAttribute('tabindex', '-1'); + avatar.i18n = this.i18n; + + if (this._theme) { + avatar.setAttribute('theme', this._theme); + } + + avatar.name = item.name; + avatar.abbr = item.abbr; + avatar.img = item.img; + avatar.colorIndex = item.colorIndex; + + if (item.name) { + const text = document.createTextNode(item.name); + itemElement.appendChild(text); + } + + return itemElement; + } + /** @private */ _onOverflowClick(e) { e.stopPropagation(); @@ -361,10 +403,10 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) } /** @private */ - __computeExtraItems(arr, itemsInView, maxItemsVisible) { + __computeOverflowItems(arr, itemsInView, maxItemsVisible) { const items = arr.base || []; const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible); - return limit ? items.slice(limit) : items; + return limit ? items.slice(limit) : []; } /** @private */ @@ -476,7 +518,7 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) __openedChanged(opened, wasOpened) { if (opened) { if (!this._menuElement) { - this._menuElement = this._overlayElement.content.querySelector('vaadin-avatar-group-list-box'); + this._menuElement = this._overlayElement.content.querySelector('vaadin-list-box'); this._menuElement.setAttribute('role', 'listbox'); } @@ -492,6 +534,13 @@ class AvatarGroup extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement) this.$.overflow.setAttribute('aria-expanded', opened === true); } + /** @private */ + __overflowItemsChanged(items, oldItems) { + if (items || oldItems) { + this.$.overlay.requestContentUpdate(); + } + } + /** @private */ __setItemsInView() { const avatars = this._avatars; diff --git a/packages/avatar-group/test/avatar-group.test.js b/packages/avatar-group/test/avatar-group.test.js index ca81cb2bc83..19a12a8b6af 100644 --- a/packages/avatar-group/test/avatar-group.test.js +++ b/packages/avatar-group/test/avatar-group.test.js @@ -73,7 +73,7 @@ describe('avatar-group', () => { }); it('should render avatar based on maxItemsVisible, including overflow avatar', () => { - const items = group.shadowRoot.querySelectorAll('vaadin-avatar'); + const items = group.$.container.querySelectorAll('vaadin-avatar'); expect(items.length).to.equal(group.maxItemsVisible); }); @@ -100,7 +100,7 @@ describe('avatar-group', () => { it('should show at least two avatars if maxItemsVisible is below 2', async () => { group.maxItemsVisible = 1; await nextRender(group); - const items = group.shadowRoot.querySelectorAll('vaadin-avatar'); + const items = group.$.container.querySelectorAll('vaadin-avatar'); expect(items.length).to.equal(2); }); @@ -148,7 +148,7 @@ describe('avatar-group', () => { group.items = []; group.items = items; await nextRender(group); - const renderedElements = group.shadowRoot.querySelectorAll('vaadin-avatar'); + const renderedElements = group.$.container.querySelectorAll('vaadin-avatar'); expect(renderedElements.length).to.equal(maxItemsVisible); }); @@ -162,7 +162,7 @@ describe('avatar-group', () => { group.style.width = '100px'; await onceResized(group); - const items = group.shadowRoot.querySelectorAll('vaadin-avatar'); + const items = group.$.container.querySelectorAll('vaadin-avatar'); expect(items.length).to.equal(3); }); @@ -193,7 +193,7 @@ describe('avatar-group', () => { it('should render avatars to fit width on resize', async () => { group.style.width = '110px'; await onceResized(group); - const items = group.shadowRoot.querySelectorAll('vaadin-avatar'); + const items = group.$.container.querySelectorAll('vaadin-avatar'); expect(items.length).to.equal(3); expect(overflow.abbr).to.equal('+3'); }); @@ -202,7 +202,7 @@ describe('avatar-group', () => { group.set('items', group.items.slice(0, 2)); group.style.width = '50px'; await onceResized(group); - const items = group.shadowRoot.querySelectorAll('vaadin-avatar:not([hidden])'); + const items = group.$.container.querySelectorAll('vaadin-avatar:not([hidden])'); expect(items.length).to.equal(2); }); @@ -221,7 +221,7 @@ describe('avatar-group', () => { it('should render avatars in the list-box items', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); expect(items.length).to.equal(3); done(); }); @@ -233,7 +233,7 @@ describe('avatar-group', () => { overlay.addEventListener('vaadin-overlay-open', () => { group.style.width = '75px'; onceResized(group).then(() => { - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); expect(items.length).to.equal(4); done(); }); @@ -297,9 +297,9 @@ describe('avatar-group', () => { it('should render list-box with items in the overlay', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const list = overlay.content.querySelector('vaadin-avatar-group-list-box'); + const list = overlay.querySelector('vaadin-list-box'); expect(list).to.be.ok; - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); expect(items.length).to.equal(3); done(); }); @@ -308,7 +308,7 @@ describe('avatar-group', () => { it('should render avatar names in the list-box items', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); expect(items[0].textContent.trim()).to.equal(group.items[1].name); expect(items[1].textContent.trim()).to.equal(group.items[2].name); expect(items[2].textContent.trim()).to.equal(group.items[3].name); @@ -319,7 +319,7 @@ describe('avatar-group', () => { it('should set tabindex="-1" on the avatars in the items', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const avatars = overlay.content.querySelectorAll('vaadin-avatar'); + const avatars = overlay.querySelectorAll('vaadin-avatar'); expect(avatars[0].getAttribute('tabindex')).to.equal('-1'); expect(avatars[1].getAttribute('tabindex')).to.equal('-1'); expect(avatars[2].getAttribute('tabindex')).to.equal('-1'); @@ -342,7 +342,7 @@ describe('avatar-group', () => { it('should close overlay on list-box Escape press', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const list = overlay.content.querySelector('vaadin-avatar-group-list-box'); + const list = overlay.querySelector('vaadin-list-box'); escKeyDown(list); afterNextRender(overlay, () => { @@ -355,7 +355,7 @@ describe('avatar-group', () => { it('should close overlay on list-box Tab press', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const list = overlay.content.querySelector('vaadin-avatar-group-list-box'); + const list = overlay.querySelector('vaadin-list-box'); tabKeyDown(list); afterNextRender(overlay, () => { @@ -395,7 +395,7 @@ describe('avatar-group', () => { it('should restore focus-ring attribute on close if closed with keyboard', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const list = overlay.content.querySelector('vaadin-avatar-group-list-box'); + const list = overlay.querySelector('vaadin-list-box'); escKeyDown(list); afterNextRender(overlay, () => { @@ -409,7 +409,7 @@ describe('avatar-group', () => { it('should not restore focus-ring attribute on close if not set', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); items[0].click(); afterNextRender(overlay, () => { @@ -446,7 +446,7 @@ describe('avatar-group', () => { it('should pass color index to overlay avatars', (done) => { group.maxItemsVisible = 1; overlay.addEventListener('vaadin-overlay-open', () => { - const avatars = overlay.content.querySelectorAll('vaadin-avatar'); + const avatars = overlay.querySelectorAll('vaadin-avatar'); expect(avatars[0].colorIndex).to.equal(group.items[1].colorIndex); expect(avatars[1].colorIndex).to.equal(group.items[2].colorIndex); done(); @@ -508,7 +508,7 @@ describe('avatar-group', () => { group.i18n = customI18n; group.maxItemsVisible = 1; overlay.addEventListener('vaadin-overlay-open', () => { - const avatars = overlay.content.querySelectorAll('vaadin-avatar'); + const avatars = overlay.querySelectorAll('vaadin-avatar'); expect(avatars[0].i18n).to.deep.equal(customI18n); expect(avatars[1].i18n).to.deep.equal(customI18n); done(); @@ -551,7 +551,7 @@ describe('avatar-group', () => { it('should set role="listbox" on the overlay list-box', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const list = overlay.content.querySelector('vaadin-avatar-group-list-box'); + const list = overlay.querySelector('vaadin-list-box'); expect(list.getAttribute('role')).to.equal('listbox'); done(); }); @@ -560,7 +560,7 @@ describe('avatar-group', () => { it('should set role="option" on the overlay items', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const items = overlay.content.querySelectorAll('[theme="avatar-group-item"]'); + const items = overlay.querySelectorAll('[theme="avatar-group-item"]'); items.forEach((item) => { expect(item.getAttribute('role')).to.equal('option'); }); @@ -571,7 +571,7 @@ describe('avatar-group', () => { it('should not create tooltips for the overlay avatars', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const avatars = overlay.content.querySelectorAll('vaadin-avatar'); + const avatars = overlay.querySelectorAll('vaadin-avatar'); avatars.forEach((avatar) => { expect(avatar.withTooltip).to.be.false; expect(avatar.querySelector('vaadin-tooltip')).to.be.not.ok; @@ -583,7 +583,7 @@ describe('avatar-group', () => { it('should set aria-hidden="true" on the overlay avatars', (done) => { overlay.addEventListener('vaadin-overlay-open', () => { - const avatars = overlay.content.querySelectorAll('vaadin-avatar'); + const avatars = overlay.querySelectorAll('vaadin-avatar'); avatars.forEach((avatar) => { expect(avatar.getAttribute('aria-hidden')).to.equal('true'); }); diff --git a/packages/avatar-group/test/dom/__snapshots__/avatar-group.test.snap.js b/packages/avatar-group/test/dom/__snapshots__/avatar-group.test.snap.js index ff60f379ee3..052c0f4848a 100644 --- a/packages/avatar-group/test/dom/__snapshots__/avatar-group.test.snap.js +++ b/packages/avatar-group/test/dom/__snapshots__/avatar-group.test.snap.js @@ -31,8 +31,6 @@ snapshots["vaadin-avatar-group default"] = id="overlay" no-vertical-overlap="" > - `; /* end snapshot vaadin-avatar-group default */ @@ -88,8 +86,8 @@ snapshots["vaadin-avatar-group items"] = id="overlay" no-vertical-overlap="" > - + + `; /* end snapshot vaadin-avatar-group items */ @@ -148,8 +146,8 @@ snapshots["vaadin-avatar-group theme"] = id="overlay" no-vertical-overlap="" > - + + `; /* end snapshot vaadin-avatar-group theme */ diff --git a/packages/avatar-group/theme/lumo/vaadin-avatar-group-styles.js b/packages/avatar-group/theme/lumo/vaadin-avatar-group-styles.js index 38137b91f09..9432e2dd78b 100644 --- a/packages/avatar-group/theme/lumo/vaadin-avatar-group-styles.js +++ b/packages/avatar-group/theme/lumo/vaadin-avatar-group-styles.js @@ -55,24 +55,13 @@ registerStyles('vaadin-avatar-group-overlay', [overlay, menuOverlayCore, avatarG }); registerStyles( - 'vaadin-avatar-group-list-box', + 'vaadin-item', css` - [part='items'] ::slotted(vaadin-item[theme='avatar-group-item']) { + :host([theme='avatar-group-item']) { padding: var(--lumo-space-xs); - padding-right: var(--lumo-space-m); + padding-inline-end: var(--lumo-space-m); } - :host([dir='rtl']) [part='items'] ::slotted(vaadin-item[theme='avatar-group-item']) { - padding: var(--lumo-space-xs); - padding-left: var(--lumo-space-m); - } - `, - { moduleId: 'lumo-avatar-group-list-box' }, -); - -registerStyles( - 'vaadin-item', - css` :host([theme='avatar-group-item']) [part='content'] { display: flex; align-items: center; diff --git a/packages/avatar-group/theme/material/vaadin-avatar-group-styles.js b/packages/avatar-group/theme/material/vaadin-avatar-group-styles.js index 9afe1c25f1a..6b6664a46a9 100644 --- a/packages/avatar-group/theme/material/vaadin-avatar-group-styles.js +++ b/packages/avatar-group/theme/material/vaadin-avatar-group-styles.js @@ -47,24 +47,13 @@ registerStyles('vaadin-avatar-group-overlay', [menuOverlay, avatarGroupOverlay], }); registerStyles( - 'vaadin-avatar-group-list-box', + 'vaadin-item', css` - [part='items'] ::slotted(vaadin-item[theme='avatar-group-item']) { + :host([theme='avatar-group-item']) { padding: 8px; - padding-right: 24px; + padding-inline-end: 24px; } - :host([dir='rtl']) [part='items'] ::slotted(vaadin-item[theme='avatar-group-item']) { - padding: 8px; - padding-left: 24px; - } - `, - { moduleId: 'material-avatar-group-list-box' }, -); - -registerStyles( - 'vaadin-item', - css` :host([theme='avatar-group-item']) [part='content'] { display: flex; align-items: center;