react-native-drawer-layout 4.2.0 → 5.0.0-alpha.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.
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../../src/views/Drawer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,wBAAgB,MAAM,CAAC,EACrB,SAAiB,EACjB,cAAuD,EACvD,WAAW,EACX,UAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,IAAI,EACJ,YAAY,EACZ,yBAAyB,EACzB,mBAAmB,EACnB,QAAQ,EACR,KAAK,GACN,EAAE,WAAW,2CA6Hb"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../../src/views/Drawer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,wBAAgB,MAAM,CAAC,EACrB,SAAiB,EACjB,cAAuD,EACvD,WAAW,EACX,UAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,eAAe,EACf,IAAI,EACJ,YAAY,EACZ,yBAAyB,EACzB,mBAAmB,EACnB,QAAQ,EACR,KAAK,GACN,EAAE,WAAW,2CA2Hb"}
@@ -1,3 +1,3 @@
1
1
  import type { DrawerProps } from '../types';
2
- export declare function Drawer({ layout: customLayout, direction, drawerPosition, drawerStyle, drawerType, configureGestureHandler, hideStatusBarOnOpen, keyboardDismissMode, onClose, onOpen, onGestureStart, onGestureCancel, onGestureEnd, onTransitionStart, onTransitionEnd, open, overlayStyle, overlayAccessibilityLabel, statusBarAnimation, swipeEnabled, swipeEdgeWidth, swipeMinDistance, swipeMinVelocity, renderDrawerContent, children, style, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function Drawer({ direction, drawerPosition, drawerStyle, drawerType, configureGestureHandler, hideStatusBarOnOpen, keyboardDismissMode, onClose, onOpen, onGestureStart, onGestureCancel, onGestureEnd, onTransitionStart, onTransitionEnd, open, overlayStyle, overlayAccessibilityLabel, statusBarAnimation, swipeEnabled, swipeEdgeWidth, swipeMinDistance, swipeMinVelocity, renderDrawerContent, children, style, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
3
3
  //# sourceMappingURL=Drawer.native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.native.d.ts","sourceRoot":"","sources":["../../../../src/views/Drawer.native.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAuB5C,wBAAgB,MAAM,CAAC,EACrB,MAAM,EAAE,YAAY,EACpB,SAA4D,EAC5D,cAAuD,EACvD,WAAW,EACX,UAAoB,EACpB,uBAAuB,EACvB,mBAA2B,EAC3B,mBAA+B,EAC/B,OAAO,EACP,MAAM,EACN,cAAc,EACd,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,IAAI,EACJ,YAAY,EACZ,yBAAyB,EACzB,kBAA4B,EAC5B,YAEyB,EACzB,cAAiC,EACjC,gBAAqC,EACrC,gBAAqC,EACrC,mBAAmB,EACnB,QAAQ,EACR,KAAK,GACN,EAAE,WAAW,2CAgZb"}
1
+ {"version":3,"file":"Drawer.native.d.ts","sourceRoot":"","sources":["../../../../src/views/Drawer.native.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAuB5C,wBAAgB,MAAM,CAAC,EACrB,SAA4D,EAC5D,cAAuD,EACvD,WAAW,EACX,UAAoB,EACpB,uBAAuB,EACvB,mBAA2B,EAC3B,mBAA+B,EAC/B,OAAO,EACP,MAAM,EACN,cAAc,EACd,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,IAAI,EACJ,YAAY,EACZ,yBAAyB,EACzB,kBAA4B,EAC5B,YAEyB,EACzB,cAAiC,EACjC,gBAAqC,EACrC,gBAAqC,EACrC,mBAAmB,EACnB,QAAQ,EACR,KAAK,GACN,EAAE,WAAW,2CA4db"}
@@ -1 +1 @@
1
- {"version":3,"file":"Overlay.native.d.ts","sourceRoot":"","sources":["../../../../src/views/Overlay.native.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,OAAO,EACP,KAAK,EACL,kBAAmC,EACnC,GAAG,IAAI,EACR,EAAE,YAAY,2CA+Bd"}
1
+ {"version":3,"file":"Overlay.native.d.ts","sourceRoot":"","sources":["../../../../src/views/Overlay.native.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,OAAO,EACP,KAAK,EACL,kBAAmC,EACnC,GAAG,IAAI,EACR,EAAE,YAAY,2CAiCd"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-drawer-layout",
3
3
  "description": "Drawer component for React Native",
4
- "version": "4.2.0",
4
+ "version": "5.0.0-alpha.0",
5
5
  "keywords": [
6
6
  "react-native-component",
7
7
  "react-component",
@@ -43,25 +43,22 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "color": "^4.2.3",
46
- "use-latest-callback": "^0.2.4"
46
+ "use-latest-callback": "^0.3.2"
47
47
  },
48
48
  "devDependencies": {
49
- "@jest/globals": "^30.0.0",
50
- "@testing-library/react-native": "^13.2.0",
51
- "@types/color": "^4.2.0",
52
- "@types/react": "~19.0.10",
53
49
  "del-cli": "^6.0.0",
54
- "react": "19.0.0",
55
- "react-native": "0.79.3",
50
+ "react": "19.1.0",
51
+ "react-native": "0.81.4",
56
52
  "react-native-builder-bob": "^0.40.12",
57
- "react-test-renderer": "19.0.0",
58
- "typescript": "^5.8.3"
53
+ "react-native-reanimated": "4.1.3",
54
+ "react-native-worklets": "0.6.1",
55
+ "typescript": "^5.9.2"
59
56
  },
60
57
  "peerDependencies": {
61
- "react": ">= 18.2.0",
58
+ "react": ">= 19.0.0",
62
59
  "react-native": "*",
63
60
  "react-native-gesture-handler": ">= 2.0.0",
64
- "react-native-reanimated": ">= 2.0.0"
61
+ "react-native-reanimated": ">= 4.0.0"
65
62
  },
66
63
  "react-native-builder-bob": {
67
64
  "source": "src",
@@ -81,5 +78,5 @@
81
78
  ]
82
79
  ]
83
80
  },
84
- "gitHead": "6f98ce195ea29883f4242e56390b334547c68bc5"
81
+ "gitHead": "46daae524ec6a59737147ed506222dd09a5b6e39"
85
82
  }
package/src/types.tsx CHANGED
@@ -3,8 +3,6 @@ import type { StyleProp, View, ViewStyle } from 'react-native';
3
3
  import type { PanGesture } from 'react-native-gesture-handler';
4
4
  import type { SharedValue } from 'react-native-reanimated';
5
5
 
6
- export type Layout = { width: number; height: number };
7
-
8
6
  export type DrawerProps = {
9
7
  /**
10
8
  * Whether the drawer is open or not.
@@ -51,15 +49,9 @@ export type DrawerProps = {
51
49
  */
52
50
  renderDrawerContent: () => React.ReactNode;
53
51
 
54
- /**
55
- * Object containing the layout of the container.
56
- * Defaults to the dimensions of the application's window.
57
- */
58
- layout?: { width: number; height: number };
59
-
60
52
  /**
61
53
  * Locale direction of the drawer.
62
- * Defaults to `rtl` when `I18nManager.isRTL` is `true` on Android & iOS, otherwise `ltr`.
54
+ * Defaults to `rtl` when `I18nManager.getConstants().isRTL` is `true` on Android & iOS, otherwise `ltr`.
63
55
  */
64
56
  direction?: 'ltr' | 'rtl';
65
57
 
@@ -1,4 +1,4 @@
1
- import { type StyleProp, StyleSheet, type ViewStyle } from 'react-native';
1
+ import { type DimensionValue, type ViewStyle } from 'react-native';
2
2
 
3
3
  const APPROX_APP_BAR_HEIGHT = 56;
4
4
  const DEFAULT_DRAWER_WIDTH = 360;
@@ -11,25 +11,27 @@ const DEFAULT_DRAWER_WIDTH = 360;
11
11
  const DRAWER_DEFAULT_WIDTH_WEB = `min(calc(100% - ${APPROX_APP_BAR_HEIGHT}px), ${DEFAULT_DRAWER_WIDTH}px)`;
12
12
 
13
13
  export function getDrawerWidthNative({
14
- layout,
15
- drawerStyle,
14
+ containerWidth,
15
+ customWidth,
16
16
  }: {
17
- layout: { width: number; height: number };
18
- drawerStyle?: StyleProp<ViewStyle>;
17
+ containerWidth: number;
18
+ customWidth: DimensionValue | undefined | null;
19
19
  }) {
20
+ 'worklet';
21
+
20
22
  const defaultWidth =
21
- layout.width - APPROX_APP_BAR_HEIGHT <= 360
22
- ? layout.width - APPROX_APP_BAR_HEIGHT
23
+ containerWidth - APPROX_APP_BAR_HEIGHT <= 360
24
+ ? containerWidth - APPROX_APP_BAR_HEIGHT
23
25
  : DEFAULT_DRAWER_WIDTH;
24
26
 
25
- const { width = defaultWidth } = StyleSheet.flatten(drawerStyle) || {};
27
+ const width = customWidth ?? defaultWidth;
26
28
 
27
29
  if (typeof width === 'string' && width.endsWith('%')) {
28
30
  // Try to calculate width if a percentage is given
29
31
  const percentage = Number(width.replace(/%$/, ''));
30
32
 
31
33
  if (Number.isFinite(percentage)) {
32
- return layout.width * (percentage / 100);
34
+ return containerWidth * (percentage / 100);
33
35
  }
34
36
  }
35
37
 
@@ -39,9 +41,9 @@ export function getDrawerWidthNative({
39
41
  export function getDrawerWidthWeb({
40
42
  drawerStyle,
41
43
  }: {
42
- drawerStyle?: StyleProp<ViewStyle>;
44
+ drawerStyle: ViewStyle;
43
45
  }): string {
44
- const { width } = StyleSheet.flatten(drawerStyle) || {};
46
+ const { width } = drawerStyle;
45
47
 
46
48
  if (typeof width === 'number') {
47
49
  return `${width}px`;
@@ -1,12 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import {
3
+ Dimensions,
3
4
  I18nManager,
4
- InteractionManager,
5
5
  Keyboard,
6
+ type LayoutChangeEvent,
6
7
  Platform,
7
8
  StatusBar,
8
9
  StyleSheet,
9
- useWindowDimensions,
10
10
  View,
11
11
  } from 'react-native';
12
12
  import Animated, {
@@ -44,7 +44,6 @@ const minmax = (value: number, start: number, end: number) => {
44
44
  };
45
45
 
46
46
  export function Drawer({
47
- layout: customLayout,
48
47
  direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
49
48
  drawerPosition = direction === 'rtl' ? 'right' : 'left',
50
49
  drawerStyle,
@@ -73,25 +72,27 @@ export function Drawer({
73
72
  children,
74
73
  style,
75
74
  }: DrawerProps) {
76
- const windowDimensions = useWindowDimensions();
77
-
78
- const layout = customLayout ?? windowDimensions;
79
- const drawerWidth = getDrawerWidthNative({ layout, drawerStyle });
75
+ const { width: customWidth } = StyleSheet.flatten(drawerStyle) || {};
80
76
 
81
77
  const isOpen = drawerType === 'permanent' ? true : open;
82
78
  const isRight = drawerPosition === 'right';
83
79
 
84
80
  const getDrawerTranslationX = React.useCallback(
85
- (open: boolean) => {
81
+ (open: boolean, containerWidth: number) => {
86
82
  'worklet';
87
83
 
84
+ const drawerWidth = getDrawerWidthNative({
85
+ containerWidth,
86
+ customWidth,
87
+ });
88
+
88
89
  if (drawerPosition === 'left') {
89
90
  return open ? 0 : -drawerWidth;
90
91
  }
91
92
 
92
93
  return open ? 0 : drawerWidth;
93
94
  },
94
- [drawerPosition, drawerWidth]
95
+ [customWidth, drawerPosition]
95
96
  );
96
97
 
97
98
  const hideStatusBar = React.useCallback(
@@ -109,19 +110,6 @@ export function Drawer({
109
110
  return () => hideStatusBar(false);
110
111
  }, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]);
111
112
 
112
- const interactionHandleRef = React.useRef<number | null>(null);
113
-
114
- const startInteraction = useLatestCallback(() => {
115
- interactionHandleRef.current = InteractionManager.createInteractionHandle();
116
- });
117
-
118
- const endInteraction = useLatestCallback(() => {
119
- if (interactionHandleRef.current != null) {
120
- InteractionManager.clearInteractionHandle(interactionHandleRef.current);
121
- interactionHandleRef.current = null;
122
- }
123
- });
124
-
125
113
  const hideKeyboard = useLatestCallback(() => {
126
114
  if (keyboardDismissMode === 'on-drag') {
127
115
  Keyboard.dismiss();
@@ -130,19 +118,16 @@ export function Drawer({
130
118
 
131
119
  const onGestureBegin = useLatestCallback(() => {
132
120
  onGestureStart?.();
133
- startInteraction();
134
121
  hideKeyboard();
135
122
  hideStatusBar(true);
136
123
  });
137
124
 
138
125
  const onGestureFinish = useLatestCallback(() => {
139
126
  onGestureEnd?.();
140
- endInteraction();
141
127
  });
142
128
 
143
129
  const onGestureAbort = useLatestCallback(() => {
144
130
  onGestureCancel?.();
145
- endInteraction();
146
131
  });
147
132
 
148
133
  const hitSlop = React.useMemo(
@@ -155,9 +140,55 @@ export function Drawer({
155
140
  [isRight, isOpen, swipeEdgeWidth]
156
141
  );
157
142
 
143
+ const [initialWidth] = React.useState(() => Dimensions.get('window').width);
144
+
145
+ const layoutWidth = useSharedValue(initialWidth);
146
+ const translationX = useSharedValue(
147
+ getDrawerTranslationX(open, initialWidth)
148
+ );
149
+
150
+ const contentRef = React.useRef<View>(null);
151
+
152
+ React.useLayoutEffect(() => {
153
+ const measureLayout = () => {
154
+ contentRef.current?.measure((_x, _y, width) => {
155
+ layoutWidth.set(width);
156
+ translationX.set(getDrawerTranslationX(open, width));
157
+ });
158
+ };
159
+
160
+ measureLayout();
161
+
162
+ // FIXME: the layout is off after screen rotation
163
+ // Measure the layout again on screen rotation
164
+ let handle: number | undefined;
165
+
166
+ const subscription = Dimensions.addEventListener('change', () => {
167
+ // The measurement is not accurate without the delay
168
+ handle = requestAnimationFrame(() => {
169
+ measureLayout();
170
+ });
171
+ });
172
+
173
+ return () => {
174
+ if (handle != null) {
175
+ cancelAnimationFrame(handle);
176
+ }
177
+
178
+ subscription.remove();
179
+ };
180
+
181
+ // only measure on initial render
182
+ // subsequent updates will be handled by onLayout
183
+ // eslint-disable-next-line react-hooks/exhaustive-deps
184
+ }, []);
185
+
186
+ const onLayout = (event: LayoutChangeEvent) => {
187
+ layoutWidth.set(event.nativeEvent.layout.width);
188
+ };
189
+
158
190
  const touchStartX = useSharedValue(0);
159
191
  const touchX = useSharedValue(0);
160
- const translationX = useSharedValue(getDrawerTranslationX(open));
161
192
  const gestureState = useSharedValue<GestureState>(GestureState.UNDETERMINED);
162
193
 
163
194
  const onAnimationStart = useLatestCallback((open: boolean) => {
@@ -174,31 +205,47 @@ export function Drawer({
174
205
  }
175
206
  );
176
207
 
208
+ const animatingTo = useSharedValue<'open' | 'close' | null>(null);
209
+
177
210
  const toggleDrawer = React.useCallback(
178
211
  (open: boolean, velocity?: number) => {
179
212
  'worklet';
180
213
 
181
- const translateX = getDrawerTranslationX(open);
214
+ touchStartX.set(0);
215
+ touchX.set(0);
216
+
217
+ const containerWidth = layoutWidth.get();
218
+ const toValue = getDrawerTranslationX(open, containerWidth);
219
+
220
+ if (translationX.get() === toValue) {
221
+ return;
222
+ }
223
+
224
+ if (animatingTo.get() === (open ? 'open' : 'close')) {
225
+ return;
226
+ }
182
227
 
183
228
  if (velocity === undefined) {
184
229
  runOnJS(onAnimationStart)(open);
185
230
  }
186
231
 
187
- touchStartX.value = 0;
188
- touchX.value = 0;
189
- translationX.value = withSpring(
190
- translateX,
191
- {
192
- velocity,
193
- stiffness: 1000,
194
- damping: 500,
195
- mass: 3,
196
- overshootClamping: true,
197
- restDisplacementThreshold: 0.01,
198
- restSpeedThreshold: 0.01,
199
- reduceMotion: ReduceMotion.Never,
200
- },
201
- (finished) => runOnJS(onAnimationEnd)(open, finished)
232
+ animatingTo.set(open ? 'open' : 'close');
233
+ translationX.set(
234
+ withSpring(
235
+ toValue,
236
+ {
237
+ velocity,
238
+ stiffness: 1000,
239
+ damping: 500,
240
+ mass: 3,
241
+ overshootClamping: true,
242
+ reduceMotion: ReduceMotion.Never,
243
+ },
244
+ (finished) => {
245
+ animatingTo.set(null);
246
+ runOnJS(onAnimationEnd)(open, finished);
247
+ }
248
+ )
202
249
  );
203
250
 
204
251
  if (open) {
@@ -208,18 +255,22 @@ export function Drawer({
208
255
  }
209
256
  },
210
257
  [
211
- getDrawerTranslationX,
212
- onAnimationEnd,
213
- onAnimationStart,
214
- onClose,
215
- onOpen,
216
258
  touchStartX,
217
259
  touchX,
260
+ layoutWidth,
261
+ getDrawerTranslationX,
218
262
  translationX,
263
+ animatingTo,
264
+ onAnimationStart,
265
+ onAnimationEnd,
266
+ onOpen,
267
+ onClose,
219
268
  ]
220
269
  );
221
270
 
222
- React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
271
+ React.useEffect(() => {
272
+ toggleDrawer(open);
273
+ }, [animatingTo, open, toggleDrawer]);
223
274
 
224
275
  const startX = useSharedValue(0);
225
276
 
@@ -228,9 +279,9 @@ export function Drawer({
228
279
  .onBegin((event) => {
229
280
  'worklet';
230
281
 
231
- startX.value = translationX.value;
232
- gestureState.value = event.state;
233
- touchStartX.value = event.x;
282
+ startX.set(translationX.get());
283
+ gestureState.set(event.state);
284
+ touchStartX.set(event.x);
234
285
  })
235
286
  .onStart(() => {
236
287
  'worklet';
@@ -240,14 +291,14 @@ export function Drawer({
240
291
  .onChange((event) => {
241
292
  'worklet';
242
293
 
243
- touchX.value = event.x;
244
- translationX.value = startX.value + event.translationX;
245
- gestureState.value = event.state;
294
+ touchX.set(event.x);
295
+ translationX.set(startX.get() + event.translationX);
296
+ gestureState.set(event.state);
246
297
  })
247
298
  .onEnd((event, success) => {
248
299
  'worklet';
249
300
 
250
- gestureState.value = event.state;
301
+ gestureState.set(event.state);
251
302
 
252
303
  if (!success) {
253
304
  runOnJS(onGestureAbort)();
@@ -300,6 +351,11 @@ export function Drawer({
300
351
  ]);
301
352
 
302
353
  const translateX = useDerivedValue(() => {
354
+ const drawerWidth = getDrawerWidthNative({
355
+ containerWidth: layoutWidth.get(),
356
+ customWidth,
357
+ });
358
+
303
359
  // Comment stolen from react-native-gesture-handler/DrawerLayout
304
360
  //
305
361
  // While closing the drawer when user starts gesture outside of its area (in greyed
@@ -326,32 +382,38 @@ export function Drawer({
326
382
  //
327
383
  // This is used only when drawerType is "front"
328
384
  const touchDistance =
329
- drawerType === 'front' && gestureState.value === GestureState.ACTIVE
385
+ drawerType === 'front' && gestureState.get() === GestureState.ACTIVE
330
386
  ? minmax(
331
387
  drawerPosition === 'left'
332
- ? touchStartX.value - drawerWidth
333
- : layout.width - drawerWidth - touchStartX.value,
388
+ ? touchStartX.get() - drawerWidth
389
+ : layoutWidth.get() - drawerWidth - touchStartX.get(),
334
390
  0,
335
- layout.width
391
+ layoutWidth.get()
336
392
  )
337
393
  : 0;
338
394
 
339
395
  const translateX =
340
396
  drawerPosition === 'left'
341
- ? minmax(translationX.value + touchDistance, -drawerWidth, 0)
342
- : minmax(translationX.value - touchDistance, 0, drawerWidth);
397
+ ? minmax(translationX.get() + touchDistance, -drawerWidth, 0)
398
+ : minmax(translationX.get() - touchDistance, 0, drawerWidth);
343
399
 
344
400
  return translateX;
345
401
  });
346
402
 
347
403
  const drawerAnimatedStyle = useAnimatedStyle(() => {
348
- const distanceFromEdge = layout.width - drawerWidth;
404
+ const drawerWidth = getDrawerWidthNative({
405
+ containerWidth: layoutWidth.get(),
406
+ customWidth,
407
+ });
408
+
409
+ const distanceFromEdge = layoutWidth.get() - drawerWidth;
349
410
 
350
411
  return {
412
+ width: drawerWidth,
351
413
  // FIXME: Reanimated skips committing to the shadow tree if no layout props are animated
352
414
  // This results in pressables not getting their correct position and can't be pressed
353
415
  // So we animate the zIndex to force the commit - it doesn't affect the drawer visually
354
- zIndex: translateX.value === 0 ? 0 : 1,
416
+ zIndex: translateX.get() === 0 ? 0 : 1,
355
417
  transform:
356
418
  drawerType === 'permanent'
357
419
  ? // Reanimated needs the property to be present, but it results in Browser bug
@@ -361,7 +423,7 @@ export function Drawer({
361
423
  {
362
424
  translateX:
363
425
  // The drawer stays in place when `drawerType` is `back`
364
- (drawerType === 'back' ? 0 : translateX.value) +
426
+ (drawerType === 'back' ? 0 : translateX.get()) +
365
427
  (direction === 'rtl'
366
428
  ? drawerPosition === 'left'
367
429
  ? -distanceFromEdge
@@ -373,18 +435,23 @@ export function Drawer({
373
435
  ],
374
436
  };
375
437
  }, [
438
+ customWidth,
376
439
  direction,
377
440
  drawerPosition,
378
441
  drawerType,
379
- drawerWidth,
380
- layout.width,
442
+ layoutWidth,
381
443
  translateX,
382
444
  ]);
383
445
 
384
446
  const contentAnimatedStyle = useAnimatedStyle(() => {
447
+ const drawerWidth = getDrawerWidthNative({
448
+ containerWidth: layoutWidth.get(),
449
+ customWidth,
450
+ });
451
+
385
452
  return {
386
453
  // FIXME: Force Reanimated to commit to the shadow tree
387
- zIndex: translateX.value === 0 ? 0 : drawerType === 'back' ? 2 : 1,
454
+ zIndex: translateX.get() === 0 ? 0 : drawerType === 'back' ? 2 : 1,
388
455
  transform:
389
456
  drawerType === 'permanent'
390
457
  ? // Reanimated needs the property to be present, but it results in Browser bug
@@ -396,19 +463,24 @@ export function Drawer({
396
463
  // The screen content stays in place when `drawerType` is `front`
397
464
  drawerType === 'front'
398
465
  ? 0
399
- : translateX.value +
466
+ : translateX.get() +
400
467
  drawerWidth * (drawerPosition === 'left' ? 1 : -1),
401
468
  },
402
469
  ],
403
470
  };
404
- }, [drawerPosition, drawerType, drawerWidth, translateX]);
471
+ }, [customWidth, drawerPosition, drawerType, layoutWidth, translateX]);
405
472
 
406
473
  const progress = useDerivedValue(() => {
474
+ const containerWidth = layoutWidth.get();
475
+
407
476
  return drawerType === 'permanent'
408
477
  ? 1
409
478
  : interpolate(
410
- translateX.value,
411
- [getDrawerTranslationX(false), getDrawerTranslationX(true)],
479
+ translateX.get(),
480
+ [
481
+ getDrawerTranslationX(false, containerWidth),
482
+ getDrawerTranslationX(true, containerWidth),
483
+ ],
412
484
  [0, 1]
413
485
  );
414
486
  });
@@ -433,7 +505,11 @@ export function Drawer({
433
505
  },
434
506
  ]}
435
507
  >
436
- <Animated.View style={[styles.content, contentAnimatedStyle]}>
508
+ <Animated.View
509
+ ref={contentRef}
510
+ onLayout={onLayout}
511
+ style={[styles.content, contentAnimatedStyle]}
512
+ >
437
513
  <View
438
514
  aria-hidden={isOpen && drawerType !== 'permanent'}
439
515
  style={styles.content}
@@ -455,7 +531,6 @@ export function Drawer({
455
531
  style={[
456
532
  styles.drawer,
457
533
  {
458
- width: drawerWidth,
459
534
  position:
460
535
  drawerType === 'permanent' ? 'relative' : 'absolute',
461
536
  zIndex: drawerType === 'back' ? -1 : 0,
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { StyleSheet, View } from 'react-native';
2
+ import { StyleSheet, View, type ViewStyle } from 'react-native';
3
3
  import useLatestCallback from 'use-latest-callback';
4
4
 
5
5
  import type { DrawerProps } from '../types';
@@ -23,14 +23,15 @@ export function Drawer({
23
23
  children,
24
24
  style,
25
25
  }: DrawerProps) {
26
+ const flattenedDrawerStyle = StyleSheet.flatten(drawerStyle) || {};
26
27
  const drawerWidth = getDrawerWidthWeb({
27
- drawerStyle,
28
+ drawerStyle: flattenedDrawerStyle,
28
29
  });
29
30
 
30
31
  const progress = useFakeSharedValue(open ? 1 : 0);
31
32
 
32
33
  React.useEffect(() => {
33
- progress.value = open ? 1 : 0;
34
+ progress.set(open ? 1 : 0);
34
35
  }, [open, progress]);
35
36
 
36
37
  const drawerRef = React.useRef<View>(null);
@@ -99,9 +100,9 @@ export function Drawer({
99
100
  position: drawerType === 'permanent' ? 'relative' : 'absolute',
100
101
  zIndex: drawerType === 'back' ? -1 : 1,
101
102
  },
102
- // @ts-expect-error: width contains `calc` for web
103
- { width: drawerWidth },
104
- // @ts-expect-error: offset contains `calc` for web
103
+ // FIXME: width contains `px` on web
104
+ { width: drawerWidth } as ViewStyle,
105
+ // @ts-expect-error offset contains `calc` for web
105
106
  drawerType !== 'permanent'
106
107
  ? // Position drawer off-screen by default in closed state
107
108
  // And add a translation only when drawer is open
@@ -114,18 +115,15 @@ export function Drawer({
114
115
  drawerStyle,
115
116
  ]}
116
117
  >
117
- {renderDrawerContent()}
118
+ <Inert enabled={drawerType !== 'permanent' && !isOpen}>
119
+ {renderDrawerContent()}
120
+ </Inert>
118
121
  </View>
119
122
  );
120
123
 
121
124
  const mainContent = (
122
125
  <View key="content" style={[styles.content, contentAnimatedStyle]}>
123
- <View
124
- aria-hidden={isOpen && drawerType !== 'permanent'}
125
- style={styles.content}
126
- >
127
- {children}
128
- </View>
126
+ <Inert enabled={drawerType !== 'permanent' && isOpen}>{children}</Inert>
129
127
  {drawerType !== 'permanent' ? (
130
128
  <Overlay
131
129
  open={open}
@@ -149,6 +147,30 @@ export function Drawer({
149
147
  );
150
148
  }
151
149
 
150
+ function Inert({
151
+ enabled,
152
+ children,
153
+ }: {
154
+ enabled: boolean;
155
+ children: React.ReactNode;
156
+ }) {
157
+ return (
158
+ <div
159
+ inert={enabled}
160
+ aria-hidden={enabled}
161
+ style={{
162
+ display: 'flex',
163
+ flexDirection: 'column',
164
+ flexGrow: 1,
165
+ flexShrink: 1,
166
+ flexBasis: 'auto',
167
+ }}
168
+ >
169
+ {children}
170
+ </div>
171
+ );
172
+ }
173
+
152
174
  const styles = StyleSheet.create({
153
175
  container: {
154
176
  flex: 1,
@@ -16,8 +16,11 @@ export function Overlay({
16
16
  ...rest
17
17
  }: OverlayProps) {
18
18
  const animatedStyle = useAnimatedStyle(() => {
19
+ const active = progress.value > PROGRESS_EPSILON;
20
+
19
21
  return {
20
22
  opacity: progress.value,
23
+ pointerEvents: active ? 'auto' : 'none',
21
24
  };
22
25
  }, [progress]);
23
26
 
@@ -25,7 +28,6 @@ export function Overlay({
25
28
  const active = progress.value > PROGRESS_EPSILON;
26
29
 
27
30
  return {
28
- 'pointerEvents': active ? 'auto' : 'none',
29
31
  'aria-hidden': !active,
30
32
  } as const;
31
33
  }, [progress]);