11$ ( document ) . ready ( function ( ) {
22 let isModalPresent = false ;
33 const $askAiButton = $ ( "#ask-ai-button" ) ;
4+ const observedShadowRoots = new WeakSet ( ) ;
45
56 function updateModalState ( ) {
67 const $kapaContainer = $ ( "#kapa-widget-container" ) ;
@@ -16,10 +17,12 @@ $(document).ready(function () {
1617 if ( exists && ! isModalPresent ) {
1718 isModalPresent = true ;
1819 $askAiButton . attr ( "aria-expanded" , "true" ) ;
20+ $askAiButton . attr ( "aria-controls" , "kapa-modal-content" ) ;
1921 enhanceModalAccessibility ( shadowRoot ) ;
2022 } else if ( ! exists && isModalPresent ) {
2123 isModalPresent = false ;
2224 $askAiButton . attr ( "aria-expanded" , "false" ) ;
25+ $askAiButton . removeAttr ( "aria-controls" ) ;
2326 }
2427 }
2528
@@ -61,11 +64,12 @@ $(document).ready(function () {
6164 } ) ;
6265
6366 // Watch for dynamically added buttons
64- if ( ! shadowRoot . getAttribute ( "data-answer-buttons-observer" ) ) {
65- shadowRoot . setAttribute ( "data-answer-buttons-observer" , "true" ) ;
67+ if ( ! observedShadowRoots . has ( shadowRoot ) ) {
68+ observedShadowRoots . add ( shadowRoot ) ;
6669
6770 const answerButtonsObserver = new MutationObserver ( ( ) => {
6871 setupAnswerButtonsAccessibility ( shadowRoot ) ;
72+ setupRecaptchaAccessibility ( shadowRoot ) ;
6973 } ) ;
7074
7175 answerButtonsObserver . observe ( shadowRoot , {
@@ -75,6 +79,72 @@ $(document).ready(function () {
7579 }
7680 }
7781
82+ function setupRecaptchaAccessibility ( shadowRoot ) {
83+ const recaptchaLink = Array . from ( shadowRoot . querySelectorAll ( "a" ) ) . find ( a => a . textContent . trim ( ) === "reCAPTCHA" ) ;
84+
85+ if ( ! recaptchaLink ) {
86+ return ;
87+ }
88+
89+ if ( recaptchaLink . hasAttribute ( "data-recaptcha-a11y-setup" ) ) {
90+ return ;
91+ }
92+
93+ recaptchaLink . setAttribute ( "data-recaptcha-a11y-setup" , "true" ) ;
94+
95+ // Set proper button semantics since it opens a dialog
96+ recaptchaLink . setAttribute ( "role" , "button" ) ;
97+
98+ // Set accessible name
99+ if ( ! recaptchaLink . getAttribute ( "aria-label" ) ) {
100+ recaptchaLink . setAttribute ( "aria-label" , "Protected by reCAPTCHA" ) ;
101+ }
102+
103+ // Make keyboard accessible
104+ if ( ! recaptchaLink . hasAttribute ( "tabindex" ) ) {
105+ recaptchaLink . setAttribute ( "tabindex" , "0" ) ;
106+ }
107+
108+ // Setup reCAPTCHA dialog accessibility
109+ recaptchaLink . addEventListener ( "click" , function ( ) {
110+ recaptchaLink . setAttribute ( "aria-expanded" , "true" ) ;
111+
112+ setTimeout ( ( ) => {
113+ const dialog = shadowRoot . querySelector ( ".mantine-Popover-dropdown[role='dialog']" ) ;
114+ if ( dialog ) {
115+ const dialogText = dialog . querySelector ( "p" ) ;
116+ if ( dialogText && ! dialogText . id ) {
117+ dialogText . id = "recaptcha-dialog-description" ;
118+ }
119+
120+ if ( ! dialog . getAttribute ( "aria-label" ) ) {
121+ dialog . setAttribute ( "aria-label" , "reCAPTCHA information" ) ;
122+ }
123+
124+ if ( dialogText && dialogText . id ) {
125+ dialog . setAttribute ( "aria-describedby" , dialogText . id ) ;
126+ }
127+
128+ dialog . setAttribute ( "aria-modal" , "true" ) ;
129+ dialog . focus ( ) ;
130+ }
131+ } , 100 ) ;
132+ } ) ;
133+
134+ // Handle dialog close to update aria-expanded
135+ const closeObserver = new MutationObserver ( ( ) => {
136+ const dialog = shadowRoot . querySelector ( ".mantine-Popover-dropdown[role='dialog']" ) ;
137+ if ( ! dialog ) {
138+ recaptchaLink . setAttribute ( "aria-expanded" , "false" ) ;
139+ }
140+ } ) ;
141+
142+ closeObserver . observe ( shadowRoot , {
143+ childList : true ,
144+ subtree : true
145+ } ) ;
146+ }
147+
78148 function setupDeepThinkingButtonAccessibility ( shadowRoot ) {
79149 const deepThinkingIcon = shadowRoot . querySelector ( ".tabler-icon-file-search" ) ;
80150 if ( ! deepThinkingIcon ) return ;
@@ -171,12 +241,12 @@ $(document).ready(function () {
171241 }
172242 }
173243
174- // Style back to top button
244+ // Style send message button (up arrow icon)
175245 const backToTopSvg = shadowRoot . querySelector ( ".tabler-icon-arrow-up" ) ;
176246 if ( backToTopSvg ) {
177247 const backToTopButton = backToTopSvg . closest ( "button" ) ;
178248 if ( backToTopButton && ! backToTopButton . getAttribute ( "aria-label" ) ) {
179- backToTopButton . setAttribute ( "aria-label" , "Back to top " ) ;
249+ backToTopButton . setAttribute ( "aria-label" , "Send message " ) ;
180250 if ( ! backToTopSvg . getAttribute ( "aria-hidden" ) ) {
181251 backToTopSvg . setAttribute ( "aria-hidden" , "true" ) ;
182252 }
@@ -185,51 +255,14 @@ $(document).ready(function () {
185255
186256 setupDeepThinkingButtonAccessibility ( shadowRoot ) ;
187257 setupAnswerButtonsAccessibility ( shadowRoot ) ;
258+ setupRecaptchaAccessibility ( shadowRoot ) ;
188259
189260 // Fix input accessibility
190261 const kpaInput = shadowRoot . querySelector ( "#kapa-ask-ai-input" ) ;
191262 if ( kpaInput ) {
192263 kpaInput . setAttribute ( "aria-label" , "Ask me a question about GoodData" ) ;
193264 kpaInput . setAttribute ( "autocomplete" , "off" ) ;
194265 }
195-
196- // Fix reCAPTCHA link accessibility
197- const recaptchaLink = shadowRoot . querySelector ( "a[data-underline='hover']:not([href])" ) ;
198- if ( recaptchaLink && recaptchaLink . textContent . includes ( "reCAPTCHA" ) ) {
199- if ( ! recaptchaLink . getAttribute ( "aria-label" ) ) {
200- recaptchaLink . setAttribute ( "aria-label" , "Protected by reCAPTCHA" ) ;
201- }
202-
203- recaptchaLink . setAttribute ( "role" , "button" ) ;
204-
205- if ( ! recaptchaLink . hasAttribute ( "href" ) ) {
206- recaptchaLink . setAttribute ( "tabindex" , "0" ) ;
207- }
208-
209- // Setup reCAPTCHA dialog accessibility
210- recaptchaLink . addEventListener ( "click" , function ( ) {
211- setTimeout ( ( ) => {
212- const dialog = shadowRoot . querySelector ( ".mantine-Popover-dropdown[role='dialog']" ) ;
213- if ( dialog ) {
214- const dialogText = dialog . querySelector ( "p" ) ;
215- if ( dialogText && ! dialogText . id ) {
216- dialogText . id = "recaptcha-dialog-description" ;
217- }
218-
219- if ( ! dialog . getAttribute ( "aria-label" ) ) {
220- dialog . setAttribute ( "aria-label" , "reCAPTCHA information" ) ;
221- }
222-
223- if ( dialogText && dialogText . id ) {
224- dialog . setAttribute ( "aria-describedby" , dialogText . id ) ;
225- }
226-
227- dialog . setAttribute ( "aria-modal" , "true" ) ;
228- dialog . focus ( ) ;
229- }
230- } , 100 ) ;
231- } ) ;
232- }
233266 }
234267
235268 setTimeout ( function ( ) {
0 commit comments