framer-motion
Version:
A simple and powerful JavaScript animation library
1 lines • 6.21 kB
Source Map (JSON)
{"version":3,"file":"MeasureLayout.mjs","sources":["../../../../../src/motion/features/layout/MeasureLayout.tsx"],"sourcesContent":["\"use client\"\n\nimport { frame, microtask } from \"motion-dom\"\nimport { Component, useContext } from \"react\"\nimport { usePresence } from \"../../../components/AnimatePresence/use-presence\"\nimport {\n LayoutGroupContext,\n LayoutGroupContextProps,\n} from \"../../../context/LayoutGroupContext\"\nimport { SwitchLayoutGroupContext } from \"../../../context/SwitchLayoutGroupContext\"\nimport { globalProjectionState } from \"../../../projection/node/state\"\nimport { VisualElement } from \"../../../render/VisualElement\"\nimport { MotionProps } from \"../../types\"\n\ninterface MeasureContextProps {\n layoutGroup: LayoutGroupContextProps\n switchLayoutGroup?: SwitchLayoutGroupContext\n isPresent: boolean\n safeToRemove?: VoidFunction | null\n}\n\ntype MeasureProps = MotionProps &\n MeasureContextProps & { visualElement: VisualElement }\n\n/**\n * Track whether we've taken any snapshots yet. If not,\n * we can safely skip notification of didUpdate.\n *\n * Difficult to capture in a test but to prevent flickering\n * we must set this to true either on update or unmount.\n * Running `next-env/layout-id` in Safari will show this behaviour if broken.\n */\nlet hasTakenAnySnapshot = false\n\nclass MeasureLayoutWithContext extends Component<MeasureProps> {\n /**\n * This only mounts projection nodes for components that\n * need measuring, we might want to do it for all components\n * in order to incorporate transforms\n */\n componentDidMount() {\n const { visualElement, layoutGroup, switchLayoutGroup, layoutId } =\n this.props\n const { projection } = visualElement\n\n if (projection) {\n if (layoutGroup.group) layoutGroup.group.add(projection)\n\n if (switchLayoutGroup && switchLayoutGroup.register && layoutId) {\n switchLayoutGroup.register(projection)\n }\n\n if (hasTakenAnySnapshot) {\n projection.root!.didUpdate()\n }\n\n projection.addEventListener(\"animationComplete\", () => {\n this.safeToRemove()\n })\n projection.setOptions({\n ...projection.options,\n onExitComplete: () => this.safeToRemove(),\n })\n }\n\n globalProjectionState.hasEverUpdated = true\n }\n\n getSnapshotBeforeUpdate(prevProps: MeasureProps) {\n const { layoutDependency, visualElement, drag, isPresent } = this.props\n const { projection } = visualElement\n\n if (!projection) return null\n\n /**\n * TODO: We use this data in relegate to determine whether to\n * promote a previous element. There's no guarantee its presence data\n * will have updated by this point - if a bug like this arises it will\n * have to be that we markForRelegation and then find a new lead some other way,\n * perhaps in didUpdate\n */\n projection.isPresent = isPresent\n\n hasTakenAnySnapshot = true\n\n if (\n drag ||\n prevProps.layoutDependency !== layoutDependency ||\n layoutDependency === undefined ||\n prevProps.isPresent !== isPresent\n ) {\n projection.willUpdate()\n } else {\n this.safeToRemove()\n }\n\n if (prevProps.isPresent !== isPresent) {\n if (isPresent) {\n projection.promote()\n } else if (!projection.relegate()) {\n /**\n * If there's another stack member taking over from this one,\n * it's in charge of the exit animation and therefore should\n * be in charge of the safe to remove. Otherwise we call it here.\n */\n frame.postRender(() => {\n const stack = projection.getStack()\n if (!stack || !stack.members.length) {\n this.safeToRemove()\n }\n })\n }\n }\n\n return null\n }\n\n componentDidUpdate() {\n const { projection } = this.props.visualElement\n if (projection) {\n projection.root!.didUpdate()\n\n microtask.postRender(() => {\n if (!projection.currentAnimation && projection.isLead()) {\n this.safeToRemove()\n }\n })\n }\n }\n\n componentWillUnmount() {\n const {\n visualElement,\n layoutGroup,\n switchLayoutGroup: promoteContext,\n } = this.props\n const { projection } = visualElement\n\n hasTakenAnySnapshot = true\n\n if (projection) {\n projection.scheduleCheckAfterUnmount()\n if (layoutGroup && layoutGroup.group)\n layoutGroup.group.remove(projection)\n if (promoteContext && promoteContext.deregister)\n promoteContext.deregister(projection)\n }\n }\n\n safeToRemove() {\n const { safeToRemove } = this.props\n safeToRemove && safeToRemove()\n }\n\n render() {\n return null\n }\n}\n\nexport function MeasureLayout(\n props: MotionProps & { visualElement: VisualElement }\n) {\n const [isPresent, safeToRemove] = usePresence()\n const layoutGroup = useContext(LayoutGroupContext)\n\n return (\n <MeasureLayoutWithContext\n {...props}\n layoutGroup={layoutGroup}\n switchLayoutGroup={useContext(SwitchLayoutGroupContext)}\n isPresent={isPresent}\n safeToRemove={safeToRemove}\n />\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAwBA;;;;;;;AAOG;AACH;AAEA;AACI;;;;AAIG;;AAEC;AAEA;;;AAG2B;;AAGnB;;;AAIA;;AAGJ;;AAEA;;;AAGI;AACH;;AAGL;;AAGJ;AACI;AACA;AAEA;AAAiB;AAEjB;;;;;;AAMG;AACH;;AAIA;;AAGI;AACA;;;;;;AAOJ;;;;AAGW;AACH;;;;AAIG;AACH;AACI;;;;AAIJ;;;AAIR;;;;;AAMI;AAEA;;;;AAIA;;;;AAKJ;AAKA;;;;AAMI;AACI;AACJ;AACI;;;;AAKR;;;;AAKA;;AAEP;AAEK;;AAIF;;AAWJ;;"}