@tamagui/sheet 1.112.1 → 1.112.3
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.
- package/dist/cjs/SheetImplementationCustom.js +247 -249
- package/dist/cjs/SheetImplementationCustom.js.map +2 -2
- package/dist/cjs/SheetImplementationCustom.native.js +3 -3
- package/dist/cjs/SheetImplementationCustom.native.js.map +2 -2
- package/dist/cjs/constants.js +1 -4
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/constants.native.js +1 -4
- package/dist/cjs/constants.native.js.map +2 -2
- package/dist/esm/SheetImplementationCustom.js +253 -252
- package/dist/esm/SheetImplementationCustom.js.map +1 -1
- package/dist/esm/SheetImplementationCustom.mjs +8 -8
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -1
- package/dist/esm/SheetImplementationCustom.native.js +7 -7
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -1
- package/dist/esm/constants.js +1 -4
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/constants.mjs +2 -5
- package/dist/esm/constants.mjs.map +1 -1
- package/dist/esm/constants.native.js +2 -5
- package/dist/esm/constants.native.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.js +253 -252
- package/dist/jsx/SheetImplementationCustom.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.mjs +8 -8
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -1
- package/dist/jsx/SheetImplementationCustom.native.js +6 -7
- package/dist/jsx/SheetImplementationCustom.native.js.map +2 -2
- package/dist/jsx/constants.js +1 -4
- package/dist/jsx/constants.js.map +1 -1
- package/dist/jsx/constants.mjs +2 -5
- package/dist/jsx/constants.mjs.map +1 -1
- package/dist/jsx/constants.native.js +1 -4
- package/dist/jsx/constants.native.js.map +2 -2
- package/package.json +16 -16
- package/src/SheetImplementationCustom.tsx +442 -433
- package/src/constants.tsx +0 -9
- package/src/createSheet.tsx +1 -1
- package/types/SheetImplementationCustom.d.ts +2 -2
- package/types/SheetImplementationCustom.d.ts.map +1 -1
- package/types/constants.d.ts +0 -1
- package/types/constants.d.ts.map +1 -1
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
1
|
import { AdaptParentContext } from '@tamagui/adapt'
|
|
3
2
|
import { AnimatePresence } from '@tamagui/animate-presence'
|
|
4
3
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
currentPlatform,
|
|
6
|
+
isClient,
|
|
7
|
+
isWeb,
|
|
8
|
+
useIsomorphicLayoutEffect,
|
|
9
|
+
} from '@tamagui/constants'
|
|
6
10
|
import {
|
|
7
11
|
getConfig,
|
|
8
12
|
Stack,
|
|
9
13
|
Theme,
|
|
10
|
-
themeable,
|
|
11
14
|
useConfiguration,
|
|
12
15
|
useEvent,
|
|
13
16
|
useThemeName,
|
|
14
17
|
} from '@tamagui/core'
|
|
15
18
|
import { Portal } from '@tamagui/portal'
|
|
16
19
|
import { useKeyboardVisible } from '@tamagui/use-keyboard-visible'
|
|
20
|
+
import React from 'react'
|
|
17
21
|
import type {
|
|
18
22
|
Animated,
|
|
19
23
|
GestureResponderEvent,
|
|
@@ -21,8 +25,6 @@ import type {
|
|
|
21
25
|
PanResponderGestureState,
|
|
22
26
|
} from 'react-native'
|
|
23
27
|
import { Dimensions, Keyboard, PanResponder, View } from 'react-native'
|
|
24
|
-
|
|
25
|
-
import { SHEET_HIDDEN_STYLESHEET } from './constants'
|
|
26
28
|
import { ParentSheetContext, SheetInsideSheetContext } from './contexts'
|
|
27
29
|
import { resisted } from './helpers'
|
|
28
30
|
import { SheetProvider } from './SheetContext'
|
|
@@ -32,488 +34,495 @@ import { useSheetProviderProps } from './useSheetProviderProps'
|
|
|
32
34
|
|
|
33
35
|
let hiddenSize = 10_000.1
|
|
34
36
|
|
|
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
|
-
* This is a hacky workaround for native:
|
|
96
|
-
*/
|
|
97
|
-
const [isShowingInnerSheet, setIsShowingInnerSheet] = React.useState(false)
|
|
98
|
-
const shouldHideParentSheet = !isWeb && modal && isShowingInnerSheet
|
|
99
|
-
const parentSheetContext = React.useContext(SheetInsideSheetContext)
|
|
100
|
-
const onInnerSheet = React.useCallback((hasChild: boolean) => {
|
|
101
|
-
setIsShowingInnerSheet(hasChild)
|
|
102
|
-
}, [])
|
|
103
|
-
|
|
104
|
-
const positions = React.useMemo(
|
|
105
|
-
() =>
|
|
106
|
-
snapPoints.map((point) =>
|
|
107
|
-
getYPositions(snapPointsMode, point, screenSize, frameSize)
|
|
108
|
-
),
|
|
109
|
-
[screenSize, frameSize, snapPoints, snapPointsMode]
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
const { animationDriver } = useConfiguration()
|
|
113
|
-
const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
|
|
114
|
-
animationDriver
|
|
115
|
-
const AnimatedView = (animationDriver.View ?? Stack) as typeof Animated.View
|
|
116
|
-
|
|
117
|
-
useIsomorphicLayoutEffect(() => {
|
|
118
|
-
if (!(parentSheetContext && open)) return
|
|
119
|
-
parentSheetContext(true)
|
|
120
|
-
return () => {
|
|
121
|
-
parentSheetContext(false)
|
|
122
|
-
}
|
|
123
|
-
}, [parentSheetContext, open])
|
|
124
|
-
|
|
125
|
-
const nextParentContext = React.useMemo(
|
|
126
|
-
() => ({
|
|
127
|
-
zIndex,
|
|
128
|
-
}),
|
|
129
|
-
[zIndex]
|
|
37
|
+
let sheetHiddenStyleSheet: HTMLStyleElement | null = null
|
|
38
|
+
|
|
39
|
+
export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
40
|
+
function SheetImplementationCustom(props, forwardedRef) {
|
|
41
|
+
const parentSheet = React.useContext(ParentSheetContext)
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
animation,
|
|
45
|
+
animationConfig: animationConfigProp,
|
|
46
|
+
modal = false,
|
|
47
|
+
zIndex = parentSheet.zIndex + 1,
|
|
48
|
+
moveOnKeyboardChange = false,
|
|
49
|
+
unmountChildrenWhenHidden = false,
|
|
50
|
+
portalProps,
|
|
51
|
+
containerComponent: ContainerComponent = React.Fragment,
|
|
52
|
+
} = props
|
|
53
|
+
|
|
54
|
+
const keyboardIsVisible = useKeyboardVisible()
|
|
55
|
+
const state = useSheetOpenState(props)
|
|
56
|
+
const [overlayComponent, setOverlayComponent] = React.useState<any>(null)
|
|
57
|
+
|
|
58
|
+
const providerProps = useSheetProviderProps(props, state, {
|
|
59
|
+
onOverlayComponent: setOverlayComponent,
|
|
60
|
+
})
|
|
61
|
+
const {
|
|
62
|
+
frameSize,
|
|
63
|
+
setFrameSize,
|
|
64
|
+
snapPoints,
|
|
65
|
+
snapPointsMode,
|
|
66
|
+
hasFit,
|
|
67
|
+
position,
|
|
68
|
+
setPosition,
|
|
69
|
+
scrollBridge,
|
|
70
|
+
screenSize,
|
|
71
|
+
setMaxContentSize,
|
|
72
|
+
maxSnapPoint,
|
|
73
|
+
} = providerProps
|
|
74
|
+
const { open, controller, isHidden } = state
|
|
75
|
+
|
|
76
|
+
const sheetRef = React.useRef<View>(null)
|
|
77
|
+
const ref = useComposedRefs(forwardedRef, sheetRef)
|
|
78
|
+
|
|
79
|
+
// TODO this can be extracted into a helper getAnimationConfig(animationProp as array | string)
|
|
80
|
+
const animationConfig = (() => {
|
|
81
|
+
const [animationProp, animationPropConfig] = !animation
|
|
82
|
+
? []
|
|
83
|
+
: Array.isArray(animation)
|
|
84
|
+
? animation
|
|
85
|
+
: ([animation] as const)
|
|
86
|
+
return (
|
|
87
|
+
animationConfigProp ??
|
|
88
|
+
(animationProp
|
|
89
|
+
? {
|
|
90
|
+
...(getConfig().animations.animations[animationProp as string] as Object),
|
|
91
|
+
...animationPropConfig,
|
|
92
|
+
}
|
|
93
|
+
: null)
|
|
130
94
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
95
|
+
})()
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* This is a hacky workaround for native:
|
|
99
|
+
*/
|
|
100
|
+
const [isShowingInnerSheet, setIsShowingInnerSheet] = React.useState(false)
|
|
101
|
+
const shouldHideParentSheet = !isWeb && modal && isShowingInnerSheet
|
|
102
|
+
const parentSheetContext = React.useContext(SheetInsideSheetContext)
|
|
103
|
+
const onInnerSheet = React.useCallback((hasChild: boolean) => {
|
|
104
|
+
setIsShowingInnerSheet(hasChild)
|
|
105
|
+
}, [])
|
|
106
|
+
|
|
107
|
+
const positions = React.useMemo(
|
|
108
|
+
() =>
|
|
109
|
+
snapPoints.map((point) =>
|
|
110
|
+
getYPositions(snapPointsMode, point, screenSize, frameSize)
|
|
111
|
+
),
|
|
112
|
+
[screenSize, frameSize, snapPoints, snapPointsMode]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const { animationDriver } = useConfiguration()
|
|
116
|
+
const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
|
|
117
|
+
animationDriver
|
|
118
|
+
const AnimatedView = (animationDriver.View ?? Stack) as typeof Animated.View
|
|
119
|
+
|
|
120
|
+
useIsomorphicLayoutEffect(() => {
|
|
121
|
+
if (!(parentSheetContext && open)) return
|
|
122
|
+
parentSheetContext(true)
|
|
123
|
+
return () => {
|
|
124
|
+
parentSheetContext(false)
|
|
125
|
+
}
|
|
126
|
+
}, [parentSheetContext, open])
|
|
127
|
+
|
|
128
|
+
const nextParentContext = React.useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
zIndex,
|
|
131
|
+
}),
|
|
132
|
+
[zIndex]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const animatedNumber = useAnimatedNumber(hiddenSize)
|
|
136
|
+
const at = React.useRef(hiddenSize)
|
|
137
|
+
|
|
138
|
+
useAnimatedNumberReaction(
|
|
139
|
+
{
|
|
140
|
+
value: animatedNumber,
|
|
141
|
+
hostRef: sheetRef,
|
|
142
|
+
},
|
|
143
|
+
React.useCallback(
|
|
144
|
+
(value) => {
|
|
145
|
+
at.current = value
|
|
146
|
+
scrollBridge.paneY = value
|
|
139
147
|
},
|
|
140
|
-
|
|
141
|
-
(value) => {
|
|
142
|
-
at.current = value
|
|
143
|
-
scrollBridge.paneY = value
|
|
144
|
-
},
|
|
145
|
-
[animationDriver]
|
|
146
|
-
)
|
|
148
|
+
[animationDriver]
|
|
147
149
|
)
|
|
150
|
+
)
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
152
|
+
function stopSpring() {
|
|
153
|
+
animatedNumber.stop()
|
|
154
|
+
if (scrollBridge.onFinishAnimate) {
|
|
155
|
+
scrollBridge.onFinishAnimate()
|
|
156
|
+
scrollBridge.onFinishAnimate = undefined
|
|
155
157
|
}
|
|
158
|
+
}
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const animateTo = useEvent((position: number) => {
|
|
160
|
-
if (frameSize === 0) return
|
|
161
|
-
|
|
162
|
-
let toValue = isHidden || position === -1 ? screenSize : positions[position]
|
|
160
|
+
const hasntMeasured = at.current === hiddenSize
|
|
163
161
|
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
const animateTo = useEvent((position: number) => {
|
|
163
|
+
if (frameSize === 0) return
|
|
166
164
|
|
|
167
|
-
|
|
165
|
+
let toValue = isHidden || position === -1 ? screenSize : positions[position]
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
animatedNumber.setValue(
|
|
172
|
-
screenSize,
|
|
173
|
-
{
|
|
174
|
-
type: 'timing',
|
|
175
|
-
duration: 0,
|
|
176
|
-
},
|
|
177
|
-
() => {
|
|
178
|
-
if (isHidden) {
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
toValue = positions[position]
|
|
183
|
-
at.current = toValue
|
|
184
|
-
|
|
185
|
-
animatedNumber.setValue(toValue, {
|
|
186
|
-
type: 'spring',
|
|
187
|
-
...animationConfig,
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
)
|
|
191
|
-
return
|
|
192
|
-
}
|
|
167
|
+
if (at.current === toValue) return
|
|
168
|
+
at.current = toValue
|
|
193
169
|
|
|
194
|
-
|
|
195
|
-
type: 'spring',
|
|
196
|
-
...animationConfig,
|
|
197
|
-
})
|
|
198
|
-
})
|
|
170
|
+
stopSpring()
|
|
199
171
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
172
|
+
if (hasntMeasured || isHidden) {
|
|
173
|
+
// first run, we need to set to screen size before running
|
|
174
|
+
animatedNumber.setValue(
|
|
175
|
+
screenSize,
|
|
176
|
+
{
|
|
203
177
|
type: 'timing',
|
|
204
178
|
duration: 0,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!frameSize || !screenSize || isHidden || (hasntMeasured && !open)) {
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
animateTo(position)
|
|
214
|
-
}, [isHidden, frameSize, screenSize, open, position])
|
|
179
|
+
},
|
|
180
|
+
() => {
|
|
181
|
+
if (isHidden) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
215
184
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const [isDragging, setIsDragging] = React.useState(false)
|
|
185
|
+
toValue = positions[position]
|
|
186
|
+
at.current = toValue
|
|
219
187
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
animatedNumber.setValue(toValue, {
|
|
189
|
+
type: 'spring',
|
|
190
|
+
...animationConfig,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
224
196
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
197
|
+
animatedNumber.setValue(toValue, {
|
|
198
|
+
type: 'spring',
|
|
199
|
+
...animationConfig,
|
|
200
|
+
})
|
|
201
|
+
})
|
|
228
202
|
|
|
229
|
-
|
|
230
|
-
|
|
203
|
+
useIsomorphicLayoutEffect(() => {
|
|
204
|
+
if (screenSize && hasntMeasured) {
|
|
205
|
+
animatedNumber.setValue(screenSize, {
|
|
206
|
+
type: 'timing',
|
|
207
|
+
duration: 0,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}, [hasntMeasured, screenSize])
|
|
231
211
|
|
|
232
|
-
|
|
233
|
-
|
|
212
|
+
useIsomorphicLayoutEffect(() => {
|
|
213
|
+
if (!frameSize || !screenSize || isHidden || (hasntMeasured && !open)) {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
animateTo(position)
|
|
217
|
+
}, [isHidden, frameSize, screenSize, open, position])
|
|
218
|
+
|
|
219
|
+
const disableDrag = props.disableDrag ?? controller?.disableDrag
|
|
220
|
+
const themeName = useThemeName()
|
|
221
|
+
const [isDragging, setIsDragging] = React.useState(false)
|
|
222
|
+
|
|
223
|
+
const panResponder = React.useMemo(() => {
|
|
224
|
+
if (disableDrag) return
|
|
225
|
+
if (!frameSize) return
|
|
226
|
+
if (isShowingInnerSheet) return
|
|
227
|
+
|
|
228
|
+
const minY = positions[0]
|
|
229
|
+
scrollBridge.paneMinY = minY
|
|
230
|
+
let startY = at.current
|
|
231
|
+
|
|
232
|
+
function setPanning(val: boolean) {
|
|
233
|
+
setIsDragging(val)
|
|
234
|
+
|
|
235
|
+
// make unselectable:
|
|
236
|
+
if (isClient) {
|
|
237
|
+
if (!sheetHiddenStyleSheet) {
|
|
238
|
+
sheetHiddenStyleSheet = document.createElement('style')
|
|
239
|
+
if (typeof document.head !== 'undefined') {
|
|
240
|
+
document.head.appendChild(sheetHiddenStyleSheet)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
234
243
|
if (!val) {
|
|
235
|
-
|
|
244
|
+
sheetHiddenStyleSheet.innerText = ''
|
|
236
245
|
} else {
|
|
237
|
-
|
|
246
|
+
sheetHiddenStyleSheet.innerText =
|
|
238
247
|
':root * { user-select: none !important; -webkit-user-select: none !important; }'
|
|
239
248
|
}
|
|
240
249
|
}
|
|
250
|
+
}
|
|
241
251
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
252
|
+
const release = ({ vy, dragAt }: { dragAt: number; vy: number }) => {
|
|
253
|
+
isExternalDrag = false
|
|
254
|
+
previouslyScrolling = false
|
|
255
|
+
setPanning(false)
|
|
256
|
+
const at = dragAt + startY
|
|
257
|
+
// seems liky vy goes up to about 4 at the very most (+ is down, - is up)
|
|
258
|
+
// lets base our multiplier on the total layout height
|
|
259
|
+
const end = at + frameSize * vy * 0.2
|
|
260
|
+
let closestPoint = 0
|
|
261
|
+
let dist = Number.POSITIVE_INFINITY
|
|
262
|
+
for (let i = 0; i < positions.length; i++) {
|
|
263
|
+
const position = positions[i]
|
|
264
|
+
const curDist = end > position ? end - position : position - end
|
|
265
|
+
if (curDist < dist) {
|
|
266
|
+
dist = curDist
|
|
267
|
+
closestPoint = i
|
|
259
268
|
}
|
|
260
|
-
// have to call both because state may not change but need to snap back
|
|
261
|
-
setPosition(closestPoint)
|
|
262
|
-
animateTo(closestPoint)
|
|
263
269
|
}
|
|
270
|
+
// have to call both because state may not change but need to snap back
|
|
271
|
+
setPosition(closestPoint)
|
|
272
|
+
animateTo(closestPoint)
|
|
273
|
+
}
|
|
264
274
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
const finish = (_e: GestureResponderEvent, state: PanResponderGestureState) => {
|
|
276
|
+
release({
|
|
277
|
+
vy: state.vy,
|
|
278
|
+
dragAt: state.dy,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
271
281
|
|
|
272
|
-
|
|
282
|
+
let previouslyScrolling = false
|
|
273
283
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
284
|
+
const onMoveShouldSet = (
|
|
285
|
+
e: GestureResponderEvent,
|
|
286
|
+
{ dy }: PanResponderGestureState
|
|
287
|
+
) => {
|
|
288
|
+
// if dragging handle always allow:
|
|
289
|
+
if (e.target === providerProps.handleRef.current) {
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
282
292
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
293
|
+
const isScrolled = scrollBridge.y !== 0
|
|
294
|
+
const isDraggingUp = dy < 0
|
|
295
|
+
// we can treat near top instead of exactly to avoid trouble with springs
|
|
296
|
+
const isNearTop = scrollBridge.paneY - 5 <= scrollBridge.paneMinY
|
|
297
|
+
if (isScrolled) {
|
|
298
|
+
previouslyScrolling = true
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
// prevent drag once at top and pulling up
|
|
302
|
+
if (isNearTop) {
|
|
303
|
+
if (!isScrolled && isDraggingUp) {
|
|
304
|
+
// TODO: pulling past the limit breaks scroll on native, need to better make ScrollView
|
|
305
|
+
if (!isWeb) {
|
|
306
|
+
return false
|
|
298
307
|
}
|
|
299
308
|
}
|
|
300
|
-
// we could do some detection of other touchables and cancel here..
|
|
301
|
-
return Math.abs(dy) > 5
|
|
302
309
|
}
|
|
310
|
+
// we could do some detection of other touchables and cancel here..
|
|
311
|
+
return Math.abs(dy) > 5
|
|
312
|
+
}
|
|
303
313
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
314
|
+
const grant = () => {
|
|
315
|
+
setPanning(true)
|
|
316
|
+
stopSpring()
|
|
317
|
+
startY = at.current
|
|
318
|
+
}
|
|
309
319
|
|
|
310
|
-
|
|
320
|
+
let isExternalDrag = false
|
|
311
321
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
const to = dy + startY
|
|
318
|
-
animatedNumber.setValue(resisted(to, minY), { type: 'direct' })
|
|
322
|
+
scrollBridge.drag = (dy) => {
|
|
323
|
+
if (!isExternalDrag) {
|
|
324
|
+
isExternalDrag = true
|
|
325
|
+
grant()
|
|
319
326
|
}
|
|
327
|
+
const to = dy + startY
|
|
328
|
+
animatedNumber.setValue(resisted(to, minY), { type: 'direct' })
|
|
329
|
+
}
|
|
320
330
|
|
|
321
|
-
|
|
331
|
+
scrollBridge.release = release
|
|
322
332
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
},
|
|
331
|
-
onPanResponderEnd: finish,
|
|
332
|
-
onPanResponderTerminate: finish,
|
|
333
|
-
onPanResponderRelease: finish,
|
|
334
|
-
})
|
|
335
|
-
}, [disableDrag, isShowingInnerSheet, animateTo, frameSize, positions, setPosition])
|
|
336
|
-
|
|
337
|
-
const handleAnimationViewLayout = React.useCallback(
|
|
338
|
-
(e: LayoutChangeEvent) => {
|
|
339
|
-
// avoid bugs where it grows forever for whatever reason
|
|
340
|
-
const next = Math.min(
|
|
341
|
-
e.nativeEvent?.layout.height,
|
|
342
|
-
Dimensions.get('screen').height
|
|
343
|
-
)
|
|
344
|
-
if (!next) return
|
|
345
|
-
setFrameSize(next)
|
|
333
|
+
return PanResponder.create({
|
|
334
|
+
onMoveShouldSetPanResponder: onMoveShouldSet,
|
|
335
|
+
onPanResponderGrant: grant,
|
|
336
|
+
onPanResponderMove: (_e, { dy }) => {
|
|
337
|
+
const toFull = dy + startY
|
|
338
|
+
const to = resisted(toFull, minY)
|
|
339
|
+
animatedNumber.setValue(to, { type: 'direct' })
|
|
346
340
|
},
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
341
|
+
onPanResponderEnd: finish,
|
|
342
|
+
onPanResponderTerminate: finish,
|
|
343
|
+
onPanResponderRelease: finish,
|
|
344
|
+
})
|
|
345
|
+
}, [disableDrag, isShowingInnerSheet, animateTo, frameSize, positions, setPosition])
|
|
346
|
+
|
|
347
|
+
const handleAnimationViewLayout = React.useCallback(
|
|
348
|
+
(e: LayoutChangeEvent) => {
|
|
349
|
+
// avoid bugs where it grows forever for whatever reason
|
|
350
|
+
const next = Math.min(
|
|
351
|
+
e.nativeEvent?.layout.height,
|
|
352
|
+
Dimensions.get('screen').height
|
|
353
|
+
)
|
|
354
|
+
if (!next) return
|
|
355
|
+
setFrameSize(next)
|
|
356
|
+
},
|
|
357
|
+
[keyboardIsVisible]
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
const handleMaxContentViewLayout = React.useCallback(
|
|
361
|
+
(e: LayoutChangeEvent) => {
|
|
362
|
+
// avoid bugs where it grows forever for whatever reason
|
|
363
|
+
const next = Math.min(
|
|
364
|
+
e.nativeEvent?.layout.height,
|
|
365
|
+
Dimensions.get('screen').height
|
|
366
|
+
)
|
|
367
|
+
if (!next) return
|
|
368
|
+
setMaxContentSize(next)
|
|
369
|
+
},
|
|
370
|
+
[keyboardIsVisible]
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
const animatedStyle = useAnimatedNumberStyle(animatedNumber, (val) => {
|
|
374
|
+
'worklet'
|
|
375
|
+
const translateY = frameSize === 0 ? hiddenSize : val
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
transform: [{ translateY }],
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
const sizeBeforeKeyboard = React.useRef<number | null>(null)
|
|
383
|
+
React.useEffect(() => {
|
|
384
|
+
if (isWeb || !moveOnKeyboardChange) return
|
|
385
|
+
const keyboardShowListener = Keyboard.addListener(
|
|
386
|
+
currentPlatform === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
|
|
387
|
+
(e) => {
|
|
388
|
+
if (sizeBeforeKeyboard.current !== null) return
|
|
389
|
+
sizeBeforeKeyboard.current =
|
|
390
|
+
isHidden || position === -1 ? screenSize : positions[position]
|
|
391
|
+
animatedNumber.setValue(
|
|
392
|
+
Math.max(sizeBeforeKeyboard.current - e.endCoordinates.height, 0),
|
|
393
|
+
{
|
|
394
|
+
type: 'timing',
|
|
395
|
+
duration: 250,
|
|
396
|
+
}
|
|
356
397
|
)
|
|
357
|
-
if (!next) return
|
|
358
|
-
setMaxContentSize(next)
|
|
359
|
-
},
|
|
360
|
-
[keyboardIsVisible]
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
const animatedStyle = useAnimatedNumberStyle(animatedNumber, (val) => {
|
|
364
|
-
'worklet'
|
|
365
|
-
const translateY = frameSize === 0 ? hiddenSize : val
|
|
366
|
-
|
|
367
|
-
return {
|
|
368
|
-
transform: [{ translateY }],
|
|
369
398
|
}
|
|
399
|
+
)
|
|
400
|
+
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
|
|
401
|
+
if (sizeBeforeKeyboard.current === null) return
|
|
402
|
+
animatedNumber.setValue(sizeBeforeKeyboard.current, {
|
|
403
|
+
type: 'timing',
|
|
404
|
+
duration: 250,
|
|
405
|
+
})
|
|
406
|
+
sizeBeforeKeyboard.current = null
|
|
370
407
|
})
|
|
371
408
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
(e) => {
|
|
378
|
-
if (sizeBeforeKeyboard.current !== null) return
|
|
379
|
-
sizeBeforeKeyboard.current =
|
|
380
|
-
isHidden || position === -1 ? screenSize : positions[position]
|
|
381
|
-
animatedNumber.setValue(
|
|
382
|
-
Math.max(sizeBeforeKeyboard.current - e.endCoordinates.height, 0),
|
|
383
|
-
{
|
|
384
|
-
type: 'timing',
|
|
385
|
-
duration: 250,
|
|
386
|
-
}
|
|
387
|
-
)
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
|
|
391
|
-
if (sizeBeforeKeyboard.current === null) return
|
|
392
|
-
animatedNumber.setValue(sizeBeforeKeyboard.current, {
|
|
393
|
-
type: 'timing',
|
|
394
|
-
duration: 250,
|
|
395
|
-
})
|
|
396
|
-
sizeBeforeKeyboard.current = null
|
|
397
|
-
})
|
|
409
|
+
return () => {
|
|
410
|
+
keyboardDidHideListener.remove()
|
|
411
|
+
keyboardShowListener.remove()
|
|
412
|
+
}
|
|
413
|
+
}, [moveOnKeyboardChange, positions, position, isHidden])
|
|
398
414
|
|
|
415
|
+
// we need to set this *after* fully closed to 0, to avoid it overlapping
|
|
416
|
+
// the page when resizing quickly on web for example
|
|
417
|
+
const [opacity, setOpacity] = React.useState(open ? 1 : 0)
|
|
418
|
+
if (open && opacity === 0) {
|
|
419
|
+
setOpacity(1)
|
|
420
|
+
}
|
|
421
|
+
React.useEffect(() => {
|
|
422
|
+
if (!open) {
|
|
423
|
+
// need to wait for animation complete, for now lets just do it naively
|
|
424
|
+
const tm = setTimeout(() => {
|
|
425
|
+
setOpacity(0)
|
|
426
|
+
}, 400)
|
|
399
427
|
return () => {
|
|
400
|
-
|
|
401
|
-
keyboardShowListener.remove()
|
|
428
|
+
clearTimeout(tm)
|
|
402
429
|
}
|
|
403
|
-
}, [moveOnKeyboardChange, positions, position, isHidden])
|
|
404
|
-
|
|
405
|
-
// we need to set this *after* fully closed to 0, to avoid it overlapping
|
|
406
|
-
// the page when resizing quickly on web for example
|
|
407
|
-
const [opacity, setOpacity] = React.useState(open ? 1 : 0)
|
|
408
|
-
if (open && opacity === 0) {
|
|
409
|
-
setOpacity(1)
|
|
410
430
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
431
|
+
}, [open])
|
|
432
|
+
|
|
433
|
+
const forcedContentHeight = hasFit
|
|
434
|
+
? undefined
|
|
435
|
+
: snapPointsMode === 'percent'
|
|
436
|
+
? `${maxSnapPoint}${isWeb ? 'dvh' : '%'}`
|
|
437
|
+
: maxSnapPoint
|
|
438
|
+
|
|
439
|
+
const contents = (
|
|
440
|
+
<ParentSheetContext.Provider value={nextParentContext}>
|
|
441
|
+
<SheetProvider {...providerProps}>
|
|
442
|
+
<AnimatePresence custom={{ open }}>
|
|
443
|
+
{shouldHideParentSheet || !open ? null : overlayComponent}
|
|
444
|
+
</AnimatePresence>
|
|
445
|
+
|
|
446
|
+
{snapPointsMode !== 'percent' && (
|
|
447
|
+
<View
|
|
448
|
+
style={{
|
|
449
|
+
opacity: 0,
|
|
450
|
+
position: 'absolute',
|
|
451
|
+
top: 0,
|
|
452
|
+
left: 0,
|
|
453
|
+
right: 0,
|
|
454
|
+
bottom: 0,
|
|
455
|
+
pointerEvents: 'none',
|
|
456
|
+
}}
|
|
457
|
+
onLayout={handleMaxContentViewLayout}
|
|
458
|
+
/>
|
|
459
|
+
)}
|
|
460
|
+
|
|
461
|
+
<AnimatedView
|
|
462
|
+
ref={ref}
|
|
463
|
+
{...panResponder?.panHandlers}
|
|
464
|
+
onLayout={handleAnimationViewLayout}
|
|
465
|
+
{...(!isDragging && {
|
|
466
|
+
// @ts-ignore for CSS driver this is necessary to attach the transition
|
|
467
|
+
animation,
|
|
468
|
+
})}
|
|
469
|
+
// @ts-ignore
|
|
470
|
+
disableClassName
|
|
471
|
+
style={[
|
|
472
|
+
{
|
|
473
|
+
position: 'absolute',
|
|
474
|
+
zIndex,
|
|
475
|
+
width: '100%',
|
|
476
|
+
height: forcedContentHeight,
|
|
477
|
+
minHeight: forcedContentHeight,
|
|
478
|
+
opacity,
|
|
479
|
+
...((shouldHideParentSheet || !open) && {
|
|
445
480
|
pointerEvents: 'none',
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
>
|
|
476
|
-
{props.children}
|
|
477
|
-
</AnimatedView>
|
|
478
|
-
</SheetProvider>
|
|
479
|
-
</ParentSheetContext.Provider>
|
|
481
|
+
}),
|
|
482
|
+
},
|
|
483
|
+
animatedStyle,
|
|
484
|
+
]}
|
|
485
|
+
>
|
|
486
|
+
{props.children}
|
|
487
|
+
</AnimatedView>
|
|
488
|
+
</SheetProvider>
|
|
489
|
+
</ParentSheetContext.Provider>
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
const adaptContext = React.useContext(AdaptParentContext)
|
|
493
|
+
|
|
494
|
+
// start mounted so we get an accurate measurement the first time
|
|
495
|
+
const shouldMountChildren = Boolean(opacity || !unmountChildrenWhenHidden)
|
|
496
|
+
|
|
497
|
+
if (modal) {
|
|
498
|
+
const modalContents = (
|
|
499
|
+
<Portal zIndex={zIndex} {...portalProps}>
|
|
500
|
+
{shouldMountChildren && (
|
|
501
|
+
<ContainerComponent>
|
|
502
|
+
<Theme forceClassName name={themeName}>
|
|
503
|
+
<AdaptParentContext.Provider value={adaptContext}>
|
|
504
|
+
{contents}
|
|
505
|
+
</AdaptParentContext.Provider>
|
|
506
|
+
</Theme>
|
|
507
|
+
</ContainerComponent>
|
|
508
|
+
)}
|
|
509
|
+
</Portal>
|
|
480
510
|
)
|
|
481
511
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// start mounted so we get an accurate measurement the first time
|
|
485
|
-
const shouldMountChildren = Boolean(opacity || !unmountChildrenWhenHidden)
|
|
486
|
-
|
|
487
|
-
if (modal) {
|
|
488
|
-
const modalContents = (
|
|
489
|
-
<Portal zIndex={zIndex} {...portalProps}>
|
|
490
|
-
{shouldMountChildren && (
|
|
491
|
-
<ContainerComponent>
|
|
492
|
-
<Theme forceClassName name={themeName}>
|
|
493
|
-
<AdaptParentContext.Provider value={adaptContext}>
|
|
494
|
-
{contents}
|
|
495
|
-
</AdaptParentContext.Provider>
|
|
496
|
-
</Theme>
|
|
497
|
-
</ContainerComponent>
|
|
498
|
-
)}
|
|
499
|
-
</Portal>
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
if (isWeb) {
|
|
503
|
-
return modalContents
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// on native we don't support multiple modals yet... fix for now is to hide outer one
|
|
507
|
-
return (
|
|
508
|
-
<SheetInsideSheetContext.Provider value={onInnerSheet}>
|
|
509
|
-
{modalContents}
|
|
510
|
-
</SheetInsideSheetContext.Provider>
|
|
511
|
-
)
|
|
512
|
+
if (isWeb) {
|
|
513
|
+
return modalContents
|
|
512
514
|
}
|
|
513
515
|
|
|
514
|
-
|
|
516
|
+
// on native we don't support multiple modals yet... fix for now is to hide outer one
|
|
517
|
+
return (
|
|
518
|
+
<SheetInsideSheetContext.Provider value={onInnerSheet}>
|
|
519
|
+
{modalContents}
|
|
520
|
+
</SheetInsideSheetContext.Provider>
|
|
521
|
+
)
|
|
515
522
|
}
|
|
516
|
-
|
|
523
|
+
|
|
524
|
+
return contents
|
|
525
|
+
}
|
|
517
526
|
)
|
|
518
527
|
|
|
519
528
|
function getYPositions(
|