Skip to content

Commit 95d930d

Browse files
Merge pull request #101 from gooddata/feat/accessibility
DP-3414: ask ai accessibility
2 parents 007de35 + 21d85f4 commit 95d930d

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

static/js/ask-ai-accessibility.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)