@@ -16,12 +16,222 @@ $(document).ready(function () {
1616 if ( exists && ! isModalPresent ) {
1717 isModalPresent = true ;
1818 $askAiButton . attr ( "aria-expanded" , "true" ) ;
19+ enhanceModalAccessibility ( shadowRoot ) ;
1920 } else if ( ! exists && isModalPresent ) {
2021 isModalPresent = false ;
2122 $askAiButton . attr ( "aria-expanded" , "false" ) ;
2223 }
2324 }
2425
26+ function setupAnswerButtonsAccessibility ( shadowRoot ) {
27+ const buttons = shadowRoot . querySelectorAll ( "button.mantine-Button-root" ) ;
28+ if ( ! buttons . length ) return ;
29+
30+ buttons . forEach ( ( button ) => {
31+ if ( button . hasAttribute ( "data-button-a11y-setup" ) ) return ;
32+
33+ const buttonText = button . textContent . trim ( ) ;
34+ const actionTypes = [ "Good answer" , "Bad answer" , "New chat" , "Copy" ] ;
35+
36+ if ( ! actionTypes . some ( text => buttonText . includes ( text ) ) ) return ;
37+
38+ button . setAttribute ( "data-button-a11y-setup" , "true" ) ;
39+
40+ if ( ! button . getAttribute ( "aria-label" ) ) {
41+ button . setAttribute ( "aria-label" , buttonText ) ;
42+ }
43+
44+ const icon = button . querySelector ( "svg" ) ;
45+ if ( icon ) {
46+ icon . setAttribute ( "role" , "img" ) ;
47+ icon . setAttribute ( "aria-label" , buttonText ) ;
48+ icon . setAttribute ( "aria-hidden" , "true" ) ;
49+ }
50+
51+ button . addEventListener ( "click" , function ( ) {
52+ setTimeout ( ( ) => {
53+ const popup = shadowRoot . querySelector ( ".mantine-Popover-dropdown" ) ;
54+ if ( popup ) {
55+ popup . focus ( ) ;
56+ }
57+ } , 50 ) ;
58+ } ) ;
59+
60+ button . setAttribute ( "data-button-style-applied" , "true" ) ;
61+ } ) ;
62+
63+ // Watch for dynamically added buttons
64+ if ( ! shadowRoot . getAttribute ( "data-answer-buttons-observer" ) ) {
65+ shadowRoot . setAttribute ( "data-answer-buttons-observer" , "true" ) ;
66+
67+ const answerButtonsObserver = new MutationObserver ( ( ) => {
68+ setupAnswerButtonsAccessibility ( shadowRoot ) ;
69+ } ) ;
70+
71+ answerButtonsObserver . observe ( shadowRoot , {
72+ childList : true ,
73+ subtree : true
74+ } ) ;
75+ }
76+ }
77+
78+ function setupDeepThinkingButtonAccessibility ( shadowRoot ) {
79+ const deepThinkingIcon = shadowRoot . querySelector ( ".tabler-icon-file-search" ) ;
80+ if ( ! deepThinkingIcon ) return ;
81+
82+ const deepThinkingButton = deepThinkingIcon . closest ( "button" ) ;
83+ if ( ! deepThinkingButton || deepThinkingButton . hasAttribute ( "data-dt-setup" ) ) return ;
84+
85+ deepThinkingButton . setAttribute ( "data-dt-setup" , "true" ) ;
86+
87+ deepThinkingButton . removeAttribute ( "aria-haspopup" ) ;
88+ deepThinkingButton . removeAttribute ( "aria-expanded" ) ;
89+ deepThinkingButton . removeAttribute ( "aria-controls" ) ;
90+
91+ deepThinkingButton . setAttribute ( "aria-pressed" , "false" ) ;
92+ deepThinkingButton . setAttribute ( "aria-label" , "Deep thinking off" ) ;
93+
94+ // Inject styles for better state indication beyond color
95+ if ( ! shadowRoot . getElementById ( "deep-thinking-styles" ) ) {
96+ const style = document . createElement ( "style" ) ;
97+ style . id = "deep-thinking-styles" ;
98+ style . textContent = `
99+ button[aria-pressed="true"] .tabler-icon-file-search {
100+ stroke-width: 2.5;
101+ }
102+ button[aria-pressed="true"] {
103+ box-shadow: inset 0 0 0 1px currentColor, 0 0 0 2px currentColor;
104+ }
105+ ` ;
106+ shadowRoot . appendChild ( style ) ;
107+ }
108+
109+ let isPressed = false ;
110+
111+ deepThinkingButton . addEventListener ( "click" , function ( e ) {
112+ setTimeout ( ( ) => {
113+ isPressed = ! isPressed ;
114+ deepThinkingButton . setAttribute ( "aria-pressed" , isPressed ? "true" : "false" ) ;
115+ deepThinkingButton . setAttribute ( "aria-label" , isPressed ? "Deep thinking on" : "Deep thinking off" ) ;
116+ } , 50 ) ;
117+ } ) ;
118+
119+ // Monitor popover appearance and attribute changes
120+ const buttonObserver = new MutationObserver ( ( mutations ) => {
121+ mutations . forEach ( ( mutation ) => {
122+ if ( mutation . type === "childList" ) {
123+ const popoverDropdown = shadowRoot . querySelector ( ".mantine-Popover-dropdown" ) ;
124+ if ( popoverDropdown && popoverDropdown . id ) {
125+ deepThinkingButton . setAttribute ( "aria-describedby" , popoverDropdown . id ) ;
126+ } else if ( ! popoverDropdown ) {
127+ deepThinkingButton . removeAttribute ( "aria-describedby" ) ;
128+ }
129+ setupAnswerButtonsAccessibility ( shadowRoot ) ;
130+ const kpaInput = shadowRoot . querySelector ( "#kapa-ask-ai-input" ) ;
131+ if ( kpaInput && ! kpaInput . hasAttribute ( "aria-label" ) ) {
132+ kpaInput . setAttribute ( "aria-label" , "Ask me a question about GoodData" ) ;
133+ kpaInput . setAttribute ( "autocomplete" , "off" ) ;
134+ }
135+ } else if ( mutation . type === "attributes" ) {
136+ if ( deepThinkingButton . hasAttribute ( "aria-expanded" ) ) {
137+ deepThinkingButton . removeAttribute ( "aria-expanded" ) ;
138+ }
139+ if ( deepThinkingButton . hasAttribute ( "aria-haspopup" ) ) {
140+ deepThinkingButton . removeAttribute ( "aria-haspopup" ) ;
141+ }
142+ if ( deepThinkingButton . hasAttribute ( "aria-controls" ) ) {
143+ deepThinkingButton . removeAttribute ( "aria-controls" ) ;
144+ }
145+ }
146+ } ) ;
147+ } ) ;
148+
149+ buttonObserver . observe ( shadowRoot , {
150+ childList : true ,
151+ subtree : true
152+ } ) ;
153+
154+ buttonObserver . observe ( deepThinkingButton , {
155+ attributes : true ,
156+ attributeFilter : [ "class" , "aria-expanded" , "aria-haspopup" , "aria-controls" ]
157+ } ) ;
158+ }
159+
160+ function enhanceModalAccessibility ( shadowRoot ) {
161+ // Style close button
162+ const closeButton = shadowRoot . querySelector ( ".mantine-Modal-close" ) ;
163+ if ( closeButton && ! closeButton . getAttribute ( "aria-label" ) ) {
164+ closeButton . setAttribute ( "aria-label" , "Close" ) ;
165+ }
166+
167+ if ( closeButton ) {
168+ const svg = closeButton . querySelector ( "svg" ) ;
169+ if ( svg && ! svg . getAttribute ( "aria-hidden" ) ) {
170+ svg . setAttribute ( "aria-hidden" , "true" ) ;
171+ }
172+ }
173+
174+ // Style back to top button
175+ const backToTopSvg = shadowRoot . querySelector ( ".tabler-icon-arrow-up" ) ;
176+ if ( backToTopSvg ) {
177+ const backToTopButton = backToTopSvg . closest ( "button" ) ;
178+ if ( backToTopButton && ! backToTopButton . getAttribute ( "aria-label" ) ) {
179+ backToTopButton . setAttribute ( "aria-label" , "Back to top" ) ;
180+ if ( ! backToTopSvg . getAttribute ( "aria-hidden" ) ) {
181+ backToTopSvg . setAttribute ( "aria-hidden" , "true" ) ;
182+ }
183+ }
184+ }
185+
186+ setupDeepThinkingButtonAccessibility ( shadowRoot ) ;
187+ setupAnswerButtonsAccessibility ( shadowRoot ) ;
188+
189+ // Fix input accessibility
190+ const kpaInput = shadowRoot . querySelector ( "#kapa-ask-ai-input" ) ;
191+ if ( kpaInput ) {
192+ kpaInput . setAttribute ( "aria-label" , "Ask me a question about GoodData" ) ;
193+ kpaInput . setAttribute ( "autocomplete" , "off" ) ;
194+ }
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+ }
233+ }
234+
25235 setTimeout ( function ( ) {
26236 const $kapaContainer = $ ( "#kapa-widget-container" ) ;
27237
@@ -37,3 +247,4 @@ $(document).ready(function () {
37247 }
38248 } , 700 ) ;
39249} ) ;
250+
0 commit comments