{"version":3,"file":"Persist.cjs","sources":["../../src/utils/Persist.tsx"],"sourcesContent":["\"use client\";\n\nimport { nn } from \"@liveblocks/core\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport type { ReactNode, RefObject } from \"react\";\nimport {\n  Children,\n  createContext,\n  isValidElement,\n  useCallback,\n  useContext,\n  useRef,\n  useState,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\n\n// Persist is an overly simplified version of Framer Motion's AnimatePresence,\n// mostly mimicking its usePresence API: https://github.com/framer/motion/blob/main/packages/framer-motion/src/components/AnimatePresence/use-presence.ts\n\nconst PERSIST_NAME = \"Persist\";\n\ninterface PersistProps {\n  children: Exclude<ReactNode, Iterable<ReactNode>>;\n}\n\ntype PersistContext = [boolean, () => void];\n\nconst PersistContext = createContext<PersistContext | null>(null);\n\nexport function usePersist() {\n  const persistContext = useContext(PersistContext);\n\n  return nn(persistContext, \"Persist is missing from the React tree.\");\n}\n\nfunction getChild(children: ReactNode) {\n  const child: ReactNode = Array.isArray(children)\n    ? Children.only(children)\n    : children;\n\n  return isValidElement(child) ? child : undefined;\n}\n\nexport function useAnimationPersist(ref: RefObject<HTMLElement>) {\n  const [isPresent, unmount] = usePersist();\n  const previousAnimationName = useRef<string | null>(null);\n  const unmountAnimationName = useRef<string | null>(null);\n\n  useLayoutEffect(() => {\n    const element = ref.current;\n\n    if (!element) {\n      return;\n    }\n\n    /**\n     * Stop persisting at the end of the last animation.\n     *\n     * We keep track of all ending animations because animations stay\n     * on getComputedStyle(element).animationName even if they're over,\n     * so we need to keep track of previous animations to truly know if\n     * an animation should be waited on.\n     */\n    const handleAnimationEnd = (event: AnimationEvent) => {\n      if (event.animationName === unmountAnimationName.current) {\n        unmount();\n      }\n\n      previousAnimationName.current = event.animationName;\n    };\n\n    element.addEventListener(\"animationcancel\", handleAnimationEnd);\n    element.addEventListener(\"animationend\", handleAnimationEnd);\n\n    return () => {\n      element.removeEventListener(\"animationcancel\", handleAnimationEnd);\n      element.removeEventListener(\"animationend\", handleAnimationEnd);\n    };\n  }, [ref, unmount]);\n\n  useLayoutEffect(() => {\n    const element = ref.current;\n    let animationFrameId: number;\n\n    if (!element) {\n      return;\n    }\n\n    if (!isPresent) {\n      // If the element should be unmounting, wait for a repaint and check\n      // if it is visible and has an animation. If not, unmount immediately.\n      animationFrameId = requestAnimationFrame(() => {\n        const styles = getComputedStyle(element);\n        unmountAnimationName.current = styles.animationName;\n\n        if (\n          styles.animationName === \"none\" ||\n          styles.animationName === previousAnimationName.current ||\n          styles.display === \"none\"\n        ) {\n          unmount();\n        }\n      });\n    }\n\n    return () => {\n      cancelAnimationFrame(animationFrameId);\n    };\n  }, [isPresent, ref, unmount]);\n}\n\n/**\n * Persist a component until it decides to unmount by\n * itself (instead of orchestrating the unmount from the parent).\n */\nexport function Persist({ children }: PersistProps) {\n  const [isPersisting, setPersisting] = useState(true);\n  const lastPresentChild = useRef<ReactNode>(null);\n  const child = getChild(children);\n\n  const unmount = useCallback(() => {\n    flushSync(() => setPersisting(false));\n  }, []);\n\n  useLayoutEffect(() => {\n    if (child) {\n      setPersisting(true);\n      lastPresentChild.current = child;\n    }\n  }, [child]);\n\n  return (\n    <PersistContext.Provider value={[Boolean(child), unmount]}>\n      {child ?? (isPersisting ? lastPresentChild.current : null)}\n    </PersistContext.Provider>\n  );\n}\n\nif (process.env.NODE_ENV !== \"production\") {\n  Persist.displayName = PERSIST_NAME;\n}\n"],"names":[],"mappings":";;;;;;;;;;AAmBA;AAQA;AAEO;AACL;AAEA;AACF;AAEA;AACE;AAIA;AACF;AAEO;AACL;AACA;AACA;AAEA;AACE;AAEA;AACE;AAAA;AAWF;AACE;AACE;AAAQ;AAGV;AAAsC;AAGxC;AACA;AAEA;AACE;AACA;AAA8D;AAChE;AAGF;AACE;AACA;AAEA;AACE;AAAA;AAGF;AAGE;AACE;AACA;AAEA;AAKE;AAAQ;AACV;AACD;AAGH;AACE;AAAqC;AACvC;AAEJ;AAMgB;AACd;AACA;AACA;AAEA;AACE;AAAoC;AAGtC;AACE;AACE;AACA;AAA2B;AAC7B;AAGF;AACG;AAAuD;AACD;AAG3D;AAEA;AACE;AACF;;;;"}