From 2f388cf698b4ecaa783f3aa9605d4a05d8229faf Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Fri, 15 Nov 2024 13:32:09 +0200 Subject: [PATCH] experiment: add LitElement based version of avatar-group (#8155) --- .../src/vaadin-avatar-group-mixin.js | 38 ++++++-- .../avatar-group/src/vaadin-avatar-group.js | 1 + .../src/vaadin-lit-avatar-group-menu-item.js | 60 +++++++++++++ .../src/vaadin-lit-avatar-group-menu.js | 87 +++++++++++++++++++ .../src/vaadin-lit-avatar-group-overlay.js | 64 ++++++++++++++ .../src/vaadin-lit-avatar-group.d.ts | 1 + .../src/vaadin-lit-avatar-group.js | 64 ++++++++++++++ .../test/avatar-group-lit.test.js | 2 + .../test/avatar-group-polymer.test.js | 2 + ...r-group.test.js => avatar-group.common.js} | 5 +- .../theme/lumo/vaadin-lit-avatar-group.js | 3 + .../theme/material/vaadin-lit-avatar-group.js | 3 + .../avatar-group/vaadin-lit-avatar-group.d.ts | 1 + .../avatar-group/vaadin-lit-avatar-group.js | 2 + 14 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 packages/avatar-group/src/vaadin-lit-avatar-group-menu-item.js create mode 100644 packages/avatar-group/src/vaadin-lit-avatar-group-menu.js create mode 100644 packages/avatar-group/src/vaadin-lit-avatar-group-overlay.js create mode 100644 packages/avatar-group/src/vaadin-lit-avatar-group.d.ts create mode 100644 packages/avatar-group/src/vaadin-lit-avatar-group.js create mode 100644 packages/avatar-group/test/avatar-group-lit.test.js create mode 100644 packages/avatar-group/test/avatar-group-polymer.test.js rename packages/avatar-group/test/{avatar-group.test.js => avatar-group.common.js} (99%) create mode 100644 packages/avatar-group/theme/lumo/vaadin-lit-avatar-group.js create mode 100644 packages/avatar-group/theme/material/vaadin-lit-avatar-group.js create mode 100644 packages/avatar-group/vaadin-lit-avatar-group.d.ts create mode 100644 packages/avatar-group/vaadin-lit-avatar-group.js diff --git a/packages/avatar-group/src/vaadin-avatar-group-mixin.js b/packages/avatar-group/src/vaadin-avatar-group-mixin.js index 938e5e362b..aaa0d046e1 100644 --- a/packages/avatar-group/src/vaadin-avatar-group-mixin.js +++ b/packages/avatar-group/src/vaadin-avatar-group-mixin.js @@ -54,6 +54,7 @@ export const AvatarGroupMixin = (superClass) => items: { type: Array, observer: '__itemsChanged', + sync: true, }, /** @@ -64,6 +65,7 @@ export const AvatarGroupMixin = (superClass) => */ maxItemsVisible: { type: Number, + sync: true, }, /** @@ -97,6 +99,7 @@ export const AvatarGroupMixin = (superClass) => */ i18n: { type: Object, + sync: true, value: () => { return { anonymous: 'anonymous', @@ -114,17 +117,20 @@ export const AvatarGroupMixin = (superClass) => _avatars: { type: Array, value: () => [], + sync: true, }, /** @private */ __itemsInView: { type: Number, value: null, + sync: true, }, /** @private */ _overflow: { type: Object, + sync: true, }, /** @private */ @@ -132,17 +138,19 @@ export const AvatarGroupMixin = (superClass) => type: Array, observer: '__overflowItemsChanged', computed: '__computeOverflowItems(items, __itemsInView, maxItemsVisible)', + sync: true, }, /** @private */ _overflowTooltip: { type: Object, + sync: true, }, /** @private */ _opened: { type: Boolean, - observer: '__openedChanged', + sync: true, }, }; } @@ -150,6 +158,7 @@ export const AvatarGroupMixin = (superClass) => static get observers() { return [ '__i18nItemsChanged(i18n, items)', + '__openedChanged(_opened, _overflow)', '__updateAvatarsTheme(_overflow, _avatars, _theme)', '__updateAvatars(items, __itemsInView, maxItemsVisible, _overflow, i18n)', '__updateOverflowAvatar(_overflow, items, __itemsInView, maxItemsVisible)', @@ -297,6 +306,13 @@ export const AvatarGroupMixin = (superClass) => } } + /** @private */ + _onVaadinOverlayOpen() { + if (this._menuElement) { + this._menuElement.focus(); + } + } + /** @private */ __renderAvatars(items) { render( @@ -460,22 +476,26 @@ export const AvatarGroupMixin = (superClass) => } /** @private */ - __openedChanged(opened, wasOpened) { + __openedChanged(opened, overflow) { + if (!overflow) { + return; + } + if (opened) { if (!this._menuElement) { this._menuElement = this.$.overlay.querySelector('vaadin-avatar-group-menu'); } - this._openedWithFocusRing = this._overflow.hasAttribute('focus-ring'); - - this._menuElement.focus(); - } else if (wasOpened) { - this._overflow.focus(); + this._openedWithFocusRing = overflow.hasAttribute('focus-ring'); + } else if (this.__oldOpened) { + overflow.focus(); if (this._openedWithFocusRing) { - this._overflow.setAttribute('focus-ring', ''); + overflow.setAttribute('focus-ring', ''); } } - this._overflow.setAttribute('aria-expanded', opened === true); + + overflow.setAttribute('aria-expanded', opened === true); + this.__oldOpened = opened; } /** @private */ diff --git a/packages/avatar-group/src/vaadin-avatar-group.js b/packages/avatar-group/src/vaadin-avatar-group.js index c0ac00964e..8db8b3628b 100644 --- a/packages/avatar-group/src/vaadin-avatar-group.js +++ b/packages/avatar-group/src/vaadin-avatar-group.js @@ -77,6 +77,7 @@ class AvatarGroup extends AvatarGroupMixin(ElementMixin(ThemableMixin(Controller position-target="[[_overflow]]" no-vertical-overlap on-vaadin-overlay-close="_onVaadinOverlayClose" + on-vaadin-overlay-open="_onVaadinOverlayOpen" > `; } diff --git a/packages/avatar-group/src/vaadin-lit-avatar-group-menu-item.js b/packages/avatar-group/src/vaadin-lit-avatar-group-menu-item.js new file mode 100644 index 0000000000..74842ea23d --- /dev/null +++ b/packages/avatar-group/src/vaadin-lit-avatar-group-menu-item.js @@ -0,0 +1,60 @@ +/** + * @license + * Copyright (c) 2020 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { css, html, LitElement } from 'lit'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { ItemMixin } from '@vaadin/item/src/vaadin-item-mixin.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes DirMixin + * @mixes ItemMixin + * @mixes ThemableMixin + * @protected + */ +class AvatarGroupMenuItem extends ItemMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) { + static get is() { + return 'vaadin-avatar-group-menu-item'; + } + + static get styles() { + return css` + :host { + display: inline-block; + } + + :host([hidden]) { + display: none !important; + } + `; + } + + /** @protected */ + render() { + return html` + +
+ +
+ `; + } + + /** @protected */ + ready() { + super.ready(); + + this.setAttribute('role', 'menuitem'); + } +} + +defineCustomElement(AvatarGroupMenuItem); + +export { AvatarGroupMenuItem }; diff --git a/packages/avatar-group/src/vaadin-lit-avatar-group-menu.js b/packages/avatar-group/src/vaadin-lit-avatar-group-menu.js new file mode 100644 index 0000000000..a3c7753521 --- /dev/null +++ b/packages/avatar-group/src/vaadin-lit-avatar-group-menu.js @@ -0,0 +1,87 @@ +/** + * @license + * Copyright (c) 2020 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { css, html, LitElement } from 'lit'; +import { ListMixin } from '@vaadin/a11y-base/src/list-mixin.js'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes ControllerMixin + * @mixes DirMixin + * @mixes ListMixin + * @mixes ThemableMixin + * @protected + */ +class AvatarGroupMenu extends ListMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) { + static get is() { + return 'vaadin-avatar-group-menu'; + } + + static get styles() { + return css` + :host { + display: flex; + } + + :host([hidden]) { + display: none !important; + } + + [part='items'] { + height: 100%; + width: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + `; + } + + static get properties() { + return { + // We don't need to define this property since super default is vertical, + // but we don't want it to be modified, or be shown in the API docs. + /** @private */ + orientation: { + readOnly: true, + }, + }; + } + + /** + * @return {!HTMLElement} + * @protected + * @override + */ + get _scrollerElement() { + return this.shadowRoot.querySelector('[part="items"]'); + } + + /** @protected */ + render() { + return html` +
+ +
+ `; + } + + /** @protected */ + ready() { + super.ready(); + + this.setAttribute('role', 'menu'); + } +} + +defineCustomElement(AvatarGroupMenu); + +export { AvatarGroupMenu }; diff --git a/packages/avatar-group/src/vaadin-lit-avatar-group-overlay.js b/packages/avatar-group/src/vaadin-lit-avatar-group-overlay.js new file mode 100644 index 0000000000..53d51e7f62 --- /dev/null +++ b/packages/avatar-group/src/vaadin-lit-avatar-group-overlay.js @@ -0,0 +1,64 @@ +/** + * @license + * Copyright (c) 2020 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { html, LitElement } from 'lit'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js'; +import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js'; +import { overlayStyles } from '@vaadin/overlay/src/vaadin-overlay-styles.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes PositionMixin + * @mixes OverlayMixin + * @mixes DirMixin + * @mixes ThemableMixin + * @private + */ +class AvatarGroupOverlay extends PositionMixin(OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement))))) { + static get is() { + return 'vaadin-avatar-group-overlay'; + } + + static get styles() { + return overlayStyles; + } + + static get properties() { + return { + /** + * When true, the overlay is visible and attached to body. + * This property config is overridden to set `sync: true`. + */ + opened: { + type: Boolean, + notify: true, + observer: '_openedChanged', + reflectToAttribute: true, + sync: true, + }, + }; + } + + /** @protected */ + render() { + return html` +
+
+
+ +
+
+ `; + } +} + +defineCustomElement(AvatarGroupOverlay); diff --git a/packages/avatar-group/src/vaadin-lit-avatar-group.d.ts b/packages/avatar-group/src/vaadin-lit-avatar-group.d.ts new file mode 100644 index 0000000000..49b29c04db --- /dev/null +++ b/packages/avatar-group/src/vaadin-lit-avatar-group.d.ts @@ -0,0 +1 @@ +export * from './vaadin-avatar-group.js'; diff --git a/packages/avatar-group/src/vaadin-lit-avatar-group.js b/packages/avatar-group/src/vaadin-lit-avatar-group.js new file mode 100644 index 0000000000..567230c35d --- /dev/null +++ b/packages/avatar-group/src/vaadin-lit-avatar-group.js @@ -0,0 +1,64 @@ +/** + * @license + * Copyright (c) 2020 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import '@vaadin/avatar/src/vaadin-lit-avatar.js'; +import './vaadin-lit-avatar-group-menu.js'; +import './vaadin-lit-avatar-group-menu-item.js'; +import './vaadin-lit-avatar-group-overlay.js'; +import { html, LitElement } from 'lit'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { AvatarGroupMixin } from './vaadin-avatar-group-mixin.js'; +import { avatarGroupStyles } from './vaadin-avatar-group-styles.js'; + +/** + * LitElement based version of `` web component. + * + * ## Disclaimer + * + * This component is an experiment and not yet a part of Vaadin platform. + * There is no ETA regarding specific Vaadin version where it'll land. + * Feel free to try this code in your apps as per Apache 2.0 license. + */ +class AvatarGroup extends AvatarGroupMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) { + static get is() { + return 'vaadin-avatar-group'; + } + + static get styles() { + return avatarGroupStyles; + } + + /** @protected */ + render() { + return html` +
+ + +
+ + + `; + } + + /** @private */ + _onOpenedChanged(event) { + this._opened = event.detail.value; + } +} + +defineCustomElement(AvatarGroup); + +export { AvatarGroup }; diff --git a/packages/avatar-group/test/avatar-group-lit.test.js b/packages/avatar-group/test/avatar-group-lit.test.js new file mode 100644 index 0000000000..ab6f47f3d1 --- /dev/null +++ b/packages/avatar-group/test/avatar-group-lit.test.js @@ -0,0 +1,2 @@ +import '../vaadin-lit-avatar-group.js'; +import './avatar-group.common.js'; diff --git a/packages/avatar-group/test/avatar-group-polymer.test.js b/packages/avatar-group/test/avatar-group-polymer.test.js new file mode 100644 index 0000000000..eeb5962919 --- /dev/null +++ b/packages/avatar-group/test/avatar-group-polymer.test.js @@ -0,0 +1,2 @@ +import '../vaadin-avatar-group.js'; +import './avatar-group.common.js'; diff --git a/packages/avatar-group/test/avatar-group.test.js b/packages/avatar-group/test/avatar-group.common.js similarity index 99% rename from packages/avatar-group/test/avatar-group.test.js rename to packages/avatar-group/test/avatar-group.common.js index 84c66ca4a0..5fca683d71 100644 --- a/packages/avatar-group/test/avatar-group.test.js +++ b/packages/avatar-group/test/avatar-group.common.js @@ -3,13 +3,13 @@ import { enterKeyDown, escKeyDown, fixtureSync, + nextFrame, nextRender, oneEvent, spaceKeyDown, tabKeyDown, } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import '../vaadin-avatar-group.js'; /** * Resolves once the function is invoked on the given object. @@ -65,8 +65,9 @@ describe('avatar-group', () => { expect(items.length).to.equal(group.items.length + 1); }); - it('should propagate theme attribute to all avatars', () => { + it('should propagate theme attribute to all avatars', async () => { group.setAttribute('theme', 'small'); + await nextFrame(); const items = group.querySelectorAll('vaadin-avatar'); items.forEach((avatar) => expect(avatar.getAttribute('theme')).to.equal('small')); }); diff --git a/packages/avatar-group/theme/lumo/vaadin-lit-avatar-group.js b/packages/avatar-group/theme/lumo/vaadin-lit-avatar-group.js new file mode 100644 index 0000000000..3b13481c8f --- /dev/null +++ b/packages/avatar-group/theme/lumo/vaadin-lit-avatar-group.js @@ -0,0 +1,3 @@ +import '@vaadin/avatar/theme/lumo/vaadin-lit-avatar.js'; +import './vaadin-avatar-group-styles.js'; +import '../../src/vaadin-lit-avatar-group.js'; diff --git a/packages/avatar-group/theme/material/vaadin-lit-avatar-group.js b/packages/avatar-group/theme/material/vaadin-lit-avatar-group.js new file mode 100644 index 0000000000..bbb2e178bd --- /dev/null +++ b/packages/avatar-group/theme/material/vaadin-lit-avatar-group.js @@ -0,0 +1,3 @@ +import '@vaadin/avatar/theme/material/vaadin-lit-avatar.js'; +import './vaadin-avatar-group-styles.js'; +import '../../src/vaadin-lit-avatar-group.js'; diff --git a/packages/avatar-group/vaadin-lit-avatar-group.d.ts b/packages/avatar-group/vaadin-lit-avatar-group.d.ts new file mode 100644 index 0000000000..859c6efb4c --- /dev/null +++ b/packages/avatar-group/vaadin-lit-avatar-group.d.ts @@ -0,0 +1 @@ +export * from './src/vaadin-avatar-group.js'; diff --git a/packages/avatar-group/vaadin-lit-avatar-group.js b/packages/avatar-group/vaadin-lit-avatar-group.js new file mode 100644 index 0000000000..55b5e86bce --- /dev/null +++ b/packages/avatar-group/vaadin-lit-avatar-group.js @@ -0,0 +1,2 @@ +import './theme/lumo/vaadin-lit-avatar-group.js'; +export * from './src/vaadin-lit-avatar-group.js';