UNPKG

@loke/ui

Version:
2 lines (1 loc) 3.51 kB
import{useComposedRefs}from"@loke/ui/compose-refs";import{useLayoutEffect}from"@loke/ui/use-layout-effect";import{Children,cloneElement,useCallback,useEffect,useRef,useState}from"react";import{useReducer}from"react";function useStateMachine(initialState,machine){return useReducer((state,event)=>{return machine[state][event]??state},initialState)}var Presence=(props)=>{let{present,children}=props,presence=usePresence(present),child=typeof children==="function"?children({present:presence.isPresent}):Children.only(children),ref=useComposedRefs(presence.ref,getElementRef(child));return typeof children==="function"||presence.isPresent?cloneElement(child,{ref}):null};Presence.displayName="Presence";function usePresence(present){let[node,setNode]=useState(),stylesRef=useRef(null),prevPresentRef=useRef(present),prevAnimationNameRef=useRef("none"),initialState=present?"mounted":"unmounted",[state,send]=useStateMachine(initialState,{mounted:{ANIMATION_OUT:"unmountSuspended",UNMOUNT:"unmounted"},unmounted:{MOUNT:"mounted"},unmountSuspended:{ANIMATION_END:"unmounted",MOUNT:"mounted"}});return useEffect(()=>{let currentAnimationName=getAnimationName(stylesRef.current);prevAnimationNameRef.current=state==="mounted"?currentAnimationName:"none"},[state]),useLayoutEffect(()=>{let styles=stylesRef.current,wasPresent=prevPresentRef.current;if(wasPresent!==present){let prevAnimationName=prevAnimationNameRef.current,currentAnimationName=getAnimationName(styles);if(present)send("MOUNT");else if(currentAnimationName==="none"||styles?.display==="none")send("UNMOUNT");else if(wasPresent&&prevAnimationName!==currentAnimationName)send("ANIMATION_OUT");else send("UNMOUNT");prevPresentRef.current=present}},[present,send]),useLayoutEffect(()=>{if(node){let timeoutId,ownerWindow=node.ownerDocument.defaultView??window,handleAnimationEnd=(event)=>{let isCurrentAnimation=getAnimationName(stylesRef.current).includes(CSS.escape(event.animationName));if(event.target===node&&isCurrentAnimation){if(send("ANIMATION_END"),!prevPresentRef.current){let currentFillMode=node.style.animationFillMode;node.style.animationFillMode="forwards",timeoutId=ownerWindow.setTimeout(()=>{if(node.style.animationFillMode==="forwards")node.style.animationFillMode=currentFillMode})}}},handleAnimationStart=(event)=>{if(event.target===node)prevAnimationNameRef.current=getAnimationName(stylesRef.current)};return node.addEventListener("animationstart",handleAnimationStart),node.addEventListener("animationcancel",handleAnimationEnd),node.addEventListener("animationend",handleAnimationEnd),()=>{ownerWindow.clearTimeout(timeoutId),node.removeEventListener("animationstart",handleAnimationStart),node.removeEventListener("animationcancel",handleAnimationEnd),node.removeEventListener("animationend",handleAnimationEnd)}}send("ANIMATION_END")},[node,send]),{isPresent:["mounted","unmountSuspended"].includes(state),ref:useCallback((element)=>{stylesRef.current=element?getComputedStyle(element):null,setNode(element)},[])}}function getAnimationName(styles){return styles?.animationName||"none"}function getElementRef(element){let getter=Object.getOwnPropertyDescriptor(element.props,"ref")?.get,mayWarn=getter&&"isReactWarning"in getter&&getter.isReactWarning;if(mayWarn)return element.ref;if(getter=Object.getOwnPropertyDescriptor(element,"ref")?.get,mayWarn=getter&&"isReactWarning"in getter&&getter.isReactWarning,mayWarn)return element.props.ref;return element.props.ref||element.ref}var Root=Presence;export{Root,Presence};