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 + +