UNPKG

@blueprintjs/core

Version:
65 lines 3.12 kB
/* ! * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. */ import { autoUpdate, useClick, useDismiss, useFloating, useInteractions, } from "@floating-ui/react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PopoverInteractionKind } from "../popover/popoverProps"; export function usePopover({ autoUpdateOptions, canEscapeKeyClose, disabled = false, hasBackdrop = false, interactionKind, isControlled = false, isOpen = false, middleware, placement, positioningStrategy = "absolute", onOpenChange, } = {}) { const [isOpenState, setIsOpenState] = useState(isOpen); useEffect(() => { setIsOpenState(isOpen); }, [isOpen]); const handleOpenChange = useCallback((nextOpen, event) => { // Only update internal state for uncontrolled components if (!isControlled) { setIsOpenState(nextOpen); } // Always call the external callback if provided if (onOpenChange) { onOpenChange(nextOpen, event); } }, [onOpenChange, isControlled]); // Store options in a ref so the memoized callback always reads the latest // values without changing its identity on every render. const autoUpdateOptionsRef = useRef(autoUpdateOptions); autoUpdateOptionsRef.current = autoUpdateOptions; const whileElementsMounted = useMemo(() => autoUpdateOptions != null ? (reference, floating, update) => autoUpdate(reference, floating, update, autoUpdateOptionsRef.current) : autoUpdate, // Only change identity when toggling between with/without options; // actual option values are read from the ref at call time. // eslint-disable-next-line react-hooks/exhaustive-deps [autoUpdateOptions != null]); const data = useFloating({ middleware, onOpenChange: handleOpenChange, open: isOpenState, placement, strategy: positioningStrategy, whileElementsMounted, }); const { context } = data; const click = useClick(context, { enabled: !disabled, }); const dismiss = useDismiss(context, { escapeKey: canEscapeKeyClose, // Disable Floating UI outside-press in two cases: // 1. CLICK interactions: delegate to Overlay2's stack-aware handler // (getThisOverlayAndDescendants) so clicks inside child overlays like Dialog // don't incorrectly close the popover. useDismiss is not overlay-stack-aware // and treats clicks in portaled child overlays as "outside" clicks. // 2. hasBackdrop: Overlay2 handles backdrop clicks and outside-click detection. outsidePress: interactionKind !== PopoverInteractionKind.CLICK_TARGET_ONLY && interactionKind !== PopoverInteractionKind.CLICK && !hasBackdrop, }); const interactions = useInteractions([click, dismiss]); return useMemo(() => ({ isOpen: isOpenState, setIsOpen: setIsOpenState, ...interactions, ...data, }), [data, interactions, isOpenState]); } //# sourceMappingURL=usePopover.js.map