{"version":3,"file":"use-picker-popover.cjs","sources":["../../../components/calendar/use-picker-popover.ts"],"sourcesContent":["import type React from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UsePickerPopoverOptions {\n  // Fired on outside click / blur-to-outside; listeners are torn down first.\n  onOutsideClick: () => void;\n}\n\nexport interface UsePickerPopoverReturn {\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  contentRef: React.RefObject<HTMLDivElement | null>;\n  handleInputFocus: () => void;\n  handleInputBlur: (event: React.FocusEvent) => void;\n  onOpenChange: (open?: boolean, reason?: string) => void;\n  /*\n   * Pass as Calendar's `onDropdownOpen` so the year/month dropdown isn't\n   * treated as an outside click.\n   */\n  markDropdownOpen: () => void;\n  /*\n   * Arm the outside-click listener. DatePicker engages on first input blur\n   * (typed-input pattern). Click-to-open consumers (e.g. RangePicker with\n   * readOnly inputs) should engage on open via `useEffect`.\n   */\n  engage: () => void;\n  // Programmatic close — does NOT fire `onOutsideClick`.\n  disengage: () => void;\n}\n\n/*\n * Popover machinery shared by the date pickers.\n *\n * DatePicker drives engagement off input focus/blur (typed-input pattern).\n * RangePicker (readOnly inputs) drives engagement off `isOpen` via a useEffect\n * that calls `engage()` / `disengage()`.\n *\n * Why custom instead of Base UI's dismissal: Calendar's `captionLayout='dropdown'`\n * renders Selects inside the popover; their portals look \"outside\" to a naive\n * dismiss handler. The hook carves that out via `markDropdownOpen`.\n *\n * `onOpenChange` reads `isOpen` via ref so its identity stays stable —\n * Base UI's store subscriber re-binds on identity change, which caused an\n * updateStoreInstance loop on mount.\n */\nexport function usePickerPopover({\n  onOutsideClick\n}: UsePickerPopoverOptions): UsePickerPopoverReturn {\n  const [isOpen, setIsOpen] = useState(false);\n\n  const inputRef = useRef<HTMLInputElement | null>(null);\n  const contentRef = useRef<HTMLDivElement | null>(null);\n\n  // True once focused-in and the outside-click listener is armed.\n  const isEngagedRef = useRef(false);\n\n  /*\n   * True while the Calendar's year/month dropdown is open — its clicks\n   * are not \"outside\".\n   */\n  const isDropdownOpenRef = useRef(false);\n\n  // Mirror for `onOpenChange` stability (see header comment).\n  const isOpenRef = useRef(isOpen);\n  useEffect(() => {\n    isOpenRef.current = isOpen;\n  });\n\n  // Mirror so the mouseup listener doesn't re-bind every render.\n  const onOutsideClickRef = useRef(onOutsideClick);\n  useEffect(() => {\n    onOutsideClickRef.current = onOutsideClick;\n  });\n\n  const isElementOutside = useCallback((el: HTMLElement) => {\n    return (\n      !isDropdownOpenRef.current &&\n      !inputRef.current?.contains(el) &&\n      !contentRef.current?.contains(el)\n    );\n  }, []);\n\n  const handleMouseDown = useCallback(\n    (event: MouseEvent) => {\n      const el = event.target as HTMLElement | null;\n      if (el && isElementOutside(el)) onOutsideClickRef.current();\n    },\n    [isElementOutside]\n  );\n\n  const engage = useCallback(() => {\n    isEngagedRef.current = true;\n    document.addEventListener('mouseup', handleMouseDown);\n  }, [handleMouseDown]);\n\n  const disengage = useCallback(() => {\n    isEngagedRef.current = false;\n    setIsOpen(false);\n    document.removeEventListener('mouseup', handleMouseDown);\n  }, [handleMouseDown]);\n\n  /*\n   * Safety net: if the component unmounts while engaged (or `handleMouseDown`\n   * identity changes mid-life), strip the document listener so stale\n   * `onOutsideClickRef` invocations can't fire.\n   */\n  useEffect(() => {\n    return () => {\n      document.removeEventListener('mouseup', handleMouseDown);\n    };\n  }, [handleMouseDown]);\n\n  const handleInputFocus = useCallback(() => {\n    if (isEngagedRef.current) return;\n    setIsOpen(true);\n  }, []);\n\n  const handleInputBlur = useCallback(\n    (event: React.FocusEvent) => {\n      const el = event.relatedTarget as HTMLElement | null;\n      if (isEngagedRef.current) {\n        // Engaged: blur is either outside (close) or into popover (no-op).\n        if (el && isElementOutside(el)) onOutsideClickRef.current();\n        return;\n      }\n      // Not yet engaged. If the user tab'd straight to an outside element,\n      // close immediately — otherwise keyboard users get stuck with the\n      // popover open until they mouse-click somewhere.\n      if (el && isElementOutside(el)) {\n        onOutsideClickRef.current();\n        return;\n      }\n      // First blur arms outside-click and selects text for type-to-overwrite.\n      engage();\n      setTimeout(() => inputRef.current?.select());\n    },\n    [isElementOutside, engage]\n  );\n\n  const onOpenChange = useCallback((open?: boolean, reason?: string) => {\n    // Year/month dropdown opening inside the popover triggers an open-change\n    // we don't want; swallow it and consume the flag.\n    if (isDropdownOpenRef.current) {\n      isDropdownOpenRef.current = false;\n      return;\n    }\n    /*\n     * Base UI's `Popover.Trigger` wires `useClick`, which *toggles* the popover\n     * on every trigger click. The input's `onFocus` already opens the picker, so\n     * a single click both opens (focus) and then toggles back closed\n     * (trigger-press) — the popover flickers shut on the first click and only\n     * sticks open on the second. Ignore trigger-press *closes*: opening stays\n     * owned by focus (and trigger-press open), while closing is owned by our\n     * outside-click / blur / Enter / day-select logic — plus Base UI's own\n     * Escape/outside-press, which still flow through below.\n     */\n    if (reason === 'trigger-press' && open === false) return;\n    /*\n     * Suppress only redundant *re-open* events fired by focus/click handlers\n     * while the picker is already engaged + open. Explicit close requests\n     * (Escape key, programmatic) must always go through, or users get stuck\n     * with no way to close.\n     */\n    if (open === true && isEngagedRef.current && isOpenRef.current) return;\n    setIsOpen(Boolean(open));\n  }, []);\n\n  const markDropdownOpen = useCallback(() => {\n    isDropdownOpenRef.current = true;\n  }, []);\n\n  return {\n    isOpen,\n    setIsOpen,\n    inputRef,\n    contentRef,\n    handleInputFocus,\n    handleInputBlur,\n    onOpenChange,\n    markDropdownOpen,\n    engage,\n    disengage\n  };\n}\n"],"names":["useState","useRef","useEffect","useCallback"],"mappings":"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"}