-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathPresenceChild.tsx
More file actions
119 lines (106 loc) · 3.45 KB
/
Copy pathPresenceChild.tsx
File metadata and controls
119 lines (106 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"use client"
import * as React from "react"
import { useId, useMemo, useRef } from "react"
import {
PresenceContext,
type PresenceContextProps,
} from "../../context/PresenceContext"
import { VariantLabels } from "../../motion/types"
import { useConstant } from "../../utils/use-constant"
import { useIsomorphicLayoutEffect } from "../../utils/use-isomorphic-effect"
import { PopChild } from "./PopChild"
interface PresenceChildProps {
children: React.ReactElement
isPresent: boolean
onExitComplete?: () => void
initial?: false | VariantLabels
custom?: any
presenceAffectsLayout: boolean
mode: "sync" | "popLayout" | "wait"
anchorX?: "left" | "right"
anchorY?: "top" | "bottom"
root?: HTMLElement | ShadowRoot
}
export const PresenceChild = ({
children,
initial,
isPresent,
onExitComplete,
custom,
presenceAffectsLayout,
mode,
anchorX,
anchorY,
root
}: PresenceChildProps) => {
const presenceChildren = useConstant(newChildrenMap)
const id = useId()
// Written in a layout effect (not render) so discarded concurrent
// renders can't leave the refs pointing at uncommitted state.
const isPresentRef = useRef(isPresent)
const onExitCompleteRef = useRef(onExitComplete)
useIsomorphicLayoutEffect(() => {
isPresentRef.current = isPresent
onExitCompleteRef.current = onExitComplete
})
let isReusedContext = true
let context = useMemo((): PresenceContextProps => {
isReusedContext = false
return {
id,
initial,
isPresent,
custom,
onExitComplete: (childId: string) => {
presenceChildren.set(childId, true)
for (const isComplete of presenceChildren.values()) {
if (!isComplete) return // can stop searching when any is incomplete
}
onExitComplete && onExitComplete()
},
register: (childId: string) => {
presenceChildren.set(childId, false)
return () => {
presenceChildren.delete(childId)
!isPresentRef.current &&
!presenceChildren.size &&
onExitCompleteRef.current?.()
}
},
}
}, [isPresent, presenceChildren, onExitComplete])
/**
* If the presence of a child affects the layout of the components around it,
* we want to make a new context value to ensure they get re-rendered
* so they can detect that layout change.
*/
if (presenceAffectsLayout && isReusedContext) {
context = { ...context }
}
useMemo(() => {
presenceChildren.forEach((_, key) => presenceChildren.set(key, false))
}, [isPresent])
/**
* If there's no `motion` components to fire exit animations, we want to remove this
* component immediately.
*/
React.useEffect(() => {
!isPresent &&
!presenceChildren.size &&
onExitComplete &&
onExitComplete()
}, [isPresent])
children = (
<PopChild pop={mode === "popLayout"} isPresent={isPresent} anchorX={anchorX} anchorY={anchorY} root={root}>
{children}
</PopChild>
)
return (
<PresenceContext.Provider value={context}>
{children}
</PresenceContext.Provider>
)
}
function newChildrenMap(): Map<string, boolean> {
return new Map()
}