From 03dff377e1e3943d27c6cb777bd87e8dd5884b3d Mon Sep 17 00:00:00 2001 From: Shin5hi <200498632+Shin5hi@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:51:23 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20keyboard=20shor?= =?UTF-8?q?tcuts=20for=20primary=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented keyboard shortcuts (Ctrl+Enter / Cmd+Enter) for the main action button to enhance accessibility for power users. Refactored the DOM loading state to preserve child nodes rather than destroying nested `` visual elements. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/palette.md | 4 ++++ index.html | 32 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index fa2fd8c..f05b4b1 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,3 +1,7 @@ ## 2024-05-23 - Accessible Toast Notifications **Learning:** Toast notifications often disappear too quickly for some users. Implementing a 5000ms minimum duration AND a manual close button ensures compliance with accessibility standards (WCAG 2.2.1 Timing Adjustable) and improves usability for everyone. **Action:** When implementing temporary feedback messages, always include a visual close button and ensure the timeout is sufficient (>= 5000ms), or allow user preference to extend it. + +## 2024-04-10 - Keyboard Shortcuts and DOM Node Preservation +**Learning:** When adding visual `` hints to buttons for keyboard shortcuts, using `textContent` for state saving (like loading text) strips away the nested HTML tags. +**Action:** Preserve structural integrity by saving `childNodes` (e.g., `Array.from(element.childNodes)`) and restoring them via `replaceChildren(...)` instead of `textContent`. Always implement cross-platform (Ctrl/Meta) listeners, use `event.preventDefault()`, and programmatically focus the triggered element for keyboard shortcuts. diff --git a/index.html b/index.html index 4098199..6f43046 100644 --- a/index.html +++ b/index.html @@ -108,8 +108,8 @@

Welcome to 5ive

Sample accessible button component:

- @@ -122,6 +122,20 @@

Welcome to 5ive

const feedback = document.getElementById('feedback'); let feedbackTimeout; + // Pre-create elements for optimization + const cachedSpinner = document.createElement('span'); + cachedSpinner.className = 'spinner'; + cachedSpinner.setAttribute('aria-hidden', 'true'); + const loadingText = document.createTextNode('Loading...'); + + document.addEventListener('keydown', (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + e.preventDefault(); + actionBtn.focus(); + actionBtn.click(); + } + }); + actionBtn.addEventListener('click', () => { // Clear any existing timeout if (feedbackTimeout) clearTimeout(feedbackTimeout); @@ -131,24 +145,18 @@

Welcome to 5ive

feedback.textContent = ''; // Set loading state - // ✅ Sentinel: Avoid innerHTML to prevent XSS - const originalText = actionBtn.textContent; + // ✅ Sentinel: Avoid innerHTML to prevent XSS, preserve nodes + const originalNodes = Array.from(actionBtn.childNodes); actionBtn.disabled = true; actionBtn.setAttribute('aria-busy', 'true'); - actionBtn.textContent = ''; - - const spinner = document.createElement('span'); - spinner.className = 'spinner'; - spinner.setAttribute('aria-hidden', 'true'); - actionBtn.appendChild(spinner); - actionBtn.appendChild(document.createTextNode('Loading...')); + actionBtn.replaceChildren(cachedSpinner, loadingText); // Simulate async operation setTimeout(() => { // Reset state actionBtn.disabled = false; actionBtn.removeAttribute('aria-busy'); - actionBtn.textContent = originalText; + actionBtn.replaceChildren(...originalNodes); // Show success with icon and transition // ✅ Sentinel: Using textContent and DOM methods instead of innerHTML