import * as React from 'react' ; import { composeEventHandlers } from '@radix-ui/primitive' ; import { Primitive } from '@radix-ui/react-primitive' ; import { createContext } from '@radix-ui/react-context' ; import { useBodyPointerEvents } from '@radix-ui/react-use-body-pointer-events' ; import { useCallbackRef } from '@radix-ui/react-use-callback-ref' ; import { useEscapeKeydown } from '@radix-ui/react-use-escape-keydown' ; import type * as Radix from '@radix-ui/react-primitive' ; const [ TotalLayerCountProvider , useTotalLayerCount ] = createTotalLayerCount ( ) ; const [ RunningLayerCountProvider , usePreviousRunningLayerCount ] = createRunningLayerCount ( ) ; const [ TotalLayerCountWithDisabledOutsidePointerEventsProvider , useTotalLayerCountWithDisabledOutsidePointerEvents , ] = createTotalLayerCount ( 'TotalLayerCountWithDisabledOutsidePointerEventsProvider' ) ; const [ RunningLayerCountWithDisabledOutsidePointerEventsProvider , usePreviousRunningLayerCountWithDisabledOutsidePointerEvents , ] = createRunningLayerCount ( 'RunningLayerCountWithDisabledOutsidePointerEventsProvider' ) ; const DISMISSABLE_LAYER_NAME = 'DismissableLayer' ; type DismissableLayerElement = DismissableLayerImplElement ; interface DismissableLayerProps extends DismissableLayerImplProps { } const DismissableLayer = React . forwardRef < DismissableLayerElement , DismissableLayerProps > ( ( props , forwardedRef ) => { const runningLayerCount = usePreviousRunningLayerCount ( ) ; const isRootLayer = runningLayerCount === 0 ; const layer = < DismissableLayerImpl { ... props } ref = { forwardedRef } / > ; return isRootLayer ? ( < TotalLayerCountProvider > < TotalLayerCountWithDisabledOutsidePointerEventsProvider > { layer } < /TotalLayerCountWithDisabledOutsidePointerEventsProvider> < /TotalLayerCountProvider>) : (layer); } ) ; DismissableLayer . displayName = DISMISSABLE_LAYER_NAME ; type DismissableLayerImplElement = React . ElementRef < typeof Primitive . div > ; type PrimitiveDivProps = Radix . ComponentPropsWithoutRef < typeof Primitive . div > ; interface DismissableLayerImplProps extends PrimitiveDivProps { /**
     * When `true`, hover/focus/click interactions will be disabled on elements outside
     * the `DismissableLayer`. Users will need to click twice on outside elements to
     * interact with them: once to close the `DismissableLayer`, and again to trigger the element.
     */ disableOutsidePointerEvents ? : boolean ; /**
     * Event handler called when the escape key is down.
     * Can be prevented.
     */ onEscapeKeyDown ? : ( event : KeyboardEvent ) => void ; /**
     * Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`.
     * Can be prevented.
     */ onPointerDownOutside ? : ( event : PointerDownOutsideEvent ) => void ; /**
     * Event handler called when the focus moves outside of the `DismissableLayer`.
     * Can be prevented.
     */ onFocusOutside ? : ( event : FocusOutsideEvent ) => void ; /**
     * Event handler called when an interaction happens outside the `DismissableLayer`.
     * Specifically, when a `pointerdown` event happens outside or focus moves outside of it.
     * Can be prevented.
     */ onInteractOutside ? : ( event : PointerDownOutsideEvent | FocusOutsideEvent ) => void ; /** Callback called when the `DismissableLayer` should be dismissed */ onDismiss ? : ( ) => void ; } const DismissableLayerImpl = React . forwardRef < DismissableLayerImplElement , DismissableLayerImplProps > ( ( props , forwardedRef ) => { const { disableOutsidePointerEvents = false , onEscapeKeyDown , onPointerDownOutside , onFocusOutside , onInteractOutside , onDismiss , ... layerProps } = props ; const totalLayerCount = useTotalLayerCount ( ) ; const prevRunningLayerCount = usePreviousRunningLayerCount ( ) ; const runningLayerCount = prevRunningLayerCount + 1 ; const isDeepestLayer = runningLayerCount === totalLayerCount ; const totalLayerCountWithDisabledOutsidePointerEvents = useTotalLayerCountWithDisabledOutsidePointerEvents ( disableOutsidePointerEvents ) ; const prevRunningLayerCountWithDisabledOutsidePointerEvents = usePreviousRunningLayerCountWithDisabledOutsidePointerEvents ( ) ; const runningLayerCountWithDisabledOutsidePointerEvents = prevRunningLayerCountWithDisabledOutsidePointerEvents + ( disableOutsidePointerEvents ? 1 : 0 ) ; const containsChildLayerWithDisabledOutsidePointerEvents = runningLayerCountWithDisabledOutsidePointerEvents < totalLayerCountWithDisabledOutsidePointerEvents ; useBodyPointerEvents ( { disabled : disableOutsidePointerEvents } ) ; useEscapeKeydown ( ( event ) => { if ( isDeepestLayer ) { onEscapeKeyDown ?. ( event ) ; if ( ! event . defaultPrevented ) { onDismiss ?. ( ) ; } } } ) ; const { onPointerDownCapture : handlePointerDownCapture } = usePointerDownOutside ( ( event ) => { if ( ! containsChildLayerWithDisabledOutsidePointerEvents ) { onPointerDownOutside ?. ( event ) ; onInteractOutside ?. ( event ) ; if ( ! event . defaultPrevented ) { onDismiss ?. ( ) ; } } } ) ; const { onBlurCapture : handleBlurCapture , onFocusCapture : handleFocusCapture } = useFocusOutside ( ( event ) => { onFocusOutside ?. ( event ) ; onInteractOutside ?. ( event ) ; if ( ! event . defaultPrevented ) { onDismiss ?. ( ) ; } } ) ; const isBodyPointerEventsDisabled = totalLayerCountWithDisabledOutsidePointerEvents > 0 ; return ( < RunningLayerCountProvider runningCount = { runningLayerCount } > < RunningLayerCountWithDisabledOutsidePointerEventsProvider runningCount = { runningLayerCountWithDisabledOutsidePointerEvents } > < Primitive . div { ... layerProps } ref = { forwardedRef } style = { { pointerEvents : isBodyPointerEventsDisabled ? containsChildLayerWithDisabledOutsidePointerEvents ? 'none' : 'auto' : undefined , ... layerProps . style , } } onPointerDownCapture = { composeEventHandlers ( props . onPointerDownCapture , handlePointerDownCapture ) } onBlurCapture = { composeEventHandlers ( props . onBlurCapture , handleBlurCapture ) } onFocusCapture = { composeEventHandlers ( props . onFocusCapture , handleFocusCapture ) } / > < /RunningLayerCountWithDisabledOutsidePointerEventsProvider> < /RunningLayerCountProvider>); } ) ; const POINTER_DOWN_OUTSIDE = 'dismissableLayer.pointerDownOutside' ; const FOCUS_OUTSIDE = 'dismissableLayer.focusOutside' ; type PointerDownOutsideEvent = CustomEvent < { originalEvent : PointerEvent ; } > ; type FocusOutsideEvent = CustomEvent < { originalEvent : FocusEvent ; } > ; /**
 * Sets up `pointerdown` listener which listens for events outside a react subtree.
 *
 * We use `pointerdown` rather than `pointerup` to mimic layer dismissing behaviour
 * present in OS which usually happens on `pointerdown`.
 *
 * Returns props to pass to the node we want to check for outside events.
 */ function usePointerDownOutside ( onPointerDownOutside ? ) { const handlePointerDownOutside = useCallbackRef ( onPointerDownOutside ) as EventListener ; const isPointerInsideReactTreeRef = React . useRef ( false ) ; React . useEffect ( ( ) => { const handlePointerDown = ( event ) => { const target = event . target ; if ( target && ! isPointerInsideReactTreeRef . current ) { const pointerDownOutsideEvent = new CustomEvent ( POINTER_DOWN_OUTSIDE , { bubbles : false , cancelable : true , detail : { originalEvent : event } } ) ; target . addEventListener ( POINTER_DOWN_OUTSIDE , handlePointerDownOutside , { once : true } ) ; target . dispatchEvent ( pointerDownOutsideEvent ) ; } isPointerInsideReactTreeRef . current = false ; } ; /**
         * if this hook executes in a component that mounts via a `pointerdown` event, the event
         * would bubble up to the document and trigger a `pointerDownOutside` event. We avoid
         * this by delaying the event listener registration on the document.
         * This is not React specific, but rather how the DOM works, ie:
         * ```
         * button.addEventListener('pointerdown', () => {
         *   console.log('I will log');
         *   document.addEventListener('pointerdown', () => {
         *     console.log('I will also log');
         *   })
         * });
         */ const timerId = window . setTimeout ( ( ) => { document . addEventListener ( 'pointerdown' , handlePointerDown ) ; } , 0 ) ; return ( ) => { window . clearTimeout ( timerId ) ; document . removeEventListener ( 'pointerdown' , handlePointerDown ) ; } ; } , [ handlePointerDownOutside ] ) ; return { onPointerDownCapture : ( ) => ( isPointerInsideReactTreeRef . current = true ) , } ; } /**
 * Listens for when focus happens outside a react subtree.
 * Returns props to pass to the root (node) of the subtree we want to check.
 */ function useFocusOutside ( onFocusOutside ? ) { const handleFocusOutside = useCallbackRef ( onFocusOutside ) as EventListener ; const isFocusInsideReactTreeRef = React . useRef ( false ) ; React . useEffect ( ( ) => { const handleFocus = ( event ) => { const target = event . target ; if ( target && ! isFocusInsideReactTreeRef . current ) { const focusOutsideEvent = new CustomEvent ( FOCUS_OUTSIDE , { bubbles : false , cancelable : true , detail : { originalEvent : event } , } ) ; target . addEventListener ( FOCUS_OUTSIDE , handleFocusOutside , { once : true } ) ; target . dispatchEvent ( focusOutsideEvent ) ; } } ; document . addEventListener ( 'focusin' , handleFocus ) ; return ( ) => document . removeEventListener ( 'focusin' , handleFocus ) ; } , [ handleFocusOutside ] ) ; return { onFocusCapture : ( ) => ( isFocusInsideReactTreeRef . current = true ) , onBlurCapture : ( ) => ( isFocusInsideReactTreeRef . current = false ) , } ; } function createTotalLayerCount ( displayName ? ) { const [ TotalLayerCountProviderImpl , useTotalLayerCountContext ] = createContext ( 'TotalLayerCount' , { total : 0 , onTotalIncrease : ( ) => { } , onTotalDecrease : ( ) => { } } ) ; const TotalLayerCountProvider = ( { children } ) => { const [ total , setTotal ] = React . useState ( 0 ) ; return ( < TotalLayerCountProviderImpl total = { total } onTotalIncrease = { React . useCallback ( ( ) => setTotal ( ( n ) => n + 1 ) , [ ] ) } onTotalDecrease = { React . useCallback ( ( ) => setTotal ( ( n ) => n - 1 ) , [ ] ) } > { children } < /TotalLayerCountProviderImpl>); } ; if ( displayName ) { TotalLayerCountProvider . displayName = displayName ; } function useTotalLayerCount ( counted = true ) { const { total , onTotalIncrease , onTotalDecrease } = useTotalLayerCountContext ( 'TotalLayerCountConsumer' ) ; React . useLayoutEffect ( ( ) => { if ( counted ) { onTotalIncrease ( ) ; return ( ) => onTotalDecrease ( ) ; } } , [ counted , onTotalIncrease , onTotalDecrease ] ) ; return total ; } return [ TotalLayerCountProvider , useTotalLayerCount ] as const ; } function createRunningLayerCount ( displayName ? ) { const [ RunningLayerCountProviderImp , useRunningLayerCount ] = createContext ( 'RunningLayerCount' , { count : 0 , } ) ; const RunningLayerCountProvider = ( props ) => { const { children , runningCount } = props ; return ( < RunningLayerCountProviderImp count = { runningCount } > { children } < /RunningLayerCountProviderImp>); } ; if ( displayName ) { RunningLayerCountProvider . displayName = displayName ; } function usePreviousRunningLayerCount ( ) { const context = useRunningLayerCount ( 'RunningLayerCountConsumer' ) ; return context . count || 0 ; } return [ RunningLayerCountProvider , usePreviousRunningLayerCount ] as const ; } const Root = DismissableLayer ; export { DismissableLayer , Root , } ; export type { DismissableLayerProps } ;