"use client" import * as React from "react" /** * Where a `` binding is mounted, so `useShortcut` can tell a * page-level chord from one that belongs to an open overlay. * * Keying off the *binding* rather than the *event target* is the whole point. * A non-modal rail leaves focus reachable on both sides, so "did this keystroke * come from inside the dialog?" answers the wrong question: press Escape with * focus in the rail and a page-level Escape binding would fire too, closing the * rail and running the page's handler on one keystroke. */ export type ShortcutScope = "page" | "overlay" const ShortcutScopeContext = React.createContext("page") export const ShortcutScopeProvider = ShortcutScopeContext.Provider export function useShortcutScope(): ShortcutScope { return React.useContext(ShortcutScopeContext) } const OPEN_OVERLAY_SELECTOR = '[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]' let overlayWasOpen = false let watching = false /** * Whether an overlay was open when the current keystroke began. * * Reading the DOM inside the shortcut handler is too late: Radix dismisses on * `keydown` capture and React flushes that state change synchronously for * discrete events, so by the bubble phase the overlay already reads as closed * and a page-level chord would fire on the same keystroke that dismissed it. * Snapshotting on `window` capture runs ahead of every document listener, * Radix's included. */ export function watchOverlayState(): void { if (watching || typeof window === "undefined") return watching = true window.addEventListener( "keydown", () => { overlayWasOpen = document.querySelector(OPEN_OVERLAY_SELECTOR) != null }, { capture: true }, ) } export function wasOverlayOpenAtKeydown(): boolean { return overlayWasOpen }