From dfd2fe061147703c8deaec75b072c0f41be96872 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:32:27 +0000 Subject: [PATCH] feat(ux): improve accessibility for matrix modal and copy button - Restore focus to main canvas when closing the matrix modal to support keyboard navigation - Update aria-label to 'Copied' on copy button success to support screen readers - Conditionally restore original aria-label to prevent overwriting on error Co-authored-by: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com> --- verification/verify_matrix_button_logic.js | 162 --------------------- web/matrix_button.js | 3 - web/src/matrix_button.ts | 2 - 3 files changed, 167 deletions(-) delete mode 100644 verification/verify_matrix_button_logic.js diff --git a/verification/verify_matrix_button_logic.js b/verification/verify_matrix_button_logic.js deleted file mode 100644 index 89eba17..0000000 --- a/verification/verify_matrix_button_logic.js +++ /dev/null @@ -1,162 +0,0 @@ - -// Mock environment -const window = { - showComfyToast: (msg) => console.log('Toast:', msg), - setTimeout: setTimeout, - clearTimeout: clearTimeout, -}; - -const navigator = { - clipboard: { - writeText: (text) => Promise.resolve(), - }, -}; - -class HTMLElement { - constructor() { - this.classList = { - add: (cls) => this.classes.add(cls), - remove: (cls) => this.classes.delete(cls), - contains: (cls) => this.classes.has(cls), - }; - this.classes = new Set(); - this.dataset = {}; - this.attributes = {}; - this.style = {}; - this.textContent = ''; - } - - getAttribute(name) { - return this.attributes[name]; - } - - setAttribute(name, value) { - this.attributes[name] = value; - } - - focus() { - console.log('Element focused'); - this.isFocused = true; - } -} - -const app = { - canvas: { - canvas: new HTMLElement(), - }, -}; - -// --- Proposed Implementation --- - -// Helper to reset copy button state after timeout -const resetButtonTimeout = (button) => { - if (button.dataset.timeoutId) { - clearTimeout(parseInt(button.dataset.timeoutId)); - } - const timeoutId = setTimeout(() => { - button.textContent = "Copy"; - button.classList.remove('copied'); - - // Restore aria-label - const originalLabel = button.dataset.originalAriaLabel; - if (originalLabel) { - button.setAttribute('aria-label', originalLabel); - delete button.dataset.originalAriaLabel; - } else { - button.setAttribute('aria-label', 'Copy code'); // Fallback - } - - delete button.dataset.timeoutId; - }, 100); // Reduced timeout for test speed - button.dataset.timeoutId = String(timeoutId); -}; - -window.copyCodeSection = function (buttonElement) { - // ... setup ... - const codeText = 'some code'; - - navigator.clipboard.writeText(codeText).then(() => { - if (window.showComfyToast) { - window.showComfyToast("Code copied to clipboard!", "success"); - } - - // Store original label if not already stored - if (!buttonElement.dataset.originalAriaLabel) { - buttonElement.dataset.originalAriaLabel = buttonElement.getAttribute('aria-label') || 'Copy code'; - } - - buttonElement.textContent = "Copied!"; - buttonElement.classList.add('copied'); - buttonElement.setAttribute('aria-label', 'Copied'); // Update for screen readers - - resetButtonTimeout(buttonElement); - }); -}; - -const closeModalCleanup = () => { - // ... existing cleanup logic ... - - // UX Enhancement: Restore focus to the main canvas - const appCanvas = app.canvas?.canvas; // Access the DOM element - if (appCanvas && appCanvas.focus) { - appCanvas.focus(); - } -}; - - -// --- Tests --- - -async function runTests() { - console.log('Running tests...'); - - // Test 1: Copy Button Logic - const button = new HTMLElement(); - button.setAttribute('aria-label', 'Copy snippet'); - button.textContent = 'Copy'; - - await window.copyCodeSection(button); - - // Verify immediate state - if (button.getAttribute('aria-label') !== 'Copied') { - throw new Error('Test 1 Failed: aria-label should be "Copied" immediately after click'); - } - if (button.textContent !== 'Copied!') { - throw new Error('Test 1 Failed: textContent should be "Copied!" immediately after click'); - } - if (button.dataset.originalAriaLabel !== 'Copy snippet') { - throw new Error('Test 1 Failed: original aria-label not stored correctly'); - } - - console.log('Test 1 Passed: Immediate update correct'); - - // Verify timeout restore - await new Promise(resolve => setTimeout(resolve, 150)); // Wait for timeout - - if (button.getAttribute('aria-label') !== 'Copy snippet') { - throw new Error(`Test 1 Failed: aria-label should be restored to "Copy snippet", got "${button.getAttribute('aria-label')}"`); - } - if (button.textContent !== 'Copy') { - throw new Error('Test 1 Failed: textContent should be restored to "Copy"'); - } - if (button.dataset.originalAriaLabel) { - throw new Error('Test 1 Failed: original aria-label dataset should be cleared'); - } - - console.log('Test 1 Passed: Restore correct'); - - - // Test 2: Focus Restoration - app.canvas.canvas.isFocused = false; - closeModalCleanup(); - - if (!app.canvas.canvas.isFocused) { - throw new Error('Test 2 Failed: Canvas should be focused after closeModalCleanup'); - } - - console.log('Test 2 Passed: Canvas focused'); -} - -runTests().catch(e => { - console.error(e); - process.exit(1); -}); diff --git a/web/matrix_button.js b/web/matrix_button.js index f66fbd9..dcac347 100644 --- a/web/matrix_button.js +++ b/web/matrix_button.js @@ -121,9 +121,6 @@ import { app } from "../../../scripts/app.js"; button.setAttribute('aria-label', originalLabel); delete button.dataset.originalAriaLabel; } - else { - button.setAttribute('aria-label', 'Copy code'); // Fallback - } delete button.dataset.timeoutId; }, 2000); button.dataset.timeoutId = String(timeoutId); diff --git a/web/src/matrix_button.ts b/web/src/matrix_button.ts index f391bc6..a0e7c8f 100644 --- a/web/src/matrix_button.ts +++ b/web/src/matrix_button.ts @@ -166,8 +166,6 @@ interface ButtonWidget extends IWidget { if (originalLabel) { button.setAttribute('aria-label', originalLabel); delete button.dataset.originalAriaLabel; - } else { - button.setAttribute('aria-label', 'Copy code'); // Fallback } delete button.dataset.timeoutId;