authorclover caruso <git@paperclover.net> 2026-10-06 23:11:12-07:00
committerclover caruso <git@paperclover.net> 2026-10-09 11:21:14-07:00
log8a9ff21d277822603ce8121f569bbb7664fa2e6e
treeb20a7c3fd288f7573a4d4109bbb821009f41dbf0
parent9c3463012eb3fe07f9628e4e35abce6c80fd5355
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

Focus cancellation in destructive confirmation dialogs


1 files changed, 3 insertions(+), 6 deletions(-)

dashboard/web/components/Dialog.tsx+3-6
...@@ -41,10 +41,7 @@ const SHAKE = [0, -8, 8, -8, 8, -8, 8, 0].map((x) => ({ transform: `translateX($...@@ -41,10 +41,7 @@ const SHAKE = [0, -8, 8, -8, 8, -8, 8, 0].map((x) => ({ transform: `translateX($
41const reduced = matchMedia("(prefers-reduced-motion: reduce)");41const reduced = matchMedia("(prefers-reduced-motion: reduce)");
42let dialogs = 0;42let dialogs = 0;
4343
44/**44/** Destructive confirmations focus back; fields with autofocus take precedence. */
45 * Enter confirms and Esc, the backdrop, and "back" cancel. Confirm starts focused unless the body has an
46 * `autofocus` field; the browser checks the body's constraints (`required`, `pattern`…) before `onConfirm` runs.
47 */
48export const showConfirmDialog = (options: DialogOptions) => open(options);45export const showConfirmDialog = (options: DialogOptions) => open(options);
4946
50/** Like showConfirmDialog with a text field; Enter submits once `validateInput` passes. */47/** Like showConfirmDialog with a text field; Enter submits once `validateInput` passes. */
...@@ -120,10 +117,10 @@ function open(options: DialogOptions, field?: TextDialogOptions) {...@@ -120,10 +117,10 @@ function open(options: DialogOptions, field?: TextDialogOptions) {
120 <Show when={options.body}><fieldset disabled={pending()}>{body}</fieldset></Show>117 <Show when={options.body}><fieldset disabled={pending()}>{body}</fieldset></Show>
121 <Show when={error()}><p class="error" role="alert">{error()}</p></Show>118 <Show when={error()}><p class="error" role="alert">{error()}</p></Show>
122 <div class="actions">119 <div class="actions">
123 <button type="button" class="button" disabled={pending()} onClick={close}>back<kbd aria-hidden="true">esc</kbd></button>120 <button type="button" class="button" disabled={pending()} autofocus={options.destructive} onClick={close}>back<kbd aria-hidden="true">esc</kbd></button>
124 {/* aria-disabled rather than disabled while pending, so focus stays put for a retry. */}121 {/* aria-disabled rather than disabled while pending, so focus stays put for a retry. */}
125 <button type="submit" class="button primary" classList={{ danger: options.destructive }}122 <button type="submit" class="button primary" classList={{ danger: options.destructive }}
126 autofocus={!field} disabled={!valid()} aria-disabled={pending()} aria-busy={pending()}>123 autofocus={!field && !options.destructive} disabled={!valid()} aria-disabled={pending()} aria-busy={pending()}>
127 {options.confirmLabel}<kbd aria-hidden="true">enter</kbd>124 {options.confirmLabel}<kbd aria-hidden="true">enter</kbd>
128 </button>125 </button>
129 </div>126 </div>