@lodev09/react-native-true-sheet 3.11.7 → 3.11.9

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,5 +1,5 @@
1
- import { createContext, useContext, useEffect, useRef, type RefObject } from 'react';
2
- import { View, type LayoutChangeEvent, type ViewProps } from 'react-native';
1
+ import { createContext, type RefObject, useContext, useEffect, useRef } from 'react';
2
+ import { type LayoutChangeEvent, View, type ViewProps } from 'react-native';
3
3
 
4
4
  /**
5
5
  * Reports the measured peek content height to the owning TrueSheet.
@@ -7,11 +7,25 @@ import { View, type LayoutChangeEvent, type ViewProps } from 'react-native';
7
7
  */
8
8
  export interface TrueSheetPeekContextValue {
9
9
  contentRef: RefObject<View | null>;
10
+ peekRef: RefObject<View | null>;
10
11
  setPeekContentHeight: (height: number) => void;
11
12
  }
12
13
 
13
14
  export const TrueSheetPeekContext = createContext<TrueSheetPeekContextValue | null>(null);
14
15
 
16
+ /**
17
+ * Distance from the top of the content view to the bottom of the peek view,
18
+ * so the peek view's offset within the content (padding, views above it)
19
+ * counts toward the peek detent. Both rects live in the drawer's transformed
20
+ * subtree, so an in-flight translate cancels out of the delta.
21
+ * @internal
22
+ */
23
+ export const measurePeekContentHeight = (
24
+ peekElement: HTMLElement,
25
+ contentElement: HTMLElement
26
+ ): number =>
27
+ peekElement.getBoundingClientRect().bottom - contentElement.getBoundingClientRect().top;
28
+
15
29
  /**
16
30
  * Wrapper component that marks its children as the sheet's peek content.
17
31
  * When rendered within a `TrueSheet`, the `"peek"` detent reveals everything
@@ -20,7 +34,11 @@ export const TrueSheetPeekContext = createContext<TrueSheetPeekContextValue | nu
20
34
  */
21
35
  export const TrueSheetPeek = ({ onLayout, ...rest }: ViewProps) => {
22
36
  const context = useContext(TrueSheetPeekContext);
23
- const viewRef = useRef<View>(null);
37
+ const localRef = useRef<View>(null);
38
+
39
+ // Register into the sheet's peek ref so it can measure the peek live
40
+ // during detent geometry computation (state lags the first willPresent).
41
+ const viewRef = context?.peekRef ?? localRef;
24
42
 
25
43
  useEffect(() => () => context?.setPeekContentHeight(0), [context]);
26
44
 
@@ -30,12 +48,9 @@ export const TrueSheetPeek = ({ onLayout, ...rest }: ViewProps) => {
30
48
  const peekElement = viewRef.current as unknown as HTMLElement | null;
31
49
  const contentElement = context.contentRef.current as unknown as HTMLElement | null;
32
50
 
33
- // Distance from the top of the content view to the bottom of the peek view,
34
- // so the peek view's offset within the content (padding, views above it)
35
- // counts toward the peek detent.
36
51
  const bottom =
37
52
  peekElement && contentElement
38
- ? peekElement.getBoundingClientRect().bottom - contentElement.getBoundingClientRect().top
53
+ ? measurePeekContentHeight(peekElement, contentElement)
39
54
  : event.nativeEvent.layout.height;
40
55
 
41
56
  context.setPeekContentHeight(bottom);
@@ -1,12 +1,24 @@
1
1
  import React from 'react';
2
2
  import type { DrawerDirection } from './types';
3
3
 
4
+ export interface DragEvent {
5
+ target: EventTarget;
6
+ pageX: number;
7
+ pageY: number;
8
+ pointerType: string;
9
+ }
10
+
4
11
  interface DrawerContextValue {
5
12
  drawerRef: React.RefObject<HTMLDivElement | null>;
6
13
  overlayRef: React.RefObject<HTMLDivElement | null>;
7
14
  onPress: (event: React.PointerEvent<HTMLDivElement>) => void;
8
15
  onRelease: (event: React.PointerEvent<HTMLDivElement> | null) => void;
9
16
  onDrag: (event: React.PointerEvent<HTMLDivElement>) => void;
17
+ onTouchDrag: (event: DragEvent, pointerEvent?: React.PointerEvent<HTMLDivElement>) => void;
18
+ onTouchRelease: (
19
+ event: DragEvent | null,
20
+ pointerEvent?: React.PointerEvent<HTMLDivElement>
21
+ ) => void;
10
22
  onNestedDrag: (event: React.PointerEvent<HTMLDivElement>, percentageDragged: number) => void;
11
23
  onNestedOpenChange: (o: boolean) => void;
12
24
  onNestedRelease: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
@@ -47,6 +59,8 @@ export const DrawerContext = React.createContext<DrawerContextValue>({
47
59
  onPress: () => {},
48
60
  onRelease: () => {},
49
61
  onDrag: () => {},
62
+ onTouchDrag: () => {},
63
+ onTouchRelease: () => {},
50
64
  onNestedDrag: () => {},
51
65
  onNestedOpenChange: () => {},
52
66
  onNestedRelease: () => {},
@@ -5,7 +5,7 @@ import React from 'react';
5
5
  import * as DialogPrimitive from '@radix-ui/react-dialog';
6
6
  import { Presence } from '@radix-ui/react-presence';
7
7
 
8
- import { DrawerContext, useDrawerContext } from './context';
8
+ import { DrawerContext, useDrawerContext, type DragEvent } from './context';
9
9
  import './style.css';
10
10
 
11
11
  import { isIOS, isMobileFirefox } from './browser';
@@ -491,7 +491,7 @@ export function Root({
491
491
  return true;
492
492
  }
493
493
 
494
- function onDrag(event: React.PointerEvent<HTMLDivElement>) {
494
+ function drag(event: DragEvent, pointerEvent?: React.PointerEvent<HTMLDivElement>) {
495
495
  if (!drawerRef.current) {
496
496
  return;
497
497
  }
@@ -557,7 +557,7 @@ export function Root({
557
557
  transition: 'none',
558
558
  });
559
559
 
560
- onDragProp?.(event, percentageDragged);
560
+ if (pointerEvent) onDragProp?.(pointerEvent, percentageDragged);
561
561
 
562
562
  if (snapPoints) {
563
563
  onDragSnapPoints({ draggedDistance });
@@ -761,7 +761,7 @@ export function Root({
761
761
  dragEndTime.current = new Date();
762
762
  }
763
763
 
764
- function onRelease(event: React.PointerEvent<HTMLDivElement> | null) {
764
+ function release(event: DragEvent | null, pointerEvent?: React.PointerEvent<HTMLDivElement>) {
765
765
  if (!isDragging || !drawerRef.current) return;
766
766
 
767
767
  drawerRef.current.classList.remove(DRAG_CLASS);
@@ -797,20 +797,20 @@ export function Root({
797
797
  velocity,
798
798
  dismissible,
799
799
  });
800
- onReleaseProp?.(event, true);
800
+ if (pointerEvent) onReleaseProp?.(pointerEvent, true);
801
801
  return;
802
802
  }
803
803
 
804
804
  // Moved upwards, don't do anything
805
805
  if (direction === 'bottom' || direction === 'right' ? distMoved > 0 : distMoved < 0) {
806
806
  resetDrawer();
807
- onReleaseProp?.(event, true);
807
+ if (pointerEvent) onReleaseProp?.(pointerEvent, true);
808
808
  return;
809
809
  }
810
810
 
811
811
  if (velocity > VELOCITY_THRESHOLD) {
812
812
  closeDrawer();
813
- onReleaseProp?.(event, false);
813
+ if (pointerEvent) onReleaseProp?.(pointerEvent, false);
814
814
  return;
815
815
  }
816
816
 
@@ -829,11 +829,11 @@ export function Root({
829
829
  (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold
830
830
  ) {
831
831
  closeDrawer();
832
- onReleaseProp?.(event, false);
832
+ if (pointerEvent) onReleaseProp?.(pointerEvent, false);
833
833
  return;
834
834
  }
835
835
 
836
- onReleaseProp?.(event, true);
836
+ if (pointerEvent) onReleaseProp?.(pointerEvent, true);
837
837
  resetDrawer();
838
838
  }
839
839
 
@@ -943,8 +943,10 @@ export function Root({
943
943
  overlayRef,
944
944
  onOpenChange,
945
945
  onPress,
946
- onRelease,
947
- onDrag,
946
+ onRelease: (event) => release(event, event ?? undefined),
947
+ onDrag: (event) => drag(event, event),
948
+ onTouchDrag: (event, pointerEvent) => drag(event, pointerEvent),
949
+ onTouchRelease: (event, pointerEvent) => release(event, pointerEvent),
948
950
  dismissible,
949
951
  shouldAnimate,
950
952
  handleOnly,
@@ -1088,6 +1090,8 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
1088
1090
  onPress,
1089
1091
  onRelease,
1090
1092
  onDrag,
1093
+ onTouchDrag,
1094
+ onTouchRelease,
1091
1095
  keyboardIsOpen,
1092
1096
  snapPointsOffset,
1093
1097
  activeSnapPointIndex,
@@ -1144,6 +1148,7 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
1144
1148
  const composedRef = useComposedRefs(ref, drawerRef);
1145
1149
  const pointerStartRef = React.useRef<{ x: number; y: number } | null>(null);
1146
1150
  const lastKnownPointerEventRef = React.useRef<React.PointerEvent<HTMLDivElement> | null>(null);
1151
+ const continuingCanceledTouchRef = React.useRef(false);
1147
1152
  const wasBeyondThePointRef = React.useRef(false);
1148
1153
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
1149
1154
  useScaleBackground();
@@ -1376,6 +1381,7 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
1376
1381
  onPointerDown={(event) => {
1377
1382
  if (handleOnly) return;
1378
1383
  rest.onPointerDown?.(event);
1384
+ continuingCanceledTouchRef.current = false;
1379
1385
  pointerStartRef.current = { x: event.pageX, y: event.pageY };
1380
1386
  onPress(event);
1381
1387
  }}
@@ -1462,14 +1468,68 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
1462
1468
  }}
1463
1469
  onPointerUp={(event) => {
1464
1470
  rest.onPointerUp?.(event);
1471
+ continuingCanceledTouchRef.current = false;
1465
1472
  pointerStartRef.current = null;
1466
1473
  wasBeyondThePointRef.current = false;
1467
1474
  onRelease(event);
1468
1475
  }}
1476
+ onPointerCancel={(event) => {
1477
+ rest.onPointerCancel?.(event);
1478
+ if (
1479
+ event.pointerType === 'touch' &&
1480
+ snapPoints &&
1481
+ activeSnapPointIndex === snapPoints.length - 1
1482
+ ) {
1483
+ continuingCanceledTouchRef.current = true;
1484
+ return;
1485
+ }
1486
+ handleOnPointerUp(null);
1487
+ }}
1469
1488
  onPointerOut={(event) => {
1470
1489
  rest.onPointerOut?.(event);
1490
+ if (continuingCanceledTouchRef.current) return;
1471
1491
  handleOnPointerUp(lastKnownPointerEventRef.current);
1472
1492
  }}
1493
+ onTouchMove={(event) => {
1494
+ rest.onTouchMove?.(event);
1495
+ if (!continuingCanceledTouchRef.current) return;
1496
+ const touch = event.touches[0];
1497
+ if (!touch) return;
1498
+ onTouchDrag(
1499
+ {
1500
+ target: event.target,
1501
+ pageX: touch.pageX,
1502
+ pageY: touch.pageY,
1503
+ pointerType: 'touch',
1504
+ },
1505
+ lastKnownPointerEventRef.current ?? undefined
1506
+ );
1507
+ }}
1508
+ onTouchEnd={(event) => {
1509
+ rest.onTouchEnd?.(event);
1510
+ if (!continuingCanceledTouchRef.current) return;
1511
+ continuingCanceledTouchRef.current = false;
1512
+ pointerStartRef.current = null;
1513
+ wasBeyondThePointRef.current = false;
1514
+ const touch = event.changedTouches[0];
1515
+ onTouchRelease(
1516
+ touch
1517
+ ? {
1518
+ target: event.target,
1519
+ pageX: touch.pageX,
1520
+ pageY: touch.pageY,
1521
+ pointerType: 'touch',
1522
+ }
1523
+ : null,
1524
+ lastKnownPointerEventRef.current ?? undefined
1525
+ );
1526
+ }}
1527
+ onTouchCancel={(event) => {
1528
+ rest.onTouchCancel?.(event);
1529
+ if (!continuingCanceledTouchRef.current) return;
1530
+ continuingCanceledTouchRef.current = false;
1531
+ handleOnPointerUp(null);
1532
+ }}
1473
1533
  onContextMenu={(event) => {
1474
1534
  rest.onContextMenu?.(event);
1475
1535
  if (lastKnownPointerEventRef.current) {