11import { consume } from '@lit/context' ;
22import { html , LitElement , nothing } from 'lit' ;
3+ import { state } from 'lit/decorators.js' ;
34import { repeat } from 'lit/directives/repeat.js' ;
45import { chatContext } from '../common/context.js' ;
56import { registerComponent } from '../common/definitions/register.js' ;
@@ -21,6 +22,9 @@ export default class IgcChatMessageListComponent extends LitElement {
2122 @consume ( { context : chatContext , subscribe : true } )
2223 private _chatState ?: ChatState ;
2324
25+ @state ( )
26+ private _activeMessageId = '' ;
27+
2428 /* blazorSuppress */
2529 public static register ( ) {
2630 registerComponent ( IgcChatMessageListComponent , IgcChatMessageComponent ) ;
@@ -74,6 +78,50 @@ export default class IgcChatMessageListComponent extends LitElement {
7478 } ) ;
7579 }
7680
81+ private scrollToMessage ( messageId : string ) {
82+ const messageElement = this . shadowRoot ?. querySelector (
83+ `#message-${ messageId } `
84+ ) ;
85+ messageElement ?. scrollIntoView ( ) ;
86+ }
87+
88+ private handleFocusIn ( ) {
89+ if ( ! this . _chatState ?. messages || this . _chatState . messages . length === 0 ) {
90+ return ;
91+ }
92+ const lastMessage = this . _chatState . sortedMessagesIds ?. pop ( ) ?? '' ;
93+ this . _activeMessageId = lastMessage !== '' ? `message-${ lastMessage } ` : '' ;
94+ }
95+
96+ private handleFocusOut ( ) {
97+ this . _activeMessageId = '' ;
98+ }
99+
100+ private handleKeyDown ( e : KeyboardEvent ) {
101+ if ( ! this . _chatState ?. messages || this . _chatState . messages . length === 0 ) {
102+ return ;
103+ }
104+
105+ const currentIndex = this . _chatState ?. sortedMessagesIds . findIndex (
106+ ( id ) => `message-${ id } ` === this . _activeMessageId
107+ ) ;
108+
109+ if ( e . key === 'ArrowUp' && currentIndex > 0 ) {
110+ const previousMessageId =
111+ this . _chatState . sortedMessagesIds [ currentIndex - 1 ] ;
112+ this . _activeMessageId = `message-${ previousMessageId } ` ;
113+ this . scrollToMessage ( previousMessageId ) ;
114+ }
115+ if (
116+ e . key === 'ArrowDown' &&
117+ currentIndex < this . _chatState ?. messages . length - 1
118+ ) {
119+ const nextMessageId = this . _chatState . sortedMessagesIds [ currentIndex + 1 ] ;
120+ this . _activeMessageId = `message-${ nextMessageId } ` ;
121+ this . scrollToMessage ( nextMessageId ) ;
122+ }
123+ }
124+
77125 protected override updated ( ) {
78126 if ( ! this . _chatState ?. options ?. disableAutoScroll ) {
79127 this . scrollToBottom ( ) ;
@@ -102,7 +150,13 @@ export default class IgcChatMessageListComponent extends LitElement {
102150 ) ;
103151
104152 return html `
105- < div class ='message-container '> </ div >
153+ < div
154+ class ='message-container '
155+ aria-activedescendant =${ this . _activeMessageId }
156+ tabindex ='0'
157+ @focusin=${ this . handleFocusIn }
158+ @focusout=${ this . handleFocusOut }
159+ @keydown=${ this . handleKeyDown } > </ div >
106160 < div class ="message-list ">
107161 ${ repeat (
108162 groupedMessages ,
@@ -111,9 +165,19 @@ export default class IgcChatMessageListComponent extends LitElement {
111165 ${ repeat (
112166 group . messages ,
113167 ( message ) => message . id ,
114- ( message ) => html `
115- < igc-chat-message .message =${ message } > </ igc-chat-message >
116- `
168+ ( message ) => {
169+ const messageId = `message-${ message . id } ` ;
170+ return html `
171+ < igc-chat-message
172+ id =${ messageId }
173+ class =${ this . _activeMessageId === messageId
174+ ? 'active'
175+ : '' }
176+ .message=${ message }
177+ >
178+ </ igc-chat-message >
179+ ` ;
180+ }
117181 ) }
118182 `
119183 ) }
0 commit comments