@hyperframes/studio 0.7.90 → 0.7.92

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 (86) hide show
  1. package/dist/assets/{hyperframes-player-RS9ROX_S.js → hyperframes-player-9qOa08Tv.js} +1 -1
  2. package/dist/assets/{index-Bi-CwQDX.js → index-Bw4lNdtO.js} +1 -1
  3. package/dist/assets/{index-CquRJgm-.js → index-CDTN2ZVa.js} +1 -1
  4. package/dist/assets/index-DIntkrQl.js +428 -0
  5. package/dist/index.d.ts +20 -16
  6. package/dist/index.html +1 -1
  7. package/dist/index.js +6495 -5429
  8. package/dist/index.js.map +1 -1
  9. package/package.json +7 -7
  10. package/src/components/StudioRightPanel.tsx +1 -1
  11. package/src/components/editor/GsapAnimationList.tsx +84 -0
  12. package/src/components/editor/GsapAnimationSection.test.tsx +100 -11
  13. package/src/components/editor/GsapAnimationSection.tsx +11 -41
  14. package/src/components/editor/PropertyPanel.tsx +1 -1
  15. package/src/components/editor/PropertyPanelFlat.tsx +29 -26
  16. package/src/components/editor/gsapAnimationCallbacks.ts +0 -10
  17. package/src/components/editor/propertyPanelFlatDescriptors.ts +20 -0
  18. package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +56 -55
  19. package/src/components/editor/propertyPanelFlatMotionSection.tsx +9 -37
  20. package/src/components/editor/propertyPanelTypes.ts +1 -1
  21. package/src/components/nle/useTimelineEditCallbacks.test.tsx +78 -4
  22. package/src/components/nle/useTimelineEditCallbacks.ts +12 -7
  23. package/src/components/renders/useRenderQueue.ts +16 -3
  24. package/src/components/renders/useRenderQueueTelemetry.test.tsx +104 -0
  25. package/src/contexts/DomEditContext.tsx +1 -1
  26. package/src/hooks/useDomEditSession.test.tsx +1 -1
  27. package/src/hooks/useDomEditSession.ts +1 -1
  28. package/src/hooks/useGsapKeyframeOps.test.tsx +83 -5
  29. package/src/hooks/useGsapKeyframeOps.ts +20 -9
  30. package/src/player/components/BeatStrip.test.tsx +374 -0
  31. package/src/player/components/BeatStrip.tsx +295 -37
  32. package/src/player/components/KeyframeDiamondContextMenu.test.tsx +16 -1
  33. package/src/player/components/KeyframeDiamondContextMenu.tsx +6 -5
  34. package/src/player/components/Timeline.test.ts +2 -3
  35. package/src/player/components/Timeline.tsx +47 -48
  36. package/src/player/components/TimelineCanvas.tsx +14 -71
  37. package/src/player/components/TimelineClip.tsx +6 -3
  38. package/src/player/components/TimelineClipDiamonds.test.tsx +442 -18
  39. package/src/player/components/TimelineClipDiamonds.tsx +66 -211
  40. package/src/player/components/TimelineGestureOverlay.test.tsx +90 -0
  41. package/src/player/components/TimelineGestureOverlay.tsx +96 -0
  42. package/src/player/components/TimelineLanes.test.tsx +0 -1
  43. package/src/player/components/TimelineLanes.tsx +3 -2
  44. package/src/player/components/TimelineOverlays.test.ts +161 -0
  45. package/src/player/components/TimelineOverlays.tsx +104 -11
  46. package/src/player/components/timelineCallbacks.ts +1 -1
  47. package/src/player/components/timelineClipDragGestureLifecycle.test.ts +18 -3
  48. package/src/player/components/timelineClipDragGestureLifecycle.ts +199 -51
  49. package/src/player/components/timelineClipDragPreview.test.ts +33 -0
  50. package/src/player/components/timelineClipDragPreview.ts +18 -12
  51. package/src/player/components/timelineClipDragTypes.ts +12 -0
  52. package/src/player/components/timelineDiamondTypes.ts +5 -15
  53. package/src/player/components/timelineDragDrop.test.tsx +206 -0
  54. package/src/player/components/timelineDragDrop.ts +152 -30
  55. package/src/player/components/timelineLaneProps.ts +0 -1
  56. package/src/player/components/timelineMarquee.test.ts +73 -25
  57. package/src/player/components/timelineMarquee.ts +44 -16
  58. package/src/player/components/timelineTestViewport.ts +13 -0
  59. package/src/player/components/timelineViewModel.ts +4 -4
  60. package/src/player/components/useTimelineClipDrag.resize.test.tsx +219 -17
  61. package/src/player/components/useTimelineClipDrag.ts +112 -70
  62. package/src/player/components/useTimelineClipRenderWindow.ts +4 -4
  63. package/src/player/components/useTimelineKeyframeHandlers.test.tsx +43 -7
  64. package/src/player/components/useTimelineKeyframeHandlers.ts +465 -2
  65. package/src/player/components/useTimelineLaneMoveRefresh.ts +7 -0
  66. package/src/player/components/useTimelineRangeSelection.test.tsx +204 -0
  67. package/src/player/components/useTimelineRangeSelection.ts +202 -69
  68. package/src/player/components/useTimelineRangeSelectionScrub.test.tsx +3 -2
  69. package/src/player/components/useTimelineRowVirtualization.ts +11 -4
  70. package/src/player/components/useTrackGapMenu.test.tsx +64 -0
  71. package/src/player/components/useTrackGapMenu.ts +32 -9
  72. package/src/player/store/keyframeSlice.ts +59 -18
  73. package/src/player/store/playerStore.test.ts +76 -0
  74. package/src/player/store/playerStore.ts +19 -11
  75. package/src/telemetry/canary.test.ts +325 -0
  76. package/src/telemetry/canary.ts +268 -0
  77. package/src/telemetry/client.test.ts +8 -3
  78. package/src/telemetry/client.ts +13 -43
  79. package/src/telemetry/config.ts +16 -2
  80. package/src/telemetry/distinctId.test.ts +35 -0
  81. package/src/telemetry/distinctId.ts +11 -2
  82. package/src/telemetry/policy.test.ts +81 -0
  83. package/src/telemetry/policy.ts +104 -0
  84. package/src/utils/studioTelemetry.test.ts +76 -0
  85. package/src/utils/studioTelemetry.ts +14 -6
  86. package/dist/assets/index-Cql15Yur.js +0 -428
@@ -12,7 +12,7 @@ import { TimelineEmptyState } from "./TimelineEmptyState";
12
12
  import { TimelineCanvas } from "./TimelineCanvas";
13
13
  import { type KeyframeDiamondContextMenuState } from "./KeyframeDiamondContextMenu";
14
14
  import { useTimelineClipDrag } from "./useTimelineClipDrag";
15
- import { TimelineOverlays } from "./TimelineOverlays";
15
+ import { TimelineOverlays, type ClipContextMenuState } from "./TimelineOverlays";
16
16
  import { useTimelineEditPinning } from "./useTimelineEditPinning";
17
17
  import { useTimelineStackingSync } from "./useTimelineStackingSync";
18
18
  import { useTimelineGeometry } from "./useTimelineGeometry";
@@ -29,7 +29,6 @@ import {
29
29
  import { useTimelineKeyframeHandlers } from "./useTimelineKeyframeHandlers";
30
30
  import { useTrackGapMenu } from "./useTrackGapMenu";
31
31
  import { useTimelineGapHighlights } from "./useTimelineGapHighlights";
32
- import { useStudioPlaybackContextOptional } from "../../contexts/StudioContext";
33
32
  import { TimelineRazorGuide, useTimelineRazorInteraction } from "./TimelineRazorInteraction";
34
33
  import { useTimelinePerformanceTelemetry } from "./useTimelinePerformanceTelemetry";
35
34
  import {
@@ -45,6 +44,7 @@ import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
45
44
  import { useTimelineRowVirtualization } from "./useTimelineRowVirtualization";
46
45
  import { useTimelineClipRenderWindow } from "./useTimelineClipRenderWindow";
47
46
  import { useTimelineActiveClips } from "./useTimelineActiveClips";
47
+ import { useTimelineLaneMoveRefresh } from "./useTimelineLaneMoveRefresh";
48
48
 
49
49
  export {
50
50
  shouldAutoScrollTimeline,
@@ -106,11 +106,7 @@ export const Timeline = memo(function Timeline({
106
106
  onSplitElement: onSplitElementOverride,
107
107
  });
108
108
  const theme = useMemo(() => ({ ...defaultTimelineTheme, ...themeOverrides }), [themeOverrides]);
109
- const playbackContext = useStudioPlaybackContextOptional();
110
- const setRefreshKey = playbackContext?.setRefreshKey;
111
- const refreshAfterLaneMove = useCallback(() => {
112
- setRefreshKey?.((key) => key + 1);
113
- }, [setRefreshKey]);
109
+ const refreshAfterLaneMove = useTimelineLaneMoveRefresh();
114
110
  useMusicBeatAnalysis();
115
111
  const rawElements = usePlayerStore((s) => s.elements);
116
112
  const expandedElements = useExpandedTimelineElements();
@@ -129,14 +125,8 @@ export const Timeline = memo(function Timeline({
129
125
  const clipRevealRequest = usePlayerStore((s) => s.clipRevealRequest);
130
126
  const focusedEaseSegment = usePlayerStore((s) => s.focusedEaseSegment);
131
127
  const gsapAnimations = usePlayerStore((s) => s.gsapAnimations);
132
- const hasKeyframedClips = useMemo(
133
- () => hasKeyframedTimelineClips(gsapAnimations),
134
- [gsapAnimations],
135
- );
136
- const labelMode = hasKeyframedClips;
137
- // Without the label column the pre-t=0 breathing room is still TRACKS_LEFT_PAD
138
- // (dropping it would jam clip 0 against the gutter on every non-keyframed
139
- // composition); in label mode the 232px label column already provides it.
128
+ const labelMode = useMemo(() => hasKeyframedTimelineClips(gsapAnimations), [gsapAnimations]);
129
+ // The label column provides pre-t=0 space; otherwise keep TRACKS_LEFT_PAD after the gutter.
140
130
  const contentOrigin = labelMode ? LABEL_COL_W + GUTTER : GUTTER + TRACKS_LEFT_PAD;
141
131
  const contentGutter = labelMode ? GUTTER : 0;
142
132
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
@@ -151,11 +141,7 @@ export const Timeline = memo(function Timeline({
151
141
  const shiftHeld = useTimelineShiftModifier();
152
142
  const [showPopover, setShowPopover] = useState(false);
153
143
  const [kfContextMenu, setKfContextMenu] = useState<KeyframeDiamondContextMenuState | null>(null);
154
- const [clipContextMenu, setClipContextMenu] = useState<{
155
- x: number;
156
- y: number;
157
- element: TimelineElement;
158
- } | null>(null);
144
+ const [clipContextMenu, setClipContextMenu] = useState<ClipContextMenuState | null>(null);
159
145
  const setContainerRef = useCallback((el: HTMLDivElement | null) => {
160
146
  containerRef.current = el;
161
147
  }, []);
@@ -236,7 +222,6 @@ export const Timeline = memo(function Timeline({
236
222
  setResizingClip,
237
223
  blockedClipRef,
238
224
  suppressClickRef,
239
- syncClipDragAutoScroll,
240
225
  } = useTimelineClipDrag({
241
226
  scrollRef,
242
227
  ppsRef,
@@ -253,22 +238,26 @@ export const Timeline = memo(function Timeline({
253
238
  readZIndex: zSyncEnabled ? readClipZIndex : undefined,
254
239
  onStackingPatches: zSyncEnabled ? applyStackingPatches : undefined,
255
240
  refreshAfterLaneMove,
241
+ sessionEpoch,
256
242
  });
257
243
 
258
- const { isDragOver, handleAssetDragOver, handleAssetDrop, clearDropPreview } =
259
- useTimelineAssetDrop({
260
- scrollRef,
261
- ppsRef,
262
- durationRef,
263
- trackOrderRef,
264
- rowGeometryRef,
265
- contentOrigin,
266
- onFileDrop: pinnedOnFileDrop,
267
- onAssetDrop: pinnedOnAssetDrop,
268
- onBlockDrop: pinnedOnBlockDrop,
269
- onCompositionDrop: pinnedOnCompositionDrop,
270
- });
244
+ const assetDrop = useTimelineAssetDrop({
245
+ scrollRef,
246
+ ppsRef,
247
+ durationRef,
248
+ trackOrderRef,
249
+ rowGeometryRef,
250
+ contentOrigin,
251
+ onFileDrop: pinnedOnFileDrop,
252
+ onAssetDrop: pinnedOnAssetDrop,
253
+ onBlockDrop: pinnedOnBlockDrop,
254
+ onCompositionDrop: pinnedOnCompositionDrop,
255
+ sessionEpoch,
256
+ });
271
257
  const displayLayout = useTimelineDisplayLayout(draggedClip, trackOrder, rowGeometry);
258
+ const resizingElementIds =
259
+ resizingClip?.groupPreview?.map((change) => change.key) ??
260
+ (resizingClip ? [getTimelineElementIdentity(resizingClip.element)] : undefined);
272
261
  const { recordTimelineScroll } = useTimelinePerformanceTelemetry({
273
262
  totalClipCount: expandedElements.length,
274
263
  totalRowCount: displayLayout.displayTrackOrder.length,
@@ -289,7 +278,7 @@ export const Timeline = memo(function Timeline({
289
278
  selectedElementId,
290
279
  revealElementId: clipRevealRequest?.elementId ?? null,
291
280
  draggedRowKey: draggedClip?.started ? draggedClip.previewTrack : undefined,
292
- resizingRowKey: resizingClip?.element.track,
281
+ resizingElementIds,
293
282
  clipContextMenuRowKey: clipContextMenu?.element.track,
294
283
  keyframeContextMenuRowKey: kfContextMenu?.element.track,
295
284
  lastScrollLeftRef,
@@ -332,7 +321,7 @@ export const Timeline = memo(function Timeline({
332
321
  duration: displayDuration,
333
322
  selectedElementId: selectedElementId ?? undefined,
334
323
  draggedElementId: draggedClip ? getTimelineElementIdentity(draggedClip.element) : undefined,
335
- resizingElementId: resizingClip ? getTimelineElementIdentity(resizingClip.element) : undefined,
324
+ resizingElementIds,
336
325
  revealElementId: clipRevealRequest?.elementId,
337
326
  focusedEaseElementId: focusedEaseSegment?.elementId,
338
327
  clipContextMenuElementId: clipContextMenu
@@ -405,6 +394,7 @@ export const Timeline = memo(function Timeline({
405
394
  handlePointerDown,
406
395
  handlePointerMove,
407
396
  handlePointerUp,
397
+ handlePointerCancel,
408
398
  } = useTimelineRangeSelection({
409
399
  scrollRef,
410
400
  ppsRef,
@@ -417,10 +407,11 @@ export const Timeline = memo(function Timeline({
417
407
  isDragging,
418
408
  setShowPopover,
419
409
  elementsRef: expandedElementsRef,
420
- trackOrderRef,
410
+ clipIndex,
421
411
  rowGeometryRef,
422
412
  onSelectElement,
423
413
  contentOrigin,
414
+ sessionEpoch,
424
415
  });
425
416
  setRangeSelectionRef.current = setRangeSelection; // stable ref consumed by useTimelineClipDrag
426
417
 
@@ -443,11 +434,11 @@ export const Timeline = memo(function Timeline({
443
434
  if (!timelineReady || expandedElements.length === 0) {
444
435
  return (
445
436
  <TimelineEmptyState
446
- isDragOver={isDragOver}
437
+ isDragOver={assetDrop.isDragOver}
447
438
  onFileDrop={!!onFileDrop}
448
- onDragOver={handleAssetDragOver}
449
- onDragLeave={() => clearDropPreview()}
450
- onDrop={handleAssetDrop}
439
+ onDragOver={assetDrop.handleAssetDragOver}
440
+ onDragLeave={assetDrop.handleAssetDragLeave}
441
+ onDrop={assetDrop.handleAssetDrop}
451
442
  />
452
443
  );
453
444
  }
@@ -457,7 +448,7 @@ export const Timeline = memo(function Timeline({
457
448
  ref={setContainerRef}
458
449
  aria-label="Timeline"
459
450
  data-timeline-element-count={expandedElements.length}
460
- className={`relative border-t select-none h-full overflow-hidden ${isDragOver ? "ring-1 ring-inset ring-studio-accent/60" : ""} ${activeTool === "razor" ? "cursor-crosshair" : shiftHeld ? "cursor-crosshair" : "cursor-default"}`}
451
+ className={`relative border-t select-none h-full overflow-hidden ${assetDrop.isDragOver ? "ring-1 ring-inset ring-studio-accent/60" : ""} ${activeTool === "razor" ? "cursor-crosshair" : shiftHeld ? "cursor-crosshair" : "cursor-default"}`}
461
452
  onMouseMove={updateRazorGuide}
462
453
  onMouseLeave={clearRazorGuide}
463
454
  style={{
@@ -468,6 +459,7 @@ export const Timeline = memo(function Timeline({
468
459
  >
469
460
  <div
470
461
  ref={setScrollRef}
462
+ // Stable owner for gestures that must survive virtual row/clip unmounts.
471
463
  data-timeline-scroll-viewport
472
464
  data-timeline-auto-scroll-left-inset={labelMode ? LABEL_COL_W : 0}
473
465
  tabIndex={-1}
@@ -478,9 +470,9 @@ export const Timeline = memo(function Timeline({
478
470
  syncScrollViewport(e.currentTarget, true);
479
471
  }}
480
472
  {...rowWindow.timelineFocusProps}
481
- onDragOver={handleAssetDragOver}
482
- onDragLeave={() => clearDropPreview()}
483
- onDrop={handleAssetDrop}
473
+ onDragOver={assetDrop.handleAssetDragOver}
474
+ onDragLeave={assetDrop.handleAssetDragLeave}
475
+ onDrop={assetDrop.handleAssetDrop}
484
476
  onPointerDown={(e) => {
485
477
  // Let interactive controls (keyframe nav/toggle, caret, inputs) handle
486
478
  // their own clicks — scrubbing here would preventDefault and eat them.
@@ -490,7 +482,8 @@ export const Timeline = memo(function Timeline({
490
482
  }}
491
483
  onPointerMove={handlePointerMove}
492
484
  onPointerUp={handlePointerUp}
493
- onLostPointerCapture={handlePointerUp}
485
+ onPointerCancel={handlePointerCancel}
486
+ onLostPointerCapture={handlePointerCancel}
494
487
  >
495
488
  <TimelineCanvas
496
489
  major={major}
@@ -541,7 +534,6 @@ export const Timeline = memo(function Timeline({
541
534
  setResizingClip={setResizingClip}
542
535
  setDraggedClip={setDraggedClip}
543
536
  setSelectedElementId={setSelectedElementId}
544
- syncClipDragAutoScroll={syncClipDragAutoScroll}
545
537
  shiftClickClipRef={shiftClickClipRef}
546
538
  getPreviewElement={getPreviewElement}
547
539
  getTrackStyle={getTrackStyle}
@@ -561,7 +553,12 @@ export const Timeline = memo(function Timeline({
561
553
  setSelectedElementId(el.key ?? el.id);
562
554
  onSelectElement?.(el);
563
555
  dismissGapMenu();
564
- setClipContextMenu({ x: e.clientX, y: e.clientY, element: el });
556
+ setClipContextMenu({
557
+ x: e.clientX,
558
+ y: e.clientY,
559
+ element: el,
560
+ sessionEpoch: usePlayerStore.getState().timelineSessionEpoch,
561
+ });
565
562
  }}
566
563
  onContextMenuLane={(e, track, time) => {
567
564
  if (draggedClip?.started || resizingClip) return;
@@ -572,6 +569,8 @@ export const Timeline = memo(function Timeline({
572
569
  {activeTool === "razor" && razorGuideX !== null && <TimelineRazorGuide x={razorGuideX} />}
573
570
  </div>
574
571
  <TimelineOverlays
572
+ elements={expandedElements}
573
+ elementsRef={expandedElementsRef}
575
574
  theme={theme}
576
575
  showShortcutHint={showShortcutHint}
577
576
  showPopover={showPopover}
@@ -1,8 +1,7 @@
1
1
  import { memo } from "react";
2
2
  import { TimelineRuler } from "./TimelineRuler";
3
3
  import { PlayheadIndicator } from "./PlayheadIndicator";
4
- import { getTimelineEditCapabilities, type TimelineRangeSelection } from "./timelineEditing";
5
- import { getRenderedTimelineElement } from "./timelineTheme";
4
+ import type { TimelineRangeSelection } from "./timelineEditing";
6
5
  import {
7
6
  RULER_H,
8
7
  CLIP_Y,
@@ -19,13 +18,11 @@ import type { ResizingClipState } from "./useTimelineClipDrag";
19
18
  import { type MultiDragPreviewInput } from "./timelineMultiDragPreview";
20
19
  import { useTimelineEditContextOptional } from "../../contexts/TimelineEditContext";
21
20
  import type { Rect } from "../../utils/marqueeGeometry";
22
- import { TimelineClip } from "./TimelineClip";
23
21
  import { TimelineLanes } from "./TimelineLanes";
24
22
  import type { TimelineLaneBaseProps } from "./timelineLaneProps";
25
- import { renderClipChildren } from "./timelineClipChildren";
26
23
  import type { TimelineLaneGapStrips } from "./useTimelineGapHighlights";
27
- import { isTimelineClipActive } from "./useTimelineActiveClips";
28
24
  import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
25
+ import { TimelineGestureOverlay } from "./TimelineGestureOverlay";
29
26
 
30
27
  interface TimelineCanvasProps extends TimelineLaneBaseProps {
31
28
  major: number[];
@@ -65,15 +62,6 @@ export const TimelineCanvas = memo(function TimelineCanvas(props: TimelineCanvas
65
62
  const beatDragging = usePlayerStore((s) => s.beatDragging);
66
63
  const draggedElement = draggedClip?.element ?? null;
67
64
  const draggedElementIdentity = draggedElement ? getTimelineElementIdentity(draggedElement) : null;
68
- const activeDraggedElement =
69
- draggedClip?.started === true && draggedElement && draggedElementIdentity
70
- ? getRenderedTimelineElement({
71
- element: draggedElement,
72
- draggedElementId: draggedElementIdentity,
73
- previewStart: draggedClip.previewStart,
74
- previewTrack: draggedClip.previewTrack,
75
- })
76
- : null;
77
65
  // The drag ghost follows the cursor freely (both axes) — CapCut-style. The
78
66
  // "magnetic" affordance is a highlight on the destination lane (draggedRowIndex),
79
67
  // which flips at the MAGNETIC_TRACK_THRESHOLD point; the clip drops into it.
@@ -94,22 +82,6 @@ export const TimelineCanvas = memo(function TimelineCanvas(props: TimelineCanvas
94
82
  selectedKeys: selectedElementIds,
95
83
  }
96
84
  : null;
97
- const activeDraggedPosition =
98
- draggedClip?.started === true && activeDraggedElement && scrollRef.current
99
- ? {
100
- left:
101
- draggedClip.pointerClientX -
102
- scrollRef.current.getBoundingClientRect().left +
103
- scrollRef.current.scrollLeft -
104
- draggedClip.pointerOffsetX,
105
- top:
106
- draggedClip.pointerClientY -
107
- scrollRef.current.getBoundingClientRect().top +
108
- scrollRef.current.scrollTop -
109
- draggedClip.pointerOffsetY,
110
- }
111
- : null;
112
-
113
85
  return (
114
86
  <div
115
87
  className="relative"
@@ -235,47 +207,18 @@ export const TimelineCanvas = memo(function TimelineCanvas(props: TimelineCanvas
235
207
  />
236
208
  )}
237
209
 
238
- {/* Drag ghost */}
239
- {activeDraggedElement && activeDraggedPosition && (
240
- <div
241
- className="absolute pointer-events-none"
242
- style={{
243
- top: activeDraggedPosition.top,
244
- left: activeDraggedPosition.left,
245
- width: Math.max(activeDraggedElement.duration * props.pps, 4),
246
- height: draggedClipHeight,
247
- zIndex: 40,
248
- }}
249
- >
250
- <TimelineClip
251
- el={{ ...activeDraggedElement, start: 0 }}
252
- pps={props.pps}
253
- clipY={0}
254
- isSelected={
255
- props.selectedElementId === (activeDraggedElement.key ?? activeDraggedElement.id)
256
- }
257
- isHovered={false}
258
- isDragging={true}
259
- isActive={isTimelineClipActive(activeDraggedElement, props.currentTime)}
260
- hasCustomContent={!!props.renderClipContent}
261
- capabilities={getTimelineEditCapabilities(activeDraggedElement)}
262
- theme={props.theme}
263
- isComposition={!!activeDraggedElement.compositionSrc}
264
- onHoverStart={() => {}}
265
- onHoverEnd={() => {}}
266
- onResizeStart={() => {}}
267
- onClick={() => {}}
268
- onDoubleClick={() => {}}
269
- >
270
- {renderClipChildren(
271
- activeDraggedElement,
272
- props.getTrackStyle(activeDraggedElement.tag),
273
- props.renderClipContent,
274
- props.renderClipOverlay,
275
- )}
276
- </TimelineClip>
277
- </div>
278
- )}
210
+ <TimelineGestureOverlay
211
+ drag={draggedClip}
212
+ scrollRef={scrollRef}
213
+ pixelsPerSecond={props.pps}
214
+ rowHeight={draggedClipHeight}
215
+ selectedElementId={props.selectedElementId}
216
+ currentTime={props.currentTime}
217
+ theme={props.theme}
218
+ getTrackStyle={props.getTrackStyle}
219
+ renderClipContent={props.renderClipContent}
220
+ renderClipOverlay={props.renderClipOverlay}
221
+ />
279
222
 
280
223
  {/* Marquee (rubber-band) multi-select rectangle — mirrors the canvas
281
224
  MarqueeOverlay look: semi-transparent accent fill + dashed border. */}
@@ -12,6 +12,7 @@ interface TimelineClipProps {
12
12
  isSelected: boolean;
13
13
  isHovered: boolean;
14
14
  isDragging?: boolean;
15
+ isGestureActor?: boolean;
15
16
  isActive?: boolean;
16
17
  hasCustomContent: boolean;
17
18
  capabilities: TimelineEditCapabilities;
@@ -36,6 +37,7 @@ export const TimelineClip = memo(function TimelineClip({
36
37
  isSelected,
37
38
  isHovered,
38
39
  isDragging = false,
40
+ isGestureActor = false,
39
41
  isActive = false,
40
42
  hasCustomContent,
41
43
  capabilities,
@@ -85,13 +87,14 @@ export const TimelineClip = memo(function TimelineClip({
85
87
 
86
88
  return (
87
89
  <div
88
- data-clip="true"
89
- data-el-id={el.key ?? el.id}
90
+ data-clip={isGestureActor ? undefined : "true"}
91
+ data-el-id={isGestureActor ? undefined : (el.key ?? el.id)}
90
92
  data-clip-start={el.start}
91
93
  data-clip-end={el.start + el.duration}
92
94
  data-clip-hidden={el.hidden ? "true" : undefined}
93
95
  data-active={isActive ? "" : undefined}
94
- tabIndex={-1}
96
+ aria-hidden={isGestureActor ? "true" : undefined}
97
+ tabIndex={isGestureActor ? undefined : -1}
95
98
  className={clipClassName}
96
99
  style={style}
97
100
  title={