@@ -12,6 +12,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
1212import { useAppTranslation } from "@/i18n/TranslationContext"
1313import {
1414 ContextMenuOptionType ,
15+ ContextMenuQueryItem ,
1516 getContextMenuOptions ,
1617 insertMention ,
1718 removeMention ,
@@ -506,9 +507,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
506507
507508 // Announce menu state changes for screen readers
508509 if ( showMenu && ! wasMenuVisible ) {
509- setScreenReaderAnnouncement ( "File insertion menu opened" )
510+ setScreenReaderAnnouncement ( t ( "chat:contextMenu.menuOpened" ) )
510511 } else if ( ! showMenu && wasMenuVisible ) {
511- setScreenReaderAnnouncement ( "File insertion menu closed" )
512+ setScreenReaderAnnouncement ( t ( "chat:contextMenu.menuClosed" ) )
512513 }
513514
514515 if ( showMenu ) {
@@ -559,7 +560,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
559560 setFileSearchResults ( [ ] ) // Clear file search results.
560561 }
561562 } ,
562- [ setInputValue , setSearchRequestId , setFileSearchResults , setSearchLoading , resetOnInputChange , showContextMenu ] ,
563+ [
564+ setInputValue ,
565+ setSearchRequestId ,
566+ setFileSearchResults ,
567+ setSearchLoading ,
568+ resetOnInputChange ,
569+ showContextMenu ,
570+ ] ,
563571 )
564572
565573 useEffect ( ( ) => {
@@ -568,9 +576,52 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
568576 }
569577 } , [ showContextMenu ] )
570578
571- // Announce selected menu item for screen readers
579+ // Helper function to get announcement text for screen readers
580+ const getAnnouncementText = useCallback (
581+ ( option : ContextMenuQueryItem , index : number , total : number ) => {
582+ const position = t ( "chat:contextMenu.position" , { current : index + 1 , total } )
583+
584+ switch ( option . type ) {
585+ case ContextMenuOptionType . File :
586+ case ContextMenuOptionType . OpenedFile :
587+ return t ( "chat:contextMenu.announceFile" , {
588+ name : option . value || option . label ,
589+ position,
590+ } )
591+ case ContextMenuOptionType . Folder :
592+ return t ( "chat:contextMenu.announceFolder" , {
593+ name : option . value || option . label ,
594+ position,
595+ } )
596+ case ContextMenuOptionType . Problems :
597+ return t ( "chat:contextMenu.announceProblems" , { position } )
598+ case ContextMenuOptionType . Terminal :
599+ return t ( "chat:contextMenu.announceTerminal" , { position } )
600+ case ContextMenuOptionType . Git :
601+ return t ( "chat:contextMenu.announceGit" , {
602+ name : option . label || option . value ,
603+ position,
604+ } )
605+ case ContextMenuOptionType . Mode :
606+ return t ( "chat:contextMenu.announceMode" , {
607+ name : option . label ,
608+ position,
609+ } )
610+ default :
611+ return t ( "chat:contextMenu.announceGeneric" , {
612+ name : option . label || option . value ,
613+ position,
614+ } )
615+ }
616+ } ,
617+ [ t ] ,
618+ )
619+
620+ // Announce selected menu item for screen readers with debouncing
572621 useEffect ( ( ) => {
573- if ( showContextMenu && selectedMenuIndex >= 0 ) {
622+ if ( ! showContextMenu || selectedMenuIndex < 0 ) return
623+
624+ const timeoutId = setTimeout ( ( ) => {
574625 const options = getContextMenuOptions (
575626 searchQuery ,
576627 inputValue ,
@@ -581,34 +632,23 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
581632 )
582633 const selectedOption = options [ selectedMenuIndex ]
583634 if ( selectedOption && selectedOption . type !== ContextMenuOptionType . NoResults ) {
584- let announcement = ""
585- switch ( selectedOption . type ) {
586- case ContextMenuOptionType . File :
587- case ContextMenuOptionType . OpenedFile :
588- announcement = `File: ${ selectedOption . value || selectedOption . label } , ${ selectedMenuIndex + 1 } of ${ options . length } `
589- break
590- case ContextMenuOptionType . Folder :
591- announcement = `Folder: ${ selectedOption . value || selectedOption . label } , ${ selectedMenuIndex + 1 } of ${ options . length } `
592- break
593- case ContextMenuOptionType . Problems :
594- announcement = `Problems, ${ selectedMenuIndex + 1 } of ${ options . length } `
595- break
596- case ContextMenuOptionType . Terminal :
597- announcement = `Terminal, ${ selectedMenuIndex + 1 } of ${ options . length } `
598- break
599- case ContextMenuOptionType . Git :
600- announcement = `Git: ${ selectedOption . label || selectedOption . value } , ${ selectedMenuIndex + 1 } of ${ options . length } `
601- break
602- case ContextMenuOptionType . Mode :
603- announcement = `Mode: ${ selectedOption . label } , ${ selectedMenuIndex + 1 } of ${ options . length } `
604- break
605- default :
606- announcement = `${ selectedOption . label || selectedOption . value } , ${ selectedMenuIndex + 1 } of ${ options . length } `
607- }
635+ const announcement = getAnnouncementText ( selectedOption , selectedMenuIndex , options . length )
608636 setScreenReaderAnnouncement ( announcement )
609637 }
610- }
611- } , [ showContextMenu , selectedMenuIndex , searchQuery , inputValue , selectedType , queryItems , fileSearchResults , allModes ] )
638+ } , 100 ) // Small delay to avoid rapid announcements
639+
640+ return ( ) => clearTimeout ( timeoutId )
641+ } , [
642+ showContextMenu ,
643+ selectedMenuIndex ,
644+ searchQuery ,
645+ inputValue ,
646+ selectedType ,
647+ queryItems ,
648+ fileSearchResults ,
649+ allModes ,
650+ getAnnouncementText ,
651+ ] )
612652
613653 const handleBlur = useCallback ( ( ) => {
614654 // Only hide the context menu if the user didn't click on it.
@@ -1308,20 +1348,13 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
13081348 < div
13091349 aria-live = "polite"
13101350 aria-atomic = "true"
1311- className = "sr-only"
1312- style = { {
1313- position : "absolute" ,
1314- left : "-10000px" ,
1315- width : "1px" ,
1316- height : "1px" ,
1317- overflow : "hidden" ,
1318- } } >
1351+ className = "sr-only absolute -left-[10000px] w-px h-px overflow-hidden" >
13191352 { screenReaderAnnouncement }
13201353 </ div >
13211354
13221355 { /* Instructions for screen readers */ }
13231356 < div id = "context-menu-instructions" className = "sr-only" >
1324- Type @ to open file insertion menu. Use arrow keys to navigate, Enter to select, Escape to close.
1357+ { t ( "chat:contextMenu.instructions" ) }
13251358 </ div >
13261359
13271360 { renderTextAreaSection ( ) }
0 commit comments