@tamagui/sheet 1.27.0 → 1.27.2

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 (140) hide show
  1. package/dist/cjs/Sheet.js +1 -2
  2. package/dist/cjs/Sheet.js.map +1 -1
  3. package/dist/cjs/SheetContext.js.map +1 -1
  4. package/dist/cjs/SheetImplementationCustom.js +340 -0
  5. package/dist/cjs/SheetImplementationCustom.js.map +6 -0
  6. package/dist/cjs/constants.js +10 -0
  7. package/dist/cjs/constants.js.map +1 -1
  8. package/dist/cjs/contexts.js +35 -0
  9. package/dist/cjs/contexts.js.map +6 -0
  10. package/dist/cjs/createSheet.js +68 -342
  11. package/dist/cjs/createSheet.js.map +2 -2
  12. package/dist/cjs/helpers.js +38 -0
  13. package/dist/cjs/helpers.js.map +6 -0
  14. package/dist/cjs/index.js +2 -0
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/nativeSheet.js +3 -5
  17. package/dist/cjs/nativeSheet.js.map +1 -1
  18. package/dist/cjs/useSheet.js +30 -0
  19. package/dist/cjs/useSheet.js.map +6 -0
  20. package/dist/cjs/useSheetChildren.js +4 -2
  21. package/dist/cjs/useSheetChildren.js.map +1 -1
  22. package/dist/cjs/useSheetController.js +2 -1
  23. package/dist/cjs/useSheetController.js.map +1 -1
  24. package/dist/cjs/useSheetProviderProps.js +30 -3
  25. package/dist/cjs/useSheetProviderProps.js.map +1 -1
  26. package/dist/esm/Sheet.js +1 -2
  27. package/dist/esm/Sheet.js.map +1 -1
  28. package/dist/esm/SheetContext.js.map +1 -1
  29. package/dist/esm/SheetImplementationCustom.js +336 -0
  30. package/dist/esm/SheetImplementationCustom.js.map +6 -0
  31. package/dist/esm/constants.js +8 -0
  32. package/dist/esm/constants.js.map +1 -1
  33. package/dist/esm/contexts.js +10 -0
  34. package/dist/esm/contexts.js.map +6 -0
  35. package/dist/esm/createSheet.js +72 -364
  36. package/dist/esm/createSheet.js.map +2 -2
  37. package/dist/esm/helpers.js +14 -0
  38. package/dist/esm/helpers.js.map +6 -0
  39. package/dist/esm/index.js +1 -0
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/nativeSheet.js +3 -5
  42. package/dist/esm/nativeSheet.js.map +1 -1
  43. package/dist/esm/useSheet.js +6 -0
  44. package/dist/esm/useSheet.js.map +6 -0
  45. package/dist/esm/useSheetChildren.js +4 -2
  46. package/dist/esm/useSheetChildren.js.map +1 -1
  47. package/dist/esm/useSheetController.js +2 -1
  48. package/dist/esm/useSheetController.js.map +1 -1
  49. package/dist/esm/useSheetProviderProps.js +31 -4
  50. package/dist/esm/useSheetProviderProps.js.map +1 -1
  51. package/dist/jsx/Sheet.js +1 -2
  52. package/dist/jsx/Sheet.js.map +1 -1
  53. package/dist/jsx/Sheet.mjs +1 -2
  54. package/dist/jsx/Sheet.mjs.map +1 -1
  55. package/dist/jsx/SheetContext.js.map +1 -1
  56. package/dist/jsx/SheetContext.mjs.map +1 -1
  57. package/dist/jsx/SheetImplementationCustom.js +330 -0
  58. package/dist/jsx/SheetImplementationCustom.js.map +6 -0
  59. package/dist/jsx/SheetImplementationCustom.mjs +330 -0
  60. package/dist/jsx/SheetImplementationCustom.mjs.map +6 -0
  61. package/dist/jsx/constants.js +8 -0
  62. package/dist/jsx/constants.js.map +1 -1
  63. package/dist/jsx/constants.mjs +8 -0
  64. package/dist/jsx/constants.mjs.map +1 -1
  65. package/dist/jsx/contexts.js +10 -0
  66. package/dist/jsx/contexts.js.map +6 -0
  67. package/dist/jsx/contexts.mjs +10 -0
  68. package/dist/jsx/contexts.mjs.map +6 -0
  69. package/dist/jsx/createSheet.js +63 -354
  70. package/dist/jsx/createSheet.js.map +2 -2
  71. package/dist/jsx/createSheet.mjs +63 -354
  72. package/dist/jsx/createSheet.mjs.map +2 -2
  73. package/dist/jsx/helpers.js +14 -0
  74. package/dist/jsx/helpers.js.map +6 -0
  75. package/dist/jsx/helpers.mjs +14 -0
  76. package/dist/jsx/helpers.mjs.map +6 -0
  77. package/dist/jsx/index.js +1 -0
  78. package/dist/jsx/index.js.map +1 -1
  79. package/dist/jsx/index.mjs +1 -0
  80. package/dist/jsx/index.mjs.map +1 -1
  81. package/dist/jsx/nativeSheet.js +3 -5
  82. package/dist/jsx/nativeSheet.js.map +1 -1
  83. package/dist/jsx/nativeSheet.mjs +3 -5
  84. package/dist/jsx/nativeSheet.mjs.map +1 -1
  85. package/dist/jsx/useSheet.js +6 -0
  86. package/dist/jsx/useSheet.js.map +6 -0
  87. package/dist/jsx/useSheet.mjs +6 -0
  88. package/dist/jsx/useSheet.mjs.map +6 -0
  89. package/dist/jsx/useSheetChildren.js +4 -2
  90. package/dist/jsx/useSheetChildren.js.map +1 -1
  91. package/dist/jsx/useSheetChildren.mjs +4 -2
  92. package/dist/jsx/useSheetChildren.mjs.map +1 -1
  93. package/dist/jsx/useSheetController.js +2 -1
  94. package/dist/jsx/useSheetController.js.map +1 -1
  95. package/dist/jsx/useSheetController.mjs +2 -1
  96. package/dist/jsx/useSheetController.mjs.map +1 -1
  97. package/dist/jsx/useSheetProviderProps.js +31 -4
  98. package/dist/jsx/useSheetProviderProps.js.map +1 -1
  99. package/dist/jsx/useSheetProviderProps.mjs +31 -4
  100. package/dist/jsx/useSheetProviderProps.mjs.map +1 -1
  101. package/package.json +13 -13
  102. package/src/Sheet.tsx +0 -1
  103. package/src/SheetContext.tsx +1 -16
  104. package/src/SheetImplementationCustom.tsx +404 -0
  105. package/src/constants.tsx +10 -0
  106. package/src/contexts.ts +9 -0
  107. package/src/createSheet.tsx +88 -441
  108. package/src/helpers.tsx +10 -0
  109. package/src/index.ts +1 -0
  110. package/src/nativeSheet.tsx +3 -5
  111. package/src/useSheet.tsx +3 -0
  112. package/src/useSheetController.tsx +1 -0
  113. package/src/useSheetProviderProps.tsx +48 -7
  114. package/types/Sheet.d.ts +10 -6
  115. package/types/Sheet.d.ts.map +1 -1
  116. package/types/SheetContext.d.ts +100 -27
  117. package/types/SheetContext.d.ts.map +1 -1
  118. package/types/SheetImplementationCustom.d.ts +27 -0
  119. package/types/SheetImplementationCustom.d.ts.map +1 -0
  120. package/types/constants.d.ts +2 -0
  121. package/types/constants.d.ts.map +1 -1
  122. package/types/contexts.d.ts +6 -0
  123. package/types/contexts.d.ts.map +1 -0
  124. package/types/createSheet.d.ts +8 -4
  125. package/types/createSheet.d.ts.map +1 -1
  126. package/types/helpers.d.ts +2 -0
  127. package/types/helpers.d.ts.map +1 -0
  128. package/types/index.d.ts +1 -0
  129. package/types/index.d.ts.map +1 -1
  130. package/types/nativeSheet.d.ts.map +1 -1
  131. package/types/useSheet.d.ts +22 -0
  132. package/types/useSheet.d.ts.map +1 -0
  133. package/types/useSheetChildren.d.ts +1 -0
  134. package/types/useSheetChildren.d.ts.map +1 -1
  135. package/types/useSheetController.d.ts +1 -0
  136. package/types/useSheetController.d.ts.map +1 -1
  137. package/types/useSheetProviderProps.d.ts +8 -3
  138. package/types/useSheetProviderProps.d.ts.map +1 -1
  139. package/src/useSheetChildren.tsx +0 -47
  140. package/src/useSheetSnapPoints.tsx +0 -60
@@ -0,0 +1,404 @@
1
+ import { AdaptParentContext } from '@tamagui/adapt'
2
+ import { useComposedRefs } from '@tamagui/compose-refs'
3
+ import {
4
+ Theme,
5
+ isTouchable,
6
+ isWeb,
7
+ themeable,
8
+ useAnimationDriver,
9
+ useEvent,
10
+ useIsomorphicLayoutEffect,
11
+ useThemeName,
12
+ } from '@tamagui/core'
13
+ import { Portal } from '@tamagui/portal'
14
+ import { useKeyboardVisible } from '@tamagui/use-keyboard-visible'
15
+ import {
16
+ forwardRef,
17
+ useCallback,
18
+ useContext,
19
+ useEffect,
20
+ useMemo,
21
+ useRef,
22
+ useState,
23
+ } from 'react'
24
+ import {
25
+ Animated,
26
+ GestureResponderEvent,
27
+ Keyboard,
28
+ LayoutChangeEvent,
29
+ PanResponder,
30
+ PanResponderGestureState,
31
+ View,
32
+ } from 'react-native'
33
+
34
+ import { HIDDEN_SIZE, SHEET_HIDDEN_STYLESHEET } from './constants'
35
+ import { ParentSheetContext, SheetInsideSheetContext } from './contexts'
36
+ import { resisted } from './helpers'
37
+ import { SheetProvider } from './SheetContext'
38
+ import { SheetProps } from './types'
39
+ import { useSheetOpenState } from './useSheetOpenState'
40
+ import { useSheetProviderProps } from './useSheetProviderProps'
41
+
42
+ export const SheetImplementationCustom = themeable(
43
+ forwardRef<View, SheetProps>(function SheetImplementationCustom(props, forwardedRef) {
44
+ const parentSheet = useContext(ParentSheetContext)
45
+
46
+ const {
47
+ animationConfig,
48
+ modal = false,
49
+ zIndex = parentSheet.zIndex + 1,
50
+ moveOnKeyboardChange = false,
51
+ portalProps,
52
+ } = props
53
+
54
+ const keyboardIsVisible = useKeyboardVisible()
55
+ const state = useSheetOpenState(props)
56
+ const [overlayComponent, setOverlayComponent] = useState<any>(null)
57
+ const providerProps = useSheetProviderProps(props, state, {
58
+ onOverlayComponent: setOverlayComponent,
59
+ })
60
+ const { frameSize, setFrameSize, snapPoints, position, setPosition, scrollBridge } =
61
+ providerProps
62
+ const { open, controller, isHidden } = state
63
+
64
+ const sheetRef = useRef<View>(null)
65
+ const ref = useComposedRefs(forwardedRef, sheetRef)
66
+
67
+ /**
68
+ * This is a hacky workaround for native:
69
+ */
70
+ const [isShowingInnerSheet, setIsShowingInnerSheet] = useState(false)
71
+ const shouldHideParentSheet = !isWeb && modal && isShowingInnerSheet
72
+ const parentSheetContext = useContext(SheetInsideSheetContext)
73
+ const onInnerSheet = useCallback((hasChild: boolean) => {
74
+ setIsShowingInnerSheet(hasChild)
75
+ }, [])
76
+
77
+ const maxSnapPoint = snapPoints.reduce((prev, cur) => Math.max(prev, cur))
78
+ const screenSize = frameSize / (maxSnapPoint / 100)
79
+
80
+ const positions = useMemo(
81
+ () => snapPoints.map((point) => getPercentSize(point, screenSize)),
82
+ [frameSize, snapPoints]
83
+ )
84
+
85
+ const driver = useAnimationDriver()
86
+ const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
87
+ driver
88
+
89
+ // temp until reanimated useAnimatedNumber fix
90
+ const AnimatedView = (driver['NumberView'] ?? driver.View) as typeof Animated.View
91
+
92
+ useIsomorphicLayoutEffect(() => {
93
+ if (!(parentSheetContext && open)) return
94
+ parentSheetContext(true)
95
+ return () => {
96
+ parentSheetContext(false)
97
+ }
98
+ }, [parentSheetContext, open])
99
+
100
+ const nextParentContext = useMemo(
101
+ () => ({
102
+ zIndex,
103
+ }),
104
+ [zIndex]
105
+ )
106
+
107
+ const animatedNumber = useAnimatedNumber(HIDDEN_SIZE)
108
+
109
+ // native only fix
110
+ const at = useRef(0)
111
+
112
+ useAnimatedNumberReaction(
113
+ {
114
+ value: animatedNumber,
115
+ hostRef: sheetRef,
116
+ },
117
+ (value) => {
118
+ if (!driver.isReactNative) return
119
+ at.current = value
120
+ scrollBridge.paneY = value
121
+ }
122
+ )
123
+
124
+ function stopSpring() {
125
+ animatedNumber.stop()
126
+ if (scrollBridge.onFinishAnimate) {
127
+ scrollBridge.onFinishAnimate()
128
+ scrollBridge.onFinishAnimate = undefined
129
+ }
130
+ }
131
+
132
+ const animateTo = useEvent((position: number) => {
133
+ const current = animatedNumber.getValue()
134
+ if (isHidden && open) return
135
+ if (!current) return
136
+ if (frameSize === 0) return
137
+ const hiddenValue = frameSize === 0 ? HIDDEN_SIZE : screenSize
138
+ const toValue = isHidden || position === -1 ? hiddenValue : positions[position]
139
+ if (at.current === toValue) return
140
+ stopSpring()
141
+ if (isHidden) {
142
+ animatedNumber.setValue(toValue, {
143
+ type: 'timing',
144
+ duration: 0,
145
+ })
146
+ at.current = toValue
147
+ return
148
+ }
149
+ // dont bounce on initial measure to bottom
150
+ const overshootClamping = at.current === HIDDEN_SIZE
151
+ animatedNumber.setValue(toValue, {
152
+ ...animationConfig,
153
+ type: 'spring',
154
+ overshootClamping,
155
+ })
156
+ })
157
+
158
+ useIsomorphicLayoutEffect(() => {
159
+ animateTo(position)
160
+ }, [isHidden, frameSize, position, animateTo])
161
+
162
+ const disableDrag = props.disableDrag ?? controller?.disableDrag
163
+ const themeName = useThemeName()
164
+
165
+ const panResponder = useMemo(
166
+ () => {
167
+ if (disableDrag) return
168
+ if (!frameSize) return
169
+ if (isShowingInnerSheet) return
170
+
171
+ const minY = positions[0]
172
+ scrollBridge.paneMinY = minY
173
+ let startY = at.current
174
+
175
+ function makeUnselectable(val: boolean) {
176
+ if (!SHEET_HIDDEN_STYLESHEET) return
177
+ if (!val) {
178
+ SHEET_HIDDEN_STYLESHEET.innerText = ''
179
+ } else {
180
+ SHEET_HIDDEN_STYLESHEET.innerText =
181
+ ':root * { user-select: none !important; -webkit-user-select: none !important; }'
182
+ }
183
+ }
184
+
185
+ const release = ({ vy, dragAt }: { dragAt: number; vy: number }) => {
186
+ isExternalDrag = false
187
+ previouslyScrolling = false
188
+ makeUnselectable(false)
189
+ const at = dragAt + startY
190
+ // seems liky vy goes up to about 4 at the very most (+ is down, - is up)
191
+ // lets base our multiplier on the total layout height
192
+ const end = at + frameSize * vy * 0.2
193
+ let closestPoint = 0
194
+ let dist = Infinity
195
+ for (let i = 0; i < positions.length; i++) {
196
+ const position = positions[i]
197
+ const curDist = end > position ? end - position : position - end
198
+ if (curDist < dist) {
199
+ dist = curDist
200
+ closestPoint = i
201
+ }
202
+ }
203
+ // have to call both because state may not change but need to snap back
204
+ setPosition(closestPoint)
205
+ animateTo(closestPoint)
206
+ }
207
+
208
+ const finish = (_e: GestureResponderEvent, state: PanResponderGestureState) => {
209
+ release({
210
+ vy: state.vy,
211
+ dragAt: state.dy,
212
+ })
213
+ }
214
+
215
+ let previouslyScrolling = false
216
+
217
+ const onMoveShouldSet = (
218
+ _e: GestureResponderEvent,
219
+ { dy }: PanResponderGestureState
220
+ ) => {
221
+ const isScrolled = scrollBridge.y !== 0
222
+ const isDraggingUp = dy < 0
223
+ // we can treat near top instead of exactly to avoid trouble with springs
224
+ const isNearTop = scrollBridge.paneY - 5 <= scrollBridge.paneMinY
225
+ if (isScrolled) {
226
+ previouslyScrolling = true
227
+ return false
228
+ }
229
+ // prevent drag once at top and pulling up
230
+ if (isNearTop) {
231
+ if (!isScrolled && isDraggingUp) {
232
+ return false
233
+ }
234
+ }
235
+ // we could do some detection of other touchables and cancel here..
236
+ return Math.abs(dy) > 5
237
+ }
238
+
239
+ const grant = () => {
240
+ makeUnselectable(true)
241
+ stopSpring()
242
+ startY = at.current
243
+ }
244
+
245
+ let isExternalDrag = false
246
+
247
+ scrollBridge.drag = (dy) => {
248
+ if (!isExternalDrag) {
249
+ isExternalDrag = true
250
+ grant()
251
+ }
252
+ const to = dy + startY
253
+ animatedNumber.setValue(resisted(to, minY), { type: 'direct' })
254
+ }
255
+
256
+ scrollBridge.release = release
257
+
258
+ return PanResponder.create({
259
+ onMoveShouldSetPanResponder: onMoveShouldSet,
260
+ onPanResponderGrant: grant,
261
+ onPanResponderMove: (_e, { dy }) => {
262
+ const toFull = dy + startY
263
+ const to = resisted(toFull, minY)
264
+ animatedNumber.setValue(to, { type: 'direct' })
265
+ },
266
+ onPanResponderEnd: finish,
267
+ onPanResponderTerminate: finish,
268
+ onPanResponderRelease: finish,
269
+ })
270
+ },
271
+ // eslint-disable-next-line react-hooks/exhaustive-deps
272
+ [disableDrag, isShowingInnerSheet, animateTo, frameSize, positions, setPosition]
273
+ )
274
+
275
+ const handleAnimationViewLayout = useCallback(
276
+ (e: LayoutChangeEvent) => {
277
+ let next = e.nativeEvent?.layout.height
278
+ if (isWeb && isTouchable && !open) {
279
+ // temp fix ios bug where it doesn't go below dynamic bottom...
280
+ next += 100
281
+ }
282
+ if (!next) return
283
+ setFrameSize(next)
284
+ },
285
+ [keyboardIsVisible]
286
+ )
287
+
288
+ const animatedStyle = useAnimatedNumberStyle(animatedNumber, (val) => {
289
+ 'worklet'
290
+ const translateY = frameSize === 0 ? HIDDEN_SIZE : val
291
+ return {
292
+ transform: [{ translateY }],
293
+ }
294
+ })
295
+
296
+ const sizeBeforeKeyboard = useRef<number | null>(null)
297
+ useEffect(() => {
298
+ if (isWeb || !moveOnKeyboardChange) return
299
+ const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', (e) => {
300
+ if (sizeBeforeKeyboard.current !== null) return
301
+ sizeBeforeKeyboard.current = animatedNumber.getValue()
302
+ animatedNumber.setValue(
303
+ Math.max(animatedNumber.getValue() - e.endCoordinates.height, 0)
304
+ )
305
+ })
306
+ const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
307
+ if (sizeBeforeKeyboard.current === null) return
308
+ animatedNumber.setValue(sizeBeforeKeyboard.current)
309
+ sizeBeforeKeyboard.current = null
310
+ })
311
+
312
+ return () => {
313
+ keyboardDidHideListener.remove()
314
+ keyboardDidShowListener.remove()
315
+ }
316
+ }, [moveOnKeyboardChange])
317
+
318
+ // we need to set this *after* fully closed to 0, to avoid it overlapping
319
+ // the page when resizing quickly on web for example
320
+ const [opacity, setOpacity] = useState(open ? 1 : 0)
321
+ if (open && opacity === 0) {
322
+ setOpacity(1)
323
+ }
324
+ useEffect(() => {
325
+ if (!open) {
326
+ // need to wait for animation complete, for now lets just do it naively
327
+ const tm = setTimeout(() => {
328
+ setOpacity(0)
329
+ }, 400)
330
+ return () => {
331
+ clearTimeout(tm)
332
+ }
333
+ }
334
+ }, [open])
335
+
336
+ const contents = (
337
+ <ParentSheetContext.Provider value={nextParentContext}>
338
+ <SheetProvider {...providerProps}>
339
+ {shouldHideParentSheet ? null : overlayComponent}
340
+
341
+ <AnimatedView
342
+ ref={ref}
343
+ {...panResponder?.panHandlers}
344
+ onLayout={handleAnimationViewLayout}
345
+ pointerEvents={open && !shouldHideParentSheet ? 'auto' : 'none'}
346
+ // @ts-ignore
347
+ animation={props.animation}
348
+ style={[
349
+ {
350
+ position: 'absolute',
351
+ zIndex,
352
+ width: '100%',
353
+ height: `${maxSnapPoint}%`,
354
+ opacity,
355
+ },
356
+ animatedStyle,
357
+ ]}
358
+ >
359
+ {props.children}
360
+ </AnimatedView>
361
+ </SheetProvider>
362
+ </ParentSheetContext.Provider>
363
+ )
364
+
365
+ const adaptContext = useContext(AdaptParentContext)
366
+
367
+ if (modal) {
368
+ const modalContents = (
369
+ <Portal zIndex={zIndex} {...portalProps}>
370
+ <Theme forceClassName name={themeName}>
371
+ <AdaptParentContext.Provider value={adaptContext}>
372
+ {contents}
373
+ </AdaptParentContext.Provider>
374
+ </Theme>
375
+ </Portal>
376
+ )
377
+
378
+ if (isWeb) {
379
+ return modalContents
380
+ }
381
+
382
+ // on native we don't support multiple modals yet... fix for now is to hide outer one
383
+ return (
384
+ <SheetInsideSheetContext.Provider value={onInnerSheet}>
385
+ {modalContents}
386
+ </SheetInsideSheetContext.Provider>
387
+ )
388
+ }
389
+
390
+ return contents
391
+ })
392
+ )
393
+
394
+ function getPercentSize(point?: number, screenSize?: number) {
395
+ if (!screenSize) return 0
396
+ if (point === undefined) {
397
+ console.warn('No snapPoint')
398
+ return 0
399
+ }
400
+ const pct = point / 100
401
+ const next = Math.round(screenSize - pct * screenSize)
402
+
403
+ return next
404
+ }
package/src/constants.tsx CHANGED
@@ -1,5 +1,15 @@
1
+ import { isClient } from '@tamagui/core'
2
+
1
3
  export const constants = {}
2
4
 
3
5
  export const SHEET_NAME = 'Sheet'
4
6
  export const SHEET_HANDLE_NAME = 'SheetHandle'
5
7
  export const SHEET_OVERLAY_NAME = 'SheetOverlay'
8
+
9
+ // set all the way off screen
10
+ export const HIDDEN_SIZE = 10_000
11
+
12
+ export const SHEET_HIDDEN_STYLESHEET = isClient ? document.createElement('style') : null
13
+ if (SHEET_HIDDEN_STYLESHEET) {
14
+ document.head.appendChild(SHEET_HIDDEN_STYLESHEET)
15
+ }
@@ -0,0 +1,9 @@
1
+ import { createContext } from 'react'
2
+
3
+ export const ParentSheetContext = createContext({
4
+ zIndex: 100_000,
5
+ })
6
+
7
+ export const SheetInsideSheetContext = createContext<
8
+ ((hasChild: boolean) => void) | null
9
+ >(null)