@hanzogui/sheet 2.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/LICENSE +21 -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.cjs +48 -0
- package/dist/cjs/GestureDetectorWrapper.native.js +53 -0
- package/dist/cjs/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/cjs/GestureSheetContext.cjs +52 -0
- package/dist/cjs/GestureSheetContext.native.js +56 -0
- package/dist/cjs/GestureSheetContext.native.js.map +1 -0
- package/dist/cjs/Sheet.cjs +117 -0
- package/dist/cjs/Sheet.native.js +120 -0
- package/dist/cjs/Sheet.native.js.map +1 -0
- package/dist/cjs/SheetContext.cjs +32 -0
- package/dist/cjs/SheetContext.native.js +35 -0
- package/dist/cjs/SheetContext.native.js.map +1 -0
- package/dist/cjs/SheetController.cjs +65 -0
- package/dist/cjs/SheetController.native.js +71 -0
- package/dist/cjs/SheetController.native.js.map +1 -0
- package/dist/cjs/SheetImplementationCustom.cjs +472 -0
- package/dist/cjs/SheetImplementationCustom.native.js +524 -0
- package/dist/cjs/SheetImplementationCustom.native.js.map +1 -0
- package/dist/cjs/SheetScrollView.cjs +180 -0
- package/dist/cjs/SheetScrollView.native.js +197 -0
- package/dist/cjs/SheetScrollView.native.js.map +1 -0
- package/dist/cjs/constants.cjs +32 -0
- package/dist/cjs/constants.native.js +35 -0
- package/dist/cjs/constants.native.js.map +1 -0
- package/dist/cjs/contexts.cjs +42 -0
- package/dist/cjs/contexts.native.js +45 -0
- package/dist/cjs/contexts.native.js.map +1 -0
- package/dist/cjs/controller.cjs +29 -0
- package/dist/cjs/controller.native.js +32 -0
- package/dist/cjs/controller.native.js.map +1 -0
- package/dist/cjs/createSheet.cjs +191 -0
- package/dist/cjs/createSheet.native.js +208 -0
- package/dist/cjs/createSheet.native.js.map +1 -0
- package/dist/cjs/gestureState.cjs +39 -0
- package/dist/cjs/gestureState.native.js +42 -0
- package/dist/cjs/gestureState.native.js.map +1 -0
- package/dist/cjs/helpers.cjs +31 -0
- package/dist/cjs/helpers.native.js +35 -0
- package/dist/cjs/helpers.native.js.map +1 -0
- package/dist/cjs/index.cjs +28 -0
- package/dist/cjs/index.native.js +31 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/nativeSheet.cjs +87 -0
- package/dist/cjs/nativeSheet.native.js +101 -0
- package/dist/cjs/nativeSheet.native.js.map +1 -0
- package/dist/cjs/setupGestureHandler.cjs +46 -0
- package/dist/cjs/setupGestureHandler.native.js +50 -0
- package/dist/cjs/setupGestureHandler.native.js.map +1 -0
- package/dist/cjs/types.cjs +16 -0
- package/dist/cjs/types.native.js +19 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/useGestureHandlerPan.cjs +136 -0
- package/dist/cjs/useGestureHandlerPan.native.js +145 -0
- package/dist/cjs/useGestureHandlerPan.native.js.map +1 -0
- package/dist/cjs/useKeyboardControllerSheet.cjs +38 -0
- package/dist/cjs/useKeyboardControllerSheet.native.js +114 -0
- package/dist/cjs/useKeyboardControllerSheet.native.js.map +1 -0
- package/dist/cjs/useSheet.cjs +27 -0
- package/dist/cjs/useSheet.native.js +32 -0
- package/dist/cjs/useSheet.native.js.map +1 -0
- package/dist/cjs/useSheetController.cjs +50 -0
- package/dist/cjs/useSheetController.native.js +53 -0
- package/dist/cjs/useSheetController.native.js.map +1 -0
- package/dist/cjs/useSheetOffscreenSize.cjs +51 -0
- package/dist/cjs/useSheetOffscreenSize.native.js +61 -0
- package/dist/cjs/useSheetOffscreenSize.native.js.map +1 -0
- package/dist/cjs/useSheetOpenState.cjs +49 -0
- package/dist/cjs/useSheetOpenState.native.js +56 -0
- package/dist/cjs/useSheetOpenState.native.js.map +1 -0
- package/dist/cjs/useSheetProviderProps.cjs +133 -0
- package/dist/cjs/useSheetProviderProps.native.js +153 -0
- package/dist/cjs/useSheetProviderProps.native.js.map +1 -0
- package/dist/cjs/useSheetScrollViewGestures.cjs +123 -0
- package/dist/cjs/useSheetScrollViewGestures.native.js +110 -0
- package/dist/cjs/useSheetScrollViewGestures.native.js.map +1 -0
- package/dist/esm/GestureDetectorWrapper.mjs +25 -0
- package/dist/esm/GestureDetectorWrapper.mjs.map +1 -0
- package/dist/esm/GestureDetectorWrapper.native.js +27 -0
- package/dist/esm/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/esm/GestureSheetContext.mjs +28 -0
- package/dist/esm/GestureSheetContext.mjs.map +1 -0
- package/dist/esm/GestureSheetContext.native.js +29 -0
- package/dist/esm/GestureSheetContext.native.js.map +1 -0
- package/dist/esm/Sheet.mjs +89 -0
- package/dist/esm/Sheet.mjs.map +1 -0
- package/dist/esm/Sheet.native.js +89 -0
- package/dist/esm/Sheet.native.js.map +1 -0
- package/dist/esm/SheetContext.mjs +6 -0
- package/dist/esm/SheetContext.mjs.map +1 -0
- package/dist/esm/SheetContext.native.js +6 -0
- package/dist/esm/SheetContext.native.js.map +1 -0
- package/dist/esm/SheetController.mjs +31 -0
- package/dist/esm/SheetController.mjs.map +1 -0
- package/dist/esm/SheetController.native.js +34 -0
- package/dist/esm/SheetController.native.js.map +1 -0
- package/dist/esm/SheetImplementationCustom.mjs +437 -0
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -0
- package/dist/esm/SheetImplementationCustom.native.js +487 -0
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -0
- package/dist/esm/SheetScrollView.mjs +146 -0
- package/dist/esm/SheetScrollView.mjs.map +1 -0
- package/dist/esm/SheetScrollView.native.js +160 -0
- package/dist/esm/SheetScrollView.native.js.map +1 -0
- package/dist/esm/constants.mjs +6 -0
- package/dist/esm/constants.mjs.map +1 -0
- package/dist/esm/constants.native.js +6 -0
- package/dist/esm/constants.native.js.map +1 -0
- package/dist/esm/contexts.mjs +7 -0
- package/dist/esm/contexts.mjs.map +1 -0
- package/dist/esm/contexts.native.js +7 -0
- package/dist/esm/contexts.native.js.map +1 -0
- package/dist/esm/controller.mjs +4 -0
- package/dist/esm/controller.mjs.map +1 -0
- package/dist/esm/controller.native.js +4 -0
- package/dist/esm/controller.native.js.map +1 -0
- package/dist/esm/createSheet.mjs +168 -0
- package/dist/esm/createSheet.mjs.map +1 -0
- package/dist/esm/createSheet.native.js +182 -0
- package/dist/esm/createSheet.native.js.map +1 -0
- package/dist/esm/gestureState.mjs +13 -0
- package/dist/esm/gestureState.mjs.map +1 -0
- package/dist/esm/gestureState.native.js +13 -0
- package/dist/esm/gestureState.native.js.map +1 -0
- package/dist/esm/helpers.mjs +8 -0
- package/dist/esm/helpers.mjs.map +1 -0
- package/dist/esm/helpers.native.js +9 -0
- package/dist/esm/helpers.native.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +12 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +12 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/nativeSheet.mjs +63 -0
- package/dist/esm/nativeSheet.mjs.map +1 -0
- package/dist/esm/nativeSheet.native.js +74 -0
- package/dist/esm/nativeSheet.native.js.map +1 -0
- package/dist/esm/setupGestureHandler.mjs +22 -0
- package/dist/esm/setupGestureHandler.mjs.map +1 -0
- package/dist/esm/setupGestureHandler.native.js +23 -0
- package/dist/esm/setupGestureHandler.native.js.map +1 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/useGestureHandlerPan.mjs +113 -0
- package/dist/esm/useGestureHandlerPan.mjs.map +1 -0
- package/dist/esm/useGestureHandlerPan.native.js +119 -0
- package/dist/esm/useGestureHandlerPan.native.js.map +1 -0
- package/dist/esm/useKeyboardControllerSheet.mjs +15 -0
- package/dist/esm/useKeyboardControllerSheet.mjs.map +1 -0
- package/dist/esm/useKeyboardControllerSheet.native.js +88 -0
- package/dist/esm/useKeyboardControllerSheet.native.js.map +1 -0
- package/dist/esm/useSheet.mjs +4 -0
- package/dist/esm/useSheet.mjs.map +1 -0
- package/dist/esm/useSheet.native.js +6 -0
- package/dist/esm/useSheet.native.js.map +1 -0
- package/dist/esm/useSheetController.mjs +15 -0
- package/dist/esm/useSheetController.mjs.map +1 -0
- package/dist/esm/useSheetController.native.js +15 -0
- package/dist/esm/useSheetController.native.js.map +1 -0
- package/dist/esm/useSheetOffscreenSize.mjs +28 -0
- package/dist/esm/useSheetOffscreenSize.mjs.map +1 -0
- package/dist/esm/useSheetOffscreenSize.native.js +35 -0
- package/dist/esm/useSheetOffscreenSize.native.js.map +1 -0
- package/dist/esm/useSheetOpenState.mjs +26 -0
- package/dist/esm/useSheetOpenState.mjs.map +1 -0
- package/dist/esm/useSheetOpenState.native.js +30 -0
- package/dist/esm/useSheetOpenState.native.js.map +1 -0
- package/dist/esm/useSheetProviderProps.mjs +99 -0
- package/dist/esm/useSheetProviderProps.mjs.map +1 -0
- package/dist/esm/useSheetProviderProps.native.js +116 -0
- package/dist/esm/useSheetProviderProps.native.js.map +1 -0
- package/dist/esm/useSheetScrollViewGestures.mjs +100 -0
- package/dist/esm/useSheetScrollViewGestures.mjs.map +1 -0
- package/dist/esm/useSheetScrollViewGestures.native.js +84 -0
- package/dist/esm/useSheetScrollViewGestures.native.js.map +1 -0
- package/dist/jsx/GestureDetectorWrapper.mjs +25 -0
- package/dist/jsx/GestureDetectorWrapper.mjs.map +1 -0
- package/dist/jsx/GestureDetectorWrapper.native.js +53 -0
- package/dist/jsx/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/jsx/GestureSheetContext.mjs +28 -0
- package/dist/jsx/GestureSheetContext.mjs.map +1 -0
- package/dist/jsx/GestureSheetContext.native.js +56 -0
- package/dist/jsx/GestureSheetContext.native.js.map +1 -0
- package/dist/jsx/Sheet.mjs +89 -0
- package/dist/jsx/Sheet.mjs.map +1 -0
- package/dist/jsx/Sheet.native.js +120 -0
- package/dist/jsx/Sheet.native.js.map +1 -0
- package/dist/jsx/SheetContext.mjs +6 -0
- package/dist/jsx/SheetContext.mjs.map +1 -0
- package/dist/jsx/SheetContext.native.js +35 -0
- package/dist/jsx/SheetContext.native.js.map +1 -0
- package/dist/jsx/SheetController.mjs +31 -0
- package/dist/jsx/SheetController.mjs.map +1 -0
- package/dist/jsx/SheetController.native.js +71 -0
- package/dist/jsx/SheetController.native.js.map +1 -0
- package/dist/jsx/SheetImplementationCustom.mjs +437 -0
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -0
- package/dist/jsx/SheetImplementationCustom.native.js +524 -0
- package/dist/jsx/SheetImplementationCustom.native.js.map +1 -0
- package/dist/jsx/SheetScrollView.mjs +146 -0
- package/dist/jsx/SheetScrollView.mjs.map +1 -0
- package/dist/jsx/SheetScrollView.native.js +197 -0
- package/dist/jsx/SheetScrollView.native.js.map +1 -0
- package/dist/jsx/constants.mjs +6 -0
- package/dist/jsx/constants.mjs.map +1 -0
- package/dist/jsx/constants.native.js +35 -0
- package/dist/jsx/constants.native.js.map +1 -0
- package/dist/jsx/contexts.mjs +7 -0
- package/dist/jsx/contexts.mjs.map +1 -0
- package/dist/jsx/contexts.native.js +45 -0
- package/dist/jsx/contexts.native.js.map +1 -0
- package/dist/jsx/controller.mjs +4 -0
- package/dist/jsx/controller.mjs.map +1 -0
- package/dist/jsx/controller.native.js +32 -0
- package/dist/jsx/controller.native.js.map +1 -0
- package/dist/jsx/createSheet.mjs +168 -0
- package/dist/jsx/createSheet.mjs.map +1 -0
- package/dist/jsx/createSheet.native.js +208 -0
- package/dist/jsx/createSheet.native.js.map +1 -0
- package/dist/jsx/gestureState.mjs +13 -0
- package/dist/jsx/gestureState.mjs.map +1 -0
- package/dist/jsx/gestureState.native.js +42 -0
- package/dist/jsx/gestureState.native.js.map +1 -0
- package/dist/jsx/helpers.mjs +8 -0
- package/dist/jsx/helpers.mjs.map +1 -0
- package/dist/jsx/helpers.native.js +35 -0
- package/dist/jsx/helpers.native.js.map +1 -0
- package/dist/jsx/index.js +12 -0
- package/dist/jsx/index.js.map +1 -0
- package/dist/jsx/index.mjs +12 -0
- package/dist/jsx/index.mjs.map +1 -0
- package/dist/jsx/index.native.js +31 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/dist/jsx/nativeSheet.mjs +63 -0
- package/dist/jsx/nativeSheet.mjs.map +1 -0
- package/dist/jsx/nativeSheet.native.js +101 -0
- package/dist/jsx/nativeSheet.native.js.map +1 -0
- package/dist/jsx/setupGestureHandler.mjs +22 -0
- package/dist/jsx/setupGestureHandler.mjs.map +1 -0
- package/dist/jsx/setupGestureHandler.native.js +50 -0
- package/dist/jsx/setupGestureHandler.native.js.map +1 -0
- package/dist/jsx/types.mjs +2 -0
- package/dist/jsx/types.mjs.map +1 -0
- package/dist/jsx/types.native.js +19 -0
- package/dist/jsx/types.native.js.map +1 -0
- package/dist/jsx/useGestureHandlerPan.mjs +113 -0
- package/dist/jsx/useGestureHandlerPan.mjs.map +1 -0
- package/dist/jsx/useGestureHandlerPan.native.js +145 -0
- package/dist/jsx/useGestureHandlerPan.native.js.map +1 -0
- package/dist/jsx/useKeyboardControllerSheet.mjs +15 -0
- package/dist/jsx/useKeyboardControllerSheet.mjs.map +1 -0
- package/dist/jsx/useKeyboardControllerSheet.native.js +114 -0
- package/dist/jsx/useKeyboardControllerSheet.native.js.map +1 -0
- package/dist/jsx/useSheet.mjs +4 -0
- package/dist/jsx/useSheet.mjs.map +1 -0
- package/dist/jsx/useSheet.native.js +32 -0
- package/dist/jsx/useSheet.native.js.map +1 -0
- package/dist/jsx/useSheetController.mjs +15 -0
- package/dist/jsx/useSheetController.mjs.map +1 -0
- package/dist/jsx/useSheetController.native.js +53 -0
- package/dist/jsx/useSheetController.native.js.map +1 -0
- package/dist/jsx/useSheetOffscreenSize.mjs +28 -0
- package/dist/jsx/useSheetOffscreenSize.mjs.map +1 -0
- package/dist/jsx/useSheetOffscreenSize.native.js +61 -0
- package/dist/jsx/useSheetOffscreenSize.native.js.map +1 -0
- package/dist/jsx/useSheetOpenState.mjs +26 -0
- package/dist/jsx/useSheetOpenState.mjs.map +1 -0
- package/dist/jsx/useSheetOpenState.native.js +56 -0
- package/dist/jsx/useSheetOpenState.native.js.map +1 -0
- package/dist/jsx/useSheetProviderProps.mjs +99 -0
- package/dist/jsx/useSheetProviderProps.mjs.map +1 -0
- package/dist/jsx/useSheetProviderProps.native.js +153 -0
- package/dist/jsx/useSheetProviderProps.native.js.map +1 -0
- package/dist/jsx/useSheetScrollViewGestures.mjs +100 -0
- package/dist/jsx/useSheetScrollViewGestures.mjs.map +1 -0
- package/dist/jsx/useSheetScrollViewGestures.native.js +110 -0
- package/dist/jsx/useSheetScrollViewGestures.native.js.map +1 -0
- package/next.md +78 -0
- package/package.json +96 -0
- 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/setup-gesture-handler.cjs +1 -0
- package/src/GestureDetectorWrapper.tsx +41 -0
- package/src/GestureSheetContext.tsx +62 -0
- package/src/Sheet.tsx +114 -0
- package/src/SheetContext.tsx +11 -0
- package/src/SheetController.tsx +44 -0
- package/src/SheetImplementationCustom.tsx +868 -0
- package/src/SheetScrollView.tsx +195 -0
- package/src/constants.tsx +5 -0
- package/src/contexts.ts +9 -0
- package/src/controller.ts +9 -0
- package/src/createSheet.tsx +296 -0
- package/src/gestureState.ts +24 -0
- package/src/helpers.tsx +31 -0
- package/src/index.ts +11 -0
- package/src/nativeSheet.tsx +89 -0
- package/src/setupGestureHandler.ts +34 -0
- package/src/types.tsx +181 -0
- package/src/useGestureHandlerPan.tsx +364 -0
- package/src/useKeyboardControllerSheet.native.ts +136 -0
- package/src/useKeyboardControllerSheet.ts +26 -0
- package/src/useSheet.tsx +3 -0
- package/src/useSheetController.tsx +27 -0
- package/src/useSheetOffscreenSize.tsx +49 -0
- package/src/useSheetOpenState.tsx +33 -0
- package/src/useSheetProviderProps.tsx +223 -0
- package/src/useSheetScrollViewGestures.native.ts +163 -0
- package/src/useSheetScrollViewGestures.ts +194 -0
- package/tsconfig.json +56 -0
- package/types/GestureDetectorWrapper.d.ts +14 -0
- package/types/GestureSheetContext.d.ts +36 -0
- package/types/Sheet.d.ts +183 -0
- package/types/SheetContext.d.ts +43 -0
- package/types/SheetController.d.ts +6 -0
- package/types/SheetImplementationCustom.d.ts +36 -0
- package/types/SheetScrollView.d.ts +40 -0
- package/types/constants.d.ts +5 -0
- package/types/contexts.d.ts +6 -0
- package/types/controller.d.ts +3 -0
- package/types/createSheet.d.ts +194 -0
- package/types/gestureState.d.ts +11 -0
- package/types/helpers.d.ts +2 -0
- package/types/index.d.ts +12 -0
- package/types/nativeSheet.d.ts +10 -0
- package/types/setupGestureHandler.d.ts +11 -0
- package/types/types.d.ts +147 -0
- package/types/useGestureHandlerPan.d.ts +44 -0
- package/types/useKeyboardControllerSheet.d.ts +7 -0
- package/types/useKeyboardControllerSheet.native.d.ts +13 -0
- package/types/useSheet.d.ts +2 -0
- package/types/useSheetController.d.ts +17 -0
- package/types/useSheetOffscreenSize.d.ts +3 -0
- package/types/useSheetOpenState.d.ts +9 -0
- package/types/useSheetProviderProps.d.ts +36 -0
- package/types/useSheetScrollViewGestures.d.ts +12 -0
- package/types/useSheetScrollViewGestures.native.d.ts +21 -0
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
import { ProvideAdaptContext, useAdaptContext } from '@hanzogui/adapt'
|
|
2
|
+
import { AnimatePresence } from '@hanzogui/animate-presence'
|
|
3
|
+
import { useComposedRefs } from '@hanzogui/compose-refs'
|
|
4
|
+
import { isWeb, useIsomorphicLayoutEffect } from '@hanzogui/constants'
|
|
5
|
+
import {
|
|
6
|
+
LayoutMeasurementController,
|
|
7
|
+
View as GuiView,
|
|
8
|
+
useConfiguration,
|
|
9
|
+
useDidFinishSSR,
|
|
10
|
+
useEvent,
|
|
11
|
+
useThemeName,
|
|
12
|
+
} from '@hanzogui/core'
|
|
13
|
+
import { getSafeArea } from '@hanzogui/native'
|
|
14
|
+
import { needsPortalRepropagation, Portal } from '@hanzogui/portal'
|
|
15
|
+
import React, { useState } from 'react'
|
|
16
|
+
import type {
|
|
17
|
+
Animated,
|
|
18
|
+
GestureResponderEvent,
|
|
19
|
+
LayoutChangeEvent,
|
|
20
|
+
PanResponderGestureState,
|
|
21
|
+
} from 'react-native'
|
|
22
|
+
import { Dimensions, PanResponder, View } from 'react-native'
|
|
23
|
+
import { ParentSheetContext, SheetInsideSheetContext } from './contexts'
|
|
24
|
+
import { GestureDetectorWrapper } from './GestureDetectorWrapper'
|
|
25
|
+
import { GestureSheetProvider } from './GestureSheetContext'
|
|
26
|
+
import { resisted } from './helpers'
|
|
27
|
+
import { SheetProvider } from './SheetContext'
|
|
28
|
+
import type { SheetProps, SnapPointsMode } from './types'
|
|
29
|
+
import { useGestureHandlerPan } from './useGestureHandlerPan'
|
|
30
|
+
import { useKeyboardControllerSheet } from './useKeyboardControllerSheet'
|
|
31
|
+
import { useSheetOpenState } from './useSheetOpenState'
|
|
32
|
+
import { useSheetProviderProps } from './useSheetProviderProps'
|
|
33
|
+
|
|
34
|
+
const hiddenSize = 10_000.1
|
|
35
|
+
|
|
36
|
+
// safe area top inset, cached per-session (device-constant value)
|
|
37
|
+
let _cachedSafeAreaTop: number | undefined
|
|
38
|
+
function getSafeAreaTopInset(): number {
|
|
39
|
+
if (_cachedSafeAreaTop !== undefined) return _cachedSafeAreaTop
|
|
40
|
+
// use @hanzogui/native abstraction - returns 0 when not enabled
|
|
41
|
+
_cachedSafeAreaTop = getSafeArea().getInsets().top
|
|
42
|
+
return _cachedSafeAreaTop
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let sheetHiddenStyleSheet: HTMLStyleElement | null = null
|
|
46
|
+
|
|
47
|
+
// on web we are always relative to window, on to screen
|
|
48
|
+
const relativeDimensionTo = isWeb ? 'window' : 'screen'
|
|
49
|
+
|
|
50
|
+
export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
51
|
+
function SheetImplementationCustom(props, forwardedRef) {
|
|
52
|
+
const parentSheet = React.useContext(ParentSheetContext)
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
transition,
|
|
56
|
+
transitionConfig: transitionConfigProp,
|
|
57
|
+
modal = false,
|
|
58
|
+
zIndex = parentSheet.zIndex + 1,
|
|
59
|
+
moveOnKeyboardChange = false,
|
|
60
|
+
unmountChildrenWhenHidden = false,
|
|
61
|
+
portalProps,
|
|
62
|
+
containerComponent: ContainerComponent = React.Fragment,
|
|
63
|
+
onAnimationComplete,
|
|
64
|
+
} = props
|
|
65
|
+
|
|
66
|
+
const state = useSheetOpenState(props)
|
|
67
|
+
const [overlayComponent, setOverlayComponent] = React.useState<React.ReactNode>(null)
|
|
68
|
+
|
|
69
|
+
const providerProps = useSheetProviderProps(props, state, {
|
|
70
|
+
onOverlayComponent: setOverlayComponent,
|
|
71
|
+
})
|
|
72
|
+
const {
|
|
73
|
+
frameSize,
|
|
74
|
+
setFrameSize,
|
|
75
|
+
snapPoints,
|
|
76
|
+
snapPointsMode,
|
|
77
|
+
hasFit,
|
|
78
|
+
position,
|
|
79
|
+
setPosition,
|
|
80
|
+
scrollBridge,
|
|
81
|
+
screenSize,
|
|
82
|
+
setMaxContentSize,
|
|
83
|
+
maxSnapPoint,
|
|
84
|
+
} = providerProps
|
|
85
|
+
const { open, controller, isHidden } = state
|
|
86
|
+
const openRef = React.useRef(open)
|
|
87
|
+
openRef.current = open
|
|
88
|
+
|
|
89
|
+
const sheetRef = React.useRef<View>(undefined as unknown as View)
|
|
90
|
+
const ref = useComposedRefs(forwardedRef, sheetRef, providerProps.contentRef as any)
|
|
91
|
+
|
|
92
|
+
// TODO this can be extracted into a helper getAnimationConfig(animationProp as array | string)
|
|
93
|
+
const { animationDriver } = useConfiguration()
|
|
94
|
+
|
|
95
|
+
if (!animationDriver) {
|
|
96
|
+
throw new Error(`Sheet requires an animation driver to be set`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const transitionConfig = (() => {
|
|
100
|
+
// explicit transitionConfig prop always takes precedence
|
|
101
|
+
if (transitionConfigProp) {
|
|
102
|
+
return transitionConfigProp
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const [animationProp, animationPropConfig] = !transition
|
|
106
|
+
? []
|
|
107
|
+
: Array.isArray(transition)
|
|
108
|
+
? transition
|
|
109
|
+
: ([transition] as const)
|
|
110
|
+
|
|
111
|
+
// look up named animation config from driver if available
|
|
112
|
+
if (animationProp && animationDriver.animations?.[animationProp as string]) {
|
|
113
|
+
return {
|
|
114
|
+
...animationDriver.animations[animationProp as string],
|
|
115
|
+
...animationPropConfig,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null
|
|
120
|
+
})()
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* This is a hacky workaround for native:
|
|
124
|
+
*/
|
|
125
|
+
const [isShowingInnerSheet, setIsShowingInnerSheet] = React.useState(false)
|
|
126
|
+
// when using Gorhom portal (no teleport), inner sheets need to hide parent
|
|
127
|
+
const shouldHideParentSheet =
|
|
128
|
+
!isWeb && modal && isShowingInnerSheet && needsPortalRepropagation()
|
|
129
|
+
|
|
130
|
+
const sheetInsideSheet = React.useContext(SheetInsideSheetContext)
|
|
131
|
+
const onInnerSheet = React.useCallback((hasChild: boolean) => {
|
|
132
|
+
setIsShowingInnerSheet(hasChild)
|
|
133
|
+
}, [])
|
|
134
|
+
|
|
135
|
+
// FIX: Store stable frameSize to prevent recalculation during exit animation
|
|
136
|
+
const stableFrameSize = React.useRef(frameSize)
|
|
137
|
+
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
// Only update stable size when sheet is open
|
|
140
|
+
if (open && frameSize) {
|
|
141
|
+
stableFrameSize.current = frameSize
|
|
142
|
+
}
|
|
143
|
+
}, [open, frameSize])
|
|
144
|
+
|
|
145
|
+
// use stableFrameSize when closing to prevent position jumps during exit animation
|
|
146
|
+
// but when opening, always use the current frameSize so positions update correctly
|
|
147
|
+
const effectiveFrameSize = open ? frameSize : stableFrameSize.current || frameSize
|
|
148
|
+
|
|
149
|
+
const positions = React.useMemo(
|
|
150
|
+
() =>
|
|
151
|
+
snapPoints.map((point) =>
|
|
152
|
+
getYPositions(snapPointsMode, point, screenSize, effectiveFrameSize)
|
|
153
|
+
),
|
|
154
|
+
[screenSize, effectiveFrameSize, snapPoints, snapPointsMode]
|
|
155
|
+
)
|
|
156
|
+
|
|
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
|
+
const [isDragging, setIsDragging_] = React.useState(false)
|
|
171
|
+
|
|
172
|
+
// synchronous dragging ref — set BEFORE async state commits.
|
|
173
|
+
// RNGH onBegin fires before keyboard hide event reaches JS,
|
|
174
|
+
// so the ref is true by the time activePositions memo re-evaluates.
|
|
175
|
+
// Also controls pauseKeyboardHandler to freeze keyboard state during drag.
|
|
176
|
+
const isDraggingRef = React.useRef(false)
|
|
177
|
+
const setIsDragging = React.useCallback(
|
|
178
|
+
(val: boolean) => {
|
|
179
|
+
isDraggingRef.current = val
|
|
180
|
+
pauseKeyboardHandler.current = val
|
|
181
|
+
setIsDragging_(val)
|
|
182
|
+
// when drag ends, flush any keyboard hide that was suppressed during drag
|
|
183
|
+
// so isKeyboardVisible/keyboardHeight reflect actual state
|
|
184
|
+
if (!val) {
|
|
185
|
+
flushPendingHide()
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[pauseKeyboardHandler, flushPendingHide]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// keyboard-adjusted positions: shift snap points up by keyboard height
|
|
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).
|
|
196
|
+
//
|
|
197
|
+
// IMPORTANT: frozen during drag to prevent gesture handler recreation.
|
|
198
|
+
// When user drags, TextInput may blur → keyboard dismisses → positions would revert,
|
|
199
|
+
// causing the gesture useMemo to recreate and cancel the active drag.
|
|
200
|
+
// The post-drag reconciliation effect handles animating to correct position after drag ends.
|
|
201
|
+
const activePositionsRef = React.useRef(positions)
|
|
202
|
+
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
|
+
if (isDragging || isDraggingRef.current) return activePositionsRef.current
|
|
207
|
+
|
|
208
|
+
let result: number[]
|
|
209
|
+
if (!isKeyboardVisible || keyboardHeight <= 0) {
|
|
210
|
+
result = positions
|
|
211
|
+
} else {
|
|
212
|
+
const safeAreaTop = isWeb ? 0 : getSafeAreaTopInset()
|
|
213
|
+
result = positions.map((p) => {
|
|
214
|
+
// don't adjust the off-screen/close position (from dismissOnSnapToBottom's 0% snap)
|
|
215
|
+
// — it must stay at screenSize so the user can drag between real snap points
|
|
216
|
+
// without accidentally closing the sheet
|
|
217
|
+
if (screenSize && p >= screenSize) return p
|
|
218
|
+
return Math.max(safeAreaTop, p - keyboardHeight)
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
activePositionsRef.current = result
|
|
222
|
+
return result
|
|
223
|
+
}, [positions, isKeyboardVisible, keyboardHeight, screenSize, isDragging])
|
|
224
|
+
|
|
225
|
+
const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
|
|
226
|
+
animationDriver
|
|
227
|
+
const AnimatedView = (animationDriver.View ?? GuiView) as typeof Animated.View
|
|
228
|
+
|
|
229
|
+
useIsomorphicLayoutEffect(() => {
|
|
230
|
+
if (!(sheetInsideSheet && open)) return
|
|
231
|
+
sheetInsideSheet(true)
|
|
232
|
+
return () => {
|
|
233
|
+
sheetInsideSheet(false)
|
|
234
|
+
}
|
|
235
|
+
}, [sheetInsideSheet, open])
|
|
236
|
+
|
|
237
|
+
const nextParentContext = React.useMemo(
|
|
238
|
+
() => ({
|
|
239
|
+
zIndex,
|
|
240
|
+
}),
|
|
241
|
+
[zIndex]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const isMounted = useDidFinishSSR()
|
|
245
|
+
const startPosition = isMounted && screenSize ? screenSize : hiddenSize
|
|
246
|
+
const animatedNumber = useAnimatedNumber(startPosition)
|
|
247
|
+
const at = React.useRef(startPosition)
|
|
248
|
+
const hasntMeasured = at.current === hiddenSize
|
|
249
|
+
const [disableAnimation, setDisableAnimation] = useState(hasntMeasured)
|
|
250
|
+
|
|
251
|
+
// use skipNextAnimation signal from controller (set when adapt handoff occurs)
|
|
252
|
+
const skipAdaptAnimation = React.useRef(false)
|
|
253
|
+
if (controller?.skipNextAnimation) {
|
|
254
|
+
skipAdaptAnimation.current = true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const hasScrollView = React.useRef(false)
|
|
258
|
+
|
|
259
|
+
// safety fallback timer for sheet close opacity
|
|
260
|
+
const opacityFallbackTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
261
|
+
|
|
262
|
+
useAnimatedNumberReaction(
|
|
263
|
+
{
|
|
264
|
+
value: animatedNumber,
|
|
265
|
+
hostRef: sheetRef,
|
|
266
|
+
},
|
|
267
|
+
React.useCallback(
|
|
268
|
+
(value) => {
|
|
269
|
+
at.current = value
|
|
270
|
+
scrollBridge.paneY = value
|
|
271
|
+
// update isAtTop for scroll enable/disable
|
|
272
|
+
// activePositions[0] is the top snap point (keyboard-adjusted minY)
|
|
273
|
+
const minY = activePositions[0]
|
|
274
|
+
const wasAtTop = scrollBridge.isAtTop
|
|
275
|
+
const nowAtTop = value <= minY + 5
|
|
276
|
+
if (wasAtTop !== nowAtTop) {
|
|
277
|
+
scrollBridge.isAtTop = nowAtTop
|
|
278
|
+
// when reaching top, enable scroll; when leaving top, disable scroll
|
|
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
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
[animationDriver, activePositions]
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
function stopSpring() {
|
|
300
|
+
animatedNumber.stop()
|
|
301
|
+
if (scrollBridge.onFinishAnimate) {
|
|
302
|
+
scrollBridge.onFinishAnimate()
|
|
303
|
+
scrollBridge.onFinishAnimate = undefined
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const animateTo = useEvent((position: number, animationOverride?: any) => {
|
|
308
|
+
if (frameSize === 0) return
|
|
309
|
+
|
|
310
|
+
let toValue = isHidden || position === -1 ? screenSize : activePositions[position]
|
|
311
|
+
|
|
312
|
+
if (at.current === toValue) return
|
|
313
|
+
|
|
314
|
+
at.current = toValue
|
|
315
|
+
stopSpring()
|
|
316
|
+
|
|
317
|
+
const isOpenAnimation = position !== -1 && !isHidden
|
|
318
|
+
|
|
319
|
+
// clear any pending fallback timer
|
|
320
|
+
if (opacityFallbackTimer.current) {
|
|
321
|
+
clearTimeout(opacityFallbackTimer.current)
|
|
322
|
+
opacityFallbackTimer.current = null
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const animationCompleteCallback = () => {
|
|
326
|
+
if (opacityFallbackTimer.current) {
|
|
327
|
+
clearTimeout(opacityFallbackTimer.current)
|
|
328
|
+
opacityFallbackTimer.current = null
|
|
329
|
+
}
|
|
330
|
+
if (!isOpenAnimation && !open) {
|
|
331
|
+
setOpacity(0)
|
|
332
|
+
}
|
|
333
|
+
onAnimationComplete?.({ open: isOpenAnimation })
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// safety fallback: if animation callback never fires, still hide the sheet
|
|
337
|
+
if (!isOpenAnimation) {
|
|
338
|
+
opacityFallbackTimer.current = setTimeout(() => {
|
|
339
|
+
opacityFallbackTimer.current = null
|
|
340
|
+
// check live open state via ref — sheet may have reopened (e.g. adapt handoff)
|
|
341
|
+
if (!openRef.current) {
|
|
342
|
+
setOpacity(0)
|
|
343
|
+
}
|
|
344
|
+
}, 1000)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// skip animation when adapting from dialog to sheet
|
|
348
|
+
if (skipAdaptAnimation.current) {
|
|
349
|
+
skipAdaptAnimation.current = false
|
|
350
|
+
animatedNumber.setValue(
|
|
351
|
+
toValue,
|
|
352
|
+
{ type: 'timing', duration: 0 },
|
|
353
|
+
animationCompleteCallback
|
|
354
|
+
)
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
animatedNumber.setValue(
|
|
359
|
+
toValue,
|
|
360
|
+
animationOverride || {
|
|
361
|
+
type: 'spring',
|
|
362
|
+
...transitionConfig,
|
|
363
|
+
},
|
|
364
|
+
animationCompleteCallback
|
|
365
|
+
)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
useIsomorphicLayoutEffect(() => {
|
|
369
|
+
// we need to do a *three* step process for the css driver
|
|
370
|
+
// first render off screen for ssr safety (hiddenSize)
|
|
371
|
+
// then render to bottom of screen without animation (screenSize)
|
|
372
|
+
// then add the animation as it animates from screenSize to position
|
|
373
|
+
|
|
374
|
+
if (hasntMeasured && screenSize && frameSize) {
|
|
375
|
+
at.current = screenSize
|
|
376
|
+
animatedNumber.setValue(
|
|
377
|
+
screenSize,
|
|
378
|
+
{
|
|
379
|
+
type: 'timing',
|
|
380
|
+
duration: 0,
|
|
381
|
+
},
|
|
382
|
+
() => {
|
|
383
|
+
// imperfect but struggling to render properly here
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
setDisableAnimation(false)
|
|
386
|
+
}, 10)
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (disableAnimation) {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!frameSize || !screenSize || isHidden || (hasntMeasured && !open)) {
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// finally, animate
|
|
401
|
+
animateTo(position)
|
|
402
|
+
|
|
403
|
+
// reset scroll bridge
|
|
404
|
+
if (position === -1) {
|
|
405
|
+
scrollBridge.scrollLock = false
|
|
406
|
+
scrollBridge.scrollStartY = -1
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// set initial isAtTop state when sheet opens
|
|
410
|
+
// position 0 = top snap point, so isAtTop = true
|
|
411
|
+
if (open && position >= 0) {
|
|
412
|
+
const isTopPosition = position === 0
|
|
413
|
+
scrollBridge.isAtTop = isTopPosition
|
|
414
|
+
if (isTopPosition) {
|
|
415
|
+
scrollBridge.scrollLockY = undefined
|
|
416
|
+
scrollBridge.setScrollEnabled?.(true)
|
|
417
|
+
} else {
|
|
418
|
+
scrollBridge.scrollLockY = 0
|
|
419
|
+
scrollBridge.setScrollEnabled?.(false)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}, [hasntMeasured, disableAnimation, isHidden, frameSize, screenSize, open, position])
|
|
423
|
+
|
|
424
|
+
const disableDrag = props.disableDrag ?? controller?.disableDrag
|
|
425
|
+
const themeName = useThemeName()
|
|
426
|
+
const [blockPan, setBlockPan] = React.useState(false)
|
|
427
|
+
|
|
428
|
+
const panResponder = React.useMemo(() => {
|
|
429
|
+
if (disableDrag) return
|
|
430
|
+
if (!frameSize) return
|
|
431
|
+
if (isShowingInnerSheet) return
|
|
432
|
+
|
|
433
|
+
const minY = positions[0]
|
|
434
|
+
scrollBridge.paneMinY = minY
|
|
435
|
+
let startY = at.current
|
|
436
|
+
|
|
437
|
+
function setPanning(val: boolean) {
|
|
438
|
+
setIsDragging(val)
|
|
439
|
+
|
|
440
|
+
// make unselectable:
|
|
441
|
+
if (process.env.HANZO_GUI_TARGET === 'web') {
|
|
442
|
+
if (!sheetHiddenStyleSheet) {
|
|
443
|
+
sheetHiddenStyleSheet = document.createElement('style')
|
|
444
|
+
if (typeof document.head !== 'undefined') {
|
|
445
|
+
document.head.appendChild(sheetHiddenStyleSheet)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!val) {
|
|
449
|
+
sheetHiddenStyleSheet.innerText = ''
|
|
450
|
+
} else {
|
|
451
|
+
sheetHiddenStyleSheet.innerText =
|
|
452
|
+
':root * { user-select: none !important; -webkit-user-select: none !important; }'
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const release = ({ vy }: { dragAt: number; vy: number }) => {
|
|
458
|
+
scrollBridge.setParentDragging(false)
|
|
459
|
+
if (scrollBridge.scrollLock) {
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
isExternalDrag = false
|
|
464
|
+
previouslyScrolling = false
|
|
465
|
+
setPanning(false)
|
|
466
|
+
// use the actual current animated position rather than dragAt + startY.
|
|
467
|
+
// after mid-gesture handoffs (pan→scroll→pan), startY can be stale
|
|
468
|
+
// causing the computed position to be wildly wrong (triggering dismiss).
|
|
469
|
+
const currentPos = at.current
|
|
470
|
+
// vy goes up to about 4 at most (+ is down, - is up)
|
|
471
|
+
const end = currentPos + frameSize * vy * 0.2
|
|
472
|
+
|
|
473
|
+
let closestPoint = 0
|
|
474
|
+
let dist = Number.POSITIVE_INFINITY
|
|
475
|
+
|
|
476
|
+
for (let i = 0; i < positions.length; i++) {
|
|
477
|
+
const position = positions[i]
|
|
478
|
+
const curDist = end > position ? end - position : position - end
|
|
479
|
+
if (curDist < dist) {
|
|
480
|
+
dist = curDist
|
|
481
|
+
closestPoint = i
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// have to call both because state may not change but need to snap back
|
|
486
|
+
setPosition(closestPoint)
|
|
487
|
+
animateTo(closestPoint)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const finish = (_e: GestureResponderEvent, state: PanResponderGestureState) => {
|
|
491
|
+
release({
|
|
492
|
+
vy: state.vy,
|
|
493
|
+
dragAt: state.dy,
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let previouslyScrolling = false
|
|
498
|
+
|
|
499
|
+
const onMoveShouldSet = (
|
|
500
|
+
e: GestureResponderEvent,
|
|
501
|
+
{ dy }: PanResponderGestureState
|
|
502
|
+
): boolean => {
|
|
503
|
+
function getShouldSet() {
|
|
504
|
+
// if dragging handle always allow:
|
|
505
|
+
if (e.target === providerProps.handleRef.current) {
|
|
506
|
+
return true
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (scrollBridge.hasScrollableContent === true) {
|
|
510
|
+
if (scrollBridge.scrollLock) {
|
|
511
|
+
return false
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const isScrolled = scrollBridge.y !== 0
|
|
515
|
+
|
|
516
|
+
// Update the dragging direction
|
|
517
|
+
const isDraggingUp = dy < 0
|
|
518
|
+
|
|
519
|
+
// we can treat near top instead of exactly to avoid trouble with springs
|
|
520
|
+
const isNearTop = scrollBridge.paneY - 5 <= scrollBridge.paneMinY
|
|
521
|
+
if (isScrolled) {
|
|
522
|
+
previouslyScrolling = true
|
|
523
|
+
return false
|
|
524
|
+
}
|
|
525
|
+
// prevent drag once at top and pulling up
|
|
526
|
+
if (isNearTop) {
|
|
527
|
+
if (hasScrollView.current && isDraggingUp) {
|
|
528
|
+
return false
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// we could do some detection of other touchables and cancel here..
|
|
534
|
+
return Math.abs(dy) > 10
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const granted = getShouldSet()
|
|
538
|
+
|
|
539
|
+
if (granted) {
|
|
540
|
+
scrollBridge.setParentDragging(true)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return granted
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const grant = () => {
|
|
547
|
+
setPanning(true)
|
|
548
|
+
stopSpring()
|
|
549
|
+
startY = at.current
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let isExternalDrag = false
|
|
553
|
+
|
|
554
|
+
scrollBridge.drag = (dy) => {
|
|
555
|
+
if (!isExternalDrag) {
|
|
556
|
+
isExternalDrag = true
|
|
557
|
+
grant()
|
|
558
|
+
}
|
|
559
|
+
const to = dy + startY
|
|
560
|
+
animatedNumber.setValue(resisted(to, minY), { type: 'direct' })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
scrollBridge.release = release
|
|
564
|
+
|
|
565
|
+
// direct snap to position without release calculation (for handoff cases)
|
|
566
|
+
scrollBridge.snapToPosition = (positionIndex: number) => {
|
|
567
|
+
isExternalDrag = false
|
|
568
|
+
previouslyScrolling = false
|
|
569
|
+
setPanning(false)
|
|
570
|
+
setPosition(positionIndex)
|
|
571
|
+
animateTo(positionIndex)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return PanResponder.create({
|
|
575
|
+
onMoveShouldSetPanResponder: onMoveShouldSet,
|
|
576
|
+
onPanResponderGrant: grant,
|
|
577
|
+
onPanResponderMove: (_e, { dy }) => {
|
|
578
|
+
const toFull = dy + startY
|
|
579
|
+
const to = resisted(toFull, minY)
|
|
580
|
+
|
|
581
|
+
// handles the case where you hand off back and forth more than once
|
|
582
|
+
const isAtTop = to <= minY
|
|
583
|
+
if (isAtTop) {
|
|
584
|
+
scrollBridge.setParentDragging(false)
|
|
585
|
+
} else {
|
|
586
|
+
scrollBridge.setParentDragging(true)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
animatedNumber.setValue(to, { type: 'direct' })
|
|
590
|
+
},
|
|
591
|
+
onPanResponderEnd: finish,
|
|
592
|
+
onPanResponderTerminate: finish,
|
|
593
|
+
onPanResponderRelease: finish,
|
|
594
|
+
})
|
|
595
|
+
}, [disableDrag, isShowingInnerSheet, animateTo, frameSize, positions, setPosition])
|
|
596
|
+
|
|
597
|
+
// animate to keyboard-adjusted position when keyboard state changes
|
|
598
|
+
React.useEffect(() => {
|
|
599
|
+
if (isDragging || isHidden || !open || disableAnimation) return
|
|
600
|
+
if (!frameSize || !screenSize) return
|
|
601
|
+
// use timing animation to match iOS keyboard animation (~250ms)
|
|
602
|
+
animateTo(position, { type: 'timing', duration: 250 })
|
|
603
|
+
}, [isKeyboardVisible, keyboardHeight])
|
|
604
|
+
|
|
605
|
+
// reconcile position after drag ends — if keyboard dismissed during drag
|
|
606
|
+
// (e.g., input blur), activePositions reverted but onEnd used frozen positions
|
|
607
|
+
// for snap index. This effect ensures the sheet animates to the correct
|
|
608
|
+
// non-keyboard-adjusted position for the chosen snap index.
|
|
609
|
+
const wasDragging = React.useRef(false)
|
|
610
|
+
React.useEffect(() => {
|
|
611
|
+
if (isDragging) {
|
|
612
|
+
wasDragging.current = true
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
if (!wasDragging.current) return
|
|
616
|
+
wasDragging.current = false
|
|
617
|
+
// drag just ended — reconcile position with latest activePositions
|
|
618
|
+
if (!frameSize || !screenSize || isHidden || !open) return
|
|
619
|
+
animateTo(position)
|
|
620
|
+
}, [isDragging])
|
|
621
|
+
|
|
622
|
+
// dismiss keyboard when sheet closes
|
|
623
|
+
React.useEffect(() => {
|
|
624
|
+
if (!open && isKeyboardVisible) {
|
|
625
|
+
dismissKeyboard()
|
|
626
|
+
}
|
|
627
|
+
}, [open])
|
|
628
|
+
|
|
629
|
+
// gesture handler hook for RNGH-based gesture coordination
|
|
630
|
+
const { panGesture, panGestureRef, gestureHandlerEnabled } = useGestureHandlerPan({
|
|
631
|
+
positions: activePositions,
|
|
632
|
+
frameSize,
|
|
633
|
+
setPosition,
|
|
634
|
+
animateTo,
|
|
635
|
+
stopSpring,
|
|
636
|
+
scrollBridge,
|
|
637
|
+
setIsDragging,
|
|
638
|
+
getCurrentPosition: () => at.current,
|
|
639
|
+
resisted,
|
|
640
|
+
disableDrag,
|
|
641
|
+
isShowingInnerSheet,
|
|
642
|
+
setAnimatedPosition: (val: number) => {
|
|
643
|
+
// directly set the animated value for smooth dragging
|
|
644
|
+
at.current = val
|
|
645
|
+
animatedNumber.setValue(val, { type: 'direct' })
|
|
646
|
+
},
|
|
647
|
+
pauseKeyboardHandler,
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
const handleAnimationViewLayout = useEvent((e: LayoutChangeEvent) => {
|
|
651
|
+
// don't update frameSize during exit animation to prevent position jumps
|
|
652
|
+
if (!open && stableFrameSize.current !== 0) {
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// avoid bugs where it grows forever for whatever reason
|
|
657
|
+
// For inline mode (non-modal), don't cap at window height - use actual layout
|
|
658
|
+
const layoutHeight = e.nativeEvent?.layout.height
|
|
659
|
+
const next = modal
|
|
660
|
+
? Math.min(layoutHeight, Dimensions.get(relativeDimensionTo).height)
|
|
661
|
+
: layoutHeight
|
|
662
|
+
if (!next) return
|
|
663
|
+
setFrameSize(next)
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
const handleMaxContentViewLayout = React.useCallback((e: LayoutChangeEvent) => {
|
|
667
|
+
// avoid bugs where it grows forever for whatever reason
|
|
668
|
+
const next = Math.min(
|
|
669
|
+
e.nativeEvent?.layout.height,
|
|
670
|
+
Dimensions.get(relativeDimensionTo).height
|
|
671
|
+
)
|
|
672
|
+
if (!next) return
|
|
673
|
+
setMaxContentSize(next)
|
|
674
|
+
}, [])
|
|
675
|
+
|
|
676
|
+
const animatedStyle = useAnimatedNumberStyle(animatedNumber, (val) => {
|
|
677
|
+
'worklet'
|
|
678
|
+
const translateY = frameSize === 0 ? hiddenSize : val
|
|
679
|
+
|
|
680
|
+
return {
|
|
681
|
+
transform: [{ translateY }],
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
// we need to set this *after* fully closed to 0, to avoid it overlapping
|
|
686
|
+
// the page when resizing quickly on web for example
|
|
687
|
+
const [opacity, setOpacity] = React.useState(open ? 1 : 0)
|
|
688
|
+
if (open && opacity === 0) {
|
|
689
|
+
setOpacity(1)
|
|
690
|
+
// cancel any pending close fallback — sheet is reopening
|
|
691
|
+
if (opacityFallbackTimer.current) {
|
|
692
|
+
clearTimeout(opacityFallbackTimer.current)
|
|
693
|
+
opacityFallbackTimer.current = null
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const forcedContentHeight = hasFit
|
|
698
|
+
? undefined
|
|
699
|
+
: snapPointsMode === 'percent'
|
|
700
|
+
? // Use dvh for modal (viewport-relative), % for inline (container-relative)
|
|
701
|
+
`${maxSnapPoint}${isWeb ? (modal ? 'dvh' : '%') : '%'}`
|
|
702
|
+
: maxSnapPoint
|
|
703
|
+
|
|
704
|
+
const setHasScrollView = React.useCallback((val: boolean) => {
|
|
705
|
+
hasScrollView.current = val
|
|
706
|
+
}, [])
|
|
707
|
+
// const id = useId()
|
|
708
|
+
// const { AdaptProvider, when, children } = useAdaptParent({
|
|
709
|
+
// scope: `${id}Sheet`,
|
|
710
|
+
// portal: true,
|
|
711
|
+
// })
|
|
712
|
+
|
|
713
|
+
let contents = (
|
|
714
|
+
<LayoutMeasurementController disable={!open}>
|
|
715
|
+
<ParentSheetContext.Provider value={nextParentContext}>
|
|
716
|
+
<SheetProvider {...providerProps} setHasScrollView={setHasScrollView}>
|
|
717
|
+
<GestureSheetProvider
|
|
718
|
+
isDragging={isDragging}
|
|
719
|
+
blockPan={blockPan}
|
|
720
|
+
setBlockPan={setBlockPan}
|
|
721
|
+
panGesture={panGesture}
|
|
722
|
+
panGestureRef={panGestureRef}
|
|
723
|
+
>
|
|
724
|
+
<AnimatePresence custom={{ open }}>
|
|
725
|
+
{shouldHideParentSheet || !open ? null : overlayComponent}
|
|
726
|
+
</AnimatePresence>
|
|
727
|
+
|
|
728
|
+
{snapPointsMode !== 'percent' && (
|
|
729
|
+
<View
|
|
730
|
+
style={{
|
|
731
|
+
opacity: 0,
|
|
732
|
+
position: 'absolute',
|
|
733
|
+
top: 0,
|
|
734
|
+
left: 0,
|
|
735
|
+
right: 0,
|
|
736
|
+
bottom: 0,
|
|
737
|
+
pointerEvents: 'none',
|
|
738
|
+
}}
|
|
739
|
+
onLayout={handleMaxContentViewLayout}
|
|
740
|
+
/>
|
|
741
|
+
)}
|
|
742
|
+
|
|
743
|
+
<AnimatedView
|
|
744
|
+
ref={ref}
|
|
745
|
+
onLayout={handleAnimationViewLayout}
|
|
746
|
+
// @ts-ignore for CSS driver this is necessary to attach the transition
|
|
747
|
+
// also motion driver at least though i suspect all drivers?
|
|
748
|
+
transition={isDragging || disableAnimation ? null : transition}
|
|
749
|
+
// @ts-ignore
|
|
750
|
+
disableClassName
|
|
751
|
+
style={[
|
|
752
|
+
{
|
|
753
|
+
position: 'absolute',
|
|
754
|
+
zIndex,
|
|
755
|
+
width: '100%',
|
|
756
|
+
height: forcedContentHeight,
|
|
757
|
+
minHeight: forcedContentHeight,
|
|
758
|
+
opacity: !shouldHideParentSheet ? opacity : 0,
|
|
759
|
+
...((shouldHideParentSheet || !open) && {
|
|
760
|
+
pointerEvents: 'none',
|
|
761
|
+
}),
|
|
762
|
+
},
|
|
763
|
+
animatedStyle,
|
|
764
|
+
]}
|
|
765
|
+
>
|
|
766
|
+
{/* wrap children with plain RN View for panResponder - gui views no longer handle responder events on web */}
|
|
767
|
+
{gestureHandlerEnabled && panGesture ? (
|
|
768
|
+
<GestureDetectorWrapper gesture={panGesture} style={{ flex: 1 }}>
|
|
769
|
+
{props.children}
|
|
770
|
+
</GestureDetectorWrapper>
|
|
771
|
+
) : (
|
|
772
|
+
<View
|
|
773
|
+
{...panResponder?.panHandlers}
|
|
774
|
+
style={{ flex: 1, width: '100%', height: '100%' }}
|
|
775
|
+
>
|
|
776
|
+
{props.children}
|
|
777
|
+
</View>
|
|
778
|
+
)}
|
|
779
|
+
</AnimatedView>
|
|
780
|
+
</GestureSheetProvider>
|
|
781
|
+
</SheetProvider>
|
|
782
|
+
</ParentSheetContext.Provider>
|
|
783
|
+
</LayoutMeasurementController>
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
if (process.env.HANZO_GUI_TARGET === 'native' && needsPortalRepropagation()) {
|
|
787
|
+
// TODO alongside sheet scope="" need to pass scope here
|
|
788
|
+
const adaptContext = useAdaptContext()
|
|
789
|
+
contents = (
|
|
790
|
+
<ProvideAdaptContext {...adaptContext}>
|
|
791
|
+
{/* @ts-ignore */}
|
|
792
|
+
{contents}
|
|
793
|
+
</ProvideAdaptContext>
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// start mounted so we get an accurate measurement the first time
|
|
798
|
+
const shouldMountChildren = unmountChildrenWhenHidden ? !!opacity : true
|
|
799
|
+
|
|
800
|
+
if (modal) {
|
|
801
|
+
const modalContents = (
|
|
802
|
+
<Portal stackZIndex={zIndex} {...portalProps}>
|
|
803
|
+
{shouldMountChildren && <ContainerComponent>{contents}</ContainerComponent>}
|
|
804
|
+
</Portal>
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
if (isWeb) {
|
|
808
|
+
return modalContents
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// on native we don't support multiple modals yet... fix for now is to hide outer one
|
|
812
|
+
return (
|
|
813
|
+
<SheetInsideSheetContext.Provider value={onInnerSheet}>
|
|
814
|
+
{modalContents}
|
|
815
|
+
</SheetInsideSheetContext.Provider>
|
|
816
|
+
)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return contents
|
|
820
|
+
}
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
function getYPositions(
|
|
824
|
+
mode: SnapPointsMode,
|
|
825
|
+
point: string | number,
|
|
826
|
+
screenSize?: number,
|
|
827
|
+
frameSize?: number
|
|
828
|
+
) {
|
|
829
|
+
if (!screenSize || !frameSize) {
|
|
830
|
+
return 0
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (mode === 'mixed') {
|
|
834
|
+
if (typeof point === 'number') {
|
|
835
|
+
return screenSize - Math.min(screenSize, Math.max(0, point))
|
|
836
|
+
}
|
|
837
|
+
if (point === 'fit') {
|
|
838
|
+
return screenSize - Math.min(screenSize, frameSize)
|
|
839
|
+
}
|
|
840
|
+
if (point.endsWith('%')) {
|
|
841
|
+
const pct = Math.min(100, Math.max(0, Number(point.slice(0, -1)))) / 100
|
|
842
|
+
if (Number.isNaN(pct)) {
|
|
843
|
+
console.warn('Invalid snapPoint percentage string')
|
|
844
|
+
return 0
|
|
845
|
+
}
|
|
846
|
+
return Math.round(screenSize - pct * screenSize)
|
|
847
|
+
}
|
|
848
|
+
console.warn('Invalid snapPoint unknown value')
|
|
849
|
+
return 0
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (mode === 'fit') {
|
|
853
|
+
if (point === 0) return screenSize
|
|
854
|
+
return screenSize - Math.min(screenSize, frameSize)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (mode === 'constant' && typeof point === 'number') {
|
|
858
|
+
return screenSize - Math.min(screenSize, Math.max(0, point))
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const pct = Math.min(100, Math.max(0, Number(point))) / 100
|
|
862
|
+
if (Number.isNaN(pct)) {
|
|
863
|
+
console.warn('Invalid snapPoint percentage')
|
|
864
|
+
return 0
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return Math.round(screenSize - pct * screenSize)
|
|
868
|
+
}
|