From 276cce99161ccbf712b3172ddd7ad8dd93b1d2c8 Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Fri, 29 Nov 2024 12:34:43 +0200 Subject: [PATCH] refactor: extract message and message-list logic into mixins (#8241) --- packages/message-list/package.json | 1 + .../src/vaadin-message-list-mixin.d.ts | 42 +++++ .../src/vaadin-message-list-mixin.js | 148 ++++++++++++++++++ .../message-list/src/vaadin-message-list.d.ts | 35 +---- .../message-list/src/vaadin-message-list.js | 147 +---------------- .../src/vaadin-message-mixin.d.ts | 68 ++++++++ .../message-list/src/vaadin-message-mixin.js | 119 ++++++++++++++ packages/message-list/src/vaadin-message.d.ts | 59 +------ packages/message-list/src/vaadin-message.js | 112 +------------ 9 files changed, 392 insertions(+), 339 deletions(-) create mode 100644 packages/message-list/src/vaadin-message-list-mixin.d.ts create mode 100644 packages/message-list/src/vaadin-message-list-mixin.js create mode 100644 packages/message-list/src/vaadin-message-mixin.d.ts create mode 100644 packages/message-list/src/vaadin-message-mixin.js diff --git a/packages/message-list/package.json b/packages/message-list/package.json index af84a36297..b37ca2eddd 100644 --- a/packages/message-list/package.json +++ b/packages/message-list/package.json @@ -37,6 +37,7 @@ "polymer" ], "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", "@polymer/polymer": "^3.0.0", "@vaadin/a11y-base": "24.6.0-beta1", "@vaadin/avatar": "24.6.0-beta1", diff --git a/packages/message-list/src/vaadin-message-list-mixin.d.ts b/packages/message-list/src/vaadin-message-list-mixin.d.ts new file mode 100644 index 0000000000..ebe8db1744 --- /dev/null +++ b/packages/message-list/src/vaadin-message-list-mixin.d.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright (c) 2021 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import type { Constructor } from '@open-wc/dedupe-mixin'; +import type { KeyboardDirectionMixinClass } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; + +export interface MessageListItem { + text?: string; + time?: string; + userName?: string; + userAbbr?: string; + userImg?: string; + userColorIndex?: number; + theme?: string; + className?: string; +} + +export declare function MessageListMixin>( + base: T, +): Constructor & Constructor & T; + +export declare class MessageListMixinClass { + /** + * An array of objects which will be rendered as messages. + * The message objects can have the following properties: + * ```js + * Array<{ + * text: string, + * time: string, + * userName: string, + * userAbbr: string, + * userImg: string, + * userColorIndex: number, + * className: string, + * theme: string + * }> + * ``` + */ + items: MessageListItem[] | null | undefined; +} diff --git a/packages/message-list/src/vaadin-message-list-mixin.js b/packages/message-list/src/vaadin-message-list-mixin.js new file mode 100644 index 0000000000..ceb7d56912 --- /dev/null +++ b/packages/message-list/src/vaadin-message-list-mixin.js @@ -0,0 +1,148 @@ +/** + * @license + * Copyright (c) 2021 - 2024 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { html, render } from 'lit'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; + +/** + * @polymerMixin + * @mixes KeyboardDirectionMixin + */ +export const MessageListMixin = (superClass) => + class MessageListMixinClass extends KeyboardDirectionMixin(superClass) { + static get properties() { + return { + /** + * An array of objects which will be rendered as messages. + * The message objects can have the following properties: + * ```js + * Array<{ + * text: string, + * time: string, + * userName: string, + * userAbbr: string, + * userImg: string, + * userColorIndex: number, + * className: string, + * theme: string + * }> + * ``` + */ + items: { + type: Array, + value: () => [], + observer: '_itemsChanged', + }, + }; + } + + /** @protected */ + get _messages() { + return [...this.querySelectorAll('vaadin-message')]; + } + + /** @protected */ + ready() { + super.ready(); + + // Make screen readers announce new messages + this.setAttribute('aria-relevant', 'additions'); + this.setAttribute('role', 'log'); + } + + /** + * Override method inherited from `KeyboardDirectionMixin` + * to use the list of message elements as items. + * + * @return {Element[]} + * @protected + * @override + */ + _getItems() { + return this._messages; + } + + /** @private */ + _itemsChanged(newVal, oldVal) { + const items = newVal || []; + const oldItems = oldVal || []; + + if (items.length || oldItems.length) { + const focusedIndex = this._getIndexOfFocusableElement(); + const closeToBottom = this.scrollHeight < this.clientHeight + this.scrollTop + 50; + + this._renderMessages(items); + this._setTabIndexesByIndex(focusedIndex); + + requestAnimationFrame(() => { + if (items.length > oldItems.length && closeToBottom) { + this._scrollToLastMessage(); + } + }); + } + } + + /** @private */ + _renderMessages(items) { + render( + html` + ${items.map( + (item) => html` + ${item.text} + `, + )} + `, + this, + { host: this }, + ); + } + + /** @private */ + _scrollToLastMessage() { + if (this.items.length > 0) { + this.scrollTop = this.scrollHeight - this.clientHeight; + } + } + + /** @private */ + _onMessageFocusIn(e) { + const target = e.composedPath().find((node) => node instanceof customElements.get('vaadin-message')); + this._setTabIndexesByMessage(target); + } + + /** + * @param {number} idx + * @protected + */ + _setTabIndexesByIndex(index) { + const message = this._messages[index] || this._messages[0]; + this._setTabIndexesByMessage(message); + } + + /** @private */ + _setTabIndexesByMessage(message) { + this._messages.forEach((e) => { + e.tabIndex = e === message ? 0 : -1; + }); + } + + /** @private */ + _getIndexOfFocusableElement() { + const index = this._messages.findIndex((e) => e.tabIndex === 0); + return index !== -1 ? index : 0; + } + }; diff --git a/packages/message-list/src/vaadin-message-list.d.ts b/packages/message-list/src/vaadin-message-list.d.ts index 51e3163fd3..87a2ee18a2 100644 --- a/packages/message-list/src/vaadin-message-list.d.ts +++ b/packages/message-list/src/vaadin-message-list.d.ts @@ -3,20 +3,11 @@ * Copyright (c) 2021 - 2024 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ -import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { MessageListMixin } from './vaadin-message-list-mixin.js'; -export interface MessageListItem { - text?: string; - time?: string; - userName?: string; - userAbbr?: string; - userImg?: string; - userColorIndex?: number; - theme?: string; - className?: string; -} +export { MessageListItem } from './vaadin-message-list-mixin.js'; /** * `` is a Web Component for showing an ordered list of messages. The messages are rendered as @@ -51,27 +42,7 @@ export interface MessageListItem { * * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation. */ -declare class MessageList extends KeyboardDirectionMixin(ThemableMixin(ElementMixin(HTMLElement))) { - /** - * An array of objects which will be rendered as messages. - * The message objects can have the following properties: - * ```js - * Array<{ - * text: string, - * time: string, - * userName: string, - * userAbbr: string, - * userImg: string, - * userColorIndex: number, - * className: string, - * theme: string - * }> - * ``` - * - * @type {!Array} - */ - items: MessageListItem[] | null | undefined; -} +declare class MessageList extends MessageListMixin(ThemableMixin(ElementMixin(HTMLElement))) {} declare global { interface HTMLElementTagNameMap { diff --git a/packages/message-list/src/vaadin-message-list.js b/packages/message-list/src/vaadin-message-list.js index 864a50466e..38d778fb04 100644 --- a/packages/message-list/src/vaadin-message-list.js +++ b/packages/message-list/src/vaadin-message-list.js @@ -3,14 +3,12 @@ * Copyright (c) 2021 - 2024 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ -import { html as legacyHtml, PolymerElement } from '@polymer/polymer/polymer-element.js'; -import { html, render } from 'lit'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; +import './vaadin-message.js'; +import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; -import { Message } from './vaadin-message.js'; +import { MessageListMixin } from './vaadin-message-list-mixin.js'; /** * `` is a Web Component for showing an ordered list of messages. The messages are rendered as @@ -49,41 +47,15 @@ import { Message } from './vaadin-message.js'; * @extends HTMLElement * @mixes ThemableMixin * @mixes ElementMixin - * @mixes KeyboardDirectionMixin + * @mixes MessageListMixin */ -class MessageList extends KeyboardDirectionMixin(ElementMixin(ThemableMixin(PolymerElement))) { +class MessageList extends MessageListMixin(ElementMixin(ThemableMixin(PolymerElement))) { static get is() { return 'vaadin-message-list'; } - static get properties() { - return { - /** - * An array of objects which will be rendered as messages. - * The message objects can have the following properties: - * ```js - * Array<{ - * text: string, - * time: string, - * userName: string, - * userAbbr: string, - * userImg: string, - * userColorIndex: number, - * className: string, - * theme: string - * }> - * ``` - */ - items: { - type: Array, - value: () => [], - observer: '_itemsChanged', - }, - }; - } - static get template() { - return legacyHtml` + return html`