react-native-gesture-handler 2.20.2 → 2.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/RNGestureHandler.podspec +9 -0
  2. package/ReanimatedDrawerLayout/package.json +6 -0
  3. package/android/build.gradle +19 -0
  4. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +6 -2
  5. package/android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureDetector.kt +53 -21
  6. package/apple/RNGestureHandlerPointerTracker.m +4 -2
  7. package/lib/commonjs/RNGestureHandlerModule.web.js +15 -2
  8. package/lib/commonjs/RNGestureHandlerModule.web.js.map +1 -1
  9. package/lib/commonjs/components/Pressable/Pressable.js +2 -6
  10. package/lib/commonjs/components/Pressable/Pressable.js.map +1 -1
  11. package/lib/commonjs/components/ReanimatedDrawerLayout.js +389 -0
  12. package/lib/commonjs/components/ReanimatedDrawerLayout.js.map +1 -0
  13. package/lib/commonjs/components/ReanimatedSwipeable.js +117 -148
  14. package/lib/commonjs/components/ReanimatedSwipeable.js.map +1 -1
  15. package/lib/commonjs/findNodeHandle.js +12 -0
  16. package/lib/commonjs/findNodeHandle.js.map +1 -0
  17. package/lib/commonjs/findNodeHandle.web.js +40 -0
  18. package/lib/commonjs/findNodeHandle.web.js.map +1 -0
  19. package/lib/commonjs/handlers/createHandler.js +4 -2
  20. package/lib/commonjs/handlers/createHandler.js.map +1 -1
  21. package/lib/commonjs/handlers/gestures/GestureDetector/Wrap.web.js +51 -0
  22. package/lib/commonjs/handlers/gestures/GestureDetector/Wrap.web.js.map +1 -0
  23. package/lib/commonjs/handlers/gestures/GestureDetector/index.js +3 -1
  24. package/lib/commonjs/handlers/gestures/GestureDetector/index.js.map +1 -1
  25. package/lib/commonjs/handlers/gestures/GestureDetector/useDetectorUpdater.js +5 -3
  26. package/lib/commonjs/handlers/gestures/GestureDetector/useDetectorUpdater.js.map +1 -1
  27. package/lib/commonjs/handlers/gestures/GestureDetector/useViewRefHandler.js +4 -2
  28. package/lib/commonjs/handlers/gestures/GestureDetector/useViewRefHandler.js.map +1 -1
  29. package/lib/commonjs/web/handlers/GestureHandler.js +4 -0
  30. package/lib/commonjs/web/handlers/GestureHandler.js.map +1 -1
  31. package/lib/commonjs/web/handlers/PanGestureHandler.js +59 -0
  32. package/lib/commonjs/web/handlers/PanGestureHandler.js.map +1 -1
  33. package/lib/commonjs/web/interfaces.js +10 -1
  34. package/lib/commonjs/web/interfaces.js.map +1 -1
  35. package/lib/commonjs/web/tools/EventManager.js +6 -0
  36. package/lib/commonjs/web/tools/EventManager.js.map +1 -1
  37. package/lib/commonjs/web/tools/GestureHandlerOrchestrator.js +1 -3
  38. package/lib/commonjs/web/tools/GestureHandlerOrchestrator.js.map +1 -1
  39. package/lib/commonjs/web/tools/GestureHandlerWebDelegate.js +5 -2
  40. package/lib/commonjs/web/tools/GestureHandlerWebDelegate.js.map +1 -1
  41. package/lib/commonjs/web/tools/KeyboardEventManager.js +2 -2
  42. package/lib/commonjs/web/tools/KeyboardEventManager.js.map +1 -1
  43. package/lib/commonjs/web/tools/PointerTracker.js +6 -30
  44. package/lib/commonjs/web/tools/PointerTracker.js.map +1 -1
  45. package/lib/commonjs/web/tools/WheelEventManager.js +74 -0
  46. package/lib/commonjs/web/tools/WheelEventManager.js.map +1 -0
  47. package/lib/commonjs/web/utils.js +16 -0
  48. package/lib/commonjs/web/utils.js.map +1 -1
  49. package/lib/module/RNGestureHandlerModule.web.js +16 -3
  50. package/lib/module/RNGestureHandlerModule.web.js.map +1 -1
  51. package/lib/module/components/Pressable/Pressable.js +2 -6
  52. package/lib/module/components/Pressable/Pressable.js.map +1 -1
  53. package/lib/module/components/ReanimatedDrawerLayout.js +365 -0
  54. package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -0
  55. package/lib/module/components/ReanimatedSwipeable.js +119 -145
  56. package/lib/module/components/ReanimatedSwipeable.js.map +1 -1
  57. package/lib/module/findNodeHandle.js +3 -0
  58. package/lib/module/findNodeHandle.js.map +1 -0
  59. package/lib/module/findNodeHandle.web.js +32 -0
  60. package/lib/module/findNodeHandle.web.js.map +1 -0
  61. package/lib/module/handlers/createHandler.js +2 -1
  62. package/lib/module/handlers/createHandler.js.map +1 -1
  63. package/lib/module/handlers/gestures/GestureDetector/Wrap.web.js +34 -0
  64. package/lib/module/handlers/gestures/GestureDetector/Wrap.web.js.map +1 -0
  65. package/lib/module/handlers/gestures/GestureDetector/index.js +2 -1
  66. package/lib/module/handlers/gestures/GestureDetector/index.js.map +1 -1
  67. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js +2 -2
  68. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js.map +1 -1
  69. package/lib/module/handlers/gestures/GestureDetector/useViewRefHandler.js +1 -1
  70. package/lib/module/handlers/gestures/GestureDetector/useViewRefHandler.js.map +1 -1
  71. package/lib/module/web/handlers/GestureHandler.js +4 -0
  72. package/lib/module/web/handlers/GestureHandler.js.map +1 -1
  73. package/lib/module/web/handlers/PanGestureHandler.js +58 -0
  74. package/lib/module/web/handlers/PanGestureHandler.js.map +1 -1
  75. package/lib/module/web/interfaces.js +8 -0
  76. package/lib/module/web/interfaces.js.map +1 -1
  77. package/lib/module/web/tools/EventManager.js +6 -0
  78. package/lib/module/web/tools/EventManager.js.map +1 -1
  79. package/lib/module/web/tools/GestureHandlerOrchestrator.js +1 -3
  80. package/lib/module/web/tools/GestureHandlerOrchestrator.js.map +1 -1
  81. package/lib/module/web/tools/GestureHandlerWebDelegate.js +3 -1
  82. package/lib/module/web/tools/GestureHandlerWebDelegate.js.map +1 -1
  83. package/lib/module/web/tools/KeyboardEventManager.js +2 -2
  84. package/lib/module/web/tools/KeyboardEventManager.js.map +1 -1
  85. package/lib/module/web/tools/PointerTracker.js +6 -30
  86. package/lib/module/web/tools/PointerTracker.js.map +1 -1
  87. package/lib/module/web/tools/WheelEventManager.js +60 -0
  88. package/lib/module/web/tools/WheelEventManager.js.map +1 -0
  89. package/lib/module/web/utils.js +15 -0
  90. package/lib/module/web/utils.js.map +1 -1
  91. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts +162 -0
  92. package/lib/typescript/components/ReanimatedSwipeable.d.ts +22 -16
  93. package/lib/typescript/findNodeHandle.d.ts +2 -0
  94. package/lib/typescript/findNodeHandle.web.d.ts +2 -0
  95. package/lib/typescript/handlers/gestures/GestureDetector/Wrap.web.d.ts +7 -0
  96. package/lib/typescript/web/handlers/GestureHandler.d.ts +2 -1
  97. package/lib/typescript/web/handlers/PanGestureHandler.d.ts +5 -0
  98. package/lib/typescript/web/interfaces.d.ts +16 -0
  99. package/lib/typescript/web/tools/EventManager.d.ts +2 -0
  100. package/lib/typescript/web/tools/PointerTracker.d.ts +2 -8
  101. package/lib/typescript/web/tools/WheelEventManager.d.ts +11 -0
  102. package/lib/typescript/web/utils.d.ts +2 -1
  103. package/package.json +3 -2
  104. package/src/RNGestureHandlerModule.web.ts +23 -4
  105. package/src/components/Pressable/Pressable.tsx +2 -6
  106. package/src/components/ReanimatedDrawerLayout.tsx +741 -0
  107. package/src/components/ReanimatedSwipeable.tsx +361 -305
  108. package/src/findNodeHandle.ts +3 -0
  109. package/src/findNodeHandle.web.ts +35 -0
  110. package/src/handlers/createHandler.tsx +2 -1
  111. package/src/handlers/gestures/GestureDetector/Wrap.web.tsx +44 -0
  112. package/src/handlers/gestures/GestureDetector/index.tsx +2 -1
  113. package/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts +1 -1
  114. package/src/handlers/gestures/GestureDetector/useViewRefHandler.ts +1 -1
  115. package/src/web/handlers/GestureHandler.ts +5 -1
  116. package/src/web/handlers/PanGestureHandler.ts +69 -1
  117. package/src/web/interfaces.ts +17 -0
  118. package/src/web/tools/EventManager.ts +4 -0
  119. package/src/web/tools/GestureHandlerOrchestrator.ts +1 -7
  120. package/src/web/tools/GestureHandlerWebDelegate.ts +3 -1
  121. package/src/web/tools/KeyboardEventManager.ts +2 -2
  122. package/src/web/tools/PointerTracker.ts +6 -28
  123. package/src/web/tools/WheelEventManager.ts +48 -0
  124. package/src/web/utils.ts +47 -1
@@ -0,0 +1,741 @@
1
+ // This component is based on RN's DrawerLayoutAndroid API
2
+ // It's cross-compatible with all platforms despite
3
+ // `DrawerLayoutAndroid` only being available on android
4
+
5
+ import React, {
6
+ ReactNode,
7
+ forwardRef,
8
+ useCallback,
9
+ useEffect,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useState,
13
+ } from 'react';
14
+
15
+ import {
16
+ StyleSheet,
17
+ Keyboard,
18
+ StatusBar,
19
+ I18nManager,
20
+ StatusBarAnimation,
21
+ StyleProp,
22
+ ViewStyle,
23
+ LayoutChangeEvent,
24
+ Platform,
25
+ } from 'react-native';
26
+
27
+ import Animated, {
28
+ Extrapolation,
29
+ SharedValue,
30
+ interpolate,
31
+ runOnJS,
32
+ useAnimatedProps,
33
+ useAnimatedStyle,
34
+ useDerivedValue,
35
+ useSharedValue,
36
+ withSpring,
37
+ } from 'react-native-reanimated';
38
+
39
+ import { GestureObjects as Gesture } from '../handlers/gestures/gestureObjects';
40
+ import { GestureDetector } from '../handlers/gestures/GestureDetector';
41
+ import {
42
+ UserSelect,
43
+ ActiveCursor,
44
+ MouseButton,
45
+ HitSlop,
46
+ GestureStateChangeEvent,
47
+ } from '../handlers/gestureHandlerCommon';
48
+ import { PanGestureHandlerEventPayload } from '../handlers/GestureHandlerEventPayload';
49
+
50
+ const DRAG_TOSS = 0.05;
51
+
52
+ export enum DrawerPosition {
53
+ LEFT,
54
+ RIGHT,
55
+ }
56
+
57
+ export enum DrawerState {
58
+ IDLE,
59
+ DRAGGING,
60
+ SETTLING,
61
+ }
62
+
63
+ export enum DrawerType {
64
+ FRONT,
65
+ BACK,
66
+ SLIDE,
67
+ }
68
+
69
+ export enum DrawerLockMode {
70
+ UNLOCKED,
71
+ LOCKED_CLOSED,
72
+ LOCKED_OPEN,
73
+ }
74
+
75
+ export enum DrawerKeyboardDismissMode {
76
+ NONE,
77
+ ON_DRAG,
78
+ }
79
+
80
+ export interface DrawerLayoutProps {
81
+ /**
82
+ * This attribute is present in the native android implementation already and is one
83
+ * of the required params. The gesture handler version of DrawerLayout makes it
84
+ * possible for the function passed as `renderNavigationView` to take an
85
+ * Animated value as a parameter that indicates the progress of drawer
86
+ * opening/closing animation (progress value is 0 when closed and 1 when
87
+ * opened). This can be used by the drawer component to animated its children
88
+ * while the drawer is opening or closing.
89
+ */
90
+ renderNavigationView: (
91
+ progressAnimatedValue: SharedValue<number>
92
+ ) => ReactNode;
93
+
94
+ /**
95
+ * Determines the side from which the drawer will open.
96
+ */
97
+ drawerPosition?: DrawerPosition;
98
+
99
+ /**
100
+ * Width of the drawer.
101
+ */
102
+ drawerWidth?: number;
103
+
104
+ /**
105
+ * Background color of the drawer.
106
+ */
107
+ drawerBackgroundColor?: string;
108
+
109
+ /**
110
+ * Specifies the lock mode of the drawer.
111
+ * Programatic opening/closing isn't affected by the lock mode. Defaults to `UNLOCKED`.
112
+ * - `UNLOCKED` - the drawer will respond to gestures.
113
+ * - `LOCKED_CLOSED` - the drawer will move freely until it settles in a closed position, then the gestures will be disabled.
114
+ * - `LOCKED_OPEN` - the drawer will move freely until it settles in an opened position, then the gestures will be disabled.
115
+ */
116
+ drawerLockMode?: DrawerLockMode;
117
+
118
+ /**
119
+ * Determines if system keyboard should be closed upon dragging the drawer.
120
+ */
121
+ keyboardDismissMode?: DrawerKeyboardDismissMode;
122
+
123
+ /**
124
+ * Called when the drawer is closed.
125
+ */
126
+ onDrawerClose?: () => void;
127
+
128
+ /**
129
+ * Called when the drawer is opened.
130
+ */
131
+ onDrawerOpen?: () => void;
132
+
133
+ /**
134
+ * Called when the status of the drawer changes.
135
+ */
136
+ onDrawerStateChanged?: (
137
+ newState: DrawerState,
138
+ drawerWillShow: boolean
139
+ ) => void;
140
+
141
+ /**
142
+ * Type of animation that will play when opening the drawer.
143
+ */
144
+ drawerType?: DrawerType;
145
+
146
+ /**
147
+ * Defines how far from the edge of the content view the gesture should
148
+ * activate.
149
+ */
150
+ edgeWidth?: number;
151
+
152
+ /**
153
+ * Minimal distance to swipe before the drawer starts moving.
154
+ */
155
+ minSwipeDistance?: number;
156
+
157
+ /**
158
+ * When set to true Drawer component will use
159
+ * {@link https://reactnative.dev/docs/statusbar StatusBar} API to hide the OS
160
+ * status bar whenever the drawer is pulled or when its in an "open" state.
161
+ */
162
+ hideStatusBar?: boolean;
163
+
164
+ /**
165
+ * @default 'slide'
166
+ *
167
+ * Can be used when hideStatusBar is set to true and will select the animation
168
+ * used for hiding/showing the status bar. See
169
+ * {@link https://reactnative.dev/docs/statusbar StatusBar} documentation for
170
+ * more details
171
+ */
172
+ statusBarAnimation?: StatusBarAnimation;
173
+
174
+ /**
175
+ * @default 'rgba(0, 0, 0, 0.7)'
176
+ *
177
+ * Color of the background overlay.
178
+ * Animated from `0%` to `100%` as the drawer opens.
179
+ */
180
+ overlayColor?: string;
181
+
182
+ /**
183
+ * Style wrapping the content.
184
+ */
185
+ contentContainerStyle?: StyleProp<ViewStyle>;
186
+
187
+ /**
188
+ * Style wrapping the drawer.
189
+ */
190
+ drawerContainerStyle?: StyleProp<ViewStyle>;
191
+
192
+ /**
193
+ * Enables two-finger gestures on supported devices, for example iPads with
194
+ * trackpads. If not enabled the gesture will require click + drag, with
195
+ * `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger
196
+ * the gesture.
197
+ */
198
+ enableTrackpadTwoFingerGesture?: boolean;
199
+
200
+ onDrawerSlide?: (position: number) => void;
201
+
202
+ // Implicit `children` prop has been removed in @types/react^18.0.
203
+ /**
204
+ * Elements that will be rendered inside the content view.
205
+ */
206
+ children?: ReactNode | ((openValue?: SharedValue<number>) => ReactNode);
207
+
208
+ /**
209
+ * @default 'none'
210
+ * Sets whether the text inside both the drawer and the context window can be selected.
211
+ * Values: 'none' | 'text' | 'auto'
212
+ */
213
+ userSelect?: UserSelect;
214
+
215
+ /**
216
+ * @default 'auto'
217
+ * Sets the displayed cursor pictogram when the drawer is being dragged.
218
+ * Values: see CSS cursor values
219
+ */
220
+ activeCursor?: ActiveCursor;
221
+
222
+ /**
223
+ * @default 'MouseButton.LEFT'
224
+ * Allows to choose which mouse button should underlying pan handler react to.
225
+ */
226
+ mouseButton?: MouseButton;
227
+
228
+ /**
229
+ * @default 'false if MouseButton.RIGHT is specified'
230
+ * Allows to enable/disable context menu.
231
+ */
232
+ enableContextMenu?: boolean;
233
+ }
234
+
235
+ export type DrawerMovementOption = {
236
+ initialVelocity?: number;
237
+ animationSpeed?: number;
238
+ };
239
+
240
+ export interface DrawerLayoutMethods {
241
+ openDrawer: (options?: DrawerMovementOption) => void;
242
+ closeDrawer: (options?: DrawerMovementOption) => void;
243
+ }
244
+
245
+ const defaultProps = {
246
+ drawerWidth: 200,
247
+ drawerPosition: DrawerPosition.LEFT,
248
+ drawerType: DrawerType.FRONT,
249
+ edgeWidth: 20,
250
+ minSwipeDistance: 3,
251
+ overlayColor: 'rgba(0, 0, 0, 0.7)',
252
+ drawerLockMode: DrawerLockMode.UNLOCKED,
253
+ enableTrackpadTwoFingerGesture: false,
254
+ activeCursor: 'auto' as ActiveCursor,
255
+ mouseButton: MouseButton.LEFT,
256
+ statusBarAnimation: 'slide' as StatusBarAnimation,
257
+ };
258
+
259
+ const DrawerLayout = forwardRef<DrawerLayoutMethods, DrawerLayoutProps>(
260
+ function DrawerLayout(props: DrawerLayoutProps, ref) {
261
+ const [containerWidth, setContainerWidth] = useState(0);
262
+ const [drawerState, setDrawerState] = useState<DrawerState>(
263
+ DrawerState.IDLE
264
+ );
265
+ const [drawerOpened, setDrawerOpened] = useState(false);
266
+
267
+ const {
268
+ drawerPosition = defaultProps.drawerPosition,
269
+ drawerWidth = defaultProps.drawerWidth,
270
+ drawerType = defaultProps.drawerType,
271
+ drawerBackgroundColor,
272
+ drawerContainerStyle,
273
+ contentContainerStyle,
274
+ minSwipeDistance = defaultProps.minSwipeDistance,
275
+ edgeWidth = defaultProps.edgeWidth,
276
+ drawerLockMode = defaultProps.drawerLockMode,
277
+ overlayColor = defaultProps.overlayColor,
278
+ enableTrackpadTwoFingerGesture = defaultProps.enableTrackpadTwoFingerGesture,
279
+ activeCursor = defaultProps.activeCursor,
280
+ mouseButton = defaultProps.mouseButton,
281
+ statusBarAnimation = defaultProps.statusBarAnimation,
282
+ hideStatusBar,
283
+ keyboardDismissMode,
284
+ userSelect,
285
+ enableContextMenu,
286
+ renderNavigationView,
287
+ onDrawerSlide,
288
+ onDrawerClose,
289
+ onDrawerOpen,
290
+ onDrawerStateChanged,
291
+ } = props;
292
+
293
+ const isFromLeft = drawerPosition === DrawerPosition.LEFT;
294
+
295
+ const sideCorrection = isFromLeft ? 1 : -1;
296
+
297
+ // While closing the drawer when user starts gesture in the greyed out part of the window,
298
+ // we want the drawer to follow only once the finger reaches the edge of the drawer.
299
+ // See the diagram for reference. * = starting finger position, < = current finger position
300
+ // 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
301
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
302
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
303
+ // |XXXXXXXX|..<*..| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
304
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
305
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
306
+ // +---------------+ +---------------+ +---------------+ +---------------+
307
+
308
+ const openValue = useSharedValue<number>(0);
309
+
310
+ useDerivedValue(() => {
311
+ onDrawerSlide && runOnJS(onDrawerSlide)(openValue.value);
312
+ }, []);
313
+
314
+ const isDrawerOpen = useSharedValue(false);
315
+
316
+ const handleContainerLayout = ({ nativeEvent }: LayoutChangeEvent) => {
317
+ setContainerWidth(nativeEvent.layout.width);
318
+ };
319
+
320
+ const emitStateChanged = useCallback(
321
+ (newState: DrawerState, drawerWillShow: boolean) => {
322
+ 'worklet';
323
+ onDrawerStateChanged &&
324
+ runOnJS(onDrawerStateChanged)?.(newState, drawerWillShow);
325
+ },
326
+ [onDrawerStateChanged]
327
+ );
328
+
329
+ const drawerAnimatedProps = useAnimatedProps(() => ({
330
+ accessibilityViewIsModal: isDrawerOpen.value,
331
+ }));
332
+
333
+ const overlayAnimatedProps = useAnimatedProps(() => ({
334
+ pointerEvents: isDrawerOpen.value ? ('auto' as const) : ('none' as const),
335
+ }));
336
+
337
+ // While the drawer is hidden, it's hitSlop overflows onto the main view by edgeWidth
338
+ // This way it can be swiped open even when it's hidden
339
+ const [edgeHitSlop, setEdgeHitSlop] = useState<HitSlop>(
340
+ isFromLeft
341
+ ? { left: 0, width: edgeWidth }
342
+ : { right: 0, width: edgeWidth }
343
+ );
344
+
345
+ // gestureOrientation is 1 if the gesture is expected to move from left to right and -1 otherwise
346
+ const gestureOrientation = useMemo(
347
+ () => sideCorrection * (drawerOpened ? -1 : 1),
348
+ [sideCorrection, drawerOpened]
349
+ );
350
+
351
+ useEffect(() => {
352
+ setEdgeHitSlop(
353
+ isFromLeft
354
+ ? { left: 0, width: edgeWidth }
355
+ : { right: 0, width: edgeWidth }
356
+ );
357
+ }, [isFromLeft, edgeWidth]);
358
+
359
+ const animateDrawer = useCallback(
360
+ (toValue: number, initialVelocity: number, animationSpeed?: number) => {
361
+ 'worklet';
362
+ const willShow = toValue !== 0;
363
+ isDrawerOpen.value = willShow;
364
+
365
+ emitStateChanged(DrawerState.SETTLING, willShow);
366
+ runOnJS(setDrawerState)(DrawerState.SETTLING);
367
+
368
+ if (hideStatusBar) {
369
+ runOnJS(StatusBar.setHidden)(willShow, statusBarAnimation);
370
+ }
371
+
372
+ const normalizedToValue = interpolate(
373
+ toValue,
374
+ [0, drawerWidth],
375
+ [0, 1],
376
+ Extrapolation.CLAMP
377
+ );
378
+
379
+ const normalizedInitialVelocity = interpolate(
380
+ initialVelocity,
381
+ [0, drawerWidth],
382
+ [0, 1],
383
+ Extrapolation.CLAMP
384
+ );
385
+
386
+ openValue.value = withSpring(
387
+ normalizedToValue,
388
+ {
389
+ overshootClamping: true,
390
+ velocity: normalizedInitialVelocity,
391
+ mass: animationSpeed ? 1 / animationSpeed : 1,
392
+ damping: 40,
393
+ stiffness: 500,
394
+ },
395
+ (finished) => {
396
+ if (finished) {
397
+ emitStateChanged(DrawerState.IDLE, willShow);
398
+ runOnJS(setDrawerOpened)(willShow);
399
+ runOnJS(setDrawerState)(DrawerState.IDLE);
400
+ if (willShow) {
401
+ onDrawerOpen && runOnJS(onDrawerOpen)?.();
402
+ } else {
403
+ onDrawerClose && runOnJS(onDrawerClose)?.();
404
+ }
405
+ }
406
+ }
407
+ );
408
+ },
409
+ [
410
+ openValue,
411
+ emitStateChanged,
412
+ isDrawerOpen,
413
+ hideStatusBar,
414
+ onDrawerClose,
415
+ onDrawerOpen,
416
+ drawerWidth,
417
+ statusBarAnimation,
418
+ ]
419
+ );
420
+
421
+ const handleRelease = useCallback(
422
+ (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
423
+ 'worklet';
424
+ let { translationX: dragX, velocityX, x: touchX } = event;
425
+
426
+ if (drawerPosition !== DrawerPosition.LEFT) {
427
+ // See description in _updateAnimatedEvent about why events are flipped
428
+ // for right-side drawer
429
+ dragX = -dragX;
430
+ touchX = containerWidth - touchX;
431
+ velocityX = -velocityX;
432
+ }
433
+
434
+ const gestureStartX = touchX - dragX;
435
+ let dragOffsetBasedOnStart = 0;
436
+
437
+ if (drawerType === DrawerType.FRONT) {
438
+ dragOffsetBasedOnStart =
439
+ gestureStartX > drawerWidth ? gestureStartX - drawerWidth : 0;
440
+ }
441
+
442
+ const startOffsetX =
443
+ dragX +
444
+ dragOffsetBasedOnStart +
445
+ (isDrawerOpen.value ? drawerWidth : 0);
446
+
447
+ const projOffsetX = startOffsetX + DRAG_TOSS * velocityX;
448
+
449
+ const shouldOpen = projOffsetX > drawerWidth / 2;
450
+
451
+ if (shouldOpen) {
452
+ animateDrawer(drawerWidth, velocityX);
453
+ } else {
454
+ animateDrawer(0, velocityX);
455
+ }
456
+ },
457
+ [
458
+ animateDrawer,
459
+ containerWidth,
460
+ drawerPosition,
461
+ drawerType,
462
+ drawerWidth,
463
+ isDrawerOpen,
464
+ ]
465
+ );
466
+
467
+ const openDrawer = useCallback(
468
+ (options: DrawerMovementOption = {}) => {
469
+ 'worklet';
470
+ animateDrawer(
471
+ drawerWidth,
472
+ options.initialVelocity ?? 0,
473
+ options.animationSpeed
474
+ );
475
+ },
476
+ [animateDrawer, drawerWidth]
477
+ );
478
+
479
+ const closeDrawer = useCallback(
480
+ (options: DrawerMovementOption = {}) => {
481
+ 'worklet';
482
+ animateDrawer(0, options.initialVelocity ?? 0, options.animationSpeed);
483
+ },
484
+ [animateDrawer]
485
+ );
486
+
487
+ const overlayDismissGesture = useMemo(
488
+ () =>
489
+ Gesture.Tap()
490
+ .maxDistance(25)
491
+ .onEnd(() => {
492
+ if (
493
+ isDrawerOpen.value &&
494
+ drawerLockMode !== DrawerLockMode.LOCKED_OPEN
495
+ ) {
496
+ closeDrawer();
497
+ }
498
+ }),
499
+ [closeDrawer, isDrawerOpen, drawerLockMode]
500
+ );
501
+
502
+ const overlayAnimatedStyle = useAnimatedStyle(() => ({
503
+ opacity: openValue.value,
504
+ backgroundColor: overlayColor,
505
+ }));
506
+
507
+ const fillHitSlop = useMemo(
508
+ () => (isFromLeft ? { left: drawerWidth } : { right: drawerWidth }),
509
+ [drawerWidth, isFromLeft]
510
+ );
511
+
512
+ const panGesture = useMemo(() => {
513
+ return Gesture.Pan()
514
+ .activeCursor(activeCursor)
515
+ .mouseButton(mouseButton)
516
+ .hitSlop(drawerOpened ? fillHitSlop : edgeHitSlop)
517
+ .minDistance(drawerOpened ? 100 : 0)
518
+ .activeOffsetX(gestureOrientation * minSwipeDistance)
519
+ .failOffsetY([-15, 15])
520
+ .simultaneousWithExternalGesture(overlayDismissGesture)
521
+ .enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
522
+ .enabled(
523
+ drawerState !== DrawerState.SETTLING &&
524
+ (drawerOpened
525
+ ? drawerLockMode !== DrawerLockMode.LOCKED_OPEN
526
+ : drawerLockMode !== DrawerLockMode.LOCKED_CLOSED)
527
+ )
528
+ .onStart(() => {
529
+ emitStateChanged(DrawerState.DRAGGING, false);
530
+ runOnJS(setDrawerState)(DrawerState.DRAGGING);
531
+ if (keyboardDismissMode === DrawerKeyboardDismissMode.ON_DRAG) {
532
+ runOnJS(Keyboard.dismiss)();
533
+ }
534
+ if (hideStatusBar) {
535
+ runOnJS(StatusBar.setHidden)(true, statusBarAnimation);
536
+ }
537
+ })
538
+ .onUpdate((event) => {
539
+ const startedOutsideTranslation = isFromLeft
540
+ ? interpolate(
541
+ event.x,
542
+ [0, drawerWidth, drawerWidth + 1],
543
+ [0, drawerWidth, drawerWidth]
544
+ )
545
+ : interpolate(
546
+ event.x - containerWidth,
547
+ [-drawerWidth - 1, -drawerWidth, 0],
548
+ [drawerWidth, drawerWidth, 0]
549
+ );
550
+
551
+ const startedInsideTranslation =
552
+ sideCorrection *
553
+ (event.translationX +
554
+ (drawerOpened ? drawerWidth * -gestureOrientation : 0));
555
+
556
+ const adjustedTranslation = Math.max(
557
+ drawerOpened ? startedOutsideTranslation : 0,
558
+ startedInsideTranslation
559
+ );
560
+
561
+ openValue.value = interpolate(
562
+ adjustedTranslation,
563
+ [-drawerWidth, 0, drawerWidth],
564
+ [1, 0, 1],
565
+ Extrapolation.CLAMP
566
+ );
567
+ })
568
+ .onEnd(handleRelease);
569
+ }, [
570
+ drawerLockMode,
571
+ openValue,
572
+ drawerWidth,
573
+ emitStateChanged,
574
+ gestureOrientation,
575
+ handleRelease,
576
+ edgeHitSlop,
577
+ fillHitSlop,
578
+ minSwipeDistance,
579
+ hideStatusBar,
580
+ keyboardDismissMode,
581
+ overlayDismissGesture,
582
+ drawerOpened,
583
+ isFromLeft,
584
+ containerWidth,
585
+ sideCorrection,
586
+ drawerState,
587
+ activeCursor,
588
+ enableTrackpadTwoFingerGesture,
589
+ mouseButton,
590
+ statusBarAnimation,
591
+ ]);
592
+
593
+ // When using RTL, row and row-reverse flex directions are flipped.
594
+ const reverseContentDirection = I18nManager.isRTL
595
+ ? isFromLeft
596
+ : !isFromLeft;
597
+
598
+ const dynamicDrawerStyles = {
599
+ backgroundColor: drawerBackgroundColor,
600
+ width: drawerWidth,
601
+ };
602
+
603
+ const containerStyles = useAnimatedStyle(() => {
604
+ if (drawerType === DrawerType.FRONT) {
605
+ return {};
606
+ }
607
+
608
+ return {
609
+ transform: [
610
+ {
611
+ translateX: interpolate(
612
+ openValue.value,
613
+ [0, 1],
614
+ [0, drawerWidth * sideCorrection],
615
+ Extrapolation.CLAMP
616
+ ),
617
+ },
618
+ ],
619
+ };
620
+ });
621
+
622
+ const drawerAnimatedStyle = useAnimatedStyle(() => {
623
+ const closedDrawerOffset = drawerWidth * -sideCorrection;
624
+ const isBack = drawerType === DrawerType.BACK;
625
+ const isIdle = drawerState === DrawerState.IDLE;
626
+
627
+ if (isBack) {
628
+ return {
629
+ transform: [{ translateX: 0 }],
630
+ flexDirection: reverseContentDirection ? 'row-reverse' : 'row',
631
+ };
632
+ }
633
+
634
+ let translateX = 0;
635
+
636
+ if (isIdle) {
637
+ translateX = drawerOpened ? 0 : closedDrawerOffset;
638
+ } else {
639
+ translateX = interpolate(
640
+ openValue.value,
641
+ [0, 1],
642
+ [closedDrawerOffset, 0],
643
+ Extrapolation.CLAMP
644
+ );
645
+ }
646
+
647
+ return {
648
+ transform: [{ translateX }],
649
+ flexDirection: reverseContentDirection ? 'row-reverse' : 'row',
650
+ };
651
+ });
652
+
653
+ const containerAnimatedProps = useAnimatedProps(() => ({
654
+ importantForAccessibility:
655
+ Platform.OS === 'android'
656
+ ? isDrawerOpen.value
657
+ ? ('no-hide-descendants' as const)
658
+ : ('yes' as const)
659
+ : undefined,
660
+ }));
661
+
662
+ const children =
663
+ typeof props.children === 'function'
664
+ ? props.children(openValue) // renderer function
665
+ : props.children;
666
+
667
+ useImperativeHandle(
668
+ ref,
669
+ () => ({
670
+ openDrawer,
671
+ closeDrawer,
672
+ }),
673
+ [openDrawer, closeDrawer]
674
+ );
675
+
676
+ return (
677
+ <GestureDetector
678
+ gesture={panGesture}
679
+ userSelect={userSelect}
680
+ enableContextMenu={enableContextMenu}>
681
+ <Animated.View style={styles.main} onLayout={handleContainerLayout}>
682
+ <GestureDetector gesture={overlayDismissGesture}>
683
+ <Animated.View
684
+ style={[
685
+ drawerType === DrawerType.FRONT
686
+ ? styles.containerOnBack
687
+ : styles.containerInFront,
688
+ containerStyles,
689
+ contentContainerStyle,
690
+ ]}
691
+ animatedProps={containerAnimatedProps}>
692
+ {children}
693
+ <Animated.View
694
+ animatedProps={overlayAnimatedProps}
695
+ style={[styles.overlay, overlayAnimatedStyle]}
696
+ />
697
+ </Animated.View>
698
+ </GestureDetector>
699
+ <Animated.View
700
+ pointerEvents="box-none"
701
+ animatedProps={drawerAnimatedProps}
702
+ style={[
703
+ styles.drawerContainer,
704
+ drawerAnimatedStyle,
705
+ drawerContainerStyle,
706
+ ]}>
707
+ <Animated.View style={dynamicDrawerStyles}>
708
+ {renderNavigationView(openValue)}
709
+ </Animated.View>
710
+ </Animated.View>
711
+ </Animated.View>
712
+ </GestureDetector>
713
+ );
714
+ }
715
+ );
716
+
717
+ export default DrawerLayout;
718
+
719
+ const styles = StyleSheet.create({
720
+ drawerContainer: {
721
+ ...StyleSheet.absoluteFillObject,
722
+ zIndex: 1001,
723
+ flexDirection: 'row',
724
+ },
725
+ containerInFront: {
726
+ ...StyleSheet.absoluteFillObject,
727
+ zIndex: 1002,
728
+ },
729
+ containerOnBack: {
730
+ ...StyleSheet.absoluteFillObject,
731
+ },
732
+ main: {
733
+ flex: 1,
734
+ zIndex: 0,
735
+ overflow: 'hidden',
736
+ },
737
+ overlay: {
738
+ ...StyleSheet.absoluteFillObject,
739
+ zIndex: 1000,
740
+ },
741
+ });