{"version":3,"file":"focus.cjs","names":[],"sources":["../../src/core/focus.ts"],"sourcesContent":["import { captureFocus, type FocusRestorer } from '@vielzeug/focus';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type FocusManagerOptions = {\n  /**\n   * Returns the CSS selector for the element that should receive focus on open.\n   *\n   * **Security note:** this value is passed directly to `querySelector`. It must\n   * come from trusted developer-controlled configuration, never from raw user input.\n   */\n  getInitialFocusSelector: () => string | undefined;\n  /**\n   * Returns whether focus should be restored to the previously focused element\n   * on close. `undefined` or `true` means restore; `false` means skip.\n   */\n  getReturnFocus: () => boolean | undefined;\n  /** Host element used for `querySelector` when resolving the initial focus target. */\n  host: HTMLElement;\n  /**\n   * `AbortSignal` from the component lifecycle. Cancels a pending `applyInitialFocus()`\n   * animation frame automatically on abort — prevents a stray `.focus()` call landing on an\n   * element that's been disconnected in the meantime (e.g. the dialog closes and unmounts\n   * before the deferred focus frame runs).\n   */\n  signal?: AbortSignal;\n};\n\nexport type FocusManager = {\n  [Symbol.dispose](): void;\n  /** Move focus to the element matching `getInitialFocusSelector` (deferred one frame). */\n  applyInitialFocus: () => void;\n  /** Cancel a pending `applyInitialFocus` rAF, if any. */\n  cancelInitialFocus: () => void;\n  /** Capture the currently focused element so it can be restored later. */\n  captureReturnFocus: () => void;\n  /** Cancels any pending initial-focus frame and clears captured return-focus state. */\n  dispose(): void;\n  /** `true` after `dispose()` has been called. */\n  readonly disposed: boolean;\n  /** Restore focus to the element captured by `captureReturnFocus`. */\n  restoreFocus: () => void;\n};\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/**\n * Encapsulates the three-step focus lifecycle used by dialogs and drawers:\n * 1. Capture the element that triggered the open.\n * 2. Move focus inside the overlay (optionally to a specific element).\n * 3. Restore focus when the overlay closes.\n */\nexport function createFocusManager(options: FocusManagerOptions): FocusManager {\n  let restoreReturnFocus: FocusRestorer | null = null;\n  let rafHandle: ReturnType<typeof requestAnimationFrame> | null = null;\n  let disposed = false;\n\n  const cancelInitialFocus = (): void => {\n    if (rafHandle !== null) {\n      cancelAnimationFrame(rafHandle);\n      rafHandle = null;\n    }\n  };\n\n  const dispose = (): void => {\n    if (disposed) return;\n\n    disposed = true;\n    cancelInitialFocus();\n    restoreReturnFocus = null;\n  };\n\n  options.signal?.addEventListener('abort', dispose, { once: true });\n\n  return {\n    applyInitialFocus() {\n      cancelInitialFocus();\n\n      const selector = options.getInitialFocusSelector();\n\n      if (selector) {\n        // Query the shadow root first (all refine components render into Shadow DOM),\n        // falling back to the host's light-DOM tree for non-Shadow contexts.\n        const root = options.host.shadowRoot ?? options.host;\n\n        let target: HTMLElement | null = null;\n\n        try {\n          target = root.querySelector<HTMLElement>(selector);\n        } catch {\n          // Malformed selector (SyntaxError) — skip initial focus rather than throw.\n          return;\n        }\n\n        if (target) {\n          rafHandle = requestAnimationFrame(() => {\n            rafHandle = null;\n            target.focus();\n          });\n        }\n      }\n    },\n    cancelInitialFocus,\n\n    captureReturnFocus() {\n      restoreReturnFocus = captureFocus();\n    },\n\n    dispose,\n\n    get disposed() {\n      return disposed;\n    },\n\n    restoreFocus() {\n      if (options.getReturnFocus() !== false) restoreReturnFocus?.();\n\n      restoreReturnFocus = null;\n    },\n\n    [Symbol.dispose]: dispose,\n  };\n}\n"],"mappings":"iCAoDA,SAAgB,EAAmB,EAA4C,CAC7E,IAAI,EAA2C,KAC3C,EAA6D,KAC7D,EAAW,GAET,MAAiC,CACjC,IAAc,OAChB,qBAAqB,CAAS,EAC9B,EAAY,KAEhB,EAEM,MAAsB,CACtB,IAEJ,EAAW,GACX,EAAmB,EACnB,EAAqB,KACvB,EAIA,OAFA,EAAQ,QAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAE1D,CACL,mBAAoB,CAClB,EAAmB,EAEnB,IAAM,EAAW,EAAQ,wBAAwB,EAEjD,GAAI,EAAU,CAGZ,IAAM,EAAO,EAAQ,KAAK,YAAc,EAAQ,KAE5C,EAA6B,KAEjC,GAAI,CACF,EAAS,EAAK,cAA2B,CAAQ,CACnD,MAAQ,CAEN,MACF,CAEI,IACF,EAAY,0BAA4B,CACtC,EAAY,KACZ,EAAO,MAAM,CACf,CAAC,EAEL,CACF,EACA,qBAEA,oBAAqB,CACnB,GAAA,EAAqB,EAAA,aAAA,CAAa,CACpC,EAEA,UAEA,IAAI,UAAW,CACb,OAAO,CACT,EAEA,cAAe,CACT,EAAQ,eAAe,IAAM,IAAO,IAAqB,EAE7D,EAAqB,IACvB,GAEC,OAAO,SAAU,CACpB,CACF"}