@tamagui/sheet 2.1.0 → 2.2.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.
- package/.turbo/turbo-build.log +3 -2
- package/dist/cjs/GestureDetectorWrapper.native.js.map +1 -1
- package/dist/cjs/GestureSheetContext.native.js.map +1 -1
- package/dist/cjs/Sheet.native.js.map +1 -1
- package/dist/cjs/SheetContext.native.js.map +1 -1
- package/dist/cjs/SheetController.native.js.map +1 -1
- package/dist/cjs/SheetImplementationCustom.cjs +41 -64
- package/dist/cjs/SheetImplementationCustom.native.js +56 -69
- package/dist/cjs/SheetImplementationCustom.native.js.map +1 -1
- package/dist/cjs/SheetScrollView.cjs +57 -5
- package/dist/cjs/SheetScrollView.native.js +60 -5
- package/dist/cjs/SheetScrollView.native.js.map +1 -1
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/contexts.native.js.map +1 -1
- package/dist/cjs/controller.native.js.map +1 -1
- package/dist/cjs/createSheet.cjs +5 -2
- package/dist/cjs/createSheet.native.js +5 -2
- package/dist/cjs/createSheet.native.js.map +1 -1
- package/dist/cjs/gestureState.native.js.map +1 -1
- package/dist/cjs/helpers.native.js.map +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboardAvoidance.cjs +52 -1
- package/dist/cjs/keyboardAvoidance.native.js +54 -1
- package/dist/cjs/keyboardAvoidance.native.js.map +1 -1
- package/dist/cjs/nativeSheet.cjs +0 -1
- package/dist/cjs/nativeSheet.native.js +0 -1
- package/dist/cjs/nativeSheet.native.js.map +1 -1
- package/dist/cjs/setupGestureHandler.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/useGestureHandlerPan.cjs +25 -10
- package/dist/cjs/useGestureHandlerPan.native.js +27 -10
- package/dist/cjs/useGestureHandlerPan.native.js.map +1 -1
- package/dist/cjs/useKeyboardControllerSheet.cjs +10 -4
- package/dist/cjs/useSafeAreaInsets.cjs +44 -0
- package/dist/cjs/useSafeAreaInsets.native.js +48 -0
- package/dist/cjs/useSafeAreaInsets.native.js.map +1 -0
- package/dist/cjs/useSheet.native.js.map +1 -1
- package/dist/cjs/useSheetController.native.js.map +1 -1
- package/dist/cjs/useSheetOffscreenSize.native.js.map +1 -1
- package/dist/cjs/useSheetOpenState.native.js.map +1 -1
- package/dist/cjs/useSheetProviderProps.native.js.map +1 -1
- package/dist/cjs/useSheetScrollViewGestures.cjs +12 -2
- package/dist/cjs/useSheetScrollViewGestures.native.js +4 -0
- package/dist/cjs/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/cjs/webViewport.cjs +26 -1
- package/dist/cjs/webViewport.native.js +28 -1
- package/dist/cjs/webViewport.native.js.map +1 -1
- package/dist/esm/SheetImplementationCustom.mjs +48 -73
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -1
- package/dist/esm/SheetImplementationCustom.native.js +70 -89
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -1
- package/dist/esm/SheetScrollView.mjs +58 -6
- package/dist/esm/SheetScrollView.mjs.map +1 -1
- package/dist/esm/SheetScrollView.native.js +61 -6
- package/dist/esm/SheetScrollView.native.js.map +1 -1
- package/dist/esm/createSheet.mjs +7 -5
- package/dist/esm/createSheet.mjs.map +1 -1
- package/dist/esm/createSheet.native.js +17 -14
- package/dist/esm/createSheet.native.js.map +1 -1
- package/dist/esm/keyboardAvoidance.mjs +50 -1
- package/dist/esm/keyboardAvoidance.mjs.map +1 -1
- package/dist/esm/keyboardAvoidance.native.js +52 -1
- package/dist/esm/keyboardAvoidance.native.js.map +1 -1
- package/dist/esm/nativeSheet.mjs +0 -1
- package/dist/esm/nativeSheet.mjs.map +1 -1
- package/dist/esm/nativeSheet.native.js +0 -1
- package/dist/esm/nativeSheet.native.js.map +1 -1
- package/dist/esm/useGestureHandlerPan.mjs +25 -10
- package/dist/esm/useGestureHandlerPan.mjs.map +1 -1
- package/dist/esm/useGestureHandlerPan.native.js +27 -10
- package/dist/esm/useGestureHandlerPan.native.js.map +1 -1
- package/dist/esm/useKeyboardControllerSheet.mjs +11 -5
- package/dist/esm/useKeyboardControllerSheet.mjs.map +1 -1
- package/dist/esm/useSafeAreaInsets.mjs +7 -0
- package/dist/esm/useSafeAreaInsets.mjs.map +1 -0
- package/dist/esm/useSafeAreaInsets.native.js +8 -0
- package/dist/esm/useSafeAreaInsets.native.js.map +1 -0
- package/dist/esm/useSheetProviderProps.mjs.map +1 -1
- package/dist/esm/useSheetProviderProps.native.js.map +1 -1
- package/dist/esm/useSheetScrollViewGestures.mjs +12 -2
- package/dist/esm/useSheetScrollViewGestures.mjs.map +1 -1
- package/dist/esm/useSheetScrollViewGestures.native.js +4 -0
- package/dist/esm/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/esm/webViewport.mjs +23 -2
- package/dist/esm/webViewport.mjs.map +1 -1
- package/dist/esm/webViewport.native.js +25 -2
- package/dist/esm/webViewport.native.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.mjs +48 -73
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -1
- package/dist/jsx/SheetImplementationCustom.native.js +56 -69
- package/dist/jsx/SheetImplementationCustom.native.js.map +1 -1
- package/dist/jsx/SheetScrollView.mjs +58 -6
- package/dist/jsx/SheetScrollView.mjs.map +1 -1
- package/dist/jsx/SheetScrollView.native.js +60 -5
- package/dist/jsx/SheetScrollView.native.js.map +1 -1
- package/dist/jsx/createSheet.mjs +7 -5
- package/dist/jsx/createSheet.mjs.map +1 -1
- package/dist/jsx/createSheet.native.js +5 -2
- package/dist/jsx/createSheet.native.js.map +1 -1
- package/dist/jsx/keyboardAvoidance.mjs +50 -1
- package/dist/jsx/keyboardAvoidance.mjs.map +1 -1
- package/dist/jsx/keyboardAvoidance.native.js +54 -1
- package/dist/jsx/keyboardAvoidance.native.js.map +1 -1
- package/dist/jsx/nativeSheet.mjs +0 -1
- package/dist/jsx/nativeSheet.mjs.map +1 -1
- package/dist/jsx/nativeSheet.native.js +0 -1
- package/dist/jsx/nativeSheet.native.js.map +1 -1
- package/dist/jsx/useGestureHandlerPan.mjs +25 -10
- package/dist/jsx/useGestureHandlerPan.mjs.map +1 -1
- package/dist/jsx/useGestureHandlerPan.native.js +27 -10
- package/dist/jsx/useGestureHandlerPan.native.js.map +1 -1
- package/dist/jsx/useKeyboardControllerSheet.mjs +11 -5
- package/dist/jsx/useKeyboardControllerSheet.mjs.map +1 -1
- package/dist/jsx/useSafeAreaInsets.mjs +7 -0
- package/dist/jsx/useSafeAreaInsets.mjs.map +1 -0
- package/dist/jsx/useSafeAreaInsets.native.js +48 -0
- package/dist/jsx/useSafeAreaInsets.native.js.map +1 -0
- package/dist/jsx/useSheetProviderProps.mjs.map +1 -1
- package/dist/jsx/useSheetProviderProps.native.js.map +1 -1
- package/dist/jsx/useSheetScrollViewGestures.mjs +12 -2
- package/dist/jsx/useSheetScrollViewGestures.mjs.map +1 -1
- package/dist/jsx/useSheetScrollViewGestures.native.js +4 -0
- package/dist/jsx/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/jsx/webViewport.mjs +23 -2
- package/dist/jsx/webViewport.mjs.map +1 -1
- package/dist/jsx/webViewport.native.js +28 -1
- package/dist/jsx/webViewport.native.js.map +1 -1
- package/package.json +24 -25
- package/src/SheetImplementationCustom.tsx +147 -212
- package/src/SheetScrollView.tsx +81 -21
- package/src/createSheet.tsx +18 -6
- package/src/keyboardAvoidance.ts +91 -0
- package/src/nativeSheet.tsx +0 -1
- package/src/useGestureHandlerPan.tsx +29 -12
- package/src/useKeyboardControllerSheet.ts +35 -14
- package/src/useSafeAreaInsets.native.ts +29 -0
- package/src/useSafeAreaInsets.ts +19 -0
- package/src/useSheetProviderProps.tsx +4 -10
- package/src/useSheetScrollViewGestures.native.ts +4 -0
- package/src/useSheetScrollViewGestures.ts +19 -8
- package/src/webViewport.ts +56 -8
- package/test/keyboardAvoidance.test.ts +218 -2
- package/types/SheetContext.d.ts +0 -1
- package/types/SheetContext.d.ts.map +1 -1
- package/types/SheetImplementationCustom.d.ts.map +1 -1
- package/types/SheetScrollView.d.ts.map +1 -1
- package/types/createSheet.d.ts +12 -12
- package/types/createSheet.d.ts.map +1 -1
- package/types/keyboardAvoidance.d.ts +18 -0
- package/types/keyboardAvoidance.d.ts.map +1 -1
- package/types/nativeSheet.d.ts.map +1 -1
- package/types/useGestureHandlerPan.d.ts +4 -1
- package/types/useGestureHandlerPan.d.ts.map +1 -1
- package/types/useKeyboardControllerSheet.d.ts +5 -5
- package/types/useKeyboardControllerSheet.d.ts.map +1 -1
- package/types/useSafeAreaInsets.d.ts +10 -0
- package/types/useSafeAreaInsets.d.ts.map +1 -0
- package/types/useSafeAreaInsets.native.d.ts +20 -0
- package/types/useSafeAreaInsets.native.d.ts.map +1 -0
- package/types/useSheetProviderProps.d.ts +0 -1
- package/types/useSheetProviderProps.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.native.d.ts.map +1 -1
- package/types/webViewport.d.ts +13 -7
- package/types/webViewport.d.ts.map +1 -1
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
useEvent,
|
|
11
11
|
useThemeName,
|
|
12
12
|
} from '@tamagui/core'
|
|
13
|
-
import { getSafeArea } from '@tamagui/native'
|
|
14
13
|
import { needsPortalRepropagation, Portal } from '@tamagui/portal'
|
|
15
14
|
import React, { useState } from 'react'
|
|
16
15
|
import type {
|
|
@@ -25,16 +24,23 @@ import { GestureDetectorWrapper } from './GestureDetectorWrapper'
|
|
|
25
24
|
import { getGestureHandlerState } from './gestureState'
|
|
26
25
|
import { GestureSheetProvider } from './GestureSheetContext'
|
|
27
26
|
import { resisted } from './helpers'
|
|
28
|
-
import { getKeyboardOccludedHeight } from './keyboardAvoidance'
|
|
29
27
|
import {
|
|
28
|
+
getKeyboardAdjustedSheetY,
|
|
29
|
+
getKeyboardOccludedHeight,
|
|
30
|
+
getSheetReleasePosition,
|
|
31
|
+
} from './keyboardAvoidance'
|
|
32
|
+
import {
|
|
33
|
+
getWebKeyboardResizeHeight,
|
|
34
|
+
getMaxViewportHeight,
|
|
30
35
|
getStableLayoutViewportHeight,
|
|
31
|
-
|
|
36
|
+
getWebVisualViewportOffsetTop,
|
|
32
37
|
MIN_KEYBOARD_HEIGHT,
|
|
33
38
|
} from './webViewport'
|
|
34
39
|
import { SheetProvider } from './SheetContext'
|
|
35
40
|
import type { SheetProps, SnapPointsMode } from './types'
|
|
36
41
|
import { useGestureHandlerPan } from './useGestureHandlerPan'
|
|
37
42
|
import { useKeyboardControllerSheet } from './useKeyboardControllerSheet'
|
|
43
|
+
import { SafeAreaInsetsContext, useSafeAreaInsets } from './useSafeAreaInsets'
|
|
38
44
|
import { useSheetOpenState } from './useSheetOpenState'
|
|
39
45
|
import { useSheetProviderProps } from './useSheetProviderProps'
|
|
40
46
|
|
|
@@ -48,15 +54,6 @@ const hiddenSize = 10_000.1
|
|
|
48
54
|
const rnghRootStyleOpen = { width: '100%', height: '100%' } as const
|
|
49
55
|
const rnghRootStyleClosed = { width: '100%', height: 0 } as const
|
|
50
56
|
|
|
51
|
-
// safe area top inset, cached per-session (device-constant value)
|
|
52
|
-
let _cachedSafeAreaTop: number | undefined
|
|
53
|
-
function getSafeAreaTopInset(): number {
|
|
54
|
-
if (_cachedSafeAreaTop !== undefined) return _cachedSafeAreaTop
|
|
55
|
-
// use @tamagui/native abstraction - returns 0 when not enabled
|
|
56
|
-
_cachedSafeAreaTop = getSafeArea().getInsets().top
|
|
57
|
-
return _cachedSafeAreaTop
|
|
58
|
-
}
|
|
59
|
-
|
|
60
57
|
let sheetHiddenStyleSheet: HTMLStyleElement | null = null
|
|
61
58
|
|
|
62
59
|
// on web we are always relative to window, on to screen
|
|
@@ -79,6 +76,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
79
76
|
function SheetImplementationCustom(props, forwardedRef) {
|
|
80
77
|
const parentSheet = React.useContext(ParentSheetContext)
|
|
81
78
|
|
|
79
|
+
// live safe-area insets (notch / status bar). read here in the component
|
|
80
|
+
// body — which renders INSIDE the app's SafeAreaProvider — so it is correct
|
|
81
|
+
// even though a modal sheet's CONTENT is teleported out through the portal.
|
|
82
|
+
// the keyboard-avoidance clamp below uses the top inset so a keyboard-shifted
|
|
83
|
+
// sheet tops out at the notch instead of sliding under it. web has no native
|
|
84
|
+
// safe-area context (CSS env() handles it) and uses the visual-viewport
|
|
85
|
+
// offset instead.
|
|
86
|
+
const safeAreaInsets = useSafeAreaInsets()
|
|
87
|
+
const safeAreaTopInset = safeAreaInsets?.top ?? 0
|
|
88
|
+
|
|
82
89
|
const {
|
|
83
90
|
transition,
|
|
84
91
|
transitionConfig: transitionConfigProp,
|
|
@@ -100,6 +107,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
100
107
|
const {
|
|
101
108
|
frameSize,
|
|
102
109
|
setFrameSize,
|
|
110
|
+
dismissOnSnapToBottom,
|
|
103
111
|
snapPoints,
|
|
104
112
|
snapPointsMode,
|
|
105
113
|
hasFit,
|
|
@@ -183,75 +191,34 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
183
191
|
}
|
|
184
192
|
}, [open, frameSize])
|
|
185
193
|
|
|
186
|
-
// WEB keyboard
|
|
194
|
+
// WEB keyboard frame freeze. on real iOS Safari opening the keyboard shrinks
|
|
187
195
|
// the visual viewport AND innerHeight AND the measured layout, which would
|
|
188
196
|
// re-derive screenSize/frameSize smaller, recompute the fit positions, and
|
|
189
197
|
// fly the frame up then back down ("goes back down after the keyboard opens").
|
|
190
198
|
// so we snapshot the pre-keyboard geometry — captured every render while the
|
|
191
199
|
// keyboard is CLOSED, which dodges the open-transition race where a shrunk
|
|
192
|
-
// onLayout lands before isKeyboardVisible flips — and use it for
|
|
193
|
-
// math while the keyboard is open. the
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
// this sheet is the kind the web keyboard
|
|
200
|
+
// onLayout lands before isKeyboardVisible flips — and use it for frame-size
|
|
201
|
+
// math while the keyboard is open. the active snap position still shifts up
|
|
202
|
+
// by the keyboard height, capped at the safe-area top; the scroll view gets
|
|
203
|
+
// keyboardOccludedHeight padding for any tail left behind the keyboard.
|
|
204
|
+
// this sheet is the kind the web keyboard frame freeze is designed for — a
|
|
197
205
|
// fit-mode web sheet opted into keyboard handling. percent/constant sheets
|
|
198
|
-
// keep the live geometry (their height isn't pinned, so a frozen
|
|
199
|
-
// would mismatch).
|
|
206
|
+
// keep the live geometry (their height isn't pinned, so a frozen frame
|
|
207
|
+
// height would mismatch).
|
|
200
208
|
const isWebKbSheet = isWeb && hasFit && moveOnKeyboardChange
|
|
201
209
|
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
stableKbGeom.current.frame = frameSize
|
|
215
|
-
stableKbGeom.current.screen = screenSize
|
|
216
|
-
hasCleanKbBaseline.current = true
|
|
217
|
-
} else if (
|
|
218
|
-
isWebKbSheet &&
|
|
219
|
-
isKeyboardVisible &&
|
|
220
|
-
!hasCleanKbBaseline.current &&
|
|
221
|
-
screenSize > 0
|
|
222
|
-
) {
|
|
223
|
-
// AUTOFOCUS-ON-OPEN seed. when the input autofocuses as the sheet animates
|
|
224
|
-
// in, the keyboard rises BEFORE any keyboard-free layout lands, so the
|
|
225
|
-
// branch above never runs (frameSize/screenSize were 0 the whole time the
|
|
226
|
-
// keyboard was up) and freezeForKb stays false, collapsing the sheet to the
|
|
227
|
-
// shrunk consumer maxHeight. instead, reconstruct the baseline from the
|
|
228
|
-
// keyboard-up render: screen = the stable layout viewport
|
|
229
|
-
// (keyboard-independent). the frame is grown by the seeding layout path
|
|
230
|
-
// below: while seeding, the tail padding is suppressed and the scroll view
|
|
231
|
-
// is unclipped (via keyboardStableFrameHeight = stable screen), so the frame
|
|
232
|
-
// converges on its pure pre-keyboard content height across a couple layout
|
|
233
|
-
// passes. a real keyboard-free render later replaces it with the exact value
|
|
234
|
-
// (branch above).
|
|
235
|
-
stableKbGeom.current.screen = Math.max(stableKbGeom.current.screen, screenSize)
|
|
236
|
-
}
|
|
237
|
-
// are we still reconstructing the baseline from a keyboard-up render? true
|
|
238
|
-
// until either a clean baseline lands or the seeded frame settles.
|
|
239
|
-
const seedingKbBaseline =
|
|
240
|
-
isWebKbSheet &&
|
|
241
|
-
isKeyboardVisible &&
|
|
242
|
-
!hasCleanKbBaseline.current &&
|
|
243
|
-
!seedSettled.current
|
|
244
|
-
const freezeForKb =
|
|
245
|
-
isWebKbSheet && isKeyboardVisible && stableKbGeom.current.frame > 0
|
|
246
|
-
const effScreenSize = freezeForKb ? stableKbGeom.current.screen : screenSize
|
|
247
|
-
|
|
248
|
-
// use stableFrameSize when closing to prevent position jumps during exit animation
|
|
249
|
-
// but when opening, always use the current frameSize so positions update correctly
|
|
250
|
-
const effectiveFrameSize = freezeForKb
|
|
251
|
-
? stableKbGeom.current.frame
|
|
252
|
-
: open
|
|
253
|
-
? frameSize
|
|
254
|
-
: stableFrameSize.current || frameSize
|
|
210
|
+
// the space the snap positions are built against. WEB: the stable layout
|
|
211
|
+
// viewport (document.documentElement.clientHeight), which the soft keyboard
|
|
212
|
+
// never shrinks (unlike the measured screenSize / visualViewport). NATIVE:
|
|
213
|
+
// the measured screenSize. activePositions shift up by keyboardHeight below
|
|
214
|
+
// so a small fit sheet keeps its natural height and moves with the keyboard;
|
|
215
|
+
// a tall fit sheet moves until capped at the safe-area top, leaving its tail
|
|
216
|
+
// behind the keyboard for the ScrollView padding to expose.
|
|
217
|
+
const effScreenSize = isWebKbSheet ? getStableViewportHeight() : screenSize
|
|
218
|
+
|
|
219
|
+
// use stableFrameSize when closing to prevent position jumps during the exit
|
|
220
|
+
// animation; while open use the live frameSize.
|
|
221
|
+
const effectiveFrameSize = open ? frameSize : stableFrameSize.current || frameSize
|
|
255
222
|
|
|
256
223
|
const positions = React.useMemo(
|
|
257
224
|
() =>
|
|
@@ -284,17 +251,9 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
284
251
|
|
|
285
252
|
// keyboard-adjusted snap positions.
|
|
286
253
|
//
|
|
287
|
-
// WEB
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// Instead the frame's anchor geometry is frozen (effScreenSize/effectiveFrameSize
|
|
291
|
-
// above) and its height is pinned (keyboardStableFrameHeight -> SheetScrollView);
|
|
292
|
-
// the scroll content is padded by keyboardOccludedHeight and the browser
|
|
293
|
-
// scroll-into-view lifts the focused input above the keyboard. So
|
|
294
|
-
// activePositions === positions on web.
|
|
295
|
-
//
|
|
296
|
-
// NATIVE: shift snap points up by keyboard height (the native keyboard is
|
|
297
|
-
// opaque and pushes content), capped at the safe-area top inset.
|
|
254
|
+
// WEB + NATIVE: shift snap points up by keyboard height, capped at the
|
|
255
|
+
// safe-area top inset. web fit sheets keep their frozen pre-keyboard height
|
|
256
|
+
// and add bottom spacer padding when the safe-area cap leaves a hidden tail.
|
|
298
257
|
//
|
|
299
258
|
// IMPORTANT: frozen during drag to prevent gesture handler recreation —
|
|
300
259
|
// when a TextInput blurs mid-drag the keyboard state would otherwise revert
|
|
@@ -304,55 +263,51 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
304
263
|
if (isDragging || isDraggingRef.current) return activePositionsRef.current
|
|
305
264
|
|
|
306
265
|
let result: number[]
|
|
307
|
-
|
|
266
|
+
|
|
267
|
+
if (!isKeyboardVisible || keyboardHeight <= 0) {
|
|
308
268
|
result = positions
|
|
309
269
|
} else {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
270
|
+
result = positions.map((p) =>
|
|
271
|
+
getKeyboardAdjustedSheetY({
|
|
272
|
+
sheetY: p,
|
|
273
|
+
screenSize: effScreenSize,
|
|
274
|
+
isKeyboardVisible,
|
|
275
|
+
keyboardHeight,
|
|
276
|
+
shouldTranslate: true,
|
|
277
|
+
safeAreaTop: isWeb ? getWebVisualViewportOffsetTop() : safeAreaTopInset,
|
|
278
|
+
})
|
|
279
|
+
)
|
|
318
280
|
}
|
|
319
281
|
activePositionsRef.current = result
|
|
320
282
|
return result
|
|
321
|
-
}, [
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
frameSize: effectiveFrameSize,
|
|
330
|
-
isKeyboardVisible,
|
|
331
|
-
keyboardHeight,
|
|
332
|
-
screenSize: effScreenSize,
|
|
333
|
-
sheetY: position >= 0 ? activePositions[position] : undefined,
|
|
334
|
-
})
|
|
283
|
+
}, [
|
|
284
|
+
positions,
|
|
285
|
+
isKeyboardVisible,
|
|
286
|
+
keyboardHeight,
|
|
287
|
+
effScreenSize,
|
|
288
|
+
isDragging,
|
|
289
|
+
safeAreaTopInset,
|
|
290
|
+
])
|
|
335
291
|
|
|
336
|
-
// the
|
|
337
|
-
//
|
|
338
|
-
// keyboard is
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
: 0
|
|
292
|
+
// bottom spacer for the part of the sheet hidden by the keyboard after the
|
|
293
|
+
// keyboard translation and safe-area clamping. if a small sheet fits above
|
|
294
|
+
// the keyboard this is 0; if a tall sheet is capped at the safe-area top, the
|
|
295
|
+
// spacer makes the remaining hidden tail scrollable.
|
|
296
|
+
const keyboardOccludedHeight = getKeyboardOccludedHeight({
|
|
297
|
+
frameSize: effectiveFrameSize,
|
|
298
|
+
isKeyboardVisible,
|
|
299
|
+
keyboardHeight,
|
|
300
|
+
screenSize: effScreenSize,
|
|
301
|
+
sheetY: position >= 0 ? activePositions[position] : undefined,
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// pin the scroll view to the held (pre-keyboard) frame height while the
|
|
305
|
+
// keyboard is up on web. on older iOS the consumer's window-derived maxHeight
|
|
306
|
+
// shrinks with the keyboard, which would clip the scroll view (and the frame)
|
|
307
|
+
// smaller; this override keeps it at the full height so the frame translates
|
|
308
|
+
// with the keyboard but never resizes. 0 = no override (use the consumer maxHeight).
|
|
309
|
+
const keyboardStableFrameHeight =
|
|
310
|
+
isWebKbSheet && isKeyboardVisible && frameSize > 0 ? frameSize : 0
|
|
356
311
|
|
|
357
312
|
const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
|
|
358
313
|
animationDriver
|
|
@@ -442,8 +397,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
442
397
|
// use effScreenSize (the frozen anchor space the positions were built in) for
|
|
443
398
|
// the off-screen/close target too, so a close while the keyboard is still up
|
|
444
399
|
// animates fully out instead of to a mismatched live screenSize.
|
|
445
|
-
|
|
446
|
-
|
|
400
|
+
//
|
|
401
|
+
// web: clear the maximum the viewport can ever reveal, not just the current
|
|
402
|
+
// layout viewport. iOS Safari retracts its chrome on scroll and exposes area
|
|
403
|
+
// below the current viewport, so a sheet parked at effScreenSize would peek
|
|
404
|
+
// back in as the page scrolls. getMaxViewportHeight floors the target past
|
|
405
|
+
// anything Safari can expose.
|
|
406
|
+
const closeTarget = isWeb
|
|
407
|
+
? Math.max(effScreenSize, getMaxViewportHeight())
|
|
408
|
+
: effScreenSize
|
|
409
|
+
let toValue = isHidden || position === -1 ? closeTarget : activePositions[position]
|
|
447
410
|
|
|
448
411
|
if (at.current === toValue) return
|
|
449
412
|
|
|
@@ -571,6 +534,10 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
571
534
|
scrollBridge.setScrollEnabled?.(false)
|
|
572
535
|
}
|
|
573
536
|
}
|
|
537
|
+
// NOTE: effScreenSize/effectiveFrameSize are intentionally NOT deps. With the
|
|
538
|
+
// spacer approach the frame's position target is frozen across keyboard
|
|
539
|
+
// open/close (same stable baseline), so it must NOT re-animate — keyboard
|
|
540
|
+
// avoidance is the bottom spacer + scroll, not a frame move.
|
|
574
541
|
}, [hasntMeasured, disableAnimation, isHidden, frameSize, screenSize, open, position])
|
|
575
542
|
|
|
576
543
|
const disableDrag = props.disableDrag ?? controller?.disableDrag
|
|
@@ -626,17 +593,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
626
593
|
// vy goes up to about 4 at most (+ is down, - is up)
|
|
627
594
|
const end = currentPos + frameSize * vy * 0.2
|
|
628
595
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
596
|
+
const closestPoint = getSheetReleasePosition({
|
|
597
|
+
positions: activePositions,
|
|
598
|
+
projectedEnd: end,
|
|
599
|
+
currentPosition: currentPos,
|
|
600
|
+
frameSize,
|
|
601
|
+
dismissOnSnapToBottom,
|
|
602
|
+
snapPointsMode,
|
|
603
|
+
isKeyboardVisible,
|
|
604
|
+
isWeb,
|
|
605
|
+
})
|
|
640
606
|
|
|
641
607
|
// have to call both because state may not change but need to snap back
|
|
642
608
|
setPosition(closestPoint)
|
|
@@ -750,6 +716,10 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
750
716
|
|
|
751
717
|
return PanResponder.create({
|
|
752
718
|
onMoveShouldSetPanResponder: onMoveShouldSet,
|
|
719
|
+
// once we own the drag, don't yield it to another responder
|
|
720
|
+
// (re-renders during the drag were cooperatively terminating it under
|
|
721
|
+
// load, killing the gesture mid-drag)
|
|
722
|
+
onPanResponderTerminationRequest: () => false,
|
|
753
723
|
onPanResponderGrant: grant,
|
|
754
724
|
onPanResponderMove: (_e, { dy }) => {
|
|
755
725
|
const toFull = dy + startY
|
|
@@ -776,18 +746,18 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
776
746
|
frameSize,
|
|
777
747
|
activePositions,
|
|
778
748
|
setPosition,
|
|
749
|
+
dismissOnSnapToBottom,
|
|
750
|
+
snapPointsMode,
|
|
779
751
|
])
|
|
780
752
|
|
|
781
|
-
// animate to keyboard-adjusted position when keyboard state
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
// would only introduce movement where the sheet should hold perfectly still.
|
|
753
|
+
// animate to the current keyboard-adjusted position when the keyboard state
|
|
754
|
+
// changes. activePositions shift the frame up by keyboardHeight, capped at
|
|
755
|
+
// the safe-area top; tall web fit sheets keep a frozen height and gain scroll
|
|
756
|
+
// padding for whatever tail remains behind the keyboard.
|
|
786
757
|
React.useEffect(() => {
|
|
787
|
-
if (isWeb) return
|
|
788
758
|
if (isDragging || isHidden || !open || disableAnimation) return
|
|
789
759
|
if (!frameSize || !screenSize) return
|
|
790
|
-
//
|
|
760
|
+
// timing animation matches the iOS keyboard animation (~250ms)
|
|
791
761
|
animateTo(position, { type: 'timing', duration: 250 })
|
|
792
762
|
}, [isKeyboardVisible, keyboardHeight])
|
|
793
763
|
|
|
@@ -838,6 +808,9 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
838
808
|
at.current = val
|
|
839
809
|
animatedNumber.setValue(val, { type: 'direct' })
|
|
840
810
|
},
|
|
811
|
+
dismissOnSnapToBottom,
|
|
812
|
+
snapPointsMode,
|
|
813
|
+
isKeyboardVisible,
|
|
841
814
|
pauseKeyboardHandler,
|
|
842
815
|
})
|
|
843
816
|
|
|
@@ -847,35 +820,12 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
847
820
|
// recompute the fit anchor and fly the frame. a LIVE DOM check, NOT the
|
|
848
821
|
// isKeyboardVisible React state, is required: the state lags the resize, so
|
|
849
822
|
// the first shrunk onLayout lands before the flag flips. holding the measured
|
|
850
|
-
//
|
|
823
|
+
// size keeps the fit geometry stable while the frame translates with the kb.
|
|
851
824
|
const ignoreLayoutForKeyboard = useEvent(
|
|
852
|
-
() => isWeb && moveOnKeyboardChange && getWebKeyboardHeight() >= MIN_KEYBOARD_HEIGHT
|
|
853
|
-
)
|
|
854
|
-
|
|
855
|
-
// AUTOFOCUS-ON-OPEN seed gates. normally a layout measured while the keyboard
|
|
856
|
-
// is up is dropped (it's the shrunk/collapsed sheet). but if the keyboard rose
|
|
857
|
-
// before ANY keyboard-free baseline was captured (the autofocus race),
|
|
858
|
-
// dropping every measurement leaves frameSize/screenSize stuck at 0 forever
|
|
859
|
-
// and the freeze never engages.
|
|
860
|
-
//
|
|
861
|
-
// frame: while seeding (not settled) the scroll view is unclipped and the
|
|
862
|
-
// tail padding suppressed, so the frame measures toward its pure pre-keyboard
|
|
863
|
-
// content height. the first keyboard-up pass is still clipped at the consumer
|
|
864
|
-
// maxHeight; the override only unclips it the following pass, so the frame
|
|
865
|
-
// grows. we take the MAX and mark the seed SETTLED once it stops growing
|
|
866
|
-
// (handled in handleAnimationViewLayout) — then later keyboard-up layouts are
|
|
867
|
-
// dropped again so the re-enabled tail padding can't inflate the frame.
|
|
868
|
-
const shouldSeedKbFrame = useEvent(
|
|
869
825
|
() =>
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
ignoreLayoutForKeyboard()
|
|
874
|
-
)
|
|
875
|
-
// screen (= maxContentSize): keep reconstructing from the stable viewport for
|
|
876
|
-
// the whole keyboard-up-without-clean-baseline window.
|
|
877
|
-
const shouldSeedKbScreen = useEvent(
|
|
878
|
-
() => isWebKbSheet && !hasCleanKbBaseline.current && ignoreLayoutForKeyboard()
|
|
826
|
+
isWeb &&
|
|
827
|
+
moveOnKeyboardChange &&
|
|
828
|
+
getWebKeyboardResizeHeight() >= MIN_KEYBOARD_HEIGHT
|
|
879
829
|
)
|
|
880
830
|
|
|
881
831
|
const handleAnimationViewLayout = useEvent((e: LayoutChangeEvent) => {
|
|
@@ -884,58 +834,39 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
884
834
|
return
|
|
885
835
|
}
|
|
886
836
|
|
|
887
|
-
const
|
|
888
|
-
|
|
837
|
+
const layoutHeight = e.nativeEvent?.layout.height
|
|
838
|
+
// drop a layout measured while the keyboard is up: on older iOS the web
|
|
839
|
+
// viewport shrinks and the frame would resize. keep the pre-keyboard frame
|
|
840
|
+
// height so the web frame translates without resizing.
|
|
841
|
+
// exception: if we have no frame height yet (sheet opened with the keyboard
|
|
842
|
+
// already up), accept it so the sheet can appear at all.
|
|
843
|
+
if (ignoreLayoutForKeyboard() && frameSize > 0) return
|
|
889
844
|
|
|
890
845
|
// avoid bugs where it grows forever for whatever reason
|
|
891
846
|
// For inline mode (non-modal), don't cap at window height - use actual layout
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
// suppressed, so this measures the pure pre-keyboard content height — cap it
|
|
896
|
-
// at the stable viewport like modal does.
|
|
897
|
-
const next =
|
|
898
|
-
modal || seeding
|
|
899
|
-
? Math.min(layoutHeight, getStableViewportHeight())
|
|
900
|
-
: layoutHeight
|
|
847
|
+
const next = modal
|
|
848
|
+
? Math.min(layoutHeight, getStableViewportHeight())
|
|
849
|
+
: layoutHeight
|
|
901
850
|
if (!next) return
|
|
902
851
|
// round: web onLayout reports sub-pixel heights (e.g. 499.99996) that jitter
|
|
903
852
|
// frame to frame as the view transforms; the raw float would re-fire every
|
|
904
853
|
// effect that depends on frameSize on each drag move.
|
|
905
|
-
|
|
906
|
-
// seeding the frame: grow the baseline toward the unclipped content height.
|
|
907
|
-
// the first keyboard-up pass is clipped; the scroll-view override unclips it
|
|
908
|
-
// the next pass so it grows. once a pass doesn't exceed the running max the
|
|
909
|
-
// content has settled — mark the seed done so the next render exits the seed
|
|
910
|
-
// phase (freezeForKb pins this height, tail padding re-enables for scroll).
|
|
911
|
-
if (seeding) {
|
|
912
|
-
if (rounded > stableKbGeom.current.frame) {
|
|
913
|
-
stableKbGeom.current.frame = rounded
|
|
914
|
-
} else if (stableKbGeom.current.frame > 0) {
|
|
915
|
-
seedSettled.current = true
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
setFrameSize(rounded)
|
|
854
|
+
setFrameSize(Math.round(next))
|
|
919
855
|
})
|
|
920
856
|
|
|
921
857
|
const handleMaxContentViewLayout = React.useCallback(
|
|
922
858
|
(e: LayoutChangeEvent) => {
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
if (shouldSeedKbScreen()) {
|
|
928
|
-
setMaxContentSize(Math.round(getStableViewportHeight()))
|
|
929
|
-
return
|
|
930
|
-
}
|
|
931
|
-
if (ignoreLayoutForKeyboard()) return
|
|
859
|
+
// keep maxContentSize at the full pre-keyboard viewport: drop layouts
|
|
860
|
+
// measured while the keyboard is up (the shrunk viewport), unless we have
|
|
861
|
+
// none yet (keyboard-already-up open).
|
|
862
|
+
if (ignoreLayoutForKeyboard() && screenSize > 0) return
|
|
932
863
|
// avoid bugs where it grows forever for whatever reason
|
|
933
864
|
const next = Math.min(e.nativeEvent?.layout.height, getStableViewportHeight())
|
|
934
865
|
if (!next) return
|
|
935
866
|
// round to avoid sub-pixel churn re-firing size-dependent effects
|
|
936
867
|
setMaxContentSize(Math.round(next))
|
|
937
868
|
},
|
|
938
|
-
[ignoreLayoutForKeyboard,
|
|
869
|
+
[ignoreLayoutForKeyboard, screenSize]
|
|
939
870
|
)
|
|
940
871
|
|
|
941
872
|
const getAnimatedNumberStyle = React.useCallback(
|
|
@@ -988,7 +919,6 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
988
919
|
keyboardOccludedHeight={keyboardOccludedHeight}
|
|
989
920
|
isKeyboardVisible={isKeyboardVisible}
|
|
990
921
|
keyboardStableFrameHeight={keyboardStableFrameHeight}
|
|
991
|
-
isKeyboardSeeding={seedingKbBaseline}
|
|
992
922
|
setHasScrollView={setHasScrollView}
|
|
993
923
|
>
|
|
994
924
|
<GestureSheetProvider
|
|
@@ -1065,8 +995,13 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
1065
995
|
const adaptContext = useAdaptContext()
|
|
1066
996
|
contents = (
|
|
1067
997
|
<ProvideAdaptContext {...adaptContext}>
|
|
1068
|
-
{/*
|
|
1069
|
-
|
|
998
|
+
{/* re-propagate safe-area insets across the teleport: the sheet content
|
|
999
|
+
renders at the portal host, OUTSIDE the app's SafeAreaProvider, so
|
|
1000
|
+
without this useSafeAreaInsets() inside the sheet reads 0. */}
|
|
1001
|
+
<SafeAreaInsetsContext.Provider value={safeAreaInsets}>
|
|
1002
|
+
{/* @ts-ignore */}
|
|
1003
|
+
{contents}
|
|
1004
|
+
</SafeAreaInsetsContext.Provider>
|
|
1070
1005
|
</ProvideAdaptContext>
|
|
1071
1006
|
)
|
|
1072
1007
|
}
|