UNPKG

@loke/ui

Version:
2 lines (1 loc) 6.71 kB
import{composeEventHandlers}from"@loke/ui/compose-events";import{useComposedRefs}from"@loke/ui/compose-refs";import{dispatchDiscreteCustomEvent,Primitive}from"@loke/ui/primitive";import{useCallbackRef}from"@loke/ui/use-callback-ref";import{useEscapeKeydown}from"@loke/ui/use-escape-keydown";import{createContext,forwardRef,useContext,useEffect,useRef,useState}from"react";import{jsx}from"react/jsx-runtime";var DISMISSABLE_LAYER_NAME="DismissableLayer",CONTEXT_UPDATE="dismissableLayer.update",POINTER_DOWN_OUTSIDE="dismissableLayer.pointerDownOutside",FOCUS_OUTSIDE="dismissableLayer.focusOutside",originalBodyPointerEvents,DismissableLayerContext=createContext({branches:new Set,layers:new Set,layersWithOutsidePointerEventsDisabled:new Set}),DismissableLayer=forwardRef((props,forwardedRef)=>{let{disableOutsidePointerEvents=!1,onEscapeKeyDown,onPointerDownOutside,onFocusOutside,onInteractOutside,onDismiss,...layerProps}=props,context=useContext(DismissableLayerContext),[node,setNode]=useState(null),ownerDocument=node?.ownerDocument??globalThis?.document,[,force]=useState({}),composedRefs=useComposedRefs(forwardedRef,(element)=>setNode(element)),layers=Array.from(context.layers),[highestLayerWithOutsidePointerEventsDisabled]=[...context.layersWithOutsidePointerEventsDisabled].slice(-1),highestLayerWithOutsidePointerEventsDisabledIndex=layers.indexOf(highestLayerWithOutsidePointerEventsDisabled),index=node?layers.indexOf(node):-1,isBodyPointerEventsDisabled=context.layersWithOutsidePointerEventsDisabled.size>0,isPointerEventsEnabled=index>=highestLayerWithOutsidePointerEventsDisabledIndex,pointerDownOutside=usePointerDownOutside((event)=>{let target=event.target,isPointerDownOnBranch=[...context.branches].some((branch)=>branch.contains(target));if(!isPointerEventsEnabled||isPointerDownOnBranch)return;if(onPointerDownOutside?.(event),onInteractOutside?.(event),!event.defaultPrevented)onDismiss?.()},ownerDocument),focusOutside=useFocusOutside((event)=>{let target=event.target;if([...context.branches].some((branch)=>branch.contains(target)))return;if(onFocusOutside?.(event),onInteractOutside?.(event),!event.defaultPrevented)onDismiss?.()},ownerDocument);useEscapeKeydown((event)=>{if(index!==context.layers.size-1)return;if(onEscapeKeyDown?.(event),!event.defaultPrevented&&onDismiss)event.preventDefault(),onDismiss()},ownerDocument),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)}return context.layers.add(node),dispatchUpdate(),()=>{if(disableOutsidePointerEvents&&context.layersWithOutsidePointerEventsDisabled.size===1)ownerDocument.body.style.pointerEvents=originalBodyPointerEvents}},[node,ownerDocument,disableOutsidePointerEvents,context]),useEffect(()=>{return()=>{if(!node)return;context.layers.delete(node),context.layersWithOutsidePointerEventsDisabled.delete(node),dispatchUpdate()}},[node,context]),useEffect(()=>{let handleUpdate=()=>force({});return document.addEventListener(CONTEXT_UPDATE,handleUpdate),()=>document.removeEventListener(CONTEXT_UPDATE,handleUpdate)},[]);let pointerEvents;if(isBodyPointerEventsDisabled)pointerEvents=isPointerEventsEnabled?"auto":"none";return jsx(Primitive.div,{...layerProps,onBlurCapture:composeEventHandlers(props.onBlurCapture,focusOutside.onBlurCapture),onFocusCapture:composeEventHandlers(props.onFocusCapture,focusOutside.onFocusCapture),onPointerDownCapture:composeEventHandlers(props.onPointerDownCapture,pointerDownOutside.onPointerDownCapture),ref:composedRefs,style:{pointerEvents,...props.style}})});DismissableLayer.displayName=DISMISSABLE_LAYER_NAME;var BRANCH_NAME="DismissableLayerBranch",DismissableLayerBranch=forwardRef((props,forwardedRef)=>{let context=useContext(DismissableLayerContext),ref=useRef(null),composedRefs=useComposedRefs(forwardedRef,ref);return useEffect(()=>{let node=ref.current;if(node)return context.branches.add(node),()=>{context.branches.delete(node)}},[context.branches]),jsx(Primitive.div,{...props,ref:composedRefs})});DismissableLayerBranch.displayName=BRANCH_NAME;function usePointerDownOutside(onPointerDownOutside,ownerDocument=globalThis?.document){let handlePointerDownOutside=useCallbackRef(onPointerDownOutside),isPointerInsideReactTreeRef=useRef(!1),handleClickRef=useRef(()=>{});return useEffect(()=>{let handlePointerDown=(event)=>{if(event.target&&!isPointerInsideReactTreeRef.current){let handleAndDispatchPointerDownOutsideEvent=function(){handleAndDispatchCustomEvent(POINTER_DOWN_OUTSIDE,handlePointerDownOutside,eventDetail,{discrete:!0})},eventDetail={originalEvent:event};if(event.pointerType==="touch")ownerDocument.removeEventListener("click",handleClickRef.current),handleClickRef.current=handleAndDispatchPointerDownOutsideEvent,ownerDocument.addEventListener("click",handleClickRef.current,{once:!0});else handleAndDispatchPointerDownOutsideEvent()}else ownerDocument.removeEventListener("click",handleClickRef.current);isPointerInsideReactTreeRef.current=!1},timerId=window.setTimeout(()=>{ownerDocument.addEventListener("pointerdown",handlePointerDown)},0);return()=>{window.clearTimeout(timerId),ownerDocument.removeEventListener("pointerdown",handlePointerDown),ownerDocument.removeEventListener("click",handleClickRef.current)}},[ownerDocument,handlePointerDownOutside]),{onPointerDownCapture:()=>{isPointerInsideReactTreeRef.current=!0}}}function useFocusOutside(onFocusOutside,ownerDocument=globalThis?.document){let handleFocusOutside=useCallbackRef(onFocusOutside),isFocusInsideReactTreeRef=useRef(!1);return useEffect(()=>{let handleFocus=(event)=>{if(event.target&&!isFocusInsideReactTreeRef.current)handleAndDispatchCustomEvent(FOCUS_OUTSIDE,handleFocusOutside,{originalEvent:event},{discrete:!1})};return ownerDocument.addEventListener("focusin",handleFocus),()=>ownerDocument.removeEventListener("focusin",handleFocus)},[ownerDocument,handleFocusOutside]),{onBlurCapture:()=>{isFocusInsideReactTreeRef.current=!1},onFocusCapture:()=>{isFocusInsideReactTreeRef.current=!0}}}function dispatchUpdate(){let event=new CustomEvent(CONTEXT_UPDATE);document.dispatchEvent(event)}function handleAndDispatchCustomEvent(name,handler,detail,{discrete}){let{target}=detail.originalEvent,event=new CustomEvent(name,{bubbles:!1,cancelable:!0,detail});if(handler)target.addEventListener(name,handler,{once:!0});if(discrete)dispatchDiscreteCustomEvent(target,event);else target.dispatchEvent(event)}var Root=DismissableLayer,Branch=DismissableLayerBranch;export{Root,DismissableLayerBranch,DismissableLayer,Branch};