ba36f67371
* fix: reliable Escape key close for all modals via useEscapeKey hook - Add useEscapeKey hook (document-level keydown listener) - Retrofit all 12 modal/overlay components to use it - Remove redundant overlay onKeyDown Escape handlers - Simplify modal-content onKeyDown to plain stopPropagation - Replace MedDetailModal's capture-phase useEffect with 3 useEscapeKey calls - Replace SharedSchedule's inline useEffect with useEscapeKey - Add mandatory modal rules to UI Consistency skill - All 777 frontend + 569 backend tests pass * fix: smooth mobile edit transition and align modal validation behavior * fix: keep overlay keydown non-closing for Enter key * fix: show mobile name error when validation already exists * fix: restore app-level escape priority handling * fix: prioritize schedule lightbox on Escape
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { useEffect, useRef } from "react";
|
||
|
||
/**
|
||
* Close a modal/overlay when the user presses Escape.
|
||
*
|
||
* Registers a document-level `keydown` listener so it works regardless
|
||
* of which element has focus. Every modal **must** use this hook —
|
||
* relying on `onKeyDown` on overlay divs is unreliable because those
|
||
* handlers only fire when the overlay itself (or a descendant) has focus.
|
||
*
|
||
* @param active – whether the modal is currently open
|
||
* @param onClose – callback to close the modal
|
||
* @param options.capture – use capture phase (default: false).
|
||
* Set to `true` for nested sub-modals that must intercept Escape
|
||
* before a parent's handler fires.
|
||
*/
|
||
export function useEscapeKey(active: boolean, onClose: () => void, options?: { capture?: boolean }): void {
|
||
const capture = options?.capture ?? false;
|
||
const activeRef = useRef(active);
|
||
const onCloseRef = useRef(onClose);
|
||
|
||
// Keep refs in sync without re-registering the listener
|
||
activeRef.current = active;
|
||
onCloseRef.current = onClose;
|
||
|
||
useEffect(() => {
|
||
if (!active) return;
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === "Escape" && activeRef.current) {
|
||
onCloseRef.current();
|
||
}
|
||
};
|
||
document.addEventListener("keydown", handleKeyDown, capture);
|
||
return () => document.removeEventListener("keydown", handleKeyDown, capture);
|
||
}, [active, capture]);
|
||
}
|