a11y: Setup observers for element roles (#4469) * a11y: Setup observers for element roles * Reuse a function * Add error handling

ff8d7d64fc77b60316d6cd56fc16defc2b1821f5

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
4 files changed, +111 -1Showing whitespace changes
public/login.html+1 -1
@@ -84,7 +84,7 @@
8484 </div>
8585
8686 <script src="lib/jquery-3.5.1.min.js"></script>
8787 <script src="scripts/login.js" type="module"></script>
8888</body>
8989
9090</html>
public/script.js+2 -0
@@ -266,6 +266,7 @@ import { initDataMaid } from './scripts/data-maid.js';
266266import { clearItemizedPrompts, deleteItemizedPrompts, findItemizedPromptSet, initItemizedPrompts, itemizedParams, itemizedPrompts, loadItemizedPrompts, promptItemize, replaceItemizedPromptText, saveItemizedPrompts } from './scripts/itemized-prompts.js';
267267import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMessage, system_message_types, system_messages } from './scripts/system-messages.js';
268268import { event_types, eventSource } from './scripts/events.js';
269+import { initAccessibility } from './scripts/a11y.js';
269270
270271// API OBJECT FOR EXTERNAL WIRING
271272globalThis.SillyTavern = {
@@ -687,6 +688,7 @@ async function firstLoadInit() {
687688 initCustomSelectedSamplers();
688689 initDataMaid();
689690 initItemizedPrompts();
691+ initAccessibility();
690692 addDebugFunctions();
691693 doDailyExtensionUpdatesCheck();
692694 await hideLoader();
public/scripts/a11y.js+104 -0
@@ -0,0 +1,104 @@
1+/**
2+ * Shared module between login and main app.
3+ * Be careful what you import!
4+ */
5+
6+const buttonSelectors = [
7+ '.menu_button',
8+ '.right_menu_button',
9+ '.mes_button',
10+ '.drawer-icon',
11+ '.inline-drawer-icon',
12+ '.swipe_left',
13+ '.swipe_right',
14+ '.character_select',
15+ '.tags .tag',
16+].join(', ');
17+
18+const listSelectors = [
19+ '.options-content',
20+ '.list-group',
21+ '#rm_print_characters_block',
22+ '#rm_group_members',
23+ '#rm_group_add_members',
24+ '.tag_view_list_tags',
25+ '.secretKeyManagerList',
26+ '.recentChatList',
27+ '.dataMaidCategoryContent',
28+ '#userList',
29+].join(', ');
30+
31+const listItemSelectors = [
32+ '.options-content .interactable',
33+ '.list-group .list-group-item',
34+ '#rm_print_characters_block .entity_block',
35+ '#rm_group_members .group_member',
36+ '#rm_group_add_members .group_member',
37+ '.tag_view_list_tags .tag_view_item',
38+ '.secretKeyManagerList .secretKeyManagerItem',
39+ '.recentChatList .recentChat',
40+ '.dataMaidCategoryContent .dataMaidItem',
41+ '#userList .userSelect',
42+].join(', ');
43+
44+/** @type {Record<string, (element: Element) => void>} */
45+const a11yRules = {
46+ [buttonSelectors]: (element) => {
47+ element.setAttribute('role', 'button');
48+ },
49+ [listSelectors]: (element) => {
50+ element.setAttribute('role', 'list');
51+ },
52+ [listItemSelectors]: (element) => {
53+ element.setAttribute('role', 'listitem');
54+ },
55+ '#toast-container .toast-message': (element) => {
56+ element.setAttribute('role', 'alert');
57+ },
58+};
59+
60+/**
61+ * Apply accessibility rules to an element.
62+ * @param {Element} element Element to process.
63+ */
64+function applyA11yRules(element) {
65+ try {
66+ for (const [selector, rule] of Object.entries(a11yRules)) {
67+ // Apply if the element directly matches the selector
68+ if (element.matches(selector)) {
69+ rule(element);
70+ }
71+ // Apply the rule to descendants
72+ element.querySelectorAll(selector).forEach(rule);
73+ }
74+ } catch (error) {
75+ console.error('Error applying accessibility rules to element:', element, error);
76+ }
77+}
78+
79+function setAccessibilityObserver() {
80+ // Apply for existing elements
81+ applyA11yRules(document.body);
82+
83+ // Setup observer for dynamic content
84+ const observer = new MutationObserver((mutationsList) => {
85+ for (const mutation of mutationsList) {
86+ if (mutation.type === 'childList') {
87+ for (const addedNode of mutation.addedNodes) {
88+ if (addedNode instanceof Element && addedNode.nodeType === Node.ELEMENT_NODE) {
89+ applyA11yRules(addedNode);
90+ }
91+ }
92+ }
93+ }
94+ });
95+
96+ observer.observe(document.body, {
97+ childList: true,
98+ subtree: true,
99+ });
100+}
101+
102+export function initAccessibility() {
103+ setAccessibilityObserver();
104+}
public/scripts/login.js+4 -0
@@ -1,3 +1,5 @@
1+import { initAccessibility } from './a11y.js';
2+
13/**
24 * CRSF token for requests.
35 */
@@ -265,6 +267,8 @@ function configureDiscreetLogin() {
265267}
266268
267269(async function () {
270+ initAccessibility();
271+
268272 csrfToken = await getCsrfToken();
269273 const userList = await getUserList();
270274