react-panel-layout 0.6.0 → 0.6.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 (216) hide show
  1. package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
  2. package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  4. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  5. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  6. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  7. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  8. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
  9. package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
  10. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.map} +1 -1
  11. package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
  12. package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
  13. package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
  14. package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
  15. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
  16. package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
  17. package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
  18. package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
  19. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  20. package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
  21. package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
  22. package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
  23. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  24. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  25. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  26. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  27. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  28. package/dist/components/window/Drawer.d.ts +3 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/drawerStyles.d.ts +69 -0
  31. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  32. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  33. package/dist/config.cjs +1 -1
  34. package/dist/config.js +3 -3
  35. package/dist/constants/styles.d.ts +17 -0
  36. package/dist/dialog/index.d.ts +69 -0
  37. package/dist/floating.js +1 -1
  38. package/dist/grid.cjs +1 -1
  39. package/dist/grid.js +2 -2
  40. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  41. package/dist/hooks/gesture/types.d.ts +48 -5
  42. package/dist/hooks/gesture/utils.d.ts +19 -0
  43. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  44. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  45. package/dist/hooks/useResizeObserver.d.ts +33 -1
  46. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  47. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.js +7 -7
  50. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  51. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  52. package/dist/modules/dialog/Modal.d.ts +26 -0
  53. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  54. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  55. package/dist/modules/dialog/types.d.ts +183 -0
  56. package/dist/modules/dialog/useDialog.d.ts +39 -0
  57. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  58. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  59. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  60. package/dist/modules/drawer/types.d.ts +74 -0
  61. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  62. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  63. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  64. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  65. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  66. package/dist/panels.cjs +1 -1
  67. package/dist/panels.js +1 -1
  68. package/dist/pivot.cjs +1 -1
  69. package/dist/pivot.js +1 -1
  70. package/dist/resizer.cjs +1 -1
  71. package/dist/resizer.js +2 -2
  72. package/dist/stack.cjs +1 -1
  73. package/dist/stack.cjs.map +1 -1
  74. package/dist/stack.js +503 -762
  75. package/dist/stack.js.map +1 -1
  76. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  77. package/dist/sticky-header.cjs +1 -1
  78. package/dist/sticky-header.cjs.map +1 -1
  79. package/dist/sticky-header.js +59 -51
  80. package/dist/sticky-header.js.map +1 -1
  81. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  82. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  83. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  84. package/dist/types.d.ts +16 -0
  85. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  86. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  87. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  88. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  89. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  90. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  91. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  92. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  93. package/dist/window/index.d.ts +2 -0
  94. package/dist/window.cjs +1 -1
  95. package/dist/window.cjs.map +1 -1
  96. package/dist/window.js +114 -103
  97. package/dist/window.js.map +1 -1
  98. package/package.json +6 -1
  99. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  100. package/src/components/window/Drawer.tsx +249 -162
  101. package/src/components/window/DrawerLayers.tsx +13 -3
  102. package/src/components/window/drawerStyles.spec.ts +263 -0
  103. package/src/components/window/drawerStyles.ts +228 -0
  104. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  105. package/src/components/window/drawerSwipeConfig.ts +112 -0
  106. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  107. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  108. package/src/constants/styles.ts +19 -0
  109. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  110. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  111. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  112. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  113. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  114. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  115. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  116. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  117. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  118. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  119. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  120. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  121. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  122. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  123. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  124. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  125. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  126. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  127. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  128. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  129. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  130. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  131. package/src/demo/routes.tsx +22 -1
  132. package/src/dialog/index.ts +85 -0
  133. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  134. package/src/hooks/gesture/testing/createGestureSimulator.ts +112 -37
  135. package/src/hooks/gesture/types.ts +83 -6
  136. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  137. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +91 -31
  138. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  139. package/src/hooks/gesture/utils.ts +91 -0
  140. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  141. package/src/hooks/useAnimatedVisibility.ts +28 -2
  142. package/src/hooks/useAnimationFrame.ts +8 -0
  143. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  144. package/src/hooks/useOperationContinuity.ts +135 -0
  145. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  146. package/src/hooks/useResizeObserver.tsx +108 -39
  147. package/src/hooks/useScrollContainer.ts +4 -10
  148. package/src/hooks/useSharedElementTransition.ts +333 -0
  149. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  150. package/src/hooks/useSwipeContentTransform.ts +166 -28
  151. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  152. package/src/modules/dialog/AlertDialog.tsx +221 -0
  153. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  154. package/src/modules/dialog/DialogContainer.tsx +188 -0
  155. package/src/modules/dialog/Modal.spec.tsx +220 -0
  156. package/src/modules/dialog/Modal.tsx +182 -0
  157. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  158. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  159. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  160. package/src/modules/dialog/types.ts +186 -0
  161. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  162. package/src/modules/dialog/useDialog.ts +214 -0
  163. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  164. package/src/modules/dialog/useDialogContainer.ts +150 -0
  165. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  166. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  167. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  168. package/src/modules/dialog/useDialogTransform.ts +407 -0
  169. package/src/modules/drawer/types.ts +102 -0
  170. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  171. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  172. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  173. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  174. package/src/modules/pivot/SwipePivotContent.spec.tsx +55 -25
  175. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  176. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  177. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  178. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  179. package/src/modules/pivot/usePivot.spec.ts +17 -3
  180. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  181. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  182. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  183. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  184. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  185. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  186. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  187. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  188. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  189. package/src/modules/stack/useStackAnimationState.ts +18 -13
  190. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  191. package/src/modules/stack/useStackNavigation.tsx +113 -56
  192. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  193. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  194. package/src/sticky-header/StickyArea.tsx +29 -57
  195. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  196. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  197. package/src/types.ts +18 -0
  198. package/src/window/index.ts +2 -0
  199. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  200. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  201. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  202. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  203. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  204. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  205. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  206. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  207. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  208. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  209. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  210. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  211. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  212. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  213. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  214. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  215. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  216. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -0,0 +1,333 @@
1
+ /**
2
+ * @file Hook for View Transitions API shared element animations.
3
+ *
4
+ * Enables smooth morph animations between a source element (e.g., card)
5
+ * and a target element (e.g., expanded view) using CSS view-transition-name.
6
+ * Supports swipe-to-dismiss with the expanded view animating back to source.
7
+ */
8
+ import * as React from "react";
9
+ import { flushSync } from "react-dom";
10
+
11
+ /**
12
+ * Check if View Transitions API is supported.
13
+ */
14
+ export function supportsViewTransitions(): boolean {
15
+ return typeof document !== "undefined" && "startViewTransition" in document;
16
+ }
17
+
18
+ type ViewTransitionHandle = {
19
+ finished: Promise<void>;
20
+ ready: Promise<void>;
21
+ updateCallbackDone: Promise<void>;
22
+ skipTransition: () => void;
23
+ };
24
+
25
+ /**
26
+ * Start a view transition with the given callback.
27
+ */
28
+ export function startViewTransition(callback: () => void): ViewTransitionHandle | null {
29
+ if (supportsViewTransitions()) {
30
+ return (document as Document & { startViewTransition: (cb: () => void) => ViewTransitionHandle }).startViewTransition(callback);
31
+ }
32
+ callback();
33
+ return null;
34
+ }
35
+
36
+ /** Default dismiss threshold (30% of viewport height) */
37
+ const DEFAULT_DISMISS_THRESHOLD = 0.3;
38
+
39
+ /** Velocity threshold for quick flick dismissal (px/ms) */
40
+ const VELOCITY_THRESHOLD = 0.5;
41
+
42
+ /**
43
+ * Options for useSharedElementTransition hook.
44
+ */
45
+ export type UseSharedElementTransitionOptions<T> = {
46
+ /**
47
+ * Function to generate a unique transition name for an item.
48
+ * Multiple names can be returned for nested shared elements.
49
+ */
50
+ getTransitionName: (item: T) => string | string[];
51
+ /**
52
+ * Function to get a unique key for comparison.
53
+ * Defaults to using getTransitionName result.
54
+ */
55
+ getKey?: (item: T) => string;
56
+ /** Enable swipe to dismiss. @default true */
57
+ swipeDismissible?: boolean;
58
+ /** Threshold ratio (0-1) to trigger dismiss. @default 0.3 */
59
+ dismissThreshold?: number;
60
+ };
61
+
62
+ /** 2D vector */
63
+ type Vector2 = { x: number; y: number };
64
+
65
+ /**
66
+ * Result from useSharedElementTransition hook.
67
+ */
68
+ export type UseSharedElementTransitionResult<T> = {
69
+ /** Currently expanded item, or null if none */
70
+ expandedItem: T | null;
71
+ /** Expand an item with view transition */
72
+ expand: (item: T) => void;
73
+ /** Collapse the expanded item with view transition */
74
+ collapse: () => void;
75
+ /**
76
+ * Get style props for a source element (e.g., card).
77
+ */
78
+ getSourceProps: (item: T, nameIndex?: number) => { style: React.CSSProperties };
79
+ /**
80
+ * Get style props for the target element (e.g., expanded view).
81
+ * Includes transform for swipe tracking.
82
+ */
83
+ getTargetProps: (nameIndex?: number) => { style: React.CSSProperties };
84
+ /**
85
+ * Get swipe container props (onPointerDown, style).
86
+ * Apply to the swipeable container element.
87
+ */
88
+ getSwipeContainerProps: () => React.HTMLAttributes<HTMLElement> & { style: React.CSSProperties };
89
+ /** Whether currently being swiped */
90
+ isSwiping: boolean;
91
+ /** Current displacement during swipe */
92
+ displacement: Vector2;
93
+ /** Whether View Transitions API is supported */
94
+ isSupported: boolean;
95
+ };
96
+
97
+ /**
98
+ * Hook for managing shared element transitions using View Transitions API.
99
+ *
100
+ * @example
101
+ * ```tsx
102
+ * const {
103
+ * expandedItem, expand, collapse,
104
+ * getSourceProps, getTargetProps, getSwipeContainerProps
105
+ * } = useSharedElementTransition({
106
+ * getTransitionName: (album) => [`album-${album.id}`, `album-art-${album.id}`],
107
+ * });
108
+ *
109
+ * // Source (card)
110
+ * <div {...getSourceProps(album, 0)}>
111
+ * <img {...getSourceProps(album, 1)} />
112
+ * </div>
113
+ *
114
+ * // Target (expanded view) - apply swipe props to container
115
+ * {expandedItem && (
116
+ * <div {...getSwipeContainerProps()}>
117
+ * <div {...getTargetProps(0)}>
118
+ * <img {...getTargetProps(1)} />
119
+ * </div>
120
+ * </div>
121
+ * )}
122
+ * ```
123
+ */
124
+ export function useSharedElementTransition<T>(
125
+ options: UseSharedElementTransitionOptions<T>,
126
+ ): UseSharedElementTransitionResult<T> {
127
+ const {
128
+ getTransitionName,
129
+ getKey,
130
+ swipeDismissible = true,
131
+ dismissThreshold = DEFAULT_DISMISS_THRESHOLD,
132
+ } = options;
133
+
134
+ const [expandedItem, setExpandedItem] = React.useState<T | null>(null);
135
+ // Track which item is about to be expanded (for view transition name assignment)
136
+ const [pendingExpandItem, setPendingExpandItem] = React.useState<T | null>(null);
137
+ // Track which item is collapsing (for view transition name assignment on the card)
138
+ const [collapsingItem, setCollapsingItem] = React.useState<T | null>(null);
139
+ const isSupported = React.useMemo(() => supportsViewTransitions(), []);
140
+
141
+ // Swipe tracking state
142
+ const [isSwiping, setIsSwiping] = React.useState(false);
143
+ const [displacement, setDisplacement] = React.useState<Vector2>({ x: 0, y: 0 });
144
+ const startPointRef = React.useRef<Vector2 | null>(null);
145
+ const startTimeRef = React.useRef<number>(0);
146
+
147
+ // Get item key for comparison
148
+ const getItemKey = React.useCallback((item: T): string => {
149
+ if (getKey) {
150
+ return getKey(item);
151
+ }
152
+ const names = getTransitionName(item);
153
+ return Array.isArray(names) ? names[0] : names;
154
+ }, [getKey, getTransitionName]);
155
+
156
+ const expand = React.useCallback((item: T) => {
157
+ // First, set pending item so only this card gets view-transition-name
158
+ setPendingExpandItem(item);
159
+
160
+ // Use requestAnimationFrame to ensure React re-renders before view transition
161
+ requestAnimationFrame(() => {
162
+ const transition = startViewTransition(() => {
163
+ flushSync(() => {
164
+ setExpandedItem(item);
165
+ setPendingExpandItem(null);
166
+ });
167
+ });
168
+ // Fallback if viewTransition not supported
169
+ if (!transition) {
170
+ setExpandedItem(item);
171
+ setPendingExpandItem(null);
172
+ }
173
+ });
174
+ }, []);
175
+
176
+ const collapse = React.useCallback(() => {
177
+ if (!expandedItem) return;
178
+
179
+ setIsSwiping(false);
180
+
181
+ // For closing, expanded view already has view-transition-name
182
+ // We just need the card to also have it in the new state
183
+ // Use flushSync immediately since old state (expanded) already has the name
184
+ const itemToCollapse = expandedItem;
185
+ const transition = startViewTransition(() => {
186
+ flushSync(() => {
187
+ setExpandedItem(null);
188
+ setCollapsingItem(itemToCollapse);
189
+ });
190
+ });
191
+
192
+ // Reset states after transition completes (or immediately if not supported)
193
+ if (transition) {
194
+ transition.finished.then(() => {
195
+ setDisplacement({ x: 0, y: 0 });
196
+ setCollapsingItem(null);
197
+ });
198
+ } else {
199
+ setExpandedItem(null);
200
+ setDisplacement({ x: 0, y: 0 });
201
+ setCollapsingItem(null);
202
+ }
203
+ }, [expandedItem]);
204
+
205
+ // Pointer event handlers for swipe
206
+ const handlePointerDown = React.useCallback((event: React.PointerEvent) => {
207
+ if (!swipeDismissible || !expandedItem) {
208
+ return;
209
+ }
210
+ (event.target as HTMLElement).setPointerCapture(event.pointerId);
211
+ startPointRef.current = { x: event.clientX, y: event.clientY };
212
+ startTimeRef.current = Date.now();
213
+ setIsSwiping(true);
214
+ }, [swipeDismissible, expandedItem]);
215
+
216
+ const handlePointerMove = React.useCallback((event: React.PointerEvent) => {
217
+ if (!isSwiping || !startPointRef.current) {
218
+ return;
219
+ }
220
+ const dx = event.clientX - startPointRef.current.x;
221
+ const dy = event.clientY - startPointRef.current.y;
222
+ setDisplacement({ x: dx, y: dy });
223
+ }, [isSwiping]);
224
+
225
+ const handlePointerUp = React.useCallback((event: React.PointerEvent) => {
226
+ if (!isSwiping || !startPointRef.current) {
227
+ return;
228
+ }
229
+
230
+ const duration = Date.now() - startTimeRef.current;
231
+ const velocity = Math.abs(displacement.y) / Math.max(1, duration);
232
+ const viewportHeight = window.innerHeight;
233
+ const ratio = Math.abs(displacement.y) / viewportHeight;
234
+
235
+ // Check if should dismiss (downward swipe)
236
+ const shouldDismiss = displacement.y > 0 && (ratio >= dismissThreshold || velocity >= VELOCITY_THRESHOLD);
237
+
238
+ if (shouldDismiss) {
239
+ collapse();
240
+ } else {
241
+ // Snap back
242
+ setDisplacement({ x: 0, y: 0 });
243
+ }
244
+
245
+ setIsSwiping(false);
246
+ startPointRef.current = null;
247
+ }, [isSwiping, displacement, dismissThreshold, collapse]);
248
+
249
+ const handlePointerCancel = React.useCallback(() => {
250
+ setIsSwiping(false);
251
+ setDisplacement({ x: 0, y: 0 });
252
+ startPointRef.current = null;
253
+ }, []);
254
+
255
+ const getSourceProps = React.useCallback(
256
+ (item: T, nameIndex = 0): { style: React.CSSProperties } => {
257
+ const names = getTransitionName(item);
258
+ const nameArray = Array.isArray(names) ? names : [names];
259
+ const name = nameArray[nameIndex];
260
+
261
+ const itemKey = getItemKey(item);
262
+ const isThisItemExpanded = expandedItem !== null && getItemKey(expandedItem) === itemKey;
263
+ const isThisItemPending = pendingExpandItem !== null && getItemKey(pendingExpandItem) === itemKey;
264
+ const isThisItemCollapsing = collapsingItem !== null && getItemKey(collapsingItem) === itemKey;
265
+
266
+ // Only give view-transition-name to:
267
+ // - The pending item (clicked card, for old state capture during expand)
268
+ // - The collapsing item (card returning to, for new state capture during collapse)
269
+ // - Never to other cards (they don't participate in the transition)
270
+ const shouldHaveTransitionName = isThisItemPending || isThisItemCollapsing;
271
+
272
+ return {
273
+ style: {
274
+ viewTransitionName: shouldHaveTransitionName ? name : undefined,
275
+ // Hide the source card when expanded (it's now represented by the target)
276
+ visibility: isThisItemExpanded ? "hidden" : undefined,
277
+ },
278
+ };
279
+ },
280
+ [expandedItem, pendingExpandItem, collapsingItem, getTransitionName, getItemKey],
281
+ );
282
+
283
+ const getTargetProps = React.useCallback(
284
+ (nameIndex = 0): { style: React.CSSProperties } => {
285
+ if (expandedItem === null) {
286
+ return { style: {} };
287
+ }
288
+
289
+ const names = getTransitionName(expandedItem);
290
+ const nameArray = Array.isArray(names) ? names : [names];
291
+ const name = nameArray[nameIndex];
292
+
293
+ // Apply transform during swipe
294
+ const transform = isSwiping || displacement.x !== 0 || displacement.y !== 0
295
+ ? `translate(${displacement.x}px, ${displacement.y}px)`
296
+ : undefined;
297
+
298
+ return {
299
+ style: {
300
+ viewTransitionName: name,
301
+ transform,
302
+ transition: !isSwiping ? "transform 0.3s ease-out" : undefined,
303
+ },
304
+ };
305
+ },
306
+ [expandedItem, getTransitionName, isSwiping, displacement],
307
+ );
308
+
309
+ const getSwipeContainerProps = React.useCallback((): React.HTMLAttributes<HTMLElement> & { style: React.CSSProperties } => {
310
+ return {
311
+ onPointerDown: handlePointerDown,
312
+ onPointerMove: handlePointerMove,
313
+ onPointerUp: handlePointerUp,
314
+ onPointerCancel: handlePointerCancel,
315
+ style: {
316
+ touchAction: "none",
317
+ userSelect: "none",
318
+ },
319
+ };
320
+ }, [handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel]);
321
+
322
+ return {
323
+ expandedItem,
324
+ expand,
325
+ collapse,
326
+ getSourceProps,
327
+ getTargetProps,
328
+ getSwipeContainerProps,
329
+ isSwiping,
330
+ displacement,
331
+ isSupported,
332
+ };
333
+ }
@@ -17,7 +17,7 @@ describe("useSwipeContentTransform", () => {
17
17
  elementRef: ref,
18
18
  targetPx: 0,
19
19
  displacement: 0,
20
- isSwiping: false,
20
+ isOperating: false,
21
21
  }),
22
22
  );
23
23
 
@@ -28,20 +28,20 @@ describe("useSwipeContentTransform", () => {
28
28
  it("updates element transform during swipe", () => {
29
29
  const { element, ref } = createMockElementRef();
30
30
  const { rerender } = renderHook(
31
- ({ displacement, isSwiping }) =>
31
+ ({ displacement, isOperating }) =>
32
32
  useSwipeContentTransform({
33
33
  elementRef: ref,
34
34
  targetPx: 0,
35
35
  displacement,
36
- isSwiping,
36
+ isOperating,
37
37
  }),
38
38
  {
39
- initialProps: { displacement: 0, isSwiping: true },
39
+ initialProps: { displacement: 0, isOperating: true },
40
40
  },
41
41
  );
42
42
 
43
43
  // Simulate swipe movement
44
- rerender({ displacement: 100, isSwiping: true });
44
+ rerender({ displacement: 100, isOperating: true });
45
45
 
46
46
  expect(element.style.transform).toBe("translateX(100px)");
47
47
  });
@@ -49,20 +49,20 @@ describe("useSwipeContentTransform", () => {
49
49
  it("uses correct transform function for vertical axis", () => {
50
50
  const { element, ref } = createMockElementRef();
51
51
  const { rerender } = renderHook(
52
- ({ displacement, isSwiping }) =>
52
+ ({ displacement, isOperating }) =>
53
53
  useSwipeContentTransform({
54
54
  elementRef: ref,
55
55
  targetPx: 0,
56
56
  displacement,
57
- isSwiping,
57
+ isOperating,
58
58
  axis: "vertical",
59
59
  }),
60
60
  {
61
- initialProps: { displacement: 0, isSwiping: true },
61
+ initialProps: { displacement: 0, isOperating: true },
62
62
  },
63
63
  );
64
64
 
65
- rerender({ displacement: 50, isSwiping: true });
65
+ rerender({ displacement: 50, isOperating: true });
66
66
 
67
67
  expect(element.style.transform).toBe("translateY(50px)");
68
68
  });
@@ -70,20 +70,20 @@ describe("useSwipeContentTransform", () => {
70
70
  it("applies target position with displacement", () => {
71
71
  const { element, ref } = createMockElementRef();
72
72
  const { rerender } = renderHook(
73
- ({ displacement, isSwiping }) =>
73
+ ({ displacement, isOperating }) =>
74
74
  useSwipeContentTransform({
75
75
  elementRef: ref,
76
76
  targetPx: -300, // target is off-screen left
77
77
  displacement,
78
- isSwiping,
78
+ isOperating,
79
79
  }),
80
80
  {
81
- initialProps: { displacement: 0, isSwiping: true },
81
+ initialProps: { displacement: 0, isOperating: true },
82
82
  },
83
83
  );
84
84
 
85
85
  // During swipe, position = targetPx + displacement
86
- rerender({ displacement: 100, isSwiping: true });
86
+ rerender({ displacement: 100, isOperating: true });
87
87
 
88
88
  expect(element.style.transform).toBe("translateX(-200px)");
89
89
  });
@@ -92,21 +92,21 @@ describe("useSwipeContentTransform", () => {
92
92
  const nullRef: React.RefObject<HTMLElement | null> = { current: null };
93
93
 
94
94
  const { rerender } = renderHook(
95
- ({ displacement, isSwiping }) =>
95
+ ({ displacement, isOperating }) =>
96
96
  useSwipeContentTransform({
97
97
  elementRef: nullRef,
98
98
  targetPx: 0,
99
99
  displacement,
100
- isSwiping,
100
+ isOperating,
101
101
  }),
102
102
  {
103
- initialProps: { displacement: 0, isSwiping: true },
103
+ initialProps: { displacement: 0, isOperating: true },
104
104
  },
105
105
  );
106
106
 
107
107
  // Should not throw
108
108
  expect(() => {
109
- rerender({ displacement: 100, isSwiping: true });
109
+ rerender({ displacement: 100, isOperating: true });
110
110
  }).not.toThrow();
111
111
  });
112
112
 
@@ -118,7 +118,7 @@ describe("useSwipeContentTransform", () => {
118
118
  elementRef: ref,
119
119
  targetPx,
120
120
  displacement: 0,
121
- isSwiping: false,
121
+ isOperating: false,
122
122
  }),
123
123
  {
124
124
  initialProps: { targetPx: 0 },