@hyperframes/studio 0.7.55 → 0.7.57

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 (274) hide show
  1. package/dist/assets/hyperframes-player--Z69cEkE.js +459 -0
  2. package/dist/assets/{index-CPetwHFV.js → index-Bf1x1y8H.js} +1 -1
  3. package/dist/assets/index-DfmYkU44.js +423 -0
  4. package/dist/assets/{index-BXaqaVKt.js → index-KsfE1bUu.js} +1 -1
  5. package/dist/assets/index-_pqzyxB1.css +1 -0
  6. package/dist/{chunk-SOTCF4DF.js → chunk-5QSIMBEJ.js} +2 -1
  7. package/dist/chunk-5QSIMBEJ.js.map +1 -0
  8. package/dist/{domEditingLayers-2ECJK24D.js → domEditingLayers-6LQGKPOI.js} +2 -2
  9. package/dist/index.d.ts +172 -144
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +43613 -40346
  12. package/dist/index.js.map +1 -1
  13. package/package.json +7 -7
  14. package/src/App.tsx +106 -96
  15. package/src/components/EditorShell.tsx +253 -0
  16. package/src/components/StudioGlobalDragOverlay.tsx +1 -3
  17. package/src/components/StudioLeftSidebar.tsx +13 -3
  18. package/src/components/StudioRightPanel.tsx +8 -3
  19. package/src/components/TimelineToolbar.test.tsx +2 -2
  20. package/src/components/TimelineToolbar.tsx +179 -121
  21. package/src/components/editor/CanvasContextMenu.test.tsx +152 -1
  22. package/src/components/editor/CanvasContextMenu.tsx +33 -18
  23. package/src/components/editor/DomEditCropHandles.test.tsx +74 -3
  24. package/src/components/editor/DomEditCropHandles.tsx +64 -29
  25. package/src/components/editor/DomEditOverlay.test.ts +245 -279
  26. package/src/components/editor/DomEditOverlay.tsx +126 -125
  27. package/src/components/editor/DomEditOverlayHover.test.tsx +90 -0
  28. package/src/components/editor/DomEditRotateHandle.tsx +38 -18
  29. package/src/components/editor/DomEditSelectionChrome.test.tsx +80 -0
  30. package/src/components/editor/DomEditSelectionChrome.tsx +257 -0
  31. package/src/components/editor/LayersPanel.test.ts +66 -2
  32. package/src/components/editor/LayersPanel.tsx +53 -2
  33. package/src/components/editor/OffCanvasIndicators.test.tsx +34 -0
  34. package/src/components/editor/OffCanvasIndicators.tsx +49 -10
  35. package/src/components/editor/SnapGuideOverlay.tsx +28 -23
  36. package/src/components/editor/SnapToolbar.test.tsx +0 -1
  37. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +214 -0
  38. package/src/components/editor/anchoredResizeReleaseShift.test.ts +106 -10
  39. package/src/components/editor/canvasContextMenuZOrder.test.ts +186 -2
  40. package/src/components/editor/canvasContextMenuZOrder.ts +144 -22
  41. package/src/components/editor/domEditNudge.test.ts +80 -0
  42. package/src/components/editor/domEditNudge.ts +44 -0
  43. package/src/components/editor/domEditOverlayGeometry.test.ts +167 -1
  44. package/src/components/editor/domEditOverlayGeometry.ts +280 -20
  45. package/src/components/editor/domEditOverlayGestures.ts +44 -41
  46. package/src/components/editor/domEditOverlayStartGesture.ts +62 -2
  47. package/src/components/editor/domEditResizeLocal.test.ts +131 -0
  48. package/src/components/editor/domEditResizeLocal.ts +125 -0
  49. package/src/components/editor/domEditingDom.ts +1 -1
  50. package/src/components/editor/manualEditsDomPatches.test.ts +47 -0
  51. package/src/components/editor/manualOffsetDrag.ts +55 -12
  52. package/src/components/editor/offCanvasIndicatorGeometry.ts +31 -10
  53. package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +25 -1
  54. package/src/components/editor/propertyPanelColorGradingSection.tsx +15 -21
  55. package/src/components/editor/resizeDraft.ts +108 -0
  56. package/src/components/editor/snapEngine.test.ts +12 -78
  57. package/src/components/editor/snapEngine.ts +13 -53
  58. package/src/components/editor/useCanvasContextMenuState.ts +92 -0
  59. package/src/components/editor/useDomEditNudge.test.tsx +228 -0
  60. package/src/components/editor/useDomEditNudge.ts +254 -0
  61. package/src/components/editor/useDomEditOverlayGestures.ts +115 -116
  62. package/src/components/editor/useDomEditOverlayRects.ts +9 -2
  63. package/src/components/editor/useZOrderCrossedFlash.tsx +68 -0
  64. package/src/components/nle/AssetPreviewOverlay.test.tsx +106 -0
  65. package/src/components/nle/AssetPreviewOverlay.tsx +171 -0
  66. package/src/components/nle/NLEContext.test.ts +144 -0
  67. package/src/components/nle/NLEContext.tsx +328 -0
  68. package/src/components/nle/NLEPreview.tsx +1 -1
  69. package/src/components/nle/PreviewOverlays.tsx +260 -0
  70. package/src/components/nle/PreviewPane.tsx +163 -0
  71. package/src/components/nle/TimelinePane.test.ts +60 -0
  72. package/src/components/nle/TimelinePane.tsx +293 -0
  73. package/src/components/nle/TimelineResizeDivider.tsx +8 -2
  74. package/src/components/nle/useCompositionStack.test.tsx +44 -0
  75. package/src/components/nle/useCompositionStack.ts +17 -9
  76. package/src/components/nle/useTimelineEditCallbacks.ts +217 -0
  77. package/src/components/sidebar/AssetCard.test.tsx +106 -0
  78. package/src/components/sidebar/AssetCard.tsx +343 -0
  79. package/src/components/sidebar/AssetContextMenu.tsx +15 -30
  80. package/src/components/sidebar/AssetsTab.test.ts +96 -0
  81. package/src/components/sidebar/AssetsTab.tsx +71 -206
  82. package/src/components/sidebar/AudioRow.tsx +56 -2
  83. package/src/components/sidebar/BlocksTab.tsx +8 -1
  84. package/src/components/sidebar/LeftSidebar.tsx +4 -1
  85. package/src/components/sidebar/assetHelpers.ts +29 -0
  86. package/src/contexts/DomEditContext.tsx +9 -0
  87. package/src/contexts/StudioContext.tsx +9 -8
  88. package/src/contexts/TimelineEditContext.tsx +3 -7
  89. package/src/hooks/deleteSelectedKeyframes.ts +35 -0
  90. package/src/hooks/domEditCommitTypes.ts +23 -1
  91. package/src/hooks/domSelectionTestHarness.ts +14 -0
  92. package/src/hooks/gestureTransaction.test.ts +311 -0
  93. package/src/hooks/gestureTransaction.ts +199 -0
  94. package/src/hooks/gsapDragCommit.test.ts +141 -41
  95. package/src/hooks/gsapDragCommit.ts +64 -74
  96. package/src/hooks/gsapDragStaticSetHelpers.ts +9 -21
  97. package/src/hooks/gsapResizeIntercept.test.ts +69 -0
  98. package/src/hooks/gsapResizeIntercept.ts +38 -10
  99. package/src/hooks/gsapRuntimeBridge.test.ts +49 -13
  100. package/src/hooks/gsapRuntimeBridge.ts +4 -4
  101. package/src/hooks/gsapScriptCommitTypes.ts +16 -5
  102. package/src/hooks/gsapShared.test.ts +19 -1
  103. package/src/hooks/gsapShared.ts +14 -0
  104. package/src/hooks/patchDocumentRootDuration.test.ts +71 -0
  105. package/src/hooks/timelineEditingGsap.ts +14 -0
  106. package/src/hooks/timelineEditingHelpers.test.ts +125 -1
  107. package/src/hooks/timelineEditingHelpers.ts +53 -292
  108. package/src/hooks/timelineMoveAdapter.test.ts +81 -0
  109. package/src/hooks/timelineMoveAdapter.ts +39 -0
  110. package/src/hooks/timelineTimingSync.test.ts +150 -0
  111. package/src/hooks/timelineTimingSync.ts +412 -0
  112. package/src/hooks/useAnimatedPropertyCommit.test.tsx +36 -8
  113. package/src/hooks/useAnimatedPropertyCommit.ts +31 -26
  114. package/src/hooks/useAppHotkeys.ts +16 -25
  115. package/src/hooks/useBlockHandlers.ts +55 -27
  116. package/src/hooks/useCaptionDetection.ts +2 -0
  117. package/src/hooks/useCompositionDimensions.ts +32 -5
  118. package/src/hooks/useContextMenuDismiss.ts +31 -7
  119. package/src/hooks/useDomEditCommits.test.tsx +245 -1
  120. package/src/hooks/useDomEditCommits.ts +162 -3
  121. package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
  122. package/src/hooks/useDomEditSession.ts +2 -2
  123. package/src/hooks/useDomEditTextCommits.test.tsx +135 -0
  124. package/src/hooks/useDomEditTextCommits.ts +8 -3
  125. package/src/hooks/useDomEditWiring.ts +7 -9
  126. package/src/hooks/useDomGeometryCommits.ts +17 -2
  127. package/src/hooks/useDomSelection.test.ts +2 -36
  128. package/src/hooks/useDomSelection.ts +81 -51
  129. package/src/hooks/useDomSelectionSelectionGuards.test.ts +276 -0
  130. package/src/hooks/useElementLifecycleOps.test.tsx +423 -0
  131. package/src/hooks/useElementLifecycleOps.ts +53 -71
  132. package/src/hooks/useElementPicker.ts +7 -8
  133. package/src/hooks/useEnableKeyframes.test.ts +142 -1
  134. package/src/hooks/useEnableKeyframes.ts +73 -29
  135. package/src/hooks/useGestureCommit.test.tsx +130 -0
  136. package/src/hooks/useGestureCommit.ts +32 -8
  137. package/src/hooks/useGroupCommits.ts +7 -28
  138. package/src/hooks/useGsapAwareEditing.test.tsx +239 -0
  139. package/src/hooks/useGsapAwareEditing.ts +115 -20
  140. package/src/hooks/useGsapKeyframeOps.test.tsx +59 -0
  141. package/src/hooks/useGsapKeyframeOps.ts +38 -10
  142. package/src/hooks/useGsapScriptCommits.test.tsx +40 -1
  143. package/src/hooks/useGsapScriptCommits.ts +134 -50
  144. package/src/hooks/useGsapSelectionHandlers.ts +49 -16
  145. package/src/hooks/useMusicBeatAnalysis.ts +72 -36
  146. package/src/hooks/usePersistentEditHistory.test.ts +33 -0
  147. package/src/hooks/usePersistentEditHistory.ts +92 -58
  148. package/src/hooks/usePreviewPersistence.ts +25 -7
  149. package/src/hooks/useRazorSplit.history.test.tsx +148 -18
  150. package/src/hooks/useRazorSplit.test.ts +269 -0
  151. package/src/hooks/useRazorSplit.testHelpers.ts +64 -0
  152. package/src/hooks/useRazorSplit.ts +107 -36
  153. package/src/hooks/useRenderClipContent.ts +24 -6
  154. package/src/hooks/useStudioContextValue.ts +15 -5
  155. package/src/hooks/useStudioUrlState.ts +2 -4
  156. package/src/hooks/useTimelineAssetDropOps.ts +175 -0
  157. package/src/hooks/useTimelineEditing.test.tsx +398 -241
  158. package/src/hooks/useTimelineEditing.ts +181 -265
  159. package/src/hooks/useTimelineEditingTypes.ts +6 -0
  160. package/src/hooks/useTimelineGroupEditing.ts +167 -132
  161. package/src/index.ts +1 -1
  162. package/src/player/components/ImageThumbnail.test.tsx +173 -0
  163. package/src/player/components/ImageThumbnail.tsx +160 -0
  164. package/src/player/components/PlayerControls.tsx +5 -5
  165. package/src/player/components/PlayheadIndicator.tsx +55 -5
  166. package/src/player/components/Timeline.test.ts +183 -23
  167. package/src/player/components/Timeline.tsx +194 -194
  168. package/src/player/components/TimelineCanvas.tsx +218 -523
  169. package/src/player/components/TimelineClip.tsx +4 -1
  170. package/src/player/components/TimelineClipDiamonds.tsx +9 -40
  171. package/src/player/components/TimelineEmptyState.tsx +3 -1
  172. package/src/player/components/TimelineLanes.tsx +487 -0
  173. package/src/player/components/TimelineOverlays.tsx +122 -0
  174. package/src/player/components/TimelineRuler.tsx +58 -48
  175. package/src/player/components/VideoThumbnail.test.tsx +152 -0
  176. package/src/player/components/VideoThumbnail.tsx +28 -7
  177. package/src/player/components/thumbnailUtils.test.ts +127 -0
  178. package/src/player/components/thumbnailUtils.ts +54 -0
  179. package/src/player/components/timelineCallbacks.ts +18 -18
  180. package/src/player/components/timelineClipChildren.tsx +39 -0
  181. package/src/player/components/timelineClipDragCommit.test.ts +1026 -0
  182. package/src/player/components/timelineClipDragCommit.ts +498 -0
  183. package/src/player/components/timelineClipDragPreview.test.ts +144 -0
  184. package/src/player/components/timelineClipDragPreview.ts +323 -38
  185. package/src/player/components/timelineClipDragTypes.ts +65 -0
  186. package/src/player/components/timelineCollision.test.ts +42 -2
  187. package/src/player/components/timelineDragDrop.ts +69 -60
  188. package/src/player/components/timelineEditCapabilities.ts +52 -0
  189. package/src/player/components/timelineEditing.test.ts +1 -1
  190. package/src/player/components/timelineEditing.ts +76 -59
  191. package/src/player/components/timelineGroupEditing.test.ts +126 -0
  192. package/src/player/components/timelineGroupEditing.ts +133 -0
  193. package/src/player/components/timelineGroupResizeCommit.ts +54 -0
  194. package/src/player/components/timelineLayout.test.ts +95 -23
  195. package/src/player/components/timelineLayout.ts +207 -75
  196. package/src/player/components/timelineMarquee.test.ts +197 -0
  197. package/src/player/components/timelineMarquee.ts +112 -0
  198. package/src/player/components/timelineOptimisticRevision.ts +42 -0
  199. package/src/player/components/timelineRevealScroll.test.ts +133 -0
  200. package/src/player/components/timelineRevealScroll.ts +91 -0
  201. package/src/player/components/timelineStackingSync.test.ts +60 -0
  202. package/src/player/components/timelineStackingSync.ts +27 -2
  203. package/src/player/components/timelineTheme.ts +6 -1
  204. package/src/player/components/timelineTrackPersistPipeline.test.ts +168 -0
  205. package/src/player/components/timelineZones.test.ts +122 -310
  206. package/src/player/components/timelineZones.ts +82 -135
  207. package/src/player/components/useResolvedTimelineEditCallbacks.ts +25 -3
  208. package/src/player/components/useTimelineClipDrag.resize.test.tsx +241 -0
  209. package/src/player/components/useTimelineClipDrag.ts +380 -386
  210. package/src/player/components/useTimelineEditPinning.ts +121 -0
  211. package/src/player/components/useTimelineGeometry.ts +129 -0
  212. package/src/player/components/useTimelinePlayhead.ts +4 -16
  213. package/src/player/components/useTimelineRangeSelection.ts +341 -50
  214. package/src/player/components/useTimelineRevealClip.ts +56 -0
  215. package/src/player/components/useTimelineStackingSync.test.tsx +77 -0
  216. package/src/player/components/useTimelineStackingSync.ts +94 -0
  217. package/src/player/hooks/useExpandedTimelineElements.ts +7 -3
  218. package/src/player/hooks/usePlaybackKeyboard.ts +5 -0
  219. package/src/player/hooks/useTimelinePlayer.seek.test.ts +55 -0
  220. package/src/player/hooks/useTimelinePlayer.test.ts +36 -3
  221. package/src/player/hooks/useTimelinePlayer.ts +52 -53
  222. package/src/player/hooks/useTimelinePlayerLoop.ts +85 -0
  223. package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -1
  224. package/src/player/index.ts +5 -2
  225. package/src/player/lib/runtimeProtocol.test.ts +48 -0
  226. package/src/player/lib/runtimeProtocol.ts +65 -0
  227. package/src/player/lib/time.test.ts +19 -19
  228. package/src/player/lib/time.ts +6 -2
  229. package/src/player/lib/timelineDOM.test.ts +25 -98
  230. package/src/player/lib/timelineDOM.ts +21 -81
  231. package/src/player/lib/timelineElementHelpers.ts +73 -5
  232. package/src/player/lib/timelineIframeHelpers.ts +4 -4
  233. package/src/player/store/playerStore.test.ts +38 -13
  234. package/src/player/store/playerStore.ts +99 -3
  235. package/src/styles/studio.css +27 -0
  236. package/src/utils/assetPreviewDismiss.test.ts +27 -0
  237. package/src/utils/assetPreviewDismiss.ts +29 -0
  238. package/src/utils/assetPreviewStore.ts +33 -0
  239. package/src/utils/blockInstaller.ts +10 -2
  240. package/src/utils/editHistory.test.ts +35 -0
  241. package/src/utils/gsapSoftReload.test.ts +119 -1
  242. package/src/utils/gsapSoftReload.ts +153 -0
  243. package/src/utils/mediaTypes.ts +3 -2
  244. package/src/utils/resizeDebug.ts +55 -0
  245. package/src/utils/rootDuration.test.ts +90 -1
  246. package/src/utils/rootDuration.ts +76 -13
  247. package/src/utils/sdkCutover.ts +4 -0
  248. package/src/utils/studioFileHistory.ts +37 -7
  249. package/src/utils/studioHelpers.ts +62 -0
  250. package/src/utils/studioPreviewHelpers.test.ts +65 -8
  251. package/src/utils/studioPreviewHelpers.ts +10 -0
  252. package/src/utils/studioTelemetry.ts +4 -1
  253. package/src/utils/studioUrlState.test.ts +0 -1
  254. package/src/utils/timelineAssetDrop.test.ts +144 -52
  255. package/src/utils/timelineAssetDrop.ts +67 -29
  256. package/src/utils/timelineDiscovery.ts +0 -17
  257. package/dist/assets/hyperframes-player-BBrKzTGd.js +0 -459
  258. package/dist/assets/index-BRwkMj0w.js +0 -423
  259. package/dist/assets/index-Dq7FEg0K.css +0 -1
  260. package/dist/chunk-SOTCF4DF.js.map +0 -1
  261. package/src/components/StudioPreviewArea.tsx +0 -500
  262. package/src/components/nle/NLELayout.test.ts +0 -12
  263. package/src/components/nle/NLELayout.tsx +0 -591
  264. package/src/player/components/TimelineLayerGutter.tsx +0 -61
  265. package/src/player/components/TimelineSelectionOverlays.tsx +0 -55
  266. package/src/player/components/timelineMarqueeSelection.test.ts +0 -87
  267. package/src/player/components/timelineSnapTargets.test.ts +0 -144
  268. package/src/player/components/timelineSnapTargets.ts +0 -164
  269. package/src/player/components/useTimelineClipDrag.test.tsx +0 -506
  270. package/src/player/components/useTimelineClipGroupDrag.ts +0 -417
  271. package/src/player/components/useTimelineMarqueeSelection.test.tsx +0 -231
  272. package/src/player/components/useTimelineMarqueeSelection.ts +0 -276
  273. package/src/utils/timelineDiscovery.test.ts +0 -90
  274. /package/dist/{domEditingLayers-2ECJK24D.js.map → domEditingLayers-6LQGKPOI.js.map} +0 -0
@@ -1,506 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import React, { act } from "react";
4
- import { createRoot } from "react-dom/client";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
6
- import type { TimelineElement } from "../store/playerStore";
7
- import { usePlayerStore } from "../store/playerStore";
8
- import { TRACK_H } from "./timelineLayout";
9
- import { buildStackingTimelineLayers } from "./timelineTrackOrder";
10
- import type { DraggedClipState, ResizingClipState } from "./useTimelineClipDrag";
11
- import { useTimelineClipDrag } from "./useTimelineClipDrag";
12
-
13
- (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
14
-
15
- function timelineElement(input: {
16
- id: string;
17
- tag?: string;
18
- track: number;
19
- zIndex: number;
20
- start?: number;
21
- duration?: number;
22
- sourceDuration?: number;
23
- playbackStart?: number;
24
- playbackRate?: number;
25
- timelineLocked?: boolean;
26
- }): TimelineElement {
27
- return {
28
- id: input.id,
29
- domId: input.id,
30
- tag: input.tag ?? "div",
31
- start: input.start ?? 0,
32
- duration: input.duration ?? 2,
33
- sourceDuration: input.sourceDuration,
34
- playbackStart: input.playbackStart,
35
- playbackRate: input.playbackRate,
36
- track: input.track,
37
- zIndex: input.zIndex,
38
- stackingContextId: "root",
39
- parentCompositionId: null,
40
- compositionAncestors: ["root"],
41
- sourceFile: "index.html",
42
- timingSource: "authored",
43
- timelineLocked: input.timelineLocked,
44
- };
45
- }
46
-
47
- afterEach(() => {
48
- document.body.innerHTML = "";
49
- usePlayerStore.getState().reset();
50
- });
51
-
52
- function renderDragHarness(elements: TimelineElement[]) {
53
- usePlayerStore.getState().setElements(elements);
54
- const layers = buildStackingTimelineLayers(elements).rows;
55
- const scroll = document.createElement("div");
56
- document.body.append(scroll);
57
- const onMoveElement = vi.fn();
58
- const onResizeElement = vi.fn();
59
- const onMoveElements = vi.fn();
60
- const onResizeElements = vi.fn();
61
- const onPreviewMoveElements = vi.fn();
62
- const onPreviewResizeElements = vi.fn();
63
- let setDraggedClip: ((state: DraggedClipState | null) => void) | null = null;
64
- let setResizingClip: ((state: ResizingClipState | null) => void) | null = null;
65
-
66
- function Harness() {
67
- const hook = useTimelineClipDrag({
68
- scrollRef: { current: scroll },
69
- ppsRef: { current: 100 },
70
- trackOrderRef: { current: layers.map((layer) => layer.id) },
71
- timelineLayersRef: { current: layers },
72
- timelineElementsRef: { current: elements },
73
- onMoveElement,
74
- onResizeElement,
75
- onMoveElements,
76
- onResizeElements,
77
- onPreviewMoveElements,
78
- onPreviewResizeElements,
79
- onBlockedEditAttempt: vi.fn(),
80
- setShowPopover: vi.fn(),
81
- setRangeSelectionRef: { current: vi.fn() },
82
- });
83
- setDraggedClip = hook.setDraggedClip;
84
- setResizingClip = hook.setResizingClip;
85
- return null;
86
- }
87
-
88
- const host = document.createElement("div");
89
- document.body.append(host);
90
- const root = createRoot(host);
91
- act(() => {
92
- root.render(<Harness />);
93
- });
94
- if (!setDraggedClip) throw new Error("Expected drag setter");
95
- if (!setResizingClip) throw new Error("Expected resize setter");
96
- const applyDraggedClip: (state: DraggedClipState | null) => void = setDraggedClip;
97
- const applyResizingClip: (state: ResizingClipState | null) => void = setResizingClip;
98
-
99
- return {
100
- layers,
101
- onMoveElement,
102
- onResizeElement,
103
- onMoveElements,
104
- onResizeElements,
105
- onPreviewMoveElements,
106
- onPreviewResizeElements,
107
- storeElements() {
108
- return usePlayerStore.getState().elements;
109
- },
110
- startDrag(element: TimelineElement, layerIndex: number) {
111
- const layer =
112
- layers[layerIndex] ??
113
- layers.find((candidate) =>
114
- candidate.elements.some((candidateElement) => candidateElement.id === element.id),
115
- ) ??
116
- layers[0]!;
117
- act(() => {
118
- applyDraggedClip({
119
- element,
120
- originClientX: 0,
121
- originClientY: 0,
122
- originScrollLeft: 0,
123
- originScrollTop: 0,
124
- pointerClientX: 0,
125
- pointerClientY: 0,
126
- pointerOffsetX: 0,
127
- pointerOffsetY: 0,
128
- previewStart: element.start,
129
- previewTrack: element.track,
130
- previewLayerId: layer.id,
131
- previewLayerIndex: layerIndex,
132
- previewStackingReorder: null,
133
- snapBeatTime: null,
134
- snapGuideTime: null,
135
- snapGuideKind: null,
136
- started: false,
137
- });
138
- });
139
- },
140
- startResize(element: TimelineElement, edge: "start" | "end") {
141
- act(() => {
142
- applyResizingClip({
143
- element,
144
- edge,
145
- originClientX: 0,
146
- previewStart: element.start,
147
- previewDuration: element.duration,
148
- previewPlaybackStart: element.playbackStart,
149
- snapGuideTime: null,
150
- snapGuideKind: null,
151
- started: false,
152
- });
153
- });
154
- },
155
- movePointer(clientX: number, clientY: number) {
156
- act(() => {
157
- window.dispatchEvent(
158
- new MouseEvent("pointermove", {
159
- bubbles: true,
160
- clientX,
161
- clientY,
162
- }),
163
- );
164
- });
165
- },
166
- async dropPointer() {
167
- await act(async () => {
168
- window.dispatchEvent(new MouseEvent("pointerup", { bubbles: true }));
169
- });
170
- },
171
- unmount() {
172
- act(() => root.unmount());
173
- },
174
- };
175
- }
176
-
177
- describe("useTimelineClipDrag", () => {
178
- it("allows moving a clip past the current composition duration", async () => {
179
- const clip = timelineElement({ id: "clip", track: 0, zIndex: 1 });
180
- const harness = renderDragHarness([clip]);
181
-
182
- harness.startDrag(clip, 0);
183
- harness.movePointer(1100, 0);
184
- await harness.dropPointer();
185
-
186
- expect(harness.onMoveElement).toHaveBeenCalledWith(
187
- clip,
188
- expect.objectContaining({ start: 11 }),
189
- );
190
-
191
- harness.unmount();
192
- });
193
-
194
- it("moves every selected clip body by the same delta", async () => {
195
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 2 });
196
- const second = timelineElement({ id: "second", track: 1, zIndex: 1, start: 4, duration: 2 });
197
- const harness = renderDragHarness([first, second]);
198
- act(() => {
199
- usePlayerStore.getState().setSelection(["first", "second"], "first");
200
- });
201
-
202
- harness.startDrag(first, 0);
203
- harness.movePointer(200, 0);
204
-
205
- expect(harness.storeElements().map((element) => [element.id, element.start])).toEqual([
206
- ["first", 3],
207
- ["second", 6],
208
- ]);
209
- expect(harness.onPreviewMoveElements).toHaveBeenLastCalledWith([
210
- { element: first, start: 3 },
211
- { element: second, start: 6 },
212
- ]);
213
-
214
- await harness.dropPointer();
215
-
216
- expect(harness.onMoveElement).not.toHaveBeenCalled();
217
- expect(harness.onMoveElements).toHaveBeenCalledTimes(1);
218
- expect(harness.onMoveElements).toHaveBeenCalledWith([
219
- { element: first, start: 3 },
220
- { element: second, start: 6 },
221
- ]);
222
-
223
- harness.unmount();
224
- });
225
-
226
- it("clamps a selected group move when the earliest member reaches zero", async () => {
227
- const early = timelineElement({ id: "early", track: 0, zIndex: 1, start: 1, duration: 2 });
228
- const grabbed = timelineElement({ id: "grabbed", track: 1, zIndex: 1, start: 4, duration: 2 });
229
- const harness = renderDragHarness([early, grabbed]);
230
- act(() => {
231
- usePlayerStore.getState().setSelection(["early", "grabbed"], "grabbed");
232
- });
233
-
234
- harness.startDrag(grabbed, 1);
235
- harness.movePointer(-300, 0);
236
- await harness.dropPointer();
237
-
238
- expect(harness.onMoveElements).toHaveBeenCalledWith([
239
- { element: early, start: 0 },
240
- { element: grabbed, start: 3 },
241
- ]);
242
- expect(harness.onMoveElement).not.toHaveBeenCalled();
243
-
244
- harness.unmount();
245
- });
246
-
247
- it("keeps body drag single-clip when the grabbed clip is not in the multi-selection", async () => {
248
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 2 });
249
- const second = timelineElement({ id: "second", track: 1, zIndex: 1, start: 4, duration: 2 });
250
- const outside = timelineElement({ id: "outside", track: 2, zIndex: 1, start: 7, duration: 2 });
251
- const harness = renderDragHarness([first, second, outside]);
252
- act(() => {
253
- usePlayerStore.getState().setSelection(["first", "second"], "first");
254
- });
255
-
256
- harness.startDrag(outside, 2);
257
- harness.movePointer(200, 0);
258
- await harness.dropPointer();
259
-
260
- expect(harness.onMoveElements).not.toHaveBeenCalled();
261
- expect(harness.onMoveElement).toHaveBeenCalledTimes(1);
262
- expect(harness.onMoveElement).toHaveBeenCalledWith(
263
- outside,
264
- expect.objectContaining({ start: 9 }),
265
- );
266
-
267
- harness.unmount();
268
- });
269
-
270
- it("does not form a group when a selected member is locked (grabbed clip moves alone)", async () => {
271
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 2 });
272
- const locked = timelineElement({
273
- id: "locked",
274
- track: 1,
275
- zIndex: 1,
276
- start: 4,
277
- duration: 2,
278
- timelineLocked: true,
279
- });
280
- const harness = renderDragHarness([first, locked]);
281
- act(() => {
282
- usePlayerStore.getState().setSelection(["first", "locked"], "first");
283
- });
284
-
285
- harness.startDrag(first, 0);
286
- harness.movePointer(200, 0);
287
- await harness.dropPointer();
288
-
289
- // The locked member forbids the op, so no group forms: the grabbed clip moves
290
- // alone (single-clip path) and the locked clip is never touched.
291
- expect(harness.onMoveElements).not.toHaveBeenCalled();
292
- expect(harness.onMoveElement).toHaveBeenCalledTimes(1);
293
- expect(harness.onMoveElement).toHaveBeenCalledWith(
294
- first,
295
- expect.objectContaining({ start: 3 }),
296
- );
297
- expect(harness.storeElements().find((el) => el.id === "locked")?.start).toBe(4);
298
-
299
- harness.unmount();
300
- });
301
-
302
- it("allows right-edge resize past the current composition duration", async () => {
303
- const clip = timelineElement({ id: "clip", track: 0, zIndex: 1, start: 6, duration: 2 });
304
- const harness = renderDragHarness([clip]);
305
-
306
- harness.startResize(clip, "end");
307
- harness.movePointer(400, 0);
308
- await harness.dropPointer();
309
-
310
- expect(harness.onResizeElement).toHaveBeenCalledWith(
311
- clip,
312
- expect.objectContaining({ start: 6, duration: 6 }),
313
- );
314
-
315
- harness.unmount();
316
- });
317
-
318
- it("resizes every selected start edge by the same delta", async () => {
319
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 4 });
320
- const second = timelineElement({ id: "second", track: 1, zIndex: 1, start: 5, duration: 3 });
321
- const harness = renderDragHarness([first, second]);
322
- act(() => {
323
- usePlayerStore.getState().setSelection(["first", "second"], "first");
324
- });
325
-
326
- harness.startResize(first, "start");
327
- harness.movePointer(100, 0);
328
-
329
- expect(
330
- harness.storeElements().map((element) => [element.id, element.start, element.duration]),
331
- ).toEqual([
332
- ["first", 2, 3],
333
- ["second", 6, 2],
334
- ]);
335
- expect(harness.onPreviewResizeElements).toHaveBeenLastCalledWith([
336
- { element: first, start: 2, duration: 3, playbackStart: undefined },
337
- { element: second, start: 6, duration: 2, playbackStart: undefined },
338
- ]);
339
-
340
- await harness.dropPointer();
341
-
342
- expect(harness.onResizeElement).not.toHaveBeenCalled();
343
- expect(harness.onResizeElements).toHaveBeenCalledTimes(1);
344
- expect(harness.onResizeElements).toHaveBeenCalledWith([
345
- { element: first, start: 2, duration: 3, playbackStart: undefined },
346
- { element: second, start: 6, duration: 2, playbackStart: undefined },
347
- ]);
348
-
349
- harness.unmount();
350
- });
351
-
352
- it("resizes every selected end edge by the same delta", async () => {
353
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 4 });
354
- const second = timelineElement({ id: "second", track: 1, zIndex: 1, start: 5, duration: 3 });
355
- const harness = renderDragHarness([first, second]);
356
- act(() => {
357
- usePlayerStore.getState().setSelection(["first", "second"], "first");
358
- });
359
-
360
- harness.startResize(first, "end");
361
- harness.movePointer(100, 0);
362
- await harness.dropPointer();
363
-
364
- expect(harness.onResizeElement).not.toHaveBeenCalled();
365
- expect(harness.onResizeElements).toHaveBeenCalledWith([
366
- { element: first, start: 1, duration: 5, playbackStart: undefined },
367
- { element: second, start: 5, duration: 4, playbackStart: undefined },
368
- ]);
369
-
370
- harness.unmount();
371
- });
372
-
373
- it("clamps selected start-edge resize at the most constrained duration", async () => {
374
- const short = timelineElement({ id: "short", track: 0, zIndex: 1, start: 1, duration: 0.5 });
375
- const long = timelineElement({ id: "long", track: 1, zIndex: 1, start: 4, duration: 2 });
376
- const harness = renderDragHarness([short, long]);
377
- act(() => {
378
- usePlayerStore.getState().setSelection(["short", "long"], "short");
379
- });
380
-
381
- harness.startResize(short, "start");
382
- harness.movePointer(100, 0);
383
- await harness.dropPointer();
384
-
385
- expect(harness.onResizeElements).toHaveBeenCalledWith([
386
- { element: short, start: 1.4, duration: 0.1, playbackStart: undefined },
387
- { element: long, start: 4.4, duration: 1.6, playbackStart: undefined },
388
- ]);
389
- expect(harness.onResizeElement).not.toHaveBeenCalled();
390
-
391
- harness.unmount();
392
- });
393
-
394
- it("adjusts every selected media playback start during start-edge resize", async () => {
395
- const audio = timelineElement({
396
- id: "audio",
397
- tag: "audio",
398
- track: 0,
399
- zIndex: 1,
400
- start: 2,
401
- duration: 3,
402
- playbackStart: 1,
403
- playbackRate: 1,
404
- });
405
- const video = timelineElement({
406
- id: "video",
407
- tag: "video",
408
- track: 1,
409
- zIndex: 1,
410
- start: 5,
411
- duration: 4,
412
- playbackStart: 2,
413
- playbackRate: 2,
414
- });
415
- const harness = renderDragHarness([audio, video]);
416
- act(() => {
417
- usePlayerStore.getState().setSelection(["audio", "video"], "audio");
418
- });
419
-
420
- harness.startResize(audio, "start");
421
- harness.movePointer(50, 0);
422
- await harness.dropPointer();
423
-
424
- expect(harness.onResizeElements).toHaveBeenCalledWith([
425
- { element: audio, start: 2.5, duration: 2.5, playbackStart: 1.5 },
426
- { element: video, start: 5.5, duration: 3.5, playbackStart: 3 },
427
- ]);
428
-
429
- harness.unmount();
430
- });
431
-
432
- it("keeps handle resize single-clip when the grabbed clip is not in the multi-selection", async () => {
433
- const first = timelineElement({ id: "first", track: 0, zIndex: 1, start: 1, duration: 2 });
434
- const second = timelineElement({ id: "second", track: 1, zIndex: 1, start: 4, duration: 2 });
435
- const outside = timelineElement({ id: "outside", track: 2, zIndex: 1, start: 7, duration: 2 });
436
- const harness = renderDragHarness([first, second, outside]);
437
- act(() => {
438
- usePlayerStore.getState().setSelection(["first", "second"], "first");
439
- });
440
-
441
- harness.startResize(outside, "end");
442
- harness.movePointer(100, 0);
443
- await harness.dropPointer();
444
-
445
- expect(harness.onResizeElement).toHaveBeenCalledTimes(1);
446
- expect(harness.onResizeElement).toHaveBeenCalledWith(
447
- outside,
448
- expect.objectContaining({ start: 7, duration: 3 }),
449
- );
450
-
451
- harness.unmount();
452
- });
453
-
454
- it("passes a new-lane stacking intent when a vertical drag targets an overlapping lane", async () => {
455
- const front = timelineElement({ id: "front", track: 0, zIndex: 3 });
456
- const middle = timelineElement({ id: "middle", track: 1, zIndex: 2 });
457
- const back = timelineElement({ id: "back", track: 2, zIndex: 1 });
458
- const harness = renderDragHarness([front, middle, back]);
459
-
460
- harness.startDrag(back, 2);
461
- harness.movePointer(0, -2 * TRACK_H);
462
- await harness.dropPointer();
463
-
464
- expect(harness.onMoveElement).toHaveBeenCalledTimes(1);
465
- expect(harness.onMoveElement.mock.calls[0]![1]).toMatchObject({
466
- start: 0,
467
- track: 2,
468
- stackingReorder: {
469
- contextKey: "root",
470
- placement: { type: "above", layerId: harness.layers[0]!.id },
471
- zIndexChanges: [{ key: "back", zIndex: 4 }],
472
- },
473
- });
474
-
475
- harness.unmount();
476
- });
477
-
478
- it("resolves lane stacking from the authored time span, independent of horizontal drag", async () => {
479
- const front = timelineElement({ id: "front", track: 0, zIndex: 3 });
480
- const back = timelineElement({ id: "back", track: 1, zIndex: 1 });
481
- back.start = 0;
482
- front.start = 0;
483
- const harness = renderDragHarness([front, back]);
484
-
485
- // Drag up one row AND rightward in time. The horizontal drift moves the
486
- // clip out of overlap, but the two axes never fight: the vertical restack
487
- // is resolved from the authored (overlapping) span, so it still inserts
488
- // above the target lane rather than silently joining it.
489
- harness.startDrag(back, 1);
490
- harness.movePointer(200, -TRACK_H);
491
- await harness.dropPointer();
492
-
493
- expect(harness.onMoveElement).toHaveBeenCalledTimes(1);
494
- expect(harness.onMoveElement.mock.calls[0]![1]).toMatchObject({
495
- start: 2,
496
- track: 1,
497
- stackingReorder: {
498
- contextKey: "root",
499
- placement: { type: "above", layerId: harness.layers[0]!.id },
500
- zIndexChanges: [{ key: "back", zIndex: 4 }],
501
- },
502
- });
503
-
504
- harness.unmount();
505
- });
506
- });