View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012059 | Taler | other | public | 2026-11-10 23:42 | 2026-11-10 23:43 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012059: web-util: Date fields: the calendar dialog opens with focus left behind the overlay, and closing it drops focus to the page body | ||||
| Description | What happens: absoluteTimeText/isoDateText fields (VQF 902.x KYC forms, AML decision Information 'Expiration', AML Rules) open a modal Calendar inside Dialog when the calendar button is clicked. Focus stays on the calendar button underneath the grey overlay; keyboard users have to Tab through the backdrop and the year/month arrows to reach a day. After a day is picked, the dialog unmounts and focus falls to BODY, so Tab restarts at the top of the page. Escape does nothing. Expected: Opening the picker focuses the selected day (Enter keeps it, Tab walks the month); picking a day or pressing Escape closes the picker and returns focus to the calendar button. Where: - packages/web-util/src/forms/Calendar.tsx:53 - packages/web-util/src/forms/Dialog.tsx:13 Measured in headless Chromium (document.activeElement): before: open: calendar trigger BUTTON behind the overlay; after picking a day: BODY; Escape: no effect after: open: day BUTTON '11' (selected day); after 'Next month' click: stays on 'Next month'; after picking a day: calendar trigger BUTTON; after Escape: calendar trigger BUTTON Proposed fix: Calendar focuses its selected day on mount (useAutoFocus, so navigating months does not pull focus back). Dialog remembers the focused element when it opens and restores it when it closes, and closes on Escape (onClose read through a ref, because callers pass an inline arrow). Needs the useAutoFocus helper from web-util--form-focus.diff. Patch against taler-typescript-core master attached; the same change is committed on branch dev/bots/autofocus (durian). Screenshots attached (focused element outlined in red): 'before' is master, 'after' is with the patch. Verified: tsc ok (no new errors); eslint clean; no unit tests exist for Calendar/Dialog; Chromium calendar scenario before/after | ||||
| Tags | No tags attached. | ||||
| Attached Files | web-util--date-picker-dialog.diff (3,032 bytes)
diff --git a/packages/web-util/src/forms/Calendar.tsx b/packages/web-util/src/forms/Calendar.tsx
index e25f17e13..3a24ff61d 100644
--- a/packages/web-util/src/forms/Calendar.tsx
+++ b/packages/web-util/src/forms/Calendar.tsx
@@ -19,7 +19,7 @@ import {
import { VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js";
-import { composeRef, saveRef } from "../components/utils.js";
+import { composeRef, saveRef, useAutoFocus } from "../components/utils.js";
const THIS_MONTH = getMonth(new Date());
const THIS_YEAR = getYear(new Date());
@@ -47,6 +47,12 @@ export function Calendar({
if (input.current === document.activeElement) return;
input.current.value = !year ? "" : String(year);
}, [year]);
+ // The calendar is a picker the user has just opened: start on the selected
+ // day so that Enter keeps it and Tab moves on through the month.
+ const days = useRef<HTMLDivElement>(null);
+ useAutoFocus(true, () =>
+ days.current?.querySelector<HTMLElement>('[data-selected="true"]'),
+ );
const start = startOfWeek(startOfMonth(showingDate), { weekStartsOn: 1 });
const end = endOfWeek(endOfMonth(showingDate), { weekStartsOn: 1 });
@@ -183,7 +189,10 @@ export function Calendar({
))}
</div>
<div class="isolate mt-2">
- <div class="grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm shadow ring-1 ring-gray-200">
+ <div
+ ref={days}
+ class="grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm shadow ring-1 ring-gray-200"
+ >
{daysInMonth.map((current, idx) => (
<button
type="button"
diff --git a/packages/web-util/src/forms/Dialog.tsx b/packages/web-util/src/forms/Dialog.tsx
index 4bf96a446..ddba23f14 100644
--- a/packages/web-util/src/forms/Dialog.tsx
+++ b/packages/web-util/src/forms/Dialog.tsx
@@ -1,4 +1,5 @@
import { ComponentChildren, VNode, h } from "preact";
+import { useEffect, useRef } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js";
export function Dialog({
@@ -9,6 +10,24 @@ export function Dialog({
children: ComponentChildren;
}): VNode {
const { i18n } = useTranslationContext();
+ const onCloseRef = useRef(onClose);
+ onCloseRef.current = onClose;
+ useEffect(() => {
+ // Hand focus back to the control that opened the dialog, otherwise it is
+ // lost to the page body when the dialog content goes away.
+ const previousFocus =
+ document.activeElement instanceof HTMLElement
+ ? document.activeElement
+ : null;
+ function closeOnEscape(event: KeyboardEvent): void {
+ if (event.key === "Escape") onCloseRef.current?.();
+ }
+ document.addEventListener("keydown", closeOnEscape);
+ return () => {
+ document.removeEventListener("keydown", closeOnEscape);
+ previousFocus?.focus();
+ };
+ }, []);
return (
<div
class="relative z-10"
| ||||
| child of | 0012024 | new | Web UIs (bank, wallet, exchange AML/KYC, challenger, auditor, web-util forms): dialogs and single-purpose forms open without keyboard focus on the obvious field |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-10 23:42 | bot-cg | New Issue | |
| 2026-11-10 23:42 | bot-cg | Relationship added | child of 0012024 |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--date-picker-dialog.diff | |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--calendar--before.png | |
| 2026-11-10 23:43 | bot-cg | File Added: web-util--calendar--after.png |