From dc9d16f219740b19627613be6b3cdebd0f68eb90 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:03:32 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improvement]=20?= =?UTF-8?q?Add=20ARIA=20disabled=20state=20to=20editor=20save=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 네이티브 `disabled` 속성을 사용할 경우 버튼이 탭 이동 순서에서 제외되어, 폼 유효성 검사 실패 시 화면 판독기 사용자가 버튼이 비활성화된 이유(툴팁 등)를 파악하기 어려운 접근성 문제가 있었습니다. 이를 개선하기 위해 저는 다음과 같이 작업했습니다: - 저장 버튼의 `disabled` 속성 대신 `aria-disabled="true"`를 사용하도록 변경하여 포커스 가능 상태를 유지했습니다. - 버튼이 `aria-disabled="true"`인 상태에서 클릭하거나 엔터를 눌러 폼이 제출될 때, 네이티브 HTML5 유효성 검사 팝업이 뜨거나 액션이 실행되지 않도록 클릭 및 폼 전송 이벤트에 방어 로직(`event.preventDefault()`)을 추가했습니다. - E2E 테스트에서 발생하던 `modulepreload` 누락 문제를 수정했습니다. - `.jules/palette.md` 파일에 이번 접근성 개선을 통해 얻은 배움을 기록해 두었습니다. --- .jules/palette.md | 4 ++++ app.js | 18 +++++++++++++++++- index.html | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.jules/palette.md b/.jules/palette.md index 0bbf5248..cd20f66f 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -115,3 +115,7 @@ ## $(date +%Y-%m-%d) - Prevent accidental data loss in inline editors **Learning:** Forms that take a long time to fill out (like a WBS editor) are prone to accidental closure by users pressing `Escape` or clicking cancel. This causes immediate data loss without any warning, resulting in frustration. **Action:** When working on editors that can be dismissed, track whether the user has modified any fields compared to their initial state. If there are changes, intercept the close action and present a confirmation dialog (`window.confirm`) to ensure they really want to discard their edits. Bypass this for intentional saves or explicit data overrides. + +## 2026-08-21 - Preserve focus on invalid forms using aria-disabled +**Learning:** When native `disabled` is used on a form submit button, it disappears from the tab order. This is jarring when validation fails interactively, as the user cannot focus the button to read its title or understand why it is disabled. +**Action:** Use `aria-disabled` for form submit buttons linked to interactive validation. Ensure form submit events and button click events prevent default behavior when the button is marked as disabled. diff --git a/app.js b/app.js index a04aae71..47de9ea9 100644 --- a/app.js +++ b/app.js @@ -401,6 +401,12 @@ function bindTableEvents(renderDraftValidation, updateEditorDraftFromEvent) { return; } + const saveButton = event.target.closest('button[type="submit"]'); + if (saveButton && saveButton.getAttribute('aria-disabled') === 'true') { + event.preventDefault(); + return; + } + if (!event.target.closest('input, select, button, label, .drag-handle')) { openEditor({ mode: 'edit', targetId: taskId }); } @@ -429,6 +435,12 @@ function bindTableEvents(renderDraftValidation, updateEditorDraftFromEvent) { return; } event.preventDefault(); + + const saveButton = form.querySelector('button[type="submit"]'); + if (saveButton && saveButton.getAttribute('aria-disabled') === 'true') { + return; + } + renderDraftValidation.flush(); saveEditor(); }); @@ -1068,7 +1080,11 @@ function renderEditorValidation() { const saveButton = form.querySelector('button[type="submit"]'); if (saveButton) { - saveButton.disabled = errors.length > 0; + if (errors.length > 0) { + saveButton.setAttribute('aria-disabled', 'true'); + } else { + saveButton.removeAttribute('aria-disabled'); + } saveButton.title = errors.length > 0 ? '입력값을 올바르게 수정해야 저장할 수 있습니다.' : '저장 (Enter)'; } diff --git a/index.html b/index.html index d24b2a88..acce6789 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,8 @@ ScopeWeave Planner + +