react-native-drawer-layout 2.0.0 → 3.1.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -35
  3. package/lib/commonjs/constants.js +15 -0
  4. package/lib/commonjs/constants.js.map +1 -0
  5. package/lib/commonjs/index.js +35 -0
  6. package/lib/commonjs/index.js.map +1 -0
  7. package/lib/commonjs/types.js +6 -0
  8. package/lib/commonjs/types.js.map +1 -0
  9. package/lib/commonjs/utils/DrawerGestureContext.js +12 -0
  10. package/lib/commonjs/utils/DrawerGestureContext.js.map +1 -0
  11. package/lib/commonjs/utils/DrawerProgressContext.js +12 -0
  12. package/lib/commonjs/utils/DrawerProgressContext.js.map +1 -0
  13. package/lib/commonjs/utils/useDrawerProgress.js +19 -0
  14. package/lib/commonjs/utils/useDrawerProgress.js.map +1 -0
  15. package/lib/commonjs/views/Drawer.js +89 -0
  16. package/lib/commonjs/views/Drawer.js.map +1 -0
  17. package/lib/commonjs/views/GestureHandler.android.js +17 -0
  18. package/lib/commonjs/views/GestureHandler.android.js.map +1 -0
  19. package/lib/commonjs/views/GestureHandler.ios.js +17 -0
  20. package/lib/commonjs/views/GestureHandler.ios.js.map +1 -0
  21. package/lib/commonjs/views/GestureHandler.js +33 -0
  22. package/lib/commonjs/views/GestureHandler.js.map +1 -0
  23. package/lib/commonjs/views/GestureHandlerNative.js +37 -0
  24. package/lib/commonjs/views/GestureHandlerNative.js.map +1 -0
  25. package/lib/commonjs/views/legacy/Drawer.js +419 -0
  26. package/lib/commonjs/views/legacy/Drawer.js.map +1 -0
  27. package/lib/commonjs/views/legacy/Overlay.js +74 -0
  28. package/lib/commonjs/views/legacy/Overlay.js.map +1 -0
  29. package/lib/commonjs/views/modern/Drawer.js +285 -0
  30. package/lib/commonjs/views/modern/Drawer.js.map +1 -0
  31. package/lib/commonjs/views/modern/Overlay.js +69 -0
  32. package/lib/commonjs/views/modern/Overlay.js.map +1 -0
  33. package/lib/module/constants.js +5 -0
  34. package/lib/module/constants.js.map +1 -0
  35. package/lib/module/index.js +5 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/types.js +2 -0
  38. package/lib/module/types.js.map +1 -0
  39. package/lib/module/utils/DrawerGestureContext.js +3 -0
  40. package/lib/module/utils/DrawerGestureContext.js.map +1 -0
  41. package/lib/module/utils/DrawerProgressContext.js +3 -0
  42. package/lib/module/utils/DrawerProgressContext.js.map +1 -0
  43. package/lib/module/utils/useDrawerProgress.js +10 -0
  44. package/lib/module/utils/useDrawerProgress.js.map +1 -0
  45. package/lib/module/views/Drawer.js +81 -0
  46. package/lib/module/views/Drawer.js.map +1 -0
  47. package/lib/module/views/GestureHandler.android.js +2 -0
  48. package/lib/module/views/GestureHandler.android.js.map +1 -0
  49. package/lib/module/views/GestureHandler.ios.js +2 -0
  50. package/lib/module/views/GestureHandler.ios.js.map +1 -0
  51. package/lib/module/views/GestureHandler.js +21 -0
  52. package/lib/module/views/GestureHandler.js.map +1 -0
  53. package/lib/module/views/GestureHandlerNative.js +11 -0
  54. package/lib/module/views/GestureHandlerNative.js.map +1 -0
  55. package/lib/module/views/legacy/Drawer.js +409 -0
  56. package/lib/module/views/legacy/Drawer.js.map +1 -0
  57. package/lib/module/views/legacy/Overlay.js +64 -0
  58. package/lib/module/views/legacy/Overlay.js.map +1 -0
  59. package/lib/module/views/modern/Drawer.js +276 -0
  60. package/lib/module/views/modern/Drawer.js.map +1 -0
  61. package/lib/module/views/modern/Overlay.js +60 -0
  62. package/lib/module/views/modern/Overlay.js.map +1 -0
  63. package/lib/typescript/src/constants.d.ts +5 -0
  64. package/lib/typescript/src/constants.d.ts.map +1 -0
  65. package/lib/typescript/src/index.d.ts +5 -0
  66. package/lib/typescript/src/index.d.ts.map +1 -0
  67. package/lib/typescript/src/types.d.ts +110 -0
  68. package/lib/typescript/src/types.d.ts.map +1 -0
  69. package/lib/typescript/src/utils/DrawerGestureContext.d.ts +4 -0
  70. package/lib/typescript/src/utils/DrawerGestureContext.d.ts.map +1 -0
  71. package/lib/typescript/src/utils/DrawerProgressContext.d.ts +5 -0
  72. package/lib/typescript/src/utils/DrawerProgressContext.d.ts.map +1 -0
  73. package/lib/typescript/src/utils/useDrawerProgress.d.ts +3 -0
  74. package/lib/typescript/src/utils/useDrawerProgress.d.ts.map +1 -0
  75. package/lib/typescript/src/views/Drawer.d.ts +20 -0
  76. package/lib/typescript/src/views/Drawer.d.ts.map +1 -0
  77. package/lib/typescript/src/views/GestureHandler.android.d.ts +2 -0
  78. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -0
  79. package/lib/typescript/src/views/GestureHandler.d.ts +15 -0
  80. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -0
  81. package/lib/typescript/src/views/GestureHandler.ios.d.ts +2 -0
  82. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -0
  83. package/lib/typescript/src/views/GestureHandlerNative.d.ts +6 -0
  84. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -0
  85. package/lib/typescript/src/views/legacy/Drawer.d.ts +51 -0
  86. package/lib/typescript/src/views/legacy/Drawer.d.ts.map +1 -0
  87. package/lib/typescript/src/views/legacy/Overlay.d.ts +89 -0
  88. package/lib/typescript/src/views/legacy/Overlay.d.ts.map +1 -0
  89. package/lib/typescript/src/views/modern/Drawer.d.ts +10 -0
  90. package/lib/typescript/src/views/modern/Drawer.d.ts.map +1 -0
  91. package/lib/typescript/src/views/modern/Overlay.d.ts +89 -0
  92. package/lib/typescript/src/views/modern/Overlay.d.ts.map +1 -0
  93. package/package.json +54 -94
  94. package/src/constants.tsx +4 -0
  95. package/src/index.tsx +4 -0
  96. package/src/types.tsx +122 -0
  97. package/src/utils/DrawerGestureContext.tsx +3 -0
  98. package/src/utils/DrawerProgressContext.tsx +6 -0
  99. package/src/utils/useDrawerProgress.tsx +18 -0
  100. package/src/views/Drawer.tsx +122 -0
  101. package/src/views/GestureHandler.android.tsx +1 -0
  102. package/src/views/GestureHandler.ios.tsx +1 -0
  103. package/src/views/GestureHandler.tsx +29 -0
  104. package/src/views/GestureHandlerNative.tsx +24 -0
  105. package/src/views/legacy/Drawer.tsx +682 -0
  106. package/src/views/legacy/Overlay.tsx +87 -0
  107. package/src/views/modern/Drawer.tsx +413 -0
  108. package/src/views/modern/Overlay.tsx +82 -0
  109. package/dist/DrawerLayout.js +0 -430
@@ -0,0 +1,682 @@
1
+ import * as React from 'react';
2
+ import {
3
+ I18nManager,
4
+ InteractionManager,
5
+ Keyboard,
6
+ LayoutChangeEvent,
7
+ Platform,
8
+ StatusBar,
9
+ StyleSheet,
10
+ View,
11
+ } from 'react-native';
12
+ import Animated from 'react-native-reanimated';
13
+
14
+ import {
15
+ DEFAULT_DRAWER_WIDTH,
16
+ SWIPE_MIN_DISTANCE,
17
+ SWIPE_MIN_OFFSET,
18
+ SWIPE_MIN_VELOCITY,
19
+ } from '../../constants';
20
+ import type { DrawerProps } from '../../types';
21
+ import DrawerProgressContext from '../../utils/DrawerProgressContext';
22
+ import { GestureState, PanGestureHandler } from '../GestureHandler';
23
+ import Overlay from './Overlay';
24
+
25
+ const {
26
+ Clock,
27
+ Value,
28
+ onChange,
29
+ clockRunning,
30
+ startClock,
31
+ stopClock,
32
+ spring,
33
+ abs,
34
+ add,
35
+ and,
36
+ block,
37
+ call,
38
+ cond,
39
+ divide,
40
+ eq,
41
+ event,
42
+ greaterThan,
43
+ lessThan,
44
+ max,
45
+ min,
46
+ multiply,
47
+ neq,
48
+ or,
49
+ set,
50
+ sub,
51
+ } = Animated;
52
+
53
+ const TRUE = 1;
54
+ const FALSE = 0;
55
+ const NOOP = 0;
56
+ const UNSET = -1;
57
+
58
+ const DIRECTION_LEFT = 1;
59
+ const DIRECTION_RIGHT = -1;
60
+
61
+ const SPRING_CONFIG = {
62
+ stiffness: 1000,
63
+ damping: 500,
64
+ mass: 3,
65
+ overshootClamping: true,
66
+ restDisplacementThreshold: 0.01,
67
+ restSpeedThreshold: 0.01,
68
+ };
69
+
70
+ const ANIMATED_ZERO = new Animated.Value(0);
71
+ const ANIMATED_ONE = new Animated.Value(1);
72
+
73
+ type Binary = 0 | 1;
74
+
75
+ type Props = DrawerProps & {
76
+ layout: { width: number };
77
+ };
78
+
79
+ export default class Drawer extends React.Component<Props> {
80
+ componentDidUpdate(prevProps: Props) {
81
+ const {
82
+ open,
83
+ drawerPosition,
84
+ drawerType,
85
+ swipeMinDistance,
86
+ swipeMinVelocity,
87
+ hideStatusBarOnOpen,
88
+ } = this.props;
89
+
90
+ if (
91
+ // If we're not in the middle of a transition, sync the drawer's open state
92
+ typeof this.pendingOpenValue !== 'boolean' ||
93
+ open !== this.pendingOpenValue
94
+ ) {
95
+ this.toggleDrawer(open);
96
+ }
97
+
98
+ this.pendingOpenValue = undefined;
99
+
100
+ if (open !== prevProps.open && hideStatusBarOnOpen) {
101
+ this.toggleStatusBar(open);
102
+ }
103
+
104
+ if (prevProps.drawerPosition !== drawerPosition) {
105
+ this.drawerPosition.setValue(
106
+ drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
107
+ );
108
+ }
109
+
110
+ if (prevProps.drawerType !== drawerType) {
111
+ this.isDrawerTypeFront.setValue(drawerType === 'front' ? TRUE : FALSE);
112
+ }
113
+
114
+ if (prevProps.swipeMinDistance !== swipeMinDistance) {
115
+ this.swipeDistanceThreshold.setValue(
116
+ swipeMinDistance ?? SWIPE_MIN_DISTANCE
117
+ );
118
+ }
119
+
120
+ if (prevProps.swipeMinVelocity !== swipeMinVelocity) {
121
+ this.swipeVelocityThreshold.setValue(
122
+ swipeMinVelocity ?? SWIPE_MIN_VELOCITY
123
+ );
124
+ }
125
+ }
126
+
127
+ componentWillUnmount() {
128
+ this.toggleStatusBar(false);
129
+ this.handleEndInteraction();
130
+ }
131
+
132
+ private handleEndInteraction = () => {
133
+ if (this.interactionHandle !== undefined) {
134
+ InteractionManager.clearInteractionHandle(this.interactionHandle);
135
+ this.interactionHandle = undefined;
136
+ }
137
+ };
138
+
139
+ private handleStartInteraction = () => {
140
+ if (this.interactionHandle === undefined) {
141
+ this.interactionHandle = InteractionManager.createInteractionHandle();
142
+ }
143
+ };
144
+
145
+ private getDrawerWidth = (): number => {
146
+ const { drawerStyle, layout } = this.props;
147
+ const { width = DEFAULT_DRAWER_WIDTH } =
148
+ StyleSheet.flatten(drawerStyle) || {};
149
+
150
+ if (typeof width === 'string' && width.endsWith('%')) {
151
+ // Try to calculate width if a percentage is given
152
+ const percentage = Number(width.replace(/%$/, ''));
153
+
154
+ if (Number.isFinite(percentage)) {
155
+ return layout.width * (percentage / 100);
156
+ }
157
+ }
158
+
159
+ return typeof width === 'number' ? width : 0;
160
+ };
161
+
162
+ private clock = new Clock();
163
+ private interactionHandle: number | undefined;
164
+
165
+ private isDrawerTypeFront = new Value<Binary>(
166
+ this.props.drawerType === 'front' ? TRUE : FALSE
167
+ );
168
+
169
+ private isOpen = new Value<Binary>(this.props.open ? TRUE : FALSE);
170
+ private nextIsOpen = new Value<Binary | -1>(UNSET);
171
+ private isSwiping = new Value<Binary>(FALSE);
172
+
173
+ private initialDrawerWidth = this.getDrawerWidth();
174
+
175
+ private gestureState = new Value<number>(GestureState.UNDETERMINED);
176
+ private touchX = new Value<number>(0);
177
+ private velocityX = new Value<number>(0);
178
+ private gestureX = new Value<number>(0);
179
+ private offsetX = new Value<number>(0);
180
+ private position = new Value<number>(
181
+ this.props.open
182
+ ? this.initialDrawerWidth *
183
+ (this.props.drawerPosition === 'right'
184
+ ? DIRECTION_RIGHT
185
+ : DIRECTION_LEFT)
186
+ : 0
187
+ );
188
+
189
+ private containerWidth = new Value<number>(this.props.layout.width);
190
+ private drawerWidth = new Value<number>(this.initialDrawerWidth);
191
+ private drawerOpacity = new Value<number>(
192
+ this.props.drawerType === 'permanent' ? 1 : 0
193
+ );
194
+ private drawerPosition = new Value<number>(
195
+ this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
196
+ );
197
+
198
+ // Comment stolen from react-native-gesture-handler/DrawerLayout
199
+ //
200
+ // While closing the drawer when user starts gesture outside of its area (in greyed
201
+ // out part of the window), we want the drawer to follow only once finger reaches the
202
+ // edge of the drawer.
203
+ // E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
204
+ // dots. The touch gesture starts at '*' and moves left, touch path is indicated by
205
+ // an arrow pointing left
206
+ // 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
207
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
208
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
209
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
210
+ // |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
211
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
212
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
213
+ // |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
214
+ // +---------------+ +---------------+ +---------------+ +---------------+
215
+ //
216
+ // For the above to work properly we define animated value that will keep start position
217
+ // of the gesture. Then we use that value to calculate how much we need to subtract from
218
+ // the dragX. If the gesture started on the greyed out area we take the distance from the
219
+ // edge of the drawer to the start position. Otherwise we don't subtract at all and the
220
+ // drawer be pulled back as soon as you start the pan.
221
+ //
222
+ // This is used only when drawerType is "front"
223
+ private touchDistanceFromDrawer = cond(
224
+ this.isDrawerTypeFront,
225
+ cond(
226
+ eq(this.drawerPosition, DIRECTION_LEFT),
227
+ max(
228
+ // Distance of touch start from left screen edge - Drawer width
229
+ sub(sub(this.touchX, this.gestureX), this.drawerWidth),
230
+ 0
231
+ ),
232
+ min(
233
+ multiply(
234
+ // Distance of drawer from left screen edge - Touch start point
235
+ sub(
236
+ sub(this.containerWidth, this.drawerWidth),
237
+ sub(this.touchX, this.gestureX)
238
+ ),
239
+ DIRECTION_RIGHT
240
+ ),
241
+ 0
242
+ )
243
+ ),
244
+ 0
245
+ );
246
+
247
+ private swipeDistanceThreshold = new Value<number>(
248
+ this.props.swipeMinDistance ?? SWIPE_MIN_DISTANCE
249
+ );
250
+ private swipeVelocityThreshold = new Value<number>(
251
+ this.props.swipeMinVelocity ?? SWIPE_MIN_VELOCITY
252
+ );
253
+
254
+ private currentOpenValue: boolean = this.props.open;
255
+ private pendingOpenValue: boolean | undefined;
256
+
257
+ private isStatusBarHidden: boolean = false;
258
+
259
+ private manuallyTriggerSpring = new Value<Binary>(FALSE);
260
+
261
+ private transitionTo = (isOpen: number | Animated.Node<number>) => {
262
+ const toValue = new Value(0);
263
+ const frameTime = new Value(0);
264
+
265
+ const state = {
266
+ position: this.position,
267
+ time: new Value(0),
268
+ finished: new Value(FALSE),
269
+ velocity: new Value(0),
270
+ };
271
+
272
+ return block([
273
+ cond(clockRunning(this.clock), NOOP, [
274
+ // Animation wasn't running before
275
+ // Set the initial values and start the clock
276
+ set(toValue, multiply(isOpen, this.drawerWidth, this.drawerPosition)),
277
+ set(frameTime, 0),
278
+ set(state.time, 0),
279
+ set(state.finished, FALSE),
280
+ set(state.velocity, this.velocityX),
281
+ set(this.isOpen, isOpen),
282
+ startClock(this.clock),
283
+ call([], this.handleStartInteraction),
284
+ set(this.manuallyTriggerSpring, FALSE),
285
+ ]),
286
+ spring(this.clock, state, { ...SPRING_CONFIG, toValue }),
287
+ cond(state.finished, [
288
+ // Reset gesture and velocity from previous gesture
289
+ set(this.touchX, 0),
290
+ set(this.gestureX, 0),
291
+ set(this.velocityX, 0),
292
+ set(this.offsetX, 0),
293
+ // When the animation finishes, stop the clock
294
+ stopClock(this.clock),
295
+ call([this.isOpen], ([value]: readonly Binary[]) => {
296
+ const open = Boolean(value);
297
+ this.handleEndInteraction();
298
+
299
+ if (open !== this.props.open) {
300
+ // Sync drawer's state after animation finished
301
+ // This shouldn't be necessary, but there seems to be an issue on iOS
302
+ this.toggleDrawer(this.props.open);
303
+ }
304
+ }),
305
+ ]),
306
+ ]);
307
+ };
308
+
309
+ private dragX = block([
310
+ onChange(
311
+ this.isOpen,
312
+ call([this.isOpen], ([value]: readonly Binary[]) => {
313
+ const open = Boolean(value);
314
+
315
+ this.currentOpenValue = open;
316
+
317
+ // Without this check, the drawer can go to an infinite update <-> animate loop for sync updates
318
+ if (open !== this.props.open) {
319
+ // If the mode changed, update state
320
+ if (open) {
321
+ this.props.onOpen();
322
+ } else {
323
+ this.props.onClose();
324
+ }
325
+
326
+ this.pendingOpenValue = open;
327
+
328
+ // Force componentDidUpdate to fire, whether user does a setState or not
329
+ // This allows us to detect when the user drops the update and revert back
330
+ // It's necessary to make sure that the state stays in sync
331
+ this.forceUpdate();
332
+ }
333
+ })
334
+ ),
335
+ onChange(
336
+ this.nextIsOpen,
337
+ cond(neq(this.nextIsOpen, UNSET), [
338
+ // Stop any running animations
339
+ cond(clockRunning(this.clock), stopClock(this.clock)),
340
+ // Update the open value to trigger the transition
341
+ set(this.isOpen, this.nextIsOpen),
342
+ set(this.gestureX, 0),
343
+ set(this.nextIsOpen, UNSET),
344
+ ])
345
+ ),
346
+ // This block must be after the this.isOpen listener since we check for current value
347
+ onChange(
348
+ this.isSwiping,
349
+ // Listen to updates for this value only when it changes
350
+ // Without `onChange`, this will fire even if the value didn't change
351
+ // We don't want to call the listeners if the value didn't change
352
+ call([this.isSwiping], ([value]: readonly Binary[]) => {
353
+ const { keyboardDismissMode } = this.props;
354
+
355
+ if (value === TRUE) {
356
+ if (keyboardDismissMode === 'on-drag') {
357
+ Keyboard.dismiss();
358
+ }
359
+
360
+ this.toggleStatusBar(true);
361
+ } else {
362
+ this.toggleStatusBar(this.currentOpenValue);
363
+ }
364
+ })
365
+ ),
366
+ onChange(
367
+ this.gestureState,
368
+ cond(
369
+ eq(this.gestureState, GestureState.ACTIVE),
370
+ call([], this.handleStartInteraction)
371
+ )
372
+ ),
373
+ cond(
374
+ eq(this.gestureState, GestureState.ACTIVE),
375
+ [
376
+ cond(this.isSwiping, NOOP, [
377
+ // We weren't dragging before, set it to true
378
+ set(this.isSwiping, TRUE),
379
+ // Also update the drag offset to the last position
380
+ set(this.offsetX, this.position),
381
+ ]),
382
+ // Update position with previous offset + gesture distance
383
+ set(
384
+ this.position,
385
+ add(this.offsetX, this.gestureX, this.touchDistanceFromDrawer)
386
+ ),
387
+ // Stop animations while we're dragging
388
+ stopClock(this.clock),
389
+ ],
390
+ [
391
+ set(this.isSwiping, FALSE),
392
+ set(this.touchX, 0),
393
+ this.transitionTo(
394
+ cond(
395
+ this.manuallyTriggerSpring,
396
+ this.isOpen,
397
+ cond(
398
+ or(
399
+ and(
400
+ greaterThan(abs(this.gestureX), SWIPE_MIN_OFFSET),
401
+ greaterThan(abs(this.velocityX), this.swipeVelocityThreshold)
402
+ ),
403
+ greaterThan(abs(this.gestureX), this.swipeDistanceThreshold)
404
+ ),
405
+ cond(
406
+ eq(this.drawerPosition, DIRECTION_LEFT),
407
+ // If swiped to right, open the drawer, otherwise close it
408
+ greaterThan(
409
+ cond(eq(this.velocityX, 0), this.gestureX, this.velocityX),
410
+ 0
411
+ ),
412
+ // If swiped to left, open the drawer, otherwise close it
413
+ lessThan(
414
+ cond(eq(this.velocityX, 0), this.gestureX, this.velocityX),
415
+ 0
416
+ )
417
+ ),
418
+ this.isOpen
419
+ )
420
+ )
421
+ ),
422
+ ]
423
+ ),
424
+ this.position,
425
+ ]);
426
+
427
+ private translateX = cond(
428
+ eq(this.drawerPosition, DIRECTION_RIGHT),
429
+ min(max(multiply(this.drawerWidth, -1), this.dragX), 0),
430
+ max(min(this.drawerWidth, this.dragX), 0)
431
+ );
432
+
433
+ private progress = cond(
434
+ // Check if the drawer width is available to avoid division by zero
435
+ eq(this.drawerWidth, 0),
436
+ 0,
437
+ abs(divide(this.translateX, this.drawerWidth))
438
+ );
439
+
440
+ private handleGestureEvent = event([
441
+ {
442
+ nativeEvent: {
443
+ x: this.touchX,
444
+ translationX: this.gestureX,
445
+ velocityX: this.velocityX,
446
+ },
447
+ },
448
+ ]);
449
+
450
+ private handleGestureStateChange = event([
451
+ {
452
+ nativeEvent: {
453
+ state: (s: Animated.Value<number>) => set(this.gestureState, s),
454
+ },
455
+ },
456
+ ]);
457
+
458
+ private handleContainerLayout = (e: LayoutChangeEvent) =>
459
+ this.containerWidth.setValue(e.nativeEvent.layout.width);
460
+
461
+ private handleDrawerLayout = (e: LayoutChangeEvent) => {
462
+ this.drawerWidth.setValue(e.nativeEvent.layout.width);
463
+ this.toggleDrawer(this.props.open);
464
+
465
+ // Until layout is available, drawer is hidden with opacity: 0 by default
466
+ // Show it in the next frame when layout is available
467
+ // If we don't delay it until the next frame, there's a visible flicker
468
+ requestAnimationFrame(() =>
469
+ requestAnimationFrame(() => this.drawerOpacity.setValue(1))
470
+ );
471
+ };
472
+
473
+ private toggleDrawer = (open: boolean) => {
474
+ if (this.currentOpenValue !== open) {
475
+ this.nextIsOpen.setValue(open ? TRUE : FALSE);
476
+
477
+ // This value will also be set shortly after as changing this.nextIsOpen changes this.isOpen
478
+ // However, there's a race condition on Android, so we need to set a bit earlier
479
+ this.currentOpenValue = open;
480
+ }
481
+ };
482
+
483
+ private toggleStatusBar = (hidden: boolean) => {
484
+ const { hideStatusBarOnOpen: hideStatusBar, statusBarAnimation } =
485
+ this.props;
486
+
487
+ if (hideStatusBar && this.isStatusBarHidden !== hidden) {
488
+ this.isStatusBarHidden = hidden;
489
+ StatusBar.setHidden(hidden, statusBarAnimation);
490
+ }
491
+ };
492
+
493
+ render() {
494
+ const {
495
+ open,
496
+ swipeEnabled,
497
+ drawerPosition,
498
+ drawerType,
499
+ swipeEdgeWidth,
500
+ drawerStyle,
501
+ overlayStyle,
502
+ renderDrawerContent,
503
+ children,
504
+ gestureHandlerProps,
505
+ overlayAccessibilityLabel,
506
+ } = this.props;
507
+
508
+ const isOpen = drawerType === 'permanent' ? true : open;
509
+ const isRight = drawerPosition === 'right';
510
+
511
+ const contentTranslateX =
512
+ drawerType === 'front' ? ANIMATED_ZERO : this.translateX;
513
+
514
+ const drawerTranslateX =
515
+ drawerType === 'back'
516
+ ? I18nManager.getConstants().isRTL
517
+ ? multiply(
518
+ sub(this.containerWidth, this.drawerWidth),
519
+ isRight ? 1 : -1
520
+ )
521
+ : ANIMATED_ZERO
522
+ : this.translateX;
523
+
524
+ const offset =
525
+ drawerType === 'back'
526
+ ? 0
527
+ : I18nManager.getConstants().isRTL
528
+ ? '100%'
529
+ : multiply(this.drawerWidth, -1);
530
+
531
+ // FIXME: Currently hitSlop is broken when on Android when drawer is on right
532
+ // https://github.com/software-mansion/react-native-gesture-handler/issues/569
533
+ const hitSlop = isRight
534
+ ? // Extend hitSlop to the side of the screen when drawer is closed
535
+ // This lets the user drag the drawer from the side of the screen
536
+ { right: 0, width: isOpen ? undefined : swipeEdgeWidth }
537
+ : { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
538
+
539
+ const progress = drawerType === 'permanent' ? ANIMATED_ONE : this.progress;
540
+
541
+ return (
542
+ <DrawerProgressContext.Provider value={progress}>
543
+ <PanGestureHandler
544
+ activeOffsetX={[-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]}
545
+ failOffsetY={[-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]}
546
+ onGestureEvent={this.handleGestureEvent}
547
+ onHandlerStateChange={this.handleGestureStateChange}
548
+ hitSlop={hitSlop}
549
+ enabled={drawerType !== 'permanent' && swipeEnabled}
550
+ {...gestureHandlerProps}
551
+ >
552
+ <Animated.View
553
+ onLayout={this.handleContainerLayout}
554
+ style={[
555
+ styles.main,
556
+ {
557
+ flexDirection:
558
+ drawerType === 'permanent' && !isRight
559
+ ? 'row-reverse'
560
+ : 'row',
561
+ },
562
+ ]}
563
+ >
564
+ <Animated.View
565
+ style={[
566
+ styles.content,
567
+ {
568
+ transform:
569
+ drawerType === 'permanent'
570
+ ? // Reanimated needs the property to be present, but it results in Browser bug
571
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=20574
572
+ []
573
+ : [{ translateX: contentTranslateX }],
574
+ },
575
+ ]}
576
+ >
577
+ <View
578
+ accessibilityElementsHidden={
579
+ isOpen && drawerType !== 'permanent'
580
+ }
581
+ importantForAccessibility={
582
+ isOpen && drawerType !== 'permanent'
583
+ ? 'no-hide-descendants'
584
+ : 'auto'
585
+ }
586
+ style={styles.content}
587
+ >
588
+ {children}
589
+ </View>
590
+ {
591
+ // Disable overlay if sidebar is permanent
592
+ drawerType === 'permanent' ? null : (
593
+ <Overlay
594
+ progress={progress}
595
+ onPress={() => this.toggleDrawer(false)}
596
+ accessibilityLabel={overlayAccessibilityLabel}
597
+ style={overlayStyle as any}
598
+ accessibilityElementsHidden={!isOpen}
599
+ importantForAccessibility={
600
+ isOpen ? 'auto' : 'no-hide-descendants'
601
+ }
602
+ />
603
+ )
604
+ }
605
+ </Animated.View>
606
+ <Animated.Code
607
+ // This is needed to make sure that container width updates with `setValue`
608
+ // Without this, it won't update when not used in styles
609
+ exec={this.containerWidth}
610
+ />
611
+ {drawerType === 'permanent' ? null : (
612
+ <Animated.Code
613
+ exec={block([
614
+ onChange(this.manuallyTriggerSpring, [
615
+ cond(eq(this.manuallyTriggerSpring, TRUE), [
616
+ set(this.nextIsOpen, FALSE),
617
+ call([], () => (this.currentOpenValue = false)),
618
+ ]),
619
+ ]),
620
+ ])}
621
+ />
622
+ )}
623
+ <Animated.View
624
+ removeClippedSubviews={Platform.OS !== 'ios'}
625
+ onLayout={this.handleDrawerLayout}
626
+ style={[
627
+ styles.container,
628
+ {
629
+ transform:
630
+ drawerType === 'permanent'
631
+ ? // Reanimated needs the property to be present, but it results in Browser bug
632
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=20574
633
+ []
634
+ : [{ translateX: drawerTranslateX }],
635
+ opacity: this.drawerOpacity,
636
+ },
637
+ drawerType === 'permanent'
638
+ ? // Without this, the `left`/`right` values don't get reset
639
+ isRight
640
+ ? { right: 0 }
641
+ : { left: 0 }
642
+ : [
643
+ styles.nonPermanent,
644
+ isRight ? { right: offset } : { left: offset },
645
+ { zIndex: drawerType === 'back' ? -1 : 0 },
646
+ ],
647
+ drawerStyle as any,
648
+ ]}
649
+ >
650
+ {renderDrawerContent()}
651
+ </Animated.View>
652
+ </Animated.View>
653
+ </PanGestureHandler>
654
+ </DrawerProgressContext.Provider>
655
+ );
656
+ }
657
+ }
658
+
659
+ const styles = StyleSheet.create({
660
+ container: {
661
+ backgroundColor: 'white',
662
+ maxWidth: '100%',
663
+ },
664
+ nonPermanent: {
665
+ position: 'absolute',
666
+ top: 0,
667
+ bottom: 0,
668
+ width: DEFAULT_DRAWER_WIDTH,
669
+ },
670
+ content: {
671
+ flex: 1,
672
+ },
673
+ main: {
674
+ flex: 1,
675
+ ...Platform.select({
676
+ // FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
677
+ // But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
678
+ web: null,
679
+ default: { overflow: 'hidden' },
680
+ }),
681
+ },
682
+ });