Skip to content

Commit 276cce9

Browse files
authored
refactor: extract message and message-list logic into mixins (#8241)
1 parent ea24ab2 commit 276cce9

9 files changed

+392
-339
lines changed

packages/message-list/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"polymer"
3838
],
3939
"dependencies": {
40+
"@open-wc/dedupe-mixin": "^1.3.0",
4041
"@polymer/polymer": "^3.0.0",
4142
"@vaadin/a11y-base": "24.6.0-beta1",
4243
"@vaadin/avatar": "24.6.0-beta1",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021 - 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import type { Constructor } from '@open-wc/dedupe-mixin';
7+
import type { KeyboardDirectionMixinClass } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
8+
9+
export interface MessageListItem {
10+
text?: string;
11+
time?: string;
12+
userName?: string;
13+
userAbbr?: string;
14+
userImg?: string;
15+
userColorIndex?: number;
16+
theme?: string;
17+
className?: string;
18+
}
19+
20+
export declare function MessageListMixin<T extends Constructor<HTMLElement>>(
21+
base: T,
22+
): Constructor<KeyboardDirectionMixinClass> & Constructor<MessageListMixinClass> & T;
23+
24+
export declare class MessageListMixinClass {
25+
/**
26+
* An array of objects which will be rendered as messages.
27+
* The message objects can have the following properties:
28+
* ```js
29+
* Array<{
30+
* text: string,
31+
* time: string,
32+
* userName: string,
33+
* userAbbr: string,
34+
* userImg: string,
35+
* userColorIndex: number,
36+
* className: string,
37+
* theme: string
38+
* }>
39+
* ```
40+
*/
41+
items: MessageListItem[] | null | undefined;
42+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021 - 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import { html, render } from 'lit';
7+
import { ifDefined } from 'lit/directives/if-defined.js';
8+
import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
9+
10+
/**
11+
* @polymerMixin
12+
* @mixes KeyboardDirectionMixin
13+
*/
14+
export const MessageListMixin = (superClass) =>
15+
class MessageListMixinClass extends KeyboardDirectionMixin(superClass) {
16+
static get properties() {
17+
return {
18+
/**
19+
* An array of objects which will be rendered as messages.
20+
* The message objects can have the following properties:
21+
* ```js
22+
* Array<{
23+
* text: string,
24+
* time: string,
25+
* userName: string,
26+
* userAbbr: string,
27+
* userImg: string,
28+
* userColorIndex: number,
29+
* className: string,
30+
* theme: string
31+
* }>
32+
* ```
33+
*/
34+
items: {
35+
type: Array,
36+
value: () => [],
37+
observer: '_itemsChanged',
38+
},
39+
};
40+
}
41+
42+
/** @protected */
43+
get _messages() {
44+
return [...this.querySelectorAll('vaadin-message')];
45+
}
46+
47+
/** @protected */
48+
ready() {
49+
super.ready();
50+
51+
// Make screen readers announce new messages
52+
this.setAttribute('aria-relevant', 'additions');
53+
this.setAttribute('role', 'log');
54+
}
55+
56+
/**
57+
* Override method inherited from `KeyboardDirectionMixin`
58+
* to use the list of message elements as items.
59+
*
60+
* @return {Element[]}
61+
* @protected
62+
* @override
63+
*/
64+
_getItems() {
65+
return this._messages;
66+
}
67+
68+
/** @private */
69+
_itemsChanged(newVal, oldVal) {
70+
const items = newVal || [];
71+
const oldItems = oldVal || [];
72+
73+
if (items.length || oldItems.length) {
74+
const focusedIndex = this._getIndexOfFocusableElement();
75+
const closeToBottom = this.scrollHeight < this.clientHeight + this.scrollTop + 50;
76+
77+
this._renderMessages(items);
78+
this._setTabIndexesByIndex(focusedIndex);
79+
80+
requestAnimationFrame(() => {
81+
if (items.length > oldItems.length && closeToBottom) {
82+
this._scrollToLastMessage();
83+
}
84+
});
85+
}
86+
}
87+
88+
/** @private */
89+
_renderMessages(items) {
90+
render(
91+
html`
92+
${items.map(
93+
(item) => html`
94+
<vaadin-message
95+
role="listitem"
96+
.time="${item.time}"
97+
.userAbbr="${item.userAbbr}"
98+
.userName="${item.userName}"
99+
.userImg="${item.userImg}"
100+
.userColorIndex="${item.userColorIndex}"
101+
theme="${ifDefined(item.theme)}"
102+
class="${ifDefined(item.className)}"
103+
@focusin="${this._onMessageFocusIn}"
104+
>${item.text}<vaadin-avatar slot="avatar"></vaadin-avatar
105+
></vaadin-message>
106+
`,
107+
)}
108+
`,
109+
this,
110+
{ host: this },
111+
);
112+
}
113+
114+
/** @private */
115+
_scrollToLastMessage() {
116+
if (this.items.length > 0) {
117+
this.scrollTop = this.scrollHeight - this.clientHeight;
118+
}
119+
}
120+
121+
/** @private */
122+
_onMessageFocusIn(e) {
123+
const target = e.composedPath().find((node) => node instanceof customElements.get('vaadin-message'));
124+
this._setTabIndexesByMessage(target);
125+
}
126+
127+
/**
128+
* @param {number} idx
129+
* @protected
130+
*/
131+
_setTabIndexesByIndex(index) {
132+
const message = this._messages[index] || this._messages[0];
133+
this._setTabIndexesByMessage(message);
134+
}
135+
136+
/** @private */
137+
_setTabIndexesByMessage(message) {
138+
this._messages.forEach((e) => {
139+
e.tabIndex = e === message ? 0 : -1;
140+
});
141+
}
142+
143+
/** @private */
144+
_getIndexOfFocusableElement() {
145+
const index = this._messages.findIndex((e) => e.tabIndex === 0);
146+
return index !== -1 ? index : 0;
147+
}
148+
};

packages/message-list/src/vaadin-message-list.d.ts

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,11 @@
33
* Copyright (c) 2021 - 2024 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
76
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
87
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8+
import { MessageListMixin } from './vaadin-message-list-mixin.js';
99

10-
export interface MessageListItem {
11-
text?: string;
12-
time?: string;
13-
userName?: string;
14-
userAbbr?: string;
15-
userImg?: string;
16-
userColorIndex?: number;
17-
theme?: string;
18-
className?: string;
19-
}
10+
export { MessageListItem } from './vaadin-message-list-mixin.js';
2011

2112
/**
2213
* `<vaadin-message-list>` is a Web Component for showing an ordered list of messages. The messages are rendered as <vaadin-message>
@@ -51,27 +42,7 @@ export interface MessageListItem {
5142
*
5243
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
5344
*/
54-
declare class MessageList extends KeyboardDirectionMixin(ThemableMixin(ElementMixin(HTMLElement))) {
55-
/**
56-
* An array of objects which will be rendered as messages.
57-
* The message objects can have the following properties:
58-
* ```js
59-
* Array<{
60-
* text: string,
61-
* time: string,
62-
* userName: string,
63-
* userAbbr: string,
64-
* userImg: string,
65-
* userColorIndex: number,
66-
* className: string,
67-
* theme: string
68-
* }>
69-
* ```
70-
*
71-
* @type {!Array<!MessageListItem>}
72-
*/
73-
items: MessageListItem[] | null | undefined;
74-
}
45+
declare class MessageList extends MessageListMixin(ThemableMixin(ElementMixin(HTMLElement))) {}
7546

7647
declare global {
7748
interface HTMLElementTagNameMap {

0 commit comments

Comments
 (0)