@@ -70,9 +70,28 @@ class ChatMessage extends LightElement {
7070 content-type =${ this . content_type }
7171 ?streaming=${ this . streaming }
7272 auto-scroll
73+ .onContentChange=${ this . #onContentChange}
74+ .onStreamEnd=${ this . #makeSuggestionsAccessible}
7375 > </ shiny-markdown-stream >
7476 ` ;
7577 }
78+
79+ #onContentChange( ) : void {
80+ if ( ! this . streaming ) this . #makeSuggestionsAccessible( ) ;
81+ }
82+
83+ #makeSuggestionsAccessible( ) : void {
84+ this . querySelectorAll ( ".suggestion,[data-suggestion]" ) . forEach ( ( el ) => {
85+ if ( ! ( el instanceof HTMLElement ) ) return ;
86+ if ( el . hasAttribute ( "tabindex" ) ) return ;
87+
88+ el . setAttribute ( "tabindex" , "0" ) ;
89+ el . setAttribute ( "role" , "button" ) ;
90+
91+ const suggestion = el . dataset . suggestion || el . textContent ;
92+ el . setAttribute ( "aria-label" , `Use chat suggestion: ${ suggestion } ` ) ;
93+ } ) ;
94+ }
7695}
7796
7897class ChatUserMessage extends LightElement {
@@ -262,6 +281,7 @@ class ChatContainer extends LightElement {
262281 this . #onRemoveLoadingMessage
263282 ) ;
264283 this . addEventListener ( "click" , this . #onInputSuggestionClick) ;
284+ this . addEventListener ( "keydown" , this . #onInputSuggestionKeydown) ;
265285 }
266286
267287 disconnectedCallback ( ) : void {
@@ -283,6 +303,7 @@ class ChatContainer extends LightElement {
283303 this . #onRemoveLoadingMessage
284304 ) ;
285305 this . removeEventListener ( "click" , this . #onInputSuggestionClick) ;
306+ this . removeEventListener ( "keydown" , this . #onInputSuggestionKeydown) ;
286307 }
287308
288309 // When user submits input, append it to the chat, and add a loading message
@@ -372,26 +393,41 @@ class ChatContainer extends LightElement {
372393 }
373394
374395 #onInputSuggestionClick( e : Event ) : void {
375- const target = e . target ;
376- if ( ! ( target instanceof HTMLElement ) ) return ;
396+ const { suggestion , submit } = this . #getSuggestion ( e . target ) ;
397+ if ( ! suggestion ) return ;
377398
378- const isSuggestion =
379- target . classList . contains ( " suggestion" ) ||
380- target . dataset . suggestion !== undefined ;
399+ e . preventDefault ( ) ;
400+ this . input . setInputValue ( suggestion , submit ) ;
401+ }
381402
382- if ( ! isSuggestion ) return ;
403+ #onInputSuggestionKeydown( e : KeyboardEvent ) : void {
404+ const isEnter = e . key === "Enter" || e . key === " " ;
405+ if ( ! isEnter ) return ;
406+ const { suggestion, submit } = this . #getSuggestion( e . target ) ;
407+ if ( ! suggestion ) return ;
383408
384409 e . preventDefault ( ) ;
410+ this . input . setInputValue ( suggestion , submit ) ;
411+ }
385412
386- const suggestion = target . dataset . suggestion || target . textContent ;
413+ #getSuggestion( x : EventTarget | null ) : {
414+ suggestion ?: string ;
415+ submit ?: boolean ;
416+ } {
417+ if ( ! ( x instanceof HTMLElement ) ) return { } ;
387418
388- if ( suggestion ) {
389- const doSubmit =
390- target . classList . contains ( "submit" ) ||
391- [ "" , "true" ] . includes ( target . dataset . suggestionSubmit || "false" ) ;
419+ const isSuggestion =
420+ x . classList . contains ( "suggestion" ) || x . dataset . suggestion !== undefined ;
421+ if ( ! isSuggestion ) return { } ;
392422
393- this . input . setInputValue ( suggestion , doSubmit ) ;
394- }
423+ const suggestion = x . dataset . suggestion || x . textContent ;
424+
425+ return {
426+ suggestion : suggestion || undefined ,
427+ submit :
428+ x . classList . contains ( "submit" ) ||
429+ [ "" , "true" ] . includes ( x . dataset . suggestionSubmit || "false" ) ,
430+ } ;
395431 }
396432
397433 #onRemoveLoadingMessage( ) : void {
0 commit comments