@tamagui/animate-presence 2.0.0-rc.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/AnimatePresence.cjs +112 -91
  2. package/dist/cjs/AnimatePresence.native.js +127 -106
  3. package/dist/cjs/AnimatePresence.native.js.map +1 -1
  4. package/dist/cjs/LayoutGroupContext.cjs +24 -22
  5. package/dist/cjs/LayoutGroupContext.native.js +26 -24
  6. package/dist/cjs/LayoutGroupContext.native.js.map +1 -1
  7. package/dist/cjs/PresenceChild.cjs +58 -45
  8. package/dist/cjs/PresenceChild.native.js +103 -91
  9. package/dist/cjs/PresenceChild.native.js.map +1 -1
  10. package/dist/cjs/index.cjs +7 -5
  11. package/dist/cjs/index.native.js +7 -5
  12. package/dist/cjs/index.native.js.map +1 -1
  13. package/dist/cjs/types.cjs +7 -5
  14. package/dist/cjs/types.native.js +7 -5
  15. package/dist/cjs/types.native.js.map +1 -1
  16. package/dist/esm/AnimatePresence.mjs +96 -77
  17. package/dist/esm/AnimatePresence.mjs.map +1 -1
  18. package/dist/esm/AnimatePresence.native.js +111 -92
  19. package/dist/esm/AnimatePresence.native.js.map +1 -1
  20. package/dist/esm/PresenceChild.mjs +29 -18
  21. package/dist/esm/PresenceChild.mjs.map +1 -1
  22. package/dist/esm/PresenceChild.native.js +60 -50
  23. package/dist/esm/PresenceChild.native.js.map +1 -1
  24. package/dist/esm/index.js +3 -3
  25. package/dist/esm/index.js.map +1 -6
  26. package/package.json +10 -9
  27. package/src/AnimatePresence.tsx +150 -143
  28. package/src/types.ts +10 -9
  29. package/types/AnimatePresence.d.ts.map +1 -1
  30. package/types/types.d.ts +9 -9
  31. package/types/types.d.ts.map +1 -1
  32. package/dist/cjs/AnimatePresence.js +0 -121
  33. package/dist/cjs/AnimatePresence.js.map +0 -6
  34. package/dist/cjs/LayoutGroupContext.js +0 -30
  35. package/dist/cjs/LayoutGroupContext.js.map +0 -6
  36. package/dist/cjs/PresenceChild.js +0 -77
  37. package/dist/cjs/PresenceChild.js.map +0 -6
  38. package/dist/cjs/index.js +0 -18
  39. package/dist/cjs/index.js.map +0 -6
  40. package/dist/cjs/types.js +0 -14
  41. package/dist/cjs/types.js.map +0 -6
  42. package/dist/esm/AnimatePresence.js +0 -110
  43. package/dist/esm/AnimatePresence.js.map +0 -6
  44. package/dist/esm/LayoutGroupContext.js +0 -6
  45. package/dist/esm/LayoutGroupContext.js.map +0 -6
  46. package/dist/esm/PresenceChild.js +0 -57
  47. package/dist/esm/PresenceChild.js.map +0 -6
  48. package/dist/esm/types.js +0 -1
  49. package/dist/esm/types.js.map +0 -6
@@ -5,69 +5,79 @@ import * as React from "react";
5
5
  import { useId } from "react";
6
6
  var PresenceChild = /* @__PURE__ */React.memo(function (param) {
7
7
  var {
8
- children,
8
+ children,
9
+ initial,
10
+ isPresent,
11
+ onExitComplete,
12
+ exitVariant,
13
+ enterVariant,
14
+ enterExitVariant,
15
+ presenceAffectsLayout,
16
+ custom
17
+ } = param;
18
+ var presenceChildren = useConstant(newChildrenMap);
19
+ var id = useId() || "";
20
+ var context = React.useMemo(function () {
21
+ return {
22
+ id,
9
23
  initial,
10
24
  isPresent,
11
- onExitComplete,
25
+ custom,
12
26
  exitVariant,
13
27
  enterVariant,
14
28
  enterExitVariant,
15
- presenceAffectsLayout,
16
- custom
17
- } = param,
18
- presenceChildren = useConstant(newChildrenMap),
19
- id = useId() || "",
20
- context = React.useMemo(function () {
21
- return {
22
- id,
23
- initial,
24
- isPresent,
25
- custom,
26
- exitVariant,
27
- enterVariant,
28
- enterExitVariant,
29
- onExitComplete: function () {
30
- presenceChildren.set(id, !0);
31
- var _iteratorNormalCompletion = !0,
32
- _didIteratorError = !1,
33
- _iteratorError = void 0;
29
+ onExitComplete: function () {
30
+ presenceChildren.set(id, true);
31
+ var _iteratorNormalCompletion = true,
32
+ _didIteratorError = false,
33
+ _iteratorError = void 0;
34
+ try {
35
+ for (var _iterator = presenceChildren.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
36
+ var isComplete = _step.value;
37
+ if (!isComplete) {
38
+ return;
39
+ }
40
+ }
41
+ } catch (err) {
42
+ _didIteratorError = true;
43
+ _iteratorError = err;
44
+ } finally {
34
45
  try {
35
- for (var _iterator = presenceChildren.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = !0) {
36
- var isComplete = _step.value;
37
- if (!isComplete) return;
46
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
47
+ _iterator.return();
38
48
  }
39
- } catch (err) {
40
- _didIteratorError = !0, _iteratorError = err;
41
49
  } finally {
42
- try {
43
- !_iteratorNormalCompletion && _iterator.return != null && _iterator.return();
44
- } finally {
45
- if (_didIteratorError) throw _iteratorError;
50
+ if (_didIteratorError) {
51
+ throw _iteratorError;
46
52
  }
47
53
  }
48
- onExitComplete?.();
49
- },
50
- register: function () {
51
- return presenceChildren.set(id, !1), function () {
52
- return presenceChildren.delete(id);
53
- };
54
54
  }
55
- };
56
- },
57
- /**
58
- * If the presence of a child affects the layout of the components around it,
59
- * we want to make a new context value to ensure they get re-rendered
60
- * so they can detect that layout change.
61
- */
62
- // @ts-expect-error its ok
63
- presenceAffectsLayout ? void 0 : [isPresent, exitVariant, enterVariant]);
64
- return React.useMemo(function () {
55
+ onExitComplete === null || onExitComplete === void 0 ? void 0 : onExitComplete();
56
+ },
57
+ register: function () {
58
+ presenceChildren.set(id, false);
59
+ return function () {
60
+ return presenceChildren.delete(id);
61
+ };
62
+ }
63
+ };
64
+ },
65
+ /**
66
+ * If the presence of a child affects the layout of the components around it,
67
+ * we want to make a new context value to ensure they get re-rendered
68
+ * so they can detect that layout change.
69
+ */
70
+ // @ts-expect-error its ok
71
+ presenceAffectsLayout ? void 0 : [isPresent, exitVariant, enterVariant]);
72
+ React.useMemo(function () {
65
73
  presenceChildren.forEach(function (_, key) {
66
- return presenceChildren.set(key, !1);
74
+ return presenceChildren.set(key, false);
67
75
  });
68
- }, [isPresent]), React.useEffect(function () {
69
- !isPresent && !presenceChildren.size && onExitComplete?.();
70
- }, [isPresent]), /* @__PURE__ */_jsx(PresenceContext.Provider, {
76
+ }, [isPresent]);
77
+ React.useEffect(function () {
78
+ !isPresent && !presenceChildren.size && (onExitComplete === null || onExitComplete === void 0 ? void 0 : onExitComplete());
79
+ }, [isPresent]);
80
+ return /* @__PURE__ */_jsx(PresenceContext.Provider, {
71
81
  value: context,
72
82
  children
73
83
  });
@@ -1 +1 @@
1
- {"version":3,"names":["jsx","_jsx","useConstant","PresenceContext","React","useId","PresenceChild","memo","param","children","initial","isPresent","onExitComplete","exitVariant","enterVariant","enterExitVariant","presenceAffectsLayout","custom","presenceChildren","newChildrenMap","id","context","useMemo","set","_iteratorNormalCompletion","_didIteratorError","_iteratorError","_iterator","values","Symbol","iterator","_step","next","done","isComplete","value","err","return","register","delete"],"sources":["../../src/PresenceChild.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,GAAA,IAAAC,IAAA,QAAmB;AAC5B,SAASC,WAAA,+BAAuB;AAEhC,SAAAC,eAAuB;AACvB,YAASC,KAAA,MAAa;AA+EX,SAAAC,KAAA;AA9DJ,IAAAC,aAAM,kBAAsBF,KAAA,CAAAG,IAAA,WAAAC,KAAA;EACjC,IAAC;MAAAC,QAAA;MAAAC,OAAA;MAAAC,SAAA;MAAAC,cAAA;MAAAC,WAAA;MAAAC,YAAA;MAAAC,gBAAA;MAAAC,qBAAA;MAAAC;IAAA,IAAAT,KAAA;IAAAU,gBAAA,GAAAhB,WAAA,CAAAiB,cAAA;IAAAC,EAAA,GAAAf,KAAA;IAAAgB,OAAA,GAAAjB,KAAA,CAAAkB,OAAA,CACC;MACA;QACAF,EAAA;QACAV,OAAA;QACAC,SAAA;QACAM,MAAA;QACAJ,WAAA;QACAC,YAAA;QACAC,gBAAA;QACwBH,cAAA,WAAAA,CAAA;UAClBM,gBAAA,CAAAK,GAAmB,CAAAH,EAAA;UAIvB,IACSI,yBAAA;YAAAC,iBAAA;YAAAC,cAAA;UACL;YACA,SAAAC,SAAA,GAAAT,gBAAA,CAAAU,MAAA,GAAAC,MAAA,CAAAC,QAAA,KAAAC,KAAA,IAAAP,yBAAA,IAAAO,KAAA,GAAAJ,SAAA,CAAAK,IAAA,IAAAC,IAAA,GAAAT,yBAAA;cACA,IAAAU,UAAA,GAAAH,KAAA,CAAAI,KAAA;cACA,KAAAD,UAAA,EACA;YACA;UACA,SAAAE,GAAA;YACAX,iBAAgB,GAAM,IAAAC,cAAA,GAAAU,GAAA;UACpB;YACA;cACE,CAAAZ,yBAAK,IAAAG,SAAA,CAAAU,MAAA,YAAAV,SAAA,CAAAU,MAAA;YACH;cAGJ,IAAAZ,iBAAiB,EACnB,MAAAC,cAAA;YACA;UAIF;UAAAd,cAAA;QAAA;QAAA0B,QAAA,WAAAA,CAAA;UAAA,OAAApB,gBAAA,CAAAK,GAAA,CAAAH,EAAA;YAAA,OAAAF,gBAAA,CAAAqB,MAAA,CAAAnB,EAAA;UAAA;QASF;MACF;IAEA;IACE;AAAqE;AAQrE;AAAyD;AAGA;IAE/D;IAEAJ,qBAAS,GAAuC,UAC9CL,SAAO,EACTE,WAAA,E","ignoreList":[]}
1
+ {"version":3,"names":["jsx","_jsx","useConstant","PresenceContext","React","useId","PresenceChild","memo","param","children","initial","isPresent","onExitComplete","exitVariant","enterVariant","enterExitVariant","presenceAffectsLayout","custom","presenceChildren","newChildrenMap","id","context","useMemo","set","_iteratorNormalCompletion","_didIteratorError","_iteratorError","_iterator","values","Symbol","iterator","_step","next","done","isComplete","value","err","return","register","delete"],"sources":["../../src/PresenceChild.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,GAAA,IAAAC,IAAA,QAAmB;AAC5B,SAASC,WAAA,+BAAuB;AAEhC,SAAAC,eAAuB;AACvB,YAASC,KAAA,MAAa;AA+EX,SAAAC,KAAA;AA9DJ,IAAAC,aAAM,kBAAsBF,KAAA,CAAAG,IAAA,WAAAC,KAAA;EACjC,IAAC;IAAAC,QAAA;IAAAC,OAAA;IAAAC,SAAA;IAAAC,cAAA;IAAAC,WAAA;IAAAC,YAAA;IAAAC,gBAAA;IAAAC,qBAAA;IAAAC;EAAA,IAAAT,KAAA;EAAA,IACCU,gBAAA,GAAAhB,WAAA,CAAAiB,cAAA;EAAA,IACAC,EAAA,GAAAf,KAAA;EAAA,IACAgB,OAAA,GAAAjB,KAAA,CAAAkB,OAAA,CACA;IACA;MACAF,EAAA;MACAV,OAAA;MACAC,SAAA;MACAM,MAAA;MACwBJ,WAAA;MACxBC,YAAM;MACNC,gBAAiB;MAEjBH,cAAgB,WAAAA,CAAA,EAAM;QACpBM,gBAA4B,CAAAK,GAAA,CAAAH,EAAA;QAC1B,IAAAI,yBAAO;UAAAC,iBAAA;UAAAC,cAAA;QACL;UACA,SAAAC,SAAA,GAAAT,gBAAA,CAAAU,MAAA,GAAAC,MAAA,CAAAC,QAAA,KAAAC,KAAA,IAAAP,yBAAA,IAAAO,KAAA,GAAAJ,SAAA,CAAAK,IAAA,IAAAC,IAAA,GAAAT,yBAAA;YACA,IAAAU,UAAA,GAAAH,KAAA,CAAAI,KAAA;YACA,KAAAD,UAAA;cACA;YACA;UACA;QACA,SAAAE,GAAA;UACEX,iBAAiB,OAAI;UACrBC,cAAW,GAAAU,GAAA;QACT,UAAK;UACH;YACF,KAAAZ,yBAAA,IAAAG,SAAA,CAAAU,MAAA;cACFV,SAAA,CAAAU,MAAA;YACA;UACF;YACA,IAAAZ,iBAAgB;cACd,MAAAC,cAAqB;YACrB;UACF;QACF;QACFd,cAAA,aAAAA,cAAA,uBAAAA,cAAA;MAAA;MAAA0B,QAAA,WAAAA,CAAA;QAAApB,gBAAA,CAAAK,GAAA,CAAAH,EAAA;QAAA;UAAA,OAAAF,gBAAA,CAAAqB,MAAA,CAAAnB,EAAA;QAAA;MAQA;IACF;EAEA;EACE;AAAqE;AAOvE;AACE;AAAyD;EAG3D;EACFJ,qBAAA,aACFL,SAAA,EAEAE,WAAS,EACPC,YAAO,C","ignoreList":[]}
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export * from "./AnimatePresence";
1
+ export * from "./AnimatePresence.mjs";
2
2
  export * from "@tamagui/use-presence";
3
- export * from "./types";
4
- export * from "./PresenceChild";
3
+ export * from "./types.mjs";
4
+ export * from "./PresenceChild.mjs";
5
5
  //# sourceMappingURL=index.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/index.ts"],
4
- "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
5
- "names": []
6
- }
1
+ {"version":3,"names":[],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/animate-presence",
3
- "version": "2.0.0-rc.9",
3
+ "version": "2.1.0",
4
4
  "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
5
5
  "license": "MIT",
6
6
  "source": "src/index.ts",
@@ -21,10 +21,11 @@
21
21
  ".": {
22
22
  "types": "./types/index.d.ts",
23
23
  "react-native": "./dist/esm/index.native.js",
24
+ "browser": "./dist/esm/index.mjs",
24
25
  "module": "./dist/esm/index.mjs",
25
26
  "import": "./dist/esm/index.mjs",
26
27
  "require": "./dist/cjs/index.cjs",
27
- "default": "./dist/cjs/index.native.js"
28
+ "default": "./dist/esm/index.mjs"
28
29
  }
29
30
  },
30
31
  "publishConfig": {
@@ -37,15 +38,15 @@
37
38
  "clean:build": "tamagui-build clean:build"
38
39
  },
39
40
  "dependencies": {
40
- "@tamagui/constants": "2.0.0-rc.9",
41
- "@tamagui/helpers": "2.0.0-rc.9",
42
- "@tamagui/use-constant": "2.0.0-rc.9",
43
- "@tamagui/use-force-update": "2.0.0-rc.9",
44
- "@tamagui/use-presence": "2.0.0-rc.9",
45
- "@tamagui/web": "2.0.0-rc.9"
41
+ "@tamagui/constants": "2.1.0",
42
+ "@tamagui/helpers": "2.1.0",
43
+ "@tamagui/use-constant": "2.1.0",
44
+ "@tamagui/use-force-update": "2.1.0",
45
+ "@tamagui/use-presence": "2.1.0",
46
+ "@tamagui/web": "2.1.0"
46
47
  },
47
48
  "devDependencies": {
48
- "@tamagui/build": "2.0.0-rc.9",
49
+ "@tamagui/build": "2.1.0",
49
50
  "react": ">=19"
50
51
  },
51
52
  "peerDependencies": {
@@ -1,7 +1,8 @@
1
- import { useIsomorphicLayoutEffect } from '@tamagui/constants'
1
+ import { useInsertionEffect } from 'react'
2
+ import { useConstant } from '@tamagui/use-constant'
2
3
  import { useForceUpdate } from '@tamagui/use-force-update'
3
4
  import type { FunctionComponent, PropsWithChildren, ReactElement, ReactNode } from 'react'
4
- import { Children, cloneElement, isValidElement, useContext, useRef } from 'react'
5
+ import { Children, isValidElement, useContext, useMemo, useRef, useState } from 'react'
5
6
  import { LayoutGroupContext } from './LayoutGroupContext'
6
7
  import { PresenceChild } from './PresenceChild'
7
8
  import type { AnimatePresenceProps } from './types'
@@ -24,16 +25,6 @@ const getChildKey = (child: ReactElement<any>): ComponentKey => {
24
25
  )
25
26
  }
26
27
 
27
- function updateChildLookup(
28
- children: ReactElement<any>[],
29
- allChildren: Map<ComponentKey, ReactElement<any>>
30
- ) {
31
- children.forEach((child) => {
32
- const key = getChildKey(child)
33
- allChildren.set(key, child)
34
- })
35
- }
36
-
37
28
  function onlyElements(children: ReactNode): ReactElement<any>[] {
38
29
  const filtered: ReactElement<any>[] = []
39
30
  // We use forEach here instead of map as map mutates the component key by preprending `.$`
@@ -53,158 +44,174 @@ export const AnimatePresence: FunctionComponent<
53
44
  initial = true,
54
45
  onExitComplete,
55
46
  exitBeforeEnter,
47
+ mode,
56
48
  presenceAffectsLayout = true,
57
49
  custom,
58
50
  passThrough,
59
51
  }) => {
60
- // We want to force a re-render once all exiting animations have finished. We
61
- // either use a local forceRender function, or one from a parent context if it exists.
62
- let forceRender = useContext(LayoutGroupContext).forceRender ?? useForceUpdate()
63
-
64
- // Filter out any children that aren't ReactElements. We can only track ReactElements with a props.key
65
- const filteredChildren = onlyElements(children)
66
-
67
- // Keep a living record of the children we're actually rendering so we
68
- // can diff to figure out which are entering and exiting
69
- const presentChildren = useRef(filteredChildren)
70
-
71
- // A lookup table to quickly reference components by key
72
- const allChildren = useRef(new Map<ComponentKey, ReactElement<any>>()).current
73
-
74
- const exiting = useRef(new Set<ComponentKey>()).current
75
- updateChildLookup(filteredChildren, allChildren)
76
-
77
- // If this is the initial component render, just deal with logic surrounding whether
78
- // we play onMount animations or not.
52
+ // Determine effective mode: mode prop takes precedence, then exitBeforeEnter for backwards compatibility
53
+ const effectiveMode = mode ?? (exitBeforeEnter ? 'wait' : 'sync')
54
+
55
+ /**
56
+ * Filter any children that aren't ReactElements. We can only track components
57
+ * between renders with a props.key.
58
+ * IMPORTANT: useMemo ensures reference stability for the comparison below
59
+ */
60
+ const presentChildren = useMemo(() => onlyElements(children), [children])
61
+
62
+ /**
63
+ * Track the keys of the currently rendered children.
64
+ */
65
+ const presentKeys = presentChildren.map(getChildKey)
66
+
67
+ /**
68
+ * If `initial={false}` we only want to pass this to components in the first render.
69
+ */
79
70
  const isInitialRender = useRef(true)
80
71
 
72
+ /**
73
+ * Freeze custom prop for exiting children so direction doesn't reverse mid-exit.
74
+ */
75
+ const frozenCustomRef = useRef(new Map<ComponentKey, any>())
76
+
77
+ /**
78
+ * A ref containing the currently present children. When all exit animations
79
+ * are complete, we use this to re-render with the latest children *committed*
80
+ * rather than the latest children *rendered*.
81
+ */
82
+ const pendingPresentChildren = useRef(presentChildren)
83
+
84
+ /**
85
+ * Track which exiting children have finished animating out.
86
+ */
87
+ const exitComplete = useConstant(() => new Map<ComponentKey, boolean>())
88
+
89
+ /**
90
+ * Save children to render as React state. To ensure this component is concurrent-safe,
91
+ * we check for exiting children via an effect.
92
+ */
93
+ const [diffedChildren, setDiffedChildren] = useState(presentChildren)
94
+ const [renderedChildren, setRenderedChildren] = useState(presentChildren)
95
+
96
+ /**
97
+ * If we've been provided a forceRender function by the LayoutGroupContext,
98
+ * we can use it to force a re-render amongst all surrounding components once
99
+ * all components have finished animating out.
100
+ */
101
+ const forceRender = useContext(LayoutGroupContext).forceRender ?? useForceUpdate()
102
+
81
103
  if (passThrough) {
82
- // match structure returned below
83
104
  return <>{children}</>
84
105
  }
85
106
 
86
- useIsomorphicLayoutEffect(() => {
107
+ // useInsertionEffect runs before ALL useLayoutEffects (including children's)
108
+ // this ensures pendingPresentChildren and exitComplete are set before
109
+ // animation drivers call sendExitComplete() in their layout effects
110
+ // (critical for immediate completions like animateOnly=[])
111
+ useInsertionEffect(() => {
87
112
  isInitialRender.current = false
88
- }, [])
89
-
90
- if (isInitialRender.current) {
91
- return (
92
- <>
93
- {filteredChildren.map((child) => (
94
- <PresenceChild
95
- key={getChildKey(child)}
96
- isPresent
97
- enterExitVariant={enterExitVariant}
98
- exitVariant={exitVariant}
99
- enterVariant={enterVariant}
100
- initial={initial ? undefined : false}
101
- presenceAffectsLayout={presenceAffectsLayout}
102
- custom={custom}
103
- >
104
- {child}
105
- </PresenceChild>
106
- ))}
107
- </>
108
- )
109
- }
110
-
111
- let childrenToRender = [...filteredChildren]
112
-
113
- // Diff the keys of the currently-present and target children to update our
114
- // exiting list.
115
- const presentKeys = presentChildren.current.map(getChildKey)
116
- const targetKeys = filteredChildren.map(getChildKey)
117
-
118
- // Diff the present children with our target children and mark those that are exiting
119
- const numPresent = presentKeys.length
120
- for (let i = 0; i < numPresent; i++) {
121
- const key = presentKeys[i]
122
- if (targetKeys.indexOf(key) === -1) {
123
- exiting.add(key)
124
- } else {
125
- // In case this key has re-entered, remove from the exiting list
126
- exiting.delete(key)
113
+ pendingPresentChildren.current = presentChildren
114
+
115
+ /**
116
+ * Update complete status of exiting children.
117
+ */
118
+ for (let i = 0; i < renderedChildren.length; i++) {
119
+ const key = getChildKey(renderedChildren[i])
120
+
121
+ if (!presentKeys.includes(key)) {
122
+ if (exitComplete.get(key) !== true) {
123
+ exitComplete.set(key, false)
124
+ }
125
+ } else {
126
+ exitComplete.delete(key)
127
+ frozenCustomRef.current.delete(key)
128
+ }
127
129
  }
128
- }
129
-
130
- // If we currently have exiting children, and we're deferring rendering incoming children
131
- // until after all current children have exiting, empty the childrenToRender array
132
- if (exitBeforeEnter && exiting.size) {
133
- childrenToRender = []
134
- }
135
-
136
- // Loop through all currently exiting components and clone them to overwrite `animate`
137
- // with any `exit` prop they might have defined.
138
- exiting.forEach((key) => {
139
- // If this component is actually entering again, early return
140
- if (targetKeys.indexOf(key) !== -1) return
141
-
142
- const child = allChildren.get(key)
143
- if (!child) return
144
-
145
- const insertionIndex = presentKeys.indexOf(key)
146
-
147
- const onExit = () => {
148
- allChildren.delete(key)
149
- exiting.delete(key)
150
- const removeIndex = presentChildren.current.findIndex(
151
- (presentChild) => presentChild.key === key
152
- )
153
- presentChildren.current.splice(removeIndex, 1)
154
-
155
- if (!exiting.size) {
156
- presentChildren.current = filteredChildren
157
- forceRender()
158
- onExitComplete?.()
130
+ }, [renderedChildren, presentKeys.length, presentKeys.join('-')])
131
+
132
+ if (presentChildren !== diffedChildren) {
133
+ let nextChildren = [...presentChildren]
134
+
135
+ /**
136
+ * Loop through all the currently rendered components and decide which
137
+ * are exiting.
138
+ */
139
+ for (let i = 0; i < renderedChildren.length; i++) {
140
+ const child = renderedChildren[i]
141
+ const key = getChildKey(child)
142
+
143
+ if (!presentKeys.includes(key)) {
144
+ nextChildren.splice(i, 0, child)
145
+ // freeze custom at the moment of exit so direction doesn't reverse
146
+ if (!frozenCustomRef.current.has(key)) {
147
+ frozenCustomRef.current.set(key, custom)
148
+ }
159
149
  }
160
150
  }
161
151
 
162
- const exitingComponent = (
163
- <PresenceChild
164
- key={getChildKey(child)}
165
- isPresent={false}
166
- onExitComplete={onExit}
167
- presenceAffectsLayout={presenceAffectsLayout}
168
- enterExitVariant={enterExitVariant}
169
- enterVariant={enterVariant}
170
- exitVariant={exitVariant}
171
- custom={custom}
172
- >
173
- {child}
174
- </PresenceChild>
152
+ /**
153
+ * If we're in "wait" mode, and we have exiting children, we want to
154
+ * only render these until they've all exited.
155
+ */
156
+ const exitingChildren = renderedChildren.filter(
157
+ (child) => !presentKeys.includes(getChildKey(child))
175
158
  )
159
+ if (effectiveMode === 'wait' && exitingChildren.length) {
160
+ nextChildren = exitingChildren
161
+ }
176
162
 
177
- childrenToRender.splice(insertionIndex, 0, exitingComponent)
178
- })
179
-
180
- // Add `MotionContext` even to children that don't need it to ensure we're rendering
181
- // the same tree between renders
182
- childrenToRender = childrenToRender.map((child) => {
183
- const key = child.key as ComponentKey
184
- return exiting.has(key) ? (
185
- child
186
- ) : (
187
- <PresenceChild
188
- key={getChildKey(child)}
189
- isPresent
190
- exitVariant={exitVariant}
191
- enterVariant={enterVariant}
192
- enterExitVariant={enterExitVariant}
193
- presenceAffectsLayout={presenceAffectsLayout}
194
- custom={custom}
195
- >
196
- {child}
197
- </PresenceChild>
198
- )
199
- })
163
+ setRenderedChildren(onlyElements(nextChildren))
164
+ setDiffedChildren(presentChildren)
200
165
 
201
- presentChildren.current = childrenToRender
166
+ /**
167
+ * Early return to ensure once we've set state with the latest diffed
168
+ * children, we can immediately re-render.
169
+ */
170
+ return null
171
+ }
202
172
 
203
173
  return (
204
174
  <>
205
- {exiting.size
206
- ? childrenToRender
207
- : childrenToRender.map((child) => cloneElement(child))}
175
+ {renderedChildren.map((child) => {
176
+ const key = getChildKey(child)
177
+ const isPresent =
178
+ presentChildren === renderedChildren || presentKeys.includes(key)
179
+
180
+ const onExit = () => {
181
+ if (exitComplete.has(key)) {
182
+ exitComplete.set(key, true)
183
+ } else {
184
+ return
185
+ }
186
+
187
+ let isEveryExitComplete = true
188
+ exitComplete.forEach((isExitComplete) => {
189
+ if (!isExitComplete) isEveryExitComplete = false
190
+ })
191
+
192
+ if (isEveryExitComplete) {
193
+ forceRender?.()
194
+ setRenderedChildren(pendingPresentChildren.current)
195
+ onExitComplete?.()
196
+ }
197
+ }
198
+
199
+ return (
200
+ <PresenceChild
201
+ key={key}
202
+ isPresent={isPresent}
203
+ initial={!isInitialRender.current || initial ? undefined : false}
204
+ custom={isPresent ? custom : (frozenCustomRef.current.get(key) ?? custom)}
205
+ presenceAffectsLayout={presenceAffectsLayout}
206
+ enterExitVariant={enterExitVariant}
207
+ enterVariant={enterVariant}
208
+ exitVariant={exitVariant}
209
+ onExitComplete={isPresent ? undefined : onExit}
210
+ >
211
+ {child}
212
+ </PresenceChild>
213
+ )
214
+ })}
208
215
  </>
209
216
  )
210
217
  }
package/src/types.ts CHANGED
@@ -14,6 +14,15 @@ export type VariantLabels = string | string[]
14
14
  * @public
15
15
  */
16
16
  export interface AnimatePresenceProps {
17
+ /**
18
+ * Determines how to handle entering and exiting elements.
19
+ * - "sync": Default. Elements animate in and out simultaneously.
20
+ * - "wait": Wait for exit to finish before entering.
21
+ *
22
+ * @public
23
+ */
24
+ mode?: 'sync' | 'wait'
25
+
17
26
  /**
18
27
  * By passing `initial={false}`, `AnimatePresence` will disable any initial animations on children
19
28
  * that are present when the component is first rendered.
@@ -66,15 +75,7 @@ export interface AnimatePresenceProps {
66
75
  * If set to `true`, `AnimatePresence` will only render one component at a time. The exiting component
67
76
  * will finish its exit animation before the entering component is rendered.
68
77
  *
69
- * ```jsx
70
- * const MyComponent = ({ currentItem }) => (
71
- * <AnimatePresence exitBeforeEnter>
72
- * <motion.div key={currentItem} exit={{ opacity: 0 }} />
73
- * </AnimatePresence>
74
- * )
75
- * ```
76
- *
77
- * @beta
78
+ * @deprecated Use `mode="wait"` instead.
78
79
  */
79
80
  exitBeforeEnter?: boolean
80
81
 
@@ -1 +1 @@
1
- {"version":3,"file":"AnimatePresence.d.ts","sourceRoot":"","sources":["../src/AnimatePresence.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAA2B,MAAM,OAAO,CAAA;AAI1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAuCnD,eAAO,MAAM,eAAe,EAAE,iBAAiB,CAC7C,iBAAiB,CAAC,oBAAoB,CAAC,CAmKxC,CAAA"}
1
+ {"version":3,"file":"AnimatePresence.d.ts","sourceRoot":"","sources":["../src/AnimatePresence.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAA2B,MAAM,OAAO,CAAA;AAI1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AA6BnD,eAAO,MAAM,eAAe,EAAE,iBAAiB,CAC7C,iBAAiB,CAAC,oBAAoB,CAAC,CAmLxC,CAAA"}
package/types/types.d.ts CHANGED
@@ -11,6 +11,14 @@ export type VariantLabels = string | string[];
11
11
  * @public
12
12
  */
13
13
  export interface AnimatePresenceProps {
14
+ /**
15
+ * Determines how to handle entering and exiting elements.
16
+ * - "sync": Default. Elements animate in and out simultaneously.
17
+ * - "wait": Wait for exit to finish before entering.
18
+ *
19
+ * @public
20
+ */
21
+ mode?: 'sync' | 'wait';
14
22
  /**
15
23
  * By passing `initial={false}`, `AnimatePresence` will disable any initial animations on children
16
24
  * that are present when the component is first rendered.
@@ -60,15 +68,7 @@ export interface AnimatePresenceProps {
60
68
  * If set to `true`, `AnimatePresence` will only render one component at a time. The exiting component
61
69
  * will finish its exit animation before the entering component is rendered.
62
70
  *
63
- * ```jsx
64
- * const MyComponent = ({ currentItem }) => (
65
- * <AnimatePresence exitBeforeEnter>
66
- * <motion.div key={currentItem} exit={{ opacity: 0 }} />
67
- * </AnimatePresence>
68
- * )
69
- * ```
70
- *
71
- * @beta
71
+ * @deprecated Use `mode="wait"` instead.
72
72
  */
73
73
  exitBeforeEnter?: boolean;
74
74
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,IAAI,CAAA;IACP,CAAC,EAAE,IAAI,CAAA;CACR;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;AAE7C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAE3B;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAE3B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,IAAI,CAAA;IACP,CAAC,EAAE,IAAI,CAAA;CACR;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;AAE7C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAEtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAE3B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAE3B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB"}