"use client"; // src/dismissable-layer.tsx import * as React from "react"; import { composeEventHandlers } from "@radix-ui/primitive"; import { Primitive, dispatchDiscreteCustomEvent } from "@radix-ui/react-primitive"; import { useComposedRefs } from "@radix-ui/react-compose-refs"; import { useCallbackRef } from "@radix-ui/react-use-callback-ref"; import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown"; import { jsx } from "react/jsx-runtime"; var DISMISSABLE_LAYER_NAME = "DismissableLayer"; var CONTEXT_UPDATE = "dismissableLayer.update"; var POINTER_DOWN_OUTSIDE = "dismissableLayer.pointerDownOutside"; var FOCUS_OUTSIDE = "dismissableLayer.focusOutside"; var originalBodyPointerEvents; var DismissableLayerContext = React.createContext({ layers: /* @__PURE__ */ new Set(), layersWithOutsidePointerEventsDisabled: /* @__PURE__ */ new Set(), branches: /* @__PURE__ */ new Set(), // Outside elements that belong to a layer's own dismiss affordance (eg, a // dialog overlay). Pressing them should dismiss the layer regardless of // whether or not they stop propagation. // // See https://github.com/radix-ui/primitives/issues/3346 dismissableSurfaces: /* @__PURE__ */ new Set() }); var DismissableLayer = React.forwardRef( (props, forwardedRef) => { const { disableOutsidePointerEvents = false, deferPointerDownOutside = false, onEscapeKeyDown, onPointerDownOutside, onFocusOutside, onInteractOutside, onDismiss, ...layerProps } = props; const context = React.useContext(DismissableLayerContext); const [node, setNode] = React.useState(null); const ownerDocument = node?.ownerDocument ?? globalThis?.document; const [, force] = React.useState({}); const composedRefs = useComposedRefs(forwardedRef, (node2) => setNode(node2)); const layers = Array.from(context.layers); const [highestLayerWithOutsidePointerEventsDisabled] = [...context.layersWithOutsidePointerEventsDisabled].slice(-1); const highestLayerWithOutsidePointerEventsDisabledIndex = layers.indexOf(highestLayerWithOutsidePointerEventsDisabled); const index = node ? layers.indexOf(node) : -1; const isBodyPointerEventsDisabled = context.layersWithOutsidePointerEventsDisabled.size > 0; const isPointerEventsEnabled = index >= highestLayerWithOutsidePointerEventsDisabledIndex; const isDeferredPointerDownOutsideRef = React.useRef(false); const pointerDownOutside = usePointerDownOutside( (event) => { const target = event.target; if (!(target instanceof Node)) { return; } const isPointerDownOnBranch = [...context.branches].some( (branch) => branch.contains(target) ); if (!isPointerEventsEnabled || isPointerDownOnBranch) return; onPointerDownOutside?.(event); onInteractOutside?.(event); if (!event.defaultPrevented) onDismiss?.(); }, { ownerDocument, deferPointerDownOutside, isDeferredPointerDownOutsideRef, dismissableSurfaces: context.dismissableSurfaces } ); const focusOutside = useFocusOutside((event) => { if (deferPointerDownOutside && isDeferredPointerDownOutsideRef.current) { return; } const target = event.target; const isFocusInBranch = [...context.branches].some((branch) => branch.contains(target)); if (isFocusInBranch) return; onFocusOutside?.(event); onInteractOutside?.(event); if (!event.defaultPrevented) onDismiss?.(); }, ownerDocument); useEscapeKeydown((event) => { const isHighestLayer = index === context.layers.size - 1; if (!isHighestLayer) return; onEscapeKeyDown?.(event); if (!event.defaultPrevented && onDismiss) { event.preventDefault(); onDismiss(); } }, ownerDocument); React.useEffect(() => { if (!node) return; if (disableOutsidePointerEvents) { if (context.layersWithOutsidePointerEventsDisabled.size === 0) { originalBodyPointerEvents = ownerDocument.body.style.pointerEvents; ownerDocument.body.style.pointerEvents = "none"; } context.layersWithOutsidePointerEventsDisabled.add(node); } context.layers.add(node); dispatchUpdate(); return () => { if (disableOutsidePointerEvents) { context.layersWithOutsidePointerEventsDisabled.delete(node); if (context.layersWithOutsidePointerEventsDisabled.size === 0) { ownerDocument.body.style.pointerEvents = originalBodyPointerEvents; } } }; }, [node, ownerDocument, disableOutsidePointerEvents, context]); React.useEffect(() => { return () => { if (!node) return; context.layers.delete(node); context.layersWithOutsidePointerEventsDisabled.delete(node); dispatchUpdate(); }; }, [node, context]); React.useEffect(() => { const handleUpdate = () => force({}); document.addEventListener(CONTEXT_UPDATE, handleUpdate); return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate); }, []); return /* @__PURE__ */ jsx( Primitive.div, { ...layerProps, ref: composedRefs, style: { pointerEvents: isBodyPointerEventsDisabled ? isPointerEventsEnabled ? "auto" : "none" : void 0, ...props.style }, onFocusCapture: composeEventHandlers(props.onFocusCapture, focusOutside.onFocusCapture), onBlurCapture: composeEventHandlers(props.onBlurCapture, focusOutside.onBlurCapture), onPointerDownCapture: composeEventHandlers( props.onPointerDownCapture, pointerDownOutside.onPointerDownCapture ) } ); } ); DismissableLayer.displayName = DISMISSABLE_LAYER_NAME; var BRANCH_NAME = "DismissableLayerBranch"; var DismissableLayerBranch = React.forwardRef((props, forwardedRef) => { const context = React.useContext(DismissableLayerContext); const ref = React.useRef(null); const composedRefs = useComposedRefs(forwardedRef, ref); React.useEffect(() => { const node = ref.current; if (node) { context.branches.add(node); return () => { context.branches.delete(node); }; } }, [context.branches]); return /* @__PURE__ */ jsx(Primitive.div, { ...props, ref: composedRefs }); }); DismissableLayerBranch.displayName = BRANCH_NAME; function useDismissableLayerSurface() { const context = React.useContext(DismissableLayerContext); const [node, setNode] = React.useState(null); React.useEffect(() => { if (!node) { return; } context.dismissableSurfaces.add(node); return () => { context.dismissableSurfaces.delete(node); }; }, [node, context.dismissableSurfaces]); return setNode; } function usePointerDownOutside(onPointerDownOutside, args) { const { ownerDocument = globalThis?.document, deferPointerDownOutside = false, isDeferredPointerDownOutsideRef, dismissableSurfaces } = args; const handlePointerDownOutside = useCallbackRef(onPointerDownOutside); const isPointerInsideReactTreeRef = React.useRef(false); const isPointerDownOutsideRef = React.useRef(false); const interceptedOutsideInteractionEventsRef = React.useRef(/* @__PURE__ */ new Map()); const handleClickRef = React.useRef(() => { }); React.useEffect(() => { function resetOutsideInteraction() { isPointerDownOutsideRef.current = false; isDeferredPointerDownOutsideRef.current = false; interceptedOutsideInteractionEventsRef.current.clear(); } function isOutsideInteractionIntercepted() { return Array.from(interceptedOutsideInteractionEventsRef.current.values()).some(Boolean); } function handleInteractionCapture(event) { if (!isPointerDownOutsideRef.current) { return; } const target = event.target; const isDismissableSurface = target instanceof Node && [...dismissableSurfaces].some((surface) => surface.contains(target)); if (!isDismissableSurface) { interceptedOutsideInteractionEventsRef.current.set(event.type, true); } if (event.type === "click") { window.setTimeout(() => { if (isPointerDownOutsideRef.current) { handleClickRef.current(); } }, 0); } } function handleInteractionBubble(event) { if (isPointerDownOutsideRef.current) { interceptedOutsideInteractionEventsRef.current.set(event.type, false); } } const handlePointerDown = (event) => { if (event.target && !isPointerInsideReactTreeRef.current) { let handleAndDispatchPointerDownOutsideEvent2 = function() { ownerDocument.removeEventListener("click", handleClickRef.current); const wasOutsideInteractionIntercepted = isOutsideInteractionIntercepted(); resetOutsideInteraction(); if (!wasOutsideInteractionIntercepted) { handleAndDispatchCustomEvent( POINTER_DOWN_OUTSIDE, handlePointerDownOutside, eventDetail, { discrete: true } ); } }; var handleAndDispatchPointerDownOutsideEvent = handleAndDispatchPointerDownOutsideEvent2; const eventDetail = { originalEvent: event }; isPointerDownOutsideRef.current = true; isDeferredPointerDownOutsideRef.current = deferPointerDownOutside && event.button === 0; interceptedOutsideInteractionEventsRef.current.clear(); if (!deferPointerDownOutside || event.button !== 0) { handleAndDispatchPointerDownOutsideEvent2(); } else { ownerDocument.removeEventListener("click", handleClickRef.current); handleClickRef.current = handleAndDispatchPointerDownOutsideEvent2; ownerDocument.addEventListener("click", handleClickRef.current, { once: true }); } } else { ownerDocument.removeEventListener("click", handleClickRef.current); resetOutsideInteraction(); } isPointerInsideReactTreeRef.current = false; }; const outsideInteractionEvents = [ "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "click" ]; for (const eventName of outsideInteractionEvents) { ownerDocument.addEventListener(eventName, handleInteractionCapture, true); ownerDocument.addEventListener(eventName, handleInteractionBubble); } const timerId = window.setTimeout(() => { ownerDocument.addEventListener("pointerdown", handlePointerDown); }, 0); return () => { window.clearTimeout(timerId); ownerDocument.removeEventListener("pointerdown", handlePointerDown); ownerDocument.removeEventListener("click", handleClickRef.current); for (const eventName of outsideInteractionEvents) { ownerDocument.removeEventListener(eventName, handleInteractionCapture, true); ownerDocument.removeEventListener(eventName, handleInteractionBubble); } }; }, [ ownerDocument, handlePointerDownOutside, deferPointerDownOutside, isDeferredPointerDownOutsideRef, dismissableSurfaces ]); return { // ensures we check React component tree (not just DOM tree) onPointerDownCapture: () => isPointerInsideReactTreeRef.current = true }; } function useFocusOutside(onFocusOutside, ownerDocument = globalThis?.document) { const handleFocusOutside = useCallbackRef(onFocusOutside); const isFocusInsideReactTreeRef = React.useRef(false); React.useEffect(() => { const handleFocus = (event) => { if (event.target && !isFocusInsideReactTreeRef.current) { const eventDetail = { originalEvent: event }; handleAndDispatchCustomEvent(FOCUS_OUTSIDE, handleFocusOutside, eventDetail, { discrete: false }); } }; ownerDocument.addEventListener("focusin", handleFocus); return () => ownerDocument.removeEventListener("focusin", handleFocus); }, [ownerDocument, handleFocusOutside]); return { onFocusCapture: () => isFocusInsideReactTreeRef.current = true, onBlurCapture: () => isFocusInsideReactTreeRef.current = false }; } function dispatchUpdate() { const event = new CustomEvent(CONTEXT_UPDATE); document.dispatchEvent(event); } function handleAndDispatchCustomEvent(name, handler, detail, { discrete }) { const target = detail.originalEvent.target; const event = new CustomEvent(name, { bubbles: false, cancelable: true, detail }); if (handler) target.addEventListener(name, handler, { once: true }); if (discrete) { dispatchDiscreteCustomEvent(target, event); } else { target.dispatchEvent(event); } } var Root = DismissableLayer; var Branch = DismissableLayerBranch; export { Branch, DismissableLayer, DismissableLayerBranch, Root, useDismissableLayerSurface }; //# sourceMappingURL=index.mjs.map