@hanzogui/sheet 7.3.0 → 8.0.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 +2 -0
- package/controller/index.cjs +2 -0
- package/controller/index.d.ts +5 -0
- package/controller/index.js +2 -0
- package/controller/index.native.cjs +2 -0
- package/controller/index.native.js +2 -0
- package/dist/cjs/GestureDetectorWrapper.native.js.map +1 -1
- package/dist/cjs/SheetImplementationCustom.cjs +137 -74
- package/dist/cjs/SheetImplementationCustom.native.js +162 -83
- package/dist/cjs/SheetImplementationCustom.native.js.map +1 -1
- package/dist/cjs/SheetScrollView.cjs +155 -34
- package/dist/cjs/SheetScrollView.native.js +160 -35
- package/dist/cjs/SheetScrollView.native.js.map +1 -1
- package/dist/cjs/createSheet.cjs +7 -3
- package/dist/cjs/createSheet.native.js +7 -3
- package/dist/cjs/createSheet.native.js.map +1 -1
- package/dist/cjs/gestureState.cjs +13 -2
- package/dist/cjs/gestureState.native.js +14 -2
- package/dist/cjs/gestureState.native.js.map +1 -1
- package/dist/cjs/index.cjs +12 -1
- package/dist/cjs/index.native.js +11 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboardAvoidance.cjs +93 -0
- package/dist/cjs/keyboardAvoidance.native.js +99 -0
- package/dist/cjs/keyboardAvoidance.native.js.map +1 -0
- package/dist/cjs/nativeSheet.cjs +3 -0
- package/dist/cjs/nativeSheet.native.js +3 -0
- package/dist/cjs/nativeSheet.native.js.map +1 -1
- package/dist/cjs/setupGestureHandler.cjs +9 -6
- package/dist/cjs/setupGestureHandler.native.js +11 -6
- package/dist/cjs/setupGestureHandler.native.js.map +1 -1
- package/dist/cjs/useGestureHandlerPan.cjs +86 -18
- package/dist/cjs/useGestureHandlerPan.native.js +91 -18
- package/dist/cjs/useGestureHandlerPan.native.js.map +1 -1
- package/dist/cjs/useKeyboardControllerSheet.cjs +68 -6
- 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/useSheetProviderProps.cjs +1 -1
- package/dist/cjs/useSheetProviderProps.native.js +1 -1
- package/dist/cjs/useSheetProviderProps.native.js.map +1 -1
- package/dist/cjs/useSheetScrollViewGestures.cjs +30 -5
- package/dist/cjs/useSheetScrollViewGestures.native.js +4 -0
- package/dist/cjs/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/cjs/webViewport.cjs +75 -0
- package/dist/cjs/webViewport.native.js +81 -0
- package/dist/cjs/webViewport.native.js.map +1 -0
- package/dist/esm/GestureDetectorWrapper.mjs.map +1 -1
- package/dist/esm/GestureDetectorWrapper.native.js.map +1 -1
- package/dist/esm/SheetImplementationCustom.mjs +143 -84
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -1
- package/dist/esm/SheetImplementationCustom.native.js +173 -102
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -1
- package/dist/esm/SheetScrollView.mjs +157 -36
- package/dist/esm/SheetScrollView.mjs.map +1 -1
- package/dist/esm/SheetScrollView.native.js +162 -37
- package/dist/esm/SheetScrollView.native.js.map +1 -1
- package/dist/esm/createSheet.mjs +9 -6
- package/dist/esm/createSheet.mjs.map +1 -1
- package/dist/esm/createSheet.native.js +19 -15
- package/dist/esm/createSheet.native.js.map +1 -1
- package/dist/esm/gestureState.mjs +13 -2
- package/dist/esm/gestureState.mjs.map +1 -1
- package/dist/esm/gestureState.native.js +14 -2
- package/dist/esm/gestureState.native.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -0
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/keyboardAvoidance.mjs +66 -0
- package/dist/esm/keyboardAvoidance.mjs.map +1 -0
- package/dist/esm/keyboardAvoidance.native.js +69 -0
- package/dist/esm/keyboardAvoidance.native.js.map +1 -0
- package/dist/esm/nativeSheet.mjs +3 -0
- package/dist/esm/nativeSheet.mjs.map +1 -1
- package/dist/esm/nativeSheet.native.js +3 -0
- package/dist/esm/nativeSheet.native.js.map +1 -1
- package/dist/esm/setupGestureHandler.mjs +9 -6
- package/dist/esm/setupGestureHandler.mjs.map +1 -1
- package/dist/esm/setupGestureHandler.native.js +11 -6
- package/dist/esm/setupGestureHandler.native.js.map +1 -1
- package/dist/esm/useGestureHandlerPan.mjs +87 -19
- package/dist/esm/useGestureHandlerPan.mjs.map +1 -1
- package/dist/esm/useGestureHandlerPan.native.js +92 -19
- package/dist/esm/useGestureHandlerPan.native.js.map +1 -1
- package/dist/esm/useKeyboardControllerSheet.mjs +69 -7
- 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 +1 -1
- package/dist/esm/useSheetProviderProps.mjs.map +1 -1
- package/dist/esm/useSheetProviderProps.native.js +1 -1
- package/dist/esm/useSheetProviderProps.native.js.map +1 -1
- package/dist/esm/useSheetScrollViewGestures.mjs +30 -5
- 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 +43 -0
- package/dist/esm/webViewport.mjs.map +1 -0
- package/dist/esm/webViewport.native.js +46 -0
- package/dist/esm/webViewport.native.js.map +1 -0
- package/dist/jsx/GestureDetectorWrapper.mjs.map +1 -1
- package/dist/jsx/GestureDetectorWrapper.native.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.mjs +143 -84
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -1
- package/dist/jsx/SheetImplementationCustom.native.js +162 -83
- package/dist/jsx/SheetImplementationCustom.native.js.map +1 -1
- package/dist/jsx/SheetScrollView.mjs +157 -36
- package/dist/jsx/SheetScrollView.mjs.map +1 -1
- package/dist/jsx/SheetScrollView.native.js +160 -35
- package/dist/jsx/SheetScrollView.native.js.map +1 -1
- package/dist/jsx/createSheet.mjs +9 -6
- package/dist/jsx/createSheet.mjs.map +1 -1
- package/dist/jsx/createSheet.native.js +7 -3
- package/dist/jsx/createSheet.native.js.map +1 -1
- package/dist/jsx/gestureState.mjs +13 -2
- package/dist/jsx/gestureState.mjs.map +1 -1
- package/dist/jsx/gestureState.native.js +14 -2
- package/dist/jsx/gestureState.native.js.map +1 -1
- package/dist/jsx/index.js +2 -0
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -0
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +11 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/keyboardAvoidance.mjs +66 -0
- package/dist/jsx/keyboardAvoidance.mjs.map +1 -0
- package/dist/jsx/keyboardAvoidance.native.js +99 -0
- package/dist/jsx/keyboardAvoidance.native.js.map +1 -0
- package/dist/jsx/nativeSheet.mjs +3 -0
- package/dist/jsx/nativeSheet.mjs.map +1 -1
- package/dist/jsx/nativeSheet.native.js +3 -0
- package/dist/jsx/nativeSheet.native.js.map +1 -1
- package/dist/jsx/setupGestureHandler.mjs +9 -6
- package/dist/jsx/setupGestureHandler.mjs.map +1 -1
- package/dist/jsx/setupGestureHandler.native.js +11 -6
- package/dist/jsx/setupGestureHandler.native.js.map +1 -1
- package/dist/jsx/useGestureHandlerPan.mjs +87 -19
- package/dist/jsx/useGestureHandlerPan.mjs.map +1 -1
- package/dist/jsx/useGestureHandlerPan.native.js +91 -18
- package/dist/jsx/useGestureHandlerPan.native.js.map +1 -1
- package/dist/jsx/useKeyboardControllerSheet.mjs +69 -7
- 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 +1 -1
- package/dist/jsx/useSheetProviderProps.mjs.map +1 -1
- package/dist/jsx/useSheetProviderProps.native.js +1 -1
- package/dist/jsx/useSheetProviderProps.native.js.map +1 -1
- package/dist/jsx/useSheetScrollViewGestures.mjs +30 -5
- 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 +43 -0
- package/dist/jsx/webViewport.mjs.map +1 -0
- package/dist/jsx/webViewport.native.js +81 -0
- package/dist/jsx/webViewport.native.js.map +1 -0
- package/next.md +78 -0
- package/package.json +30 -48
- package/setup-gesture-handler/index.cjs +2 -0
- package/setup-gesture-handler/index.js +2 -0
- package/setup-gesture-handler/index.native.cjs +2 -0
- package/setup-gesture-handler/index.native.js +2 -0
- package/src/GestureDetectorWrapper.tsx +0 -3
- package/src/SheetImplementationCustom.tsx +387 -127
- package/src/SheetScrollView.tsx +195 -22
- package/src/createSheet.tsx +20 -7
- package/src/gestureState.ts +22 -2
- package/src/index.ts +4 -0
- package/src/keyboardAvoidance.ts +121 -0
- package/src/nativeSheet.tsx +8 -1
- package/src/setupGestureHandler.ts +14 -5
- package/src/types.tsx +20 -1
- package/src/useGestureHandlerPan.tsx +94 -35
- package/src/useKeyboardControllerSheet.ts +127 -10
- package/src/useSafeAreaInsets.native.ts +29 -0
- package/src/useSafeAreaInsets.ts +19 -0
- package/src/useSheetProviderProps.tsx +12 -1
- package/src/useSheetScrollViewGestures.native.ts +4 -0
- package/src/useSheetScrollViewGestures.ts +52 -5
- package/src/webViewport.ts +100 -0
- package/test/keyboardAvoidance.test.ts +269 -0
- package/tsconfig.json +57 -0
- package/types/GestureDetectorWrapper.d.ts +1 -1
- package/types/GestureDetectorWrapper.d.ts.map +1 -1
- package/types/GestureSheetContext.d.ts +1 -1
- package/types/GestureSheetContext.d.ts.map +1 -1
- package/types/Sheet.d.ts +6 -5
- package/types/Sheet.d.ts.map +1 -1
- package/types/SheetContext.d.ts +5 -2
- package/types/SheetContext.d.ts.map +1 -1
- package/types/SheetController.d.ts +1 -2
- package/types/SheetController.d.ts.map +1 -1
- package/types/SheetImplementationCustom.d.ts +1 -0
- package/types/SheetImplementationCustom.d.ts.map +1 -1
- package/types/SheetScrollView.d.ts.map +1 -1
- package/types/createSheet.d.ts +13 -12
- package/types/createSheet.d.ts.map +1 -1
- package/types/gestureState.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/keyboardAvoidance.d.ts +26 -0
- package/types/keyboardAvoidance.d.ts.map +1 -0
- package/types/nativeSheet.d.ts.map +1 -1
- package/types/setupGestureHandler.d.ts.map +1 -1
- package/types/types.d.ts +10 -1
- package/types/types.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 +14 -3
- 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 +3 -0
- package/types/useSheetProviderProps.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.d.ts +2 -1
- package/types/useSheetScrollViewGestures.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.native.d.ts.map +1 -1
- package/types/webViewport.d.ts +36 -0
- package/types/webViewport.d.ts.map +1 -0
- package/LICENSE +0 -42
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
useEvent,
|
|
11
11
|
useThemeName,
|
|
12
12
|
} from '@hanzogui/core'
|
|
13
|
-
import { getSafeArea } from '@hanzogui/native'
|
|
14
13
|
import { needsPortalRepropagation, Portal } from '@hanzogui/portal'
|
|
15
14
|
import React, { useState } from 'react'
|
|
16
15
|
import type {
|
|
@@ -22,35 +21,71 @@ import type {
|
|
|
22
21
|
import { Dimensions, PanResponder, View } from 'react-native'
|
|
23
22
|
import { ParentSheetContext, SheetInsideSheetContext } from './contexts'
|
|
24
23
|
import { GestureDetectorWrapper } from './GestureDetectorWrapper'
|
|
24
|
+
import { getGestureHandlerState } from './gestureState'
|
|
25
25
|
import { GestureSheetProvider } from './GestureSheetContext'
|
|
26
26
|
import { resisted } from './helpers'
|
|
27
|
+
import {
|
|
28
|
+
getKeyboardAdjustedSheetY,
|
|
29
|
+
getKeyboardOccludedHeight,
|
|
30
|
+
getSheetReleasePosition,
|
|
31
|
+
} from './keyboardAvoidance'
|
|
32
|
+
import {
|
|
33
|
+
getWebKeyboardResizeHeight,
|
|
34
|
+
getMaxViewportHeight,
|
|
35
|
+
getStableLayoutViewportHeight,
|
|
36
|
+
getWebVisualViewportOffsetTop,
|
|
37
|
+
MIN_KEYBOARD_HEIGHT,
|
|
38
|
+
} from './webViewport'
|
|
27
39
|
import { SheetProvider } from './SheetContext'
|
|
28
40
|
import type { SheetProps, SnapPointsMode } from './types'
|
|
29
41
|
import { useGestureHandlerPan } from './useGestureHandlerPan'
|
|
30
42
|
import { useKeyboardControllerSheet } from './useKeyboardControllerSheet'
|
|
43
|
+
import { SafeAreaInsetsContext, useSafeAreaInsets } from './useSafeAreaInsets'
|
|
31
44
|
import { useSheetOpenState } from './useSheetOpenState'
|
|
32
45
|
import { useSheetProviderProps } from './useSheetProviderProps'
|
|
33
46
|
|
|
34
47
|
const hiddenSize = 10_000.1
|
|
35
48
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
49
|
+
// the re-established rngh root for a modal sheet (see modal branch below).
|
|
50
|
+
// GestureHandlerRootView does its own native touch interception and ignores
|
|
51
|
+
// pointerEvents, so it would block the whole app while the sheet sits closed
|
|
52
|
+
// but mounted. instead it stays full-width for correct child layout/measurement
|
|
53
|
+
// and collapses to 0 height when closed so it has no hit area.
|
|
54
|
+
const rnghRootStyleOpen = { width: '100%', height: '100%' } as const
|
|
55
|
+
const rnghRootStyleClosed = { width: '100%', height: 0 } as const
|
|
44
56
|
|
|
45
57
|
let sheetHiddenStyleSheet: HTMLStyleElement | null = null
|
|
46
58
|
|
|
47
59
|
// on web we are always relative to window, on to screen
|
|
48
60
|
const relativeDimensionTo = isWeb ? 'window' : 'screen'
|
|
49
61
|
|
|
62
|
+
// height of the viewport the sheet positions against. on web this MUST be the
|
|
63
|
+
// stable layout viewport and NOT Dimensions.get('window') — react-native-web's
|
|
64
|
+
// Dimensions tracks visualViewport, which shrinks by the soft keyboard. capping
|
|
65
|
+
// frameSize / maxContentSize against that shrinking value corrupts the fit-mode
|
|
66
|
+
// math (translateY = screenSize - frameSize), detaching the sheet's bottom from
|
|
67
|
+
// the screen edge when the keyboard opens. NOTE: window.innerHeight is NOT
|
|
68
|
+
// stable on real iOS Safari (it shrinks with the keyboard too), so we use the
|
|
69
|
+
// self-correcting baseline from webViewport instead. see getStableLayoutViewportHeight.
|
|
70
|
+
function getStableViewportHeight(): number {
|
|
71
|
+
if (isWeb && typeof window !== 'undefined') return getStableLayoutViewportHeight()
|
|
72
|
+
return Dimensions.get(relativeDimensionTo).height
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
51
76
|
function SheetImplementationCustom(props, forwardedRef) {
|
|
52
77
|
const parentSheet = React.useContext(ParentSheetContext)
|
|
53
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
|
+
|
|
54
89
|
const {
|
|
55
90
|
transition,
|
|
56
91
|
transitionConfig: transitionConfigProp,
|
|
@@ -58,6 +93,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
58
93
|
zIndex = parentSheet.zIndex + 1,
|
|
59
94
|
moveOnKeyboardChange = false,
|
|
60
95
|
unmountChildrenWhenHidden = false,
|
|
96
|
+
disableTransparencyHide = false,
|
|
61
97
|
portalProps,
|
|
62
98
|
containerComponent: ContainerComponent = React.Fragment,
|
|
63
99
|
onAnimationComplete,
|
|
@@ -72,6 +108,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
72
108
|
const {
|
|
73
109
|
frameSize,
|
|
74
110
|
setFrameSize,
|
|
111
|
+
dismissOnSnapToBottom,
|
|
75
112
|
snapPoints,
|
|
76
113
|
snapPointsMode,
|
|
77
114
|
hasFit,
|
|
@@ -132,6 +169,19 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
132
169
|
setIsShowingInnerSheet(hasChild)
|
|
133
170
|
}, [])
|
|
134
171
|
|
|
172
|
+
// keyboard state tracking — just tracks height/visibility, no position animation.
|
|
173
|
+
// Position animation is handled via keyboard-adjusted positions below,
|
|
174
|
+
// matching the react-native-actions-sheet pattern.
|
|
175
|
+
const {
|
|
176
|
+
keyboardHeight,
|
|
177
|
+
isKeyboardVisible,
|
|
178
|
+
dismissKeyboard,
|
|
179
|
+
pauseKeyboardHandler,
|
|
180
|
+
flushPendingHide,
|
|
181
|
+
} = useKeyboardControllerSheet({
|
|
182
|
+
enabled: Boolean(moveOnKeyboardChange),
|
|
183
|
+
})
|
|
184
|
+
|
|
135
185
|
// FIX: Store stable frameSize to prevent recalculation during exit animation
|
|
136
186
|
const stableFrameSize = React.useRef(frameSize)
|
|
137
187
|
|
|
@@ -142,31 +192,63 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
142
192
|
}
|
|
143
193
|
}, [open, frameSize])
|
|
144
194
|
|
|
145
|
-
//
|
|
146
|
-
//
|
|
195
|
+
// WEB keyboard frame freeze. on real iOS Safari opening the keyboard shrinks
|
|
196
|
+
// the visual viewport AND innerHeight AND the measured layout, which would
|
|
197
|
+
// re-derive screenSize/frameSize smaller, recompute the fit positions, and
|
|
198
|
+
// fly the frame up then back down ("goes back down after the keyboard opens").
|
|
199
|
+
// so we snapshot the pre-keyboard geometry — captured every render while the
|
|
200
|
+
// keyboard is CLOSED, which dodges the open-transition race where a shrunk
|
|
201
|
+
// onLayout lands before isKeyboardVisible flips — and use it for frame-size
|
|
202
|
+
// math while the keyboard is open. the active snap position still shifts up
|
|
203
|
+
// by the keyboard height, capped at the safe-area top; the scroll view gets
|
|
204
|
+
// keyboardOccludedHeight padding for any tail left behind the keyboard.
|
|
205
|
+
// this sheet is the kind the web keyboard frame freeze is designed for — a
|
|
206
|
+
// fit-mode web sheet opted into keyboard handling. percent/constant sheets
|
|
207
|
+
// keep the live geometry (their height isn't pinned, so a frozen frame
|
|
208
|
+
// height would mismatch).
|
|
209
|
+
const isWebKbSheet = isWeb && hasFit && moveOnKeyboardChange
|
|
210
|
+
|
|
211
|
+
// NATIVE keyboard frame freeze. native fit sheets feed keyboardOccludedHeight
|
|
212
|
+
// into the ScrollView as scrollable tail padding so content can clear the
|
|
213
|
+
// keyboard. but that padding grows the fit content, which grows the frame,
|
|
214
|
+
// which grows the occluded height — a feedback loop that balloons the sheet
|
|
215
|
+
// to the full-screen cap. web breaks the loop by freezing geometry against
|
|
216
|
+
// effScreenSize; native has no such freeze, so we pin the ScrollView to the
|
|
217
|
+
// frame height measured while the keyboard was closed (preKeyboardFrameSize)
|
|
218
|
+
// via keyboardStableFrameHeight below. unlike web, the native keyboard is an
|
|
219
|
+
// overlay that never shrinks the measured screen, so screenSize stays live.
|
|
220
|
+
const isNativeKbFitSheet = !isWeb && hasFit && Boolean(moveOnKeyboardChange)
|
|
221
|
+
|
|
222
|
+
// snapshot the fit frame height while the keyboard is closed, so the pin above
|
|
223
|
+
// holds the pre-keyboard viewport rather than a mid-feedback-loop value.
|
|
224
|
+
const preKeyboardFrameSize = React.useRef(0)
|
|
225
|
+
React.useEffect(() => {
|
|
226
|
+
if (isNativeKbFitSheet && open && !isKeyboardVisible && frameSize > 0) {
|
|
227
|
+
preKeyboardFrameSize.current = frameSize
|
|
228
|
+
}
|
|
229
|
+
}, [isNativeKbFitSheet, open, isKeyboardVisible, frameSize])
|
|
230
|
+
|
|
231
|
+
// the space the snap positions are built against. WEB: the stable layout
|
|
232
|
+
// viewport (document.documentElement.clientHeight), which the soft keyboard
|
|
233
|
+
// never shrinks (unlike the measured screenSize / visualViewport). NATIVE:
|
|
234
|
+
// the measured screenSize. activePositions shift up by keyboardHeight below
|
|
235
|
+
// so a small fit sheet keeps its natural height and moves with the keyboard;
|
|
236
|
+
// a tall fit sheet moves until capped at the safe-area top, leaving its tail
|
|
237
|
+
// behind the keyboard for the ScrollView padding to expose.
|
|
238
|
+
const effScreenSize = isWebKbSheet ? getStableViewportHeight() : screenSize
|
|
239
|
+
|
|
240
|
+
// use stableFrameSize when closing to prevent position jumps during the exit
|
|
241
|
+
// animation; while open use the live frameSize.
|
|
147
242
|
const effectiveFrameSize = open ? frameSize : stableFrameSize.current || frameSize
|
|
148
243
|
|
|
149
244
|
const positions = React.useMemo(
|
|
150
245
|
() =>
|
|
151
246
|
snapPoints.map((point) =>
|
|
152
|
-
getYPositions(snapPointsMode, point,
|
|
247
|
+
getYPositions(snapPointsMode, point, effScreenSize, effectiveFrameSize)
|
|
153
248
|
),
|
|
154
|
-
[
|
|
249
|
+
[effScreenSize, effectiveFrameSize, snapPoints, snapPointsMode]
|
|
155
250
|
)
|
|
156
251
|
|
|
157
|
-
// keyboard state tracking — just tracks height/visibility, no position animation.
|
|
158
|
-
// Position animation is handled via keyboard-adjusted positions below,
|
|
159
|
-
// matching the react-native-actions-sheet pattern.
|
|
160
|
-
const {
|
|
161
|
-
keyboardHeight,
|
|
162
|
-
isKeyboardVisible,
|
|
163
|
-
dismissKeyboard,
|
|
164
|
-
pauseKeyboardHandler,
|
|
165
|
-
flushPendingHide,
|
|
166
|
-
} = useKeyboardControllerSheet({
|
|
167
|
-
enabled: !isWeb && Boolean(moveOnKeyboardChange),
|
|
168
|
-
})
|
|
169
|
-
|
|
170
252
|
const [isDragging, setIsDragging_] = React.useState(false)
|
|
171
253
|
|
|
172
254
|
// synchronous dragging ref — set BEFORE async state commits.
|
|
@@ -188,42 +270,71 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
188
270
|
[pauseKeyboardHandler, flushPendingHide]
|
|
189
271
|
)
|
|
190
272
|
|
|
191
|
-
// keyboard-adjusted
|
|
192
|
-
// when keyboard is visible. This drives both gesture snap calculation
|
|
193
|
-
// and animation targets — keyboard never dismissed during drag.
|
|
194
|
-
// Capped at safe area top inset so the sheet never goes above the notch/status bar
|
|
195
|
-
// (matching the react-native-actions-sheet pattern).
|
|
273
|
+
// keyboard-adjusted snap positions.
|
|
196
274
|
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
275
|
+
// WEB + NATIVE: shift snap points up by keyboard height, capped at the
|
|
276
|
+
// safe-area top inset. web fit sheets keep their frozen pre-keyboard height
|
|
277
|
+
// and add bottom spacer padding when the safe-area cap leaves a hidden tail.
|
|
278
|
+
//
|
|
279
|
+
// IMPORTANT: frozen during drag to prevent gesture handler recreation —
|
|
280
|
+
// when a TextInput blurs mid-drag the keyboard state would otherwise revert
|
|
281
|
+
// and recreate the gesture useMemo, cancelling the active drag.
|
|
201
282
|
const activePositionsRef = React.useRef(positions)
|
|
202
283
|
const activePositions = React.useMemo(() => {
|
|
203
|
-
// during drag, return frozen positions to prevent gesture handler recreation.
|
|
204
|
-
// check both state (for re-render trigger) and ref (for synchronous check
|
|
205
|
-
// when keyboard hide event fires before isDragging state commits)
|
|
206
284
|
if (isDragging || isDraggingRef.current) return activePositionsRef.current
|
|
207
285
|
|
|
208
286
|
let result: number[]
|
|
287
|
+
|
|
209
288
|
if (!isKeyboardVisible || keyboardHeight <= 0) {
|
|
210
289
|
result = positions
|
|
211
290
|
} else {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
291
|
+
result = positions.map((p) =>
|
|
292
|
+
getKeyboardAdjustedSheetY({
|
|
293
|
+
sheetY: p,
|
|
294
|
+
screenSize: effScreenSize,
|
|
295
|
+
isKeyboardVisible,
|
|
296
|
+
keyboardHeight,
|
|
297
|
+
shouldTranslate: true,
|
|
298
|
+
safeAreaTop: isWeb ? getWebVisualViewportOffsetTop() : safeAreaTopInset,
|
|
299
|
+
})
|
|
300
|
+
)
|
|
220
301
|
}
|
|
221
302
|
activePositionsRef.current = result
|
|
222
303
|
return result
|
|
223
|
-
}, [
|
|
304
|
+
}, [
|
|
305
|
+
positions,
|
|
306
|
+
isKeyboardVisible,
|
|
307
|
+
keyboardHeight,
|
|
308
|
+
effScreenSize,
|
|
309
|
+
isDragging,
|
|
310
|
+
safeAreaTopInset,
|
|
311
|
+
])
|
|
312
|
+
|
|
313
|
+
// bottom spacer for the part of the sheet hidden by the keyboard after the
|
|
314
|
+
// keyboard translation and safe-area clamping. if a small sheet fits above
|
|
315
|
+
// the keyboard this is 0; if a tall sheet is capped at the safe-area top, the
|
|
316
|
+
// spacer makes the remaining hidden tail scrollable.
|
|
317
|
+
const keyboardOccludedHeight = getKeyboardOccludedHeight({
|
|
318
|
+
frameSize: effectiveFrameSize,
|
|
319
|
+
isKeyboardVisible,
|
|
320
|
+
keyboardHeight,
|
|
321
|
+
screenSize: effScreenSize,
|
|
322
|
+
sheetY: position >= 0 ? activePositions[position] : undefined,
|
|
323
|
+
})
|
|
224
324
|
|
|
225
|
-
|
|
226
|
-
|
|
325
|
+
// pin the scroll view to the held (pre-keyboard) frame height while the
|
|
326
|
+
// keyboard is up on web. on older iOS the consumer's window-derived maxHeight
|
|
327
|
+
// shrinks with the keyboard, which would clip the scroll view (and the frame)
|
|
328
|
+
// smaller; this override keeps it at the full height so the frame translates
|
|
329
|
+
// with the keyboard but never resizes. 0 = no override (use the consumer maxHeight).
|
|
330
|
+
const keyboardStableFrameHeight =
|
|
331
|
+
isWebKbSheet && isKeyboardVisible && frameSize > 0
|
|
332
|
+
? frameSize
|
|
333
|
+
: isNativeKbFitSheet && isKeyboardVisible && preKeyboardFrameSize.current > 0
|
|
334
|
+
? preKeyboardFrameSize.current
|
|
335
|
+
: 0
|
|
336
|
+
|
|
337
|
+
const { useAnimatedNumber, useAnimatedNumberStyle } = animationDriver
|
|
227
338
|
const AnimatedView = (animationDriver.View ?? GuiView) as typeof Animated.View
|
|
228
339
|
|
|
229
340
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -259,42 +370,52 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
259
370
|
// safety fallback timer for sheet close opacity
|
|
260
371
|
const opacityFallbackTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
261
372
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (
|
|
277
|
-
scrollBridge.
|
|
278
|
-
|
|
279
|
-
// this preemptively sets scroll state before any gestures start
|
|
280
|
-
if (nowAtTop) {
|
|
281
|
-
// if scroll drifted during drag (e.g. fast swipe from position 1),
|
|
282
|
-
// reset it to 0 before enabling free scroll
|
|
283
|
-
if (scrollBridge.y > 0) {
|
|
284
|
-
scrollBridge.forceScrollTo?.(0)
|
|
285
|
-
scrollBridge.y = 0
|
|
286
|
-
}
|
|
287
|
-
scrollBridge.scrollLockY = undefined
|
|
288
|
-
scrollBridge.setScrollEnabled?.(true)
|
|
289
|
-
} else {
|
|
290
|
-
scrollBridge.scrollLockY = 0
|
|
291
|
-
scrollBridge.setScrollEnabled?.(false)
|
|
292
|
-
}
|
|
373
|
+
const syncAnimatedPosition = useEvent((value: number) => {
|
|
374
|
+
at.current = value
|
|
375
|
+
scrollBridge.paneY = value
|
|
376
|
+
|
|
377
|
+
// update isAtTop for scroll enable/disable. activePositions[0] is the
|
|
378
|
+
// top snap point (keyboard-adjusted minY).
|
|
379
|
+
const minY = activePositions[0]
|
|
380
|
+
const wasAtTop = scrollBridge.isAtTop
|
|
381
|
+
const nowAtTop = value <= minY + 5
|
|
382
|
+
if (wasAtTop === nowAtTop) return
|
|
383
|
+
|
|
384
|
+
scrollBridge.isAtTop = nowAtTop
|
|
385
|
+
if (nowAtTop) {
|
|
386
|
+
if (scrollBridge.lockScrollAtTop) {
|
|
387
|
+
if (scrollBridge.y > 0) {
|
|
388
|
+
scrollBridge.forceScrollTo?.(0)
|
|
389
|
+
scrollBridge.y = 0
|
|
293
390
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
391
|
+
scrollBridge.scrollLockY = 0
|
|
392
|
+
scrollBridge.setScrollEnabled?.(false, 0)
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
// if scroll drifted during drag (e.g. fast swipe from position 1),
|
|
396
|
+
// reset it to 0 before enabling free scroll.
|
|
397
|
+
if (scrollBridge.y > 0) {
|
|
398
|
+
scrollBridge.forceScrollTo?.(0)
|
|
399
|
+
scrollBridge.y = 0
|
|
400
|
+
}
|
|
401
|
+
scrollBridge.scrollLockY = undefined
|
|
402
|
+
scrollBridge.setScrollEnabled?.(true)
|
|
403
|
+
} else {
|
|
404
|
+
scrollBridge.scrollLockY = 0
|
|
405
|
+
scrollBridge.setScrollEnabled?.(false)
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
const setAnimatedPositionDirect = useEvent((value: number) => {
|
|
410
|
+
syncAnimatedPosition(value)
|
|
411
|
+
animatedNumber.setValue(value, { type: 'direct' })
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
const getAnimatedPosition = useEvent(() => {
|
|
415
|
+
const value = animatedNumber.getValue()
|
|
416
|
+
syncAnimatedPosition(value)
|
|
417
|
+
return value
|
|
418
|
+
})
|
|
298
419
|
|
|
299
420
|
function stopSpring() {
|
|
300
421
|
animatedNumber.stop()
|
|
@@ -307,7 +428,19 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
307
428
|
const animateTo = useEvent((position: number, animationOverride?: any) => {
|
|
308
429
|
if (frameSize === 0) return
|
|
309
430
|
|
|
310
|
-
|
|
431
|
+
// use effScreenSize (the frozen anchor space the positions were built in) for
|
|
432
|
+
// the off-screen/close target too, so a close while the keyboard is still up
|
|
433
|
+
// animates fully out instead of to a mismatched live screenSize.
|
|
434
|
+
//
|
|
435
|
+
// web: clear the maximum the viewport can ever reveal, not just the current
|
|
436
|
+
// layout viewport. iOS Safari retracts its chrome on scroll and exposes area
|
|
437
|
+
// below the current viewport, so a sheet parked at effScreenSize would peek
|
|
438
|
+
// back in as the page scrolls. getMaxViewportHeight floors the target past
|
|
439
|
+
// anything Safari can expose.
|
|
440
|
+
const closeTarget = isWeb
|
|
441
|
+
? Math.max(effScreenSize, getMaxViewportHeight())
|
|
442
|
+
: effScreenSize
|
|
443
|
+
let toValue = isHidden || position === -1 ? closeTarget : activePositions[position]
|
|
311
444
|
|
|
312
445
|
if (at.current === toValue) return
|
|
313
446
|
|
|
@@ -323,6 +456,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
323
456
|
}
|
|
324
457
|
|
|
325
458
|
const animationCompleteCallback = () => {
|
|
459
|
+
syncAnimatedPosition(toValue)
|
|
326
460
|
if (opacityFallbackTimer.current) {
|
|
327
461
|
clearTimeout(opacityFallbackTimer.current)
|
|
328
462
|
opacityFallbackTimer.current = null
|
|
@@ -386,6 +520,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
386
520
|
duration: 0,
|
|
387
521
|
},
|
|
388
522
|
() => {
|
|
523
|
+
syncAnimatedPosition(screenSize)
|
|
389
524
|
// imperfect but struggling to render properly here
|
|
390
525
|
setTimeout(() => {
|
|
391
526
|
setDisableAnimation(false)
|
|
@@ -399,6 +534,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
399
534
|
return
|
|
400
535
|
}
|
|
401
536
|
|
|
537
|
+
// never fight an active drag: the gesture owns the animated position. on
|
|
538
|
+
// web the AnimatedView's onLayout re-fires with sub-pixel jitter as the
|
|
539
|
+
// frame translates (frameSize 499.99996 <-> 500.00003), and since frameSize
|
|
540
|
+
// is a dep of this effect that would re-run it mid-pull and snap the sheet
|
|
541
|
+
// back to its resting snap point. read the live ref so drag-end (which the
|
|
542
|
+
// reconcile-after-drag effect handles) isn't gated by stale deps.
|
|
543
|
+
if (isDraggingRef.current) {
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
402
547
|
if (!frameSize || !screenSize || isHidden || (hasntMeasured && !open)) {
|
|
403
548
|
return
|
|
404
549
|
}
|
|
@@ -425,6 +570,10 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
425
570
|
scrollBridge.setScrollEnabled?.(false)
|
|
426
571
|
}
|
|
427
572
|
}
|
|
573
|
+
// NOTE: effScreenSize/effectiveFrameSize are intentionally NOT deps. With the
|
|
574
|
+
// spacer approach the frame's position target is frozen across keyboard
|
|
575
|
+
// open/close (same stable baseline), so it must NOT re-animate — keyboard
|
|
576
|
+
// avoidance is the bottom spacer + scroll, not a frame move.
|
|
428
577
|
}, [hasntMeasured, disableAnimation, isHidden, frameSize, screenSize, open, position])
|
|
429
578
|
|
|
430
579
|
const disableDrag = props.disableDrag ?? controller?.disableDrag
|
|
@@ -436,7 +585,11 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
436
585
|
if (!frameSize) return
|
|
437
586
|
if (isShowingInnerSheet) return
|
|
438
587
|
|
|
439
|
-
|
|
588
|
+
// use keyboard-adjusted positions (matches the RNGH path): when the
|
|
589
|
+
// keyboard is open the sheet sits at activePositions[0], so clamping drags
|
|
590
|
+
// against the un-adjusted positions[0] would rubber-band the sheet down to
|
|
591
|
+
// near the bottom on any drag.
|
|
592
|
+
const minY = activePositions[0]
|
|
440
593
|
scrollBridge.paneMinY = minY
|
|
441
594
|
let startY = at.current
|
|
442
595
|
|
|
@@ -476,17 +629,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
476
629
|
// vy goes up to about 4 at most (+ is down, - is up)
|
|
477
630
|
const end = currentPos + frameSize * vy * 0.2
|
|
478
631
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
632
|
+
const closestPoint = getSheetReleasePosition({
|
|
633
|
+
positions: activePositions,
|
|
634
|
+
projectedEnd: end,
|
|
635
|
+
currentPosition: currentPos,
|
|
636
|
+
frameSize,
|
|
637
|
+
dismissOnSnapToBottom,
|
|
638
|
+
snapPointsMode,
|
|
639
|
+
isKeyboardVisible,
|
|
640
|
+
isWeb,
|
|
641
|
+
})
|
|
490
642
|
|
|
491
643
|
// have to call both because state may not change but need to snap back
|
|
492
644
|
setPosition(closestPoint)
|
|
@@ -512,6 +664,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
512
664
|
return true
|
|
513
665
|
}
|
|
514
666
|
|
|
667
|
+
// touch is on the ScrollView node — the web scroll-view gesture hook
|
|
668
|
+
// owns it and drives drag/release through scrollBridge directly (it
|
|
669
|
+
// re-baselines via scrollBridge.startPanDrag on each pan handoff). if
|
|
670
|
+
// we also granted here, RNW's PanResponder would set the animated
|
|
671
|
+
// position from a second, differently-based offset every move and the
|
|
672
|
+
// sheet would jitter/jump. defer entirely to the hook.
|
|
673
|
+
if (scrollBridge.scrollNodeTouched) {
|
|
674
|
+
return false
|
|
675
|
+
}
|
|
676
|
+
|
|
515
677
|
if (scrollBridge.hasScrollableContent === true) {
|
|
516
678
|
if (scrollBridge.scrollLock) {
|
|
517
679
|
return false
|
|
@@ -552,18 +714,29 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
552
714
|
const grant = () => {
|
|
553
715
|
setPanning(true)
|
|
554
716
|
stopSpring()
|
|
555
|
-
startY =
|
|
717
|
+
startY = getAnimatedPosition()
|
|
556
718
|
}
|
|
557
719
|
|
|
558
720
|
let isExternalDrag = false
|
|
559
721
|
|
|
722
|
+
// re-baseline a pan drag to the current animated position. the web
|
|
723
|
+
// scroll-view hook calls this on every transition INTO pan ownership
|
|
724
|
+
// (including handoffs back from scroll), so its panDragOffset — which it
|
|
725
|
+
// resets to 0 at each pan entry — is measured from where the sheet
|
|
726
|
+
// actually is now, not from where the gesture first grabbed it. without
|
|
727
|
+
// this the sheet would jump to a stale origin on a scroll→pan handoff.
|
|
728
|
+
scrollBridge.startPanDrag = () => {
|
|
729
|
+
isExternalDrag = true
|
|
730
|
+
grant()
|
|
731
|
+
}
|
|
732
|
+
|
|
560
733
|
scrollBridge.drag = (dy) => {
|
|
561
734
|
if (!isExternalDrag) {
|
|
562
735
|
isExternalDrag = true
|
|
563
736
|
grant()
|
|
564
737
|
}
|
|
565
738
|
const to = dy + startY
|
|
566
|
-
|
|
739
|
+
setAnimatedPositionDirect(resisted(to, minY))
|
|
567
740
|
}
|
|
568
741
|
|
|
569
742
|
scrollBridge.release = release
|
|
@@ -579,6 +752,10 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
579
752
|
|
|
580
753
|
return PanResponder.create({
|
|
581
754
|
onMoveShouldSetPanResponder: onMoveShouldSet,
|
|
755
|
+
// once we own the drag, don't yield it to another responder
|
|
756
|
+
// (re-renders during the drag were cooperatively terminating it under
|
|
757
|
+
// load, killing the gesture mid-drag)
|
|
758
|
+
onPanResponderTerminationRequest: () => false,
|
|
582
759
|
onPanResponderGrant: grant,
|
|
583
760
|
onPanResponderMove: (_e, { dy }) => {
|
|
584
761
|
const toFull = dy + startY
|
|
@@ -592,19 +769,31 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
592
769
|
scrollBridge.setParentDragging(true)
|
|
593
770
|
}
|
|
594
771
|
|
|
595
|
-
|
|
772
|
+
setAnimatedPositionDirect(to)
|
|
596
773
|
},
|
|
597
774
|
onPanResponderEnd: finish,
|
|
598
775
|
onPanResponderTerminate: finish,
|
|
599
776
|
onPanResponderRelease: finish,
|
|
600
777
|
})
|
|
601
|
-
}, [
|
|
778
|
+
}, [
|
|
779
|
+
disableDrag,
|
|
780
|
+
isShowingInnerSheet,
|
|
781
|
+
animateTo,
|
|
782
|
+
frameSize,
|
|
783
|
+
activePositions,
|
|
784
|
+
setPosition,
|
|
785
|
+
dismissOnSnapToBottom,
|
|
786
|
+
snapPointsMode,
|
|
787
|
+
])
|
|
602
788
|
|
|
603
|
-
// animate to keyboard-adjusted position when keyboard state
|
|
789
|
+
// animate to the current keyboard-adjusted position when the keyboard state
|
|
790
|
+
// changes. activePositions shift the frame up by keyboardHeight, capped at
|
|
791
|
+
// the safe-area top; tall web fit sheets keep a frozen height and gain scroll
|
|
792
|
+
// padding for whatever tail remains behind the keyboard.
|
|
604
793
|
React.useEffect(() => {
|
|
605
794
|
if (isDragging || isHidden || !open || disableAnimation) return
|
|
606
795
|
if (!frameSize || !screenSize) return
|
|
607
|
-
//
|
|
796
|
+
// timing animation matches the iOS keyboard animation (~250ms)
|
|
608
797
|
animateTo(position, { type: 'timing', duration: 250 })
|
|
609
798
|
}, [isKeyboardVisible, keyboardHeight])
|
|
610
799
|
|
|
@@ -629,6 +818,11 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
629
818
|
React.useEffect(() => {
|
|
630
819
|
if (!open && isKeyboardVisible) {
|
|
631
820
|
dismissKeyboard()
|
|
821
|
+
// if the sheet was closed mid-drag the keyboard-hide handler was paused
|
|
822
|
+
// and a hide could be left pending — clear it so isKeyboardVisible can't
|
|
823
|
+
// stick true after the sheet is gone. (no-op for a normal close.)
|
|
824
|
+
pauseKeyboardHandler.current = false
|
|
825
|
+
flushPendingHide()
|
|
632
826
|
}
|
|
633
827
|
}, [open])
|
|
634
828
|
|
|
@@ -641,43 +835,72 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
641
835
|
stopSpring,
|
|
642
836
|
scrollBridge,
|
|
643
837
|
setIsDragging,
|
|
644
|
-
getCurrentPosition:
|
|
838
|
+
getCurrentPosition: getAnimatedPosition,
|
|
645
839
|
resisted,
|
|
646
840
|
disableDrag,
|
|
647
841
|
isShowingInnerSheet,
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
842
|
+
// directly set the animated value for smooth dragging
|
|
843
|
+
setAnimatedPosition: setAnimatedPositionDirect,
|
|
844
|
+
dismissOnSnapToBottom,
|
|
845
|
+
snapPointsMode,
|
|
846
|
+
isKeyboardVisible,
|
|
653
847
|
pauseKeyboardHandler,
|
|
654
848
|
})
|
|
655
849
|
|
|
850
|
+
// ignore any layout measured while the soft keyboard is up (web +
|
|
851
|
+
// moveOnKeyboardChange): the visual viewport (which RN's web layout follows)
|
|
852
|
+
// shrinks, so the height would be the collapsed sheet — keeping it would
|
|
853
|
+
// recompute the fit anchor and fly the frame. a LIVE DOM check, NOT the
|
|
854
|
+
// isKeyboardVisible React state, is required: the state lags the resize, so
|
|
855
|
+
// the first shrunk onLayout lands before the flag flips. holding the measured
|
|
856
|
+
// size keeps the fit geometry stable while the frame translates with the kb.
|
|
857
|
+
const ignoreLayoutForKeyboard = useEvent(
|
|
858
|
+
() =>
|
|
859
|
+
isWeb &&
|
|
860
|
+
moveOnKeyboardChange &&
|
|
861
|
+
getWebKeyboardResizeHeight() >= MIN_KEYBOARD_HEIGHT
|
|
862
|
+
)
|
|
863
|
+
|
|
656
864
|
const handleAnimationViewLayout = useEvent((e: LayoutChangeEvent) => {
|
|
657
865
|
// don't update frameSize during exit animation to prevent position jumps
|
|
658
866
|
if (!open && stableFrameSize.current !== 0) {
|
|
659
867
|
return
|
|
660
868
|
}
|
|
661
869
|
|
|
870
|
+
const layoutHeight = e.nativeEvent?.layout.height
|
|
871
|
+
// drop a layout measured while the keyboard is up: on older iOS the web
|
|
872
|
+
// viewport shrinks and the frame would resize. keep the pre-keyboard frame
|
|
873
|
+
// height so the web frame translates without resizing.
|
|
874
|
+
// exception: if we have no frame height yet (sheet opened with the keyboard
|
|
875
|
+
// already up), accept it so the sheet can appear at all.
|
|
876
|
+
if (ignoreLayoutForKeyboard() && frameSize > 0) return
|
|
877
|
+
|
|
662
878
|
// avoid bugs where it grows forever for whatever reason
|
|
663
879
|
// For inline mode (non-modal), don't cap at window height - use actual layout
|
|
664
|
-
const layoutHeight = e.nativeEvent?.layout.height
|
|
665
880
|
const next = modal
|
|
666
|
-
? Math.min(layoutHeight,
|
|
881
|
+
? Math.min(layoutHeight, getStableViewportHeight())
|
|
667
882
|
: layoutHeight
|
|
668
883
|
if (!next) return
|
|
669
|
-
|
|
884
|
+
// round: web onLayout reports sub-pixel heights (e.g. 499.99996) that jitter
|
|
885
|
+
// frame to frame as the view transforms; the raw float would re-fire every
|
|
886
|
+
// effect that depends on frameSize on each drag move.
|
|
887
|
+
setFrameSize(Math.round(next))
|
|
670
888
|
})
|
|
671
889
|
|
|
672
|
-
const handleMaxContentViewLayout = React.useCallback(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
890
|
+
const handleMaxContentViewLayout = React.useCallback(
|
|
891
|
+
(e: LayoutChangeEvent) => {
|
|
892
|
+
// keep maxContentSize at the full pre-keyboard viewport: drop layouts
|
|
893
|
+
// measured while the keyboard is up (the shrunk viewport), unless we have
|
|
894
|
+
// none yet (keyboard-already-up open).
|
|
895
|
+
if (ignoreLayoutForKeyboard() && screenSize > 0) return
|
|
896
|
+
// avoid bugs where it grows forever for whatever reason
|
|
897
|
+
const next = Math.min(e.nativeEvent?.layout.height, getStableViewportHeight())
|
|
898
|
+
if (!next) return
|
|
899
|
+
// round to avoid sub-pixel churn re-firing size-dependent effects
|
|
900
|
+
setMaxContentSize(Math.round(next))
|
|
901
|
+
},
|
|
902
|
+
[ignoreLayoutForKeyboard, screenSize]
|
|
903
|
+
)
|
|
681
904
|
|
|
682
905
|
const getAnimatedNumberStyle = React.useCallback(
|
|
683
906
|
(val: number) => {
|
|
@@ -724,7 +947,13 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
724
947
|
let contents = (
|
|
725
948
|
<LayoutMeasurementController disable={!open}>
|
|
726
949
|
<ParentSheetContext.Provider value={nextParentContext}>
|
|
727
|
-
<SheetProvider
|
|
950
|
+
<SheetProvider
|
|
951
|
+
{...providerProps}
|
|
952
|
+
keyboardOccludedHeight={keyboardOccludedHeight}
|
|
953
|
+
isKeyboardVisible={isKeyboardVisible}
|
|
954
|
+
keyboardStableFrameHeight={keyboardStableFrameHeight}
|
|
955
|
+
setHasScrollView={setHasScrollView}
|
|
956
|
+
>
|
|
728
957
|
<GestureSheetProvider
|
|
729
958
|
isDragging={isDragging}
|
|
730
959
|
blockPan={blockPan}
|
|
@@ -766,7 +995,11 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
766
995
|
width: '100%',
|
|
767
996
|
height: forcedContentHeight,
|
|
768
997
|
minHeight: forcedContentHeight,
|
|
769
|
-
opacity:
|
|
998
|
+
opacity: shouldHideParentSheet
|
|
999
|
+
? 0
|
|
1000
|
+
: disableTransparencyHide
|
|
1001
|
+
? 1
|
|
1002
|
+
: opacity,
|
|
770
1003
|
...((shouldHideParentSheet || !open) && {
|
|
771
1004
|
pointerEvents: 'none',
|
|
772
1005
|
}),
|
|
@@ -774,7 +1007,7 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
774
1007
|
animatedStyle,
|
|
775
1008
|
]}
|
|
776
1009
|
>
|
|
777
|
-
{/* wrap children with plain RN View for panResponder -
|
|
1010
|
+
{/* wrap children with plain RN View for panResponder - hanzogui views no longer handle responder events on web */}
|
|
778
1011
|
{gestureHandlerEnabled && panGesture ? (
|
|
779
1012
|
<GestureDetectorWrapper gesture={panGesture} style={{ flex: 1 }}>
|
|
780
1013
|
{props.children}
|
|
@@ -799,8 +1032,13 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
799
1032
|
const adaptContext = useAdaptContext()
|
|
800
1033
|
contents = (
|
|
801
1034
|
<ProvideAdaptContext {...adaptContext}>
|
|
802
|
-
{/*
|
|
803
|
-
|
|
1035
|
+
{/* re-propagate safe-area insets across the teleport: the sheet content
|
|
1036
|
+
renders at the portal host, OUTSIDE the app's SafeAreaProvider, so
|
|
1037
|
+
without this useSafeAreaInsets() inside the sheet reads 0. */}
|
|
1038
|
+
<SafeAreaInsetsContext.Provider value={safeAreaInsets}>
|
|
1039
|
+
{/* @ts-ignore */}
|
|
1040
|
+
{contents}
|
|
1041
|
+
</SafeAreaInsetsContext.Provider>
|
|
804
1042
|
</ProvideAdaptContext>
|
|
805
1043
|
)
|
|
806
1044
|
}
|
|
@@ -809,9 +1047,31 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
809
1047
|
const shouldMountChildren = unmountChildrenWhenHidden ? !!opacity : true
|
|
810
1048
|
|
|
811
1049
|
if (modal) {
|
|
1050
|
+
// a modal sheet is teleported through <Portal> to the root portal host.
|
|
1051
|
+
// that host is mounted by GuiProvider, which may sit ABOVE the app's
|
|
1052
|
+
// GestureHandlerRootView - so the teleported content lands outside any
|
|
1053
|
+
// rngh root and every gesture inside the sheet (the drag pan, pressables
|
|
1054
|
+
// on the rngh press path) silently goes dead. re-establish an rngh root
|
|
1055
|
+
// around the teleported content so it works regardless of where the app
|
|
1056
|
+
// mounts GestureHandlerRootView.
|
|
1057
|
+
//
|
|
1058
|
+
// the root stays mounted and full-width whenever the sheet content is
|
|
1059
|
+
// (so child layout/measurement/close-animation are unaffected) and only
|
|
1060
|
+
// collapses to 0 height while closed so it occupies no hit area - see
|
|
1061
|
+
// rnghRootStyleOpen/Closed above for why pointerEvents can't be used.
|
|
1062
|
+
const RNGHRoot = getGestureHandlerState().RootView
|
|
1063
|
+
const mountedContents = shouldMountChildren ? (
|
|
1064
|
+
<ContainerComponent>{contents}</ContainerComponent>
|
|
1065
|
+
) : null
|
|
812
1066
|
const modalContents = (
|
|
813
1067
|
<Portal stackZIndex={zIndex} {...portalProps}>
|
|
814
|
-
{
|
|
1068
|
+
{mountedContents && RNGHRoot ? (
|
|
1069
|
+
<RNGHRoot style={open ? rnghRootStyleOpen : rnghRootStyleClosed}>
|
|
1070
|
+
{mountedContents}
|
|
1071
|
+
</RNGHRoot>
|
|
1072
|
+
) : (
|
|
1073
|
+
mountedContents
|
|
1074
|
+
)}
|
|
815
1075
|
</Portal>
|
|
816
1076
|
)
|
|
817
1077
|
|