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