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 @@
84 </div>84 </div>
8585
86 <script src="lib/jquery-3.5.1.min.js"></script>86 <script src="lib/jquery-3.5.1.min.js"></script>
87 <script src="scripts/login.js"></script>87 <script src="scripts/login.js" type="module"></script>
88</body>88</body>
8989
90</html>90</html>
public/script.js+2 -0
@@ -266,6 +266,7 @@ import { initDataMaid } from './scripts/data-maid.js';
266import { clearItemizedPrompts, deleteItemizedPrompts, findItemizedPromptSet, initItemizedPrompts, itemizedParams, itemizedPrompts, loadItemizedPrompts, promptItemize, replaceItemizedPromptText, saveItemizedPrompts } from './scripts/itemized-prompts.js';266import { clearItemizedPrompts, deleteItemizedPrompts, findItemizedPromptSet, initItemizedPrompts, itemizedParams, itemizedPrompts, loadItemizedPrompts, promptItemize, replaceItemizedPromptText, saveItemizedPrompts } from './scripts/itemized-prompts.js';
267import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMessage, system_message_types, system_messages } from './scripts/system-messages.js';267import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMessage, system_message_types, system_messages } from './scripts/system-messages.js';
268import { event_types, eventSource } from './scripts/events.js';268import { event_types, eventSource } from './scripts/events.js';
269import { initAccessibility } from './scripts/a11y.js';
269270
270// API OBJECT FOR EXTERNAL WIRING271// API OBJECT FOR EXTERNAL WIRING
271globalThis.SillyTavern = {272globalThis.SillyTavern = {
@@ -687,6 +688,7 @@ async function firstLoadInit() {
687 initCustomSelectedSamplers();688 initCustomSelectedSamplers();
688 initDataMaid();689 initDataMaid();
689 initItemizedPrompts();690 initItemizedPrompts();
691 initAccessibility();
690 addDebugFunctions();692 addDebugFunctions();
691 doDailyExtensionUpdatesCheck();693 doDailyExtensionUpdatesCheck();
692 await hideLoader();694 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
6const 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
18const 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
31const 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>} */
45const 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 */
64function 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
79function 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
102export function initAccessibility() {
103 setAccessibilityObserver();
104}
public/scripts/login.js+4 -0
@@ -1,3 +1,5 @@
1import { initAccessibility } from './a11y.js';
2
1/**3/**
2 * CRSF token for requests.4 * CRSF token for requests.
3 */5 */
@@ -265,6 +267,8 @@ function configureDiscreetLogin() {
265}267}
266268
267(async function () {269(async function () {
270 initAccessibility();
271
268 csrfToken = await getCsrfToken();272 csrfToken = await getCsrfToken();
269 const userList = await getUserList();273 const userList = await getUserList();
270274