@hyperframes/studio 0.7.56 → 0.7.58

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 (146) hide show
  1. package/dist/assets/hyperframes-player-CtTDO63S.js +459 -0
  2. package/dist/assets/{index-CmVCjZjp.js → index-B_UvTX3E.js} +205 -205
  3. package/dist/assets/{index-BWnOxAiH.js → index-C47jAC3Q.js} +1 -1
  4. package/dist/assets/{index-C4csZims.js → index-DeQPzqwH.js} +1 -1
  5. package/dist/assets/index-uahwWkgw.css +1 -0
  6. package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
  7. package/dist/chunk-OBAG3GWK.js.map +1 -0
  8. package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
  9. package/dist/index.d.ts +41 -4
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +7436 -5423
  12. package/dist/index.js.map +1 -1
  13. package/package.json +7 -7
  14. package/src/App.tsx +3 -3
  15. package/src/components/StudioFeedbackBar.tsx +2 -2
  16. package/src/components/editor/CanvasContextMenu.test.tsx +166 -2
  17. package/src/components/editor/CanvasContextMenu.tsx +100 -20
  18. package/src/components/editor/DomEditOverlay.tsx +34 -62
  19. package/src/components/editor/LayersPanel.tsx +111 -91
  20. package/src/components/editor/canvasContextMenuZOrder.test.ts +243 -2
  21. package/src/components/editor/canvasContextMenuZOrder.ts +184 -23
  22. package/src/components/editor/domEditingDom.ts +5 -6
  23. package/src/components/editor/domEditingElement.ts +15 -32
  24. package/src/components/editor/layersPanelSort.ts +80 -0
  25. package/src/components/editor/propertyPanelColorGradingSection.tsx +15 -21
  26. package/src/components/editor/useCanvasContextMenuState.ts +92 -0
  27. package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
  28. package/src/components/editor/useLayerRevealOverride.ts +389 -0
  29. package/src/components/editor/useZOrderCrossedFlash.tsx +68 -0
  30. package/src/components/nle/AssetPreviewOverlay.test.tsx +106 -0
  31. package/src/components/nle/AssetPreviewOverlay.tsx +61 -37
  32. package/src/components/nle/PreviewOverlays.tsx +61 -16
  33. package/src/components/nle/TimelinePane.test.ts +3 -0
  34. package/src/components/nle/TimelinePane.tsx +11 -1
  35. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
  36. package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
  37. package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
  38. package/src/components/nle/zLaneGesture.test.ts +176 -0
  39. package/src/components/nle/zLaneGesture.ts +77 -0
  40. package/src/components/sidebar/AssetCard.test.tsx +106 -0
  41. package/src/components/sidebar/AssetCard.tsx +19 -2
  42. package/src/components/sidebar/AudioRow.tsx +19 -2
  43. package/src/hooks/domEditCommitTypes.ts +29 -2
  44. package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
  45. package/src/hooks/fetchStubTestUtils.ts +16 -0
  46. package/src/hooks/timelineEditingHelpers.test.ts +183 -1
  47. package/src/hooks/timelineEditingHelpers.ts +63 -259
  48. package/src/hooks/timelineMoveAdapter.test.ts +44 -40
  49. package/src/hooks/timelineMoveAdapter.ts +7 -4
  50. package/src/hooks/timelineTimingSync.test.ts +820 -0
  51. package/src/hooks/timelineTimingSync.ts +597 -0
  52. package/src/hooks/timelineTrackVisibility.test.ts +3 -1
  53. package/src/hooks/timelineTrackVisibility.ts +4 -3
  54. package/src/hooks/useCaptionDetection.ts +2 -0
  55. package/src/hooks/useClipboard.ts +19 -17
  56. package/src/hooks/useCompositionDimensions.ts +32 -5
  57. package/src/hooks/useDomEditCommits.test.tsx +392 -23
  58. package/src/hooks/useDomEditCommits.ts +180 -53
  59. package/src/hooks/useDomEditSession.test.tsx +3 -1
  60. package/src/hooks/useDomEditSession.ts +1 -1
  61. package/src/hooks/useElementLifecycleOps.test.tsx +559 -28
  62. package/src/hooks/useElementLifecycleOps.ts +175 -69
  63. package/src/hooks/useElementPicker.ts +7 -8
  64. package/src/hooks/usePreviewPersistence.ts +2 -2
  65. package/src/hooks/useTimelineAssetDropOps.ts +175 -0
  66. package/src/hooks/useTimelineEditing.test.tsx +591 -277
  67. package/src/hooks/useTimelineEditing.ts +202 -282
  68. package/src/hooks/useTimelineEditingTypes.ts +1 -1
  69. package/src/hooks/useTimelineGroupEditing.ts +187 -134
  70. package/src/player/components/PlayerControls.tsx +5 -5
  71. package/src/player/components/PlayheadIndicator.tsx +7 -2
  72. package/src/player/components/Timeline.test.ts +105 -18
  73. package/src/player/components/Timeline.tsx +69 -98
  74. package/src/player/components/TimelineCanvas.tsx +47 -7
  75. package/src/player/components/TimelineLanes.tsx +32 -10
  76. package/src/player/components/TimelineOverlays.tsx +36 -0
  77. package/src/player/components/TimelineRuler.tsx +70 -47
  78. package/src/player/components/TrackGapContextMenu.tsx +116 -0
  79. package/src/player/components/timelineCallbacks.ts +4 -1
  80. package/src/player/components/timelineClipDragCommit.test.ts +370 -11
  81. package/src/player/components/timelineClipDragCommit.ts +267 -68
  82. package/src/player/components/timelineGapCommit.test.ts +163 -0
  83. package/src/player/components/timelineGapCommit.ts +103 -0
  84. package/src/player/components/timelineGaps.test.ts +221 -0
  85. package/src/player/components/timelineGaps.ts +182 -0
  86. package/src/player/components/timelineLayout.test.ts +2 -1
  87. package/src/player/components/timelineLayout.ts +119 -30
  88. package/src/player/components/timelineMarquee.test.ts +20 -10
  89. package/src/player/components/timelineMarquee.ts +9 -2
  90. package/src/player/components/timelineRevealScroll.test.ts +133 -0
  91. package/src/player/components/timelineRevealScroll.ts +91 -0
  92. package/src/player/components/timelineStackingSync.test.ts +102 -1
  93. package/src/player/components/timelineStackingSync.ts +45 -5
  94. package/src/player/components/timelineTrackPersistPipeline.test.ts +168 -0
  95. package/src/player/components/timelineZMirror.test.ts +667 -0
  96. package/src/player/components/timelineZMirror.ts +393 -0
  97. package/src/player/components/timelineZones.test.ts +122 -310
  98. package/src/player/components/timelineZones.ts +82 -135
  99. package/src/player/components/timelineZoom.test.ts +2 -2
  100. package/src/player/components/timelineZoom.ts +7 -3
  101. package/src/player/components/useTimelineClipDrag.ts +1 -1
  102. package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
  103. package/src/player/components/useTimelineGapHighlights.ts +110 -0
  104. package/src/player/components/useTimelinePlayhead.ts +4 -3
  105. package/src/player/components/useTimelineRangeSelection.ts +6 -4
  106. package/src/player/components/useTimelineRevealClip.ts +56 -0
  107. package/src/player/components/useTimelineScrollViewport.ts +73 -0
  108. package/src/player/components/useTimelineStackingSync.test.tsx +11 -2
  109. package/src/player/components/useTimelineStackingSync.ts +17 -4
  110. package/src/player/components/useTimelineTrackDerivations.ts +38 -0
  111. package/src/player/components/useTrackGapMenu.ts +155 -0
  112. package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
  113. package/src/player/hooks/useExpandedTimelineElements.ts +19 -1
  114. package/src/player/hooks/useTimelinePlayer.seek.test.ts +55 -0
  115. package/src/player/hooks/useTimelinePlayer.test.ts +36 -3
  116. package/src/player/hooks/useTimelinePlayer.ts +4 -0
  117. package/src/player/hooks/useTimelineSyncCallbacks.ts +11 -1
  118. package/src/player/index.ts +3 -1
  119. package/src/player/lib/layerOrdering.test.ts +1 -15
  120. package/src/player/lib/layerOrdering.ts +5 -28
  121. package/src/player/lib/runtimeProtocol.test.ts +48 -0
  122. package/src/player/lib/runtimeProtocol.ts +65 -0
  123. package/src/player/lib/timelineDOM.test.ts +72 -0
  124. package/src/player/lib/timelineDOM.ts +12 -0
  125. package/src/player/lib/timelineElementHelpers.ts +54 -11
  126. package/src/player/lib/timelineIframeHelpers.ts +2 -4
  127. package/src/player/store/playerStore.test.ts +38 -13
  128. package/src/player/store/playerStore.ts +49 -1
  129. package/src/telemetry/events.test.ts +10 -0
  130. package/src/telemetry/events.ts +1 -0
  131. package/src/utils/assetPreviewDismiss.test.ts +27 -0
  132. package/src/utils/assetPreviewDismiss.ts +29 -0
  133. package/src/utils/domEditSaveQueue.ts +4 -3
  134. package/src/utils/editHistory.test.ts +82 -0
  135. package/src/utils/editHistory.ts +14 -2
  136. package/src/utils/gsapSoftReload.test.ts +54 -115
  137. package/src/utils/gsapSoftReload.ts +42 -152
  138. package/src/utils/gsapUndoRestore.test.ts +269 -0
  139. package/src/utils/gsapUndoRestore.ts +269 -0
  140. package/src/utils/sourceScopedSelectorIndex.ts +29 -0
  141. package/src/utils/timelineAssetDrop.test.ts +144 -52
  142. package/src/utils/timelineAssetDrop.ts +62 -86
  143. package/dist/assets/hyperframes-player-BBrKzTGd.js +0 -459
  144. package/dist/assets/index-D-GyYi2d.css +0 -1
  145. package/dist/chunk-5QSIMBEJ.js.map +0 -1
  146. /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { AUDIO_EXT, IMAGE_EXT, VIDEO_EXT } from "./mediaTypes";
2
- import { patchRootCompositionDuration, readRootCompositionDuration } from "./rootDuration";
3
2
  import { roundToCenti } from "./rounding";
4
3
  import { COMPOSITION_ROOT_OPEN_TAG_RE } from "./compositionPatterns";
4
+ import { patchRootCompositionDuration, readRootCompositionDuration } from "./rootDuration";
5
5
 
6
6
  export const TIMELINE_ASSET_MIME = "application/x-hyperframes-asset";
7
7
  export const TIMELINE_BLOCK_MIME = "application/x-hyperframes-block";
@@ -49,58 +49,66 @@ export function resolveTimelineAssetSrc(targetPath: string, assetPath: string):
49
49
  return relative || assetPath.split("/").pop() || assetPath;
50
50
  }
51
51
 
52
+ /**
53
+ * Sequence one or more dropped files end-to-end starting at the drop point, all on
54
+ * the track the user dropped onto. The clip lands where the ghost showed it — we do
55
+ * NOT bump to a different track on overlap (that produced surprise "new tracks" and,
56
+ * because it jumped past high indices like a grain-overlay track, wild numbers).
57
+ * HyperFrames allows time-overlap on a track; the user can nudge if they want a gap.
58
+ */
52
59
  export function buildTimelineFileDropPlacements(
53
60
  placement: { start: number; track: number },
54
61
  durations: number[],
55
- occupiedClips: Array<{ start: number; duration: number; track: number }> = [],
56
62
  ): Array<{ start: number; track: number }> {
57
63
  let nextStart = roundToCenti(Math.max(0, placement.start));
58
- const sequenceStart = nextStart;
59
- const resolvedDurations = durations.map((duration) =>
60
- Number.isFinite(duration) && duration > 0 ? duration : FALLBACK_TIMELINE_FILE_DROP_DURATION,
61
- );
62
- const sequenceEnd = resolvedDurations.reduce(
63
- (end, duration) => roundToCenti(end + duration),
64
- sequenceStart,
65
- );
66
- const overlapsDropTrack = occupiedClips.some((clip) => {
67
- if (clip.track !== placement.track) return false;
68
- const clipStart = Math.max(0, clip.start);
69
- const clipEnd = clipStart + Math.max(0, clip.duration);
70
- return sequenceStart < clipEnd && sequenceEnd > clipStart;
71
- });
72
- const track = overlapsDropTrack
73
- ? Math.max(placement.track, ...occupiedClips.map((clip) => clip.track)) + 1
74
- : placement.track;
75
-
76
- return resolvedDurations.map((duration) => {
64
+ return durations.map((rawDuration) => {
65
+ const duration =
66
+ Number.isFinite(rawDuration) && rawDuration > 0
67
+ ? rawDuration
68
+ : FALLBACK_TIMELINE_FILE_DROP_DURATION;
77
69
  const start = nextStart;
78
70
  nextStart = roundToCenti(nextStart + duration);
79
- return { start, track };
71
+ return { start, track: placement.track };
80
72
  });
81
73
  }
82
74
 
83
- export function resolveTimelineAssetInitialGeometry(source: string): {
84
- left: number;
85
- top: number;
75
+ export function resolveTimelineAssetCompositionSize(source: string): {
86
76
  width: number;
87
77
  height: number;
88
78
  } {
89
79
  const width = Number.parseFloat(source.match(/\bdata-width=(["'])([^"']+)\1/i)?.[2] ?? "");
90
80
  const height = Number.parseFloat(source.match(/\bdata-height=(["'])([^"']+)\1/i)?.[2] ?? "");
91
-
92
81
  return {
93
- left: 0,
94
- top: 0,
95
82
  width: Number.isFinite(width) && width > 0 ? Math.round(width) : 640,
96
83
  height: Number.isFinite(height) && height > 0 ? Math.round(height) : 360,
97
84
  };
98
85
  }
99
86
 
87
+ /**
88
+ * CapCut-style placement: natural size when it fits, scaled-to-fit when
89
+ * oversized, always centered. Unknown natural size → full-frame.
90
+ */
91
+ export function fitTimelineAssetGeometry(
92
+ natural: { width: number; height: number } | null,
93
+ comp: { width: number; height: number },
94
+ ): { left: number; top: number; width: number; height: number } {
95
+ if (!natural || natural.width <= 0 || natural.height <= 0) {
96
+ return { left: 0, top: 0, width: comp.width, height: comp.height };
97
+ }
98
+ const scale = Math.min(1, comp.width / natural.width, comp.height / natural.height);
99
+ const width = Math.round(natural.width * scale);
100
+ const height = Math.round(natural.height * scale);
101
+ return {
102
+ left: Math.round((comp.width - width) / 2),
103
+ top: Math.round((comp.height - height) / 2),
104
+ width,
105
+ height,
106
+ };
107
+ }
108
+
100
109
  export function buildTimelineAssetInsertHtml(input: {
101
110
  id: string;
102
- /** Stable hf-id stamped as data-hf-id by the NLE drop path (optional in the legacy path). */
103
- hfId?: string;
111
+ hfId: string;
104
112
  assetPath: string;
105
113
  kind: TimelineAssetKind;
106
114
  start: number;
@@ -109,7 +117,7 @@ export function buildTimelineAssetInsertHtml(input: {
109
117
  zIndex: number;
110
118
  geometry?: { left: number; top: number; width: number; height: number };
111
119
  }): string {
112
- const sharedAttrs = `id="${input.id}" class="clip" src="${input.assetPath}" data-start="${input.start}" data-duration="${input.duration}" data-track-index="${input.track}"`;
120
+ const sharedAttrs = `id="${input.id}" data-hf-id="${input.hfId}" class="clip" src="${input.assetPath}" data-start="${input.start}" data-duration="${input.duration}" data-track-index="${input.track}"`;
113
121
  const geometry = input.geometry ?? { left: 0, top: 0, width: 640, height: 360 };
114
122
  const visualStyles = `position: absolute; left: ${geometry.left}px; top: ${geometry.top}px; width: ${geometry.width}px; height: ${geometry.height}px; object-fit: contain; z-index: ${input.zIndex}`;
115
123
 
@@ -121,23 +129,18 @@ export function buildTimelineAssetInsertHtml(input: {
121
129
  return `<video ${sharedAttrs} muted playsinline style="${visualStyles}"></video>`;
122
130
  }
123
131
 
124
- return `<audio ${sharedAttrs} style="z-index: ${input.zIndex}"></audio>`;
132
+ return `<audio ${sharedAttrs} data-volume="1" style="z-index: ${input.zIndex}"></audio>`;
125
133
  }
126
134
 
127
- export function insertTimelineAssetIntoSource(source: string, assetHtml: string): string {
128
- const match = COMPOSITION_ROOT_OPEN_TAG_RE.exec(source);
129
- if (!match || match.index == null) {
130
- throw new Error("No composition root found in target source");
131
- }
132
- const insertAt = match.index + match[0].length;
133
- const lineStart = source.lastIndexOf("\n", match.index);
134
- const leadingWhitespace = source.slice(lineStart + 1, match.index).match(/^(\s*)/)?.[1] ?? "";
135
- const childIndent = leadingWhitespace + " ";
136
- const indented = assetHtml
137
- .split("\n")
138
- .map((line, i) => (i === 0 ? line : childIndent + line))
139
- .join("\n");
140
- return `${source.slice(0, insertAt)}\n${childIndent}${indented}${source.slice(insertAt)}`;
135
+ /**
136
+ * A clip inserted past the composition end would exist in the HTML but never
137
+ * appear on the timeline or in playback. Extend the root's data-duration to
138
+ * cover it (mirrors blockInstaller's behavior for installed blocks).
139
+ */
140
+ export function extendCompositionDurationIfNeeded(source: string, requiredEnd: number): string {
141
+ const rootDur = readRootCompositionDuration(source);
142
+ if (rootDur == null || !Number.isFinite(rootDur) || requiredEnd <= rootDur) return source;
143
+ return patchRootCompositionDuration(source, String(roundToCenti(requiredEnd)));
141
144
  }
142
145
 
143
146
  /**
@@ -156,45 +159,18 @@ export function setCompositionDurationToContent(source: string, contentEnd: numb
156
159
  return patchRootCompositionDuration(source, String(next));
157
160
  }
158
161
 
159
- export function extendCompositionDurationIfNeeded(source: string, requiredEnd: number): string {
160
- const rootDur = readRootCompositionDuration(source);
161
- if (rootDur == null || !Number.isFinite(rootDur) || requiredEnd <= rootDur) return source;
162
- return patchRootCompositionDuration(source, String(roundToCenti(requiredEnd)));
163
- }
164
-
165
- /**
166
- * Set the composition root's `data-duration` to `contentEnd` (grow OR shrink) so the
167
- * timeline length tracks content — the content-driven counterpart to
168
- * extendCompositionDurationIfNeeded's grow-only ratchet. Used after edits that can
169
- * reduce the furthest clip end (delete/trim). No-op when `contentEnd` is not > 0, so
170
- * an empty timeline keeps its declared duration instead of collapsing to 0.
171
- */
172
- export function fitTimelineAssetGeometry(
173
- natural: { width: number; height: number } | null,
174
- comp: { width: number; height: number },
175
- ): { left: number; top: number; width: number; height: number } {
176
- if (!natural || natural.width <= 0 || natural.height <= 0) {
177
- return { left: 0, top: 0, width: comp.width, height: comp.height };
162
+ export function insertTimelineAssetIntoSource(source: string, assetHtml: string): string {
163
+ const match = COMPOSITION_ROOT_OPEN_TAG_RE.exec(source);
164
+ if (!match || match.index == null) {
165
+ throw new Error("No composition root found in target source");
178
166
  }
179
- const scale = Math.min(1, comp.width / natural.width, comp.height / natural.height);
180
- const width = Math.round(natural.width * scale);
181
- const height = Math.round(natural.height * scale);
182
- return {
183
- left: Math.round((comp.width - width) / 2),
184
- top: Math.round((comp.height - height) / 2),
185
- width,
186
- height,
187
- };
188
- }
189
-
190
- export function resolveTimelineAssetCompositionSize(source: string): {
191
- width: number;
192
- height: number;
193
- } {
194
- const width = Number.parseFloat(source.match(/\bdata-width=(["'])([^"']+)\1/i)?.[2] ?? "");
195
- const height = Number.parseFloat(source.match(/\bdata-height=(["'])([^"']+)\1/i)?.[2] ?? "");
196
- return {
197
- width: Number.isFinite(width) && width > 0 ? Math.round(width) : 640,
198
- height: Number.isFinite(height) && height > 0 ? Math.round(height) : 360,
199
- };
167
+ const insertAt = match.index + match[0].length;
168
+ const lineStart = source.lastIndexOf("\n", match.index);
169
+ const leadingWhitespace = source.slice(lineStart + 1, match.index).match(/^(\s*)/)?.[1] ?? "";
170
+ const childIndent = leadingWhitespace + " ";
171
+ const indented = assetHtml
172
+ .split("\n")
173
+ .map((line, i) => (i === 0 ? line : childIndent + line))
174
+ .join("\n");
175
+ return `${source.slice(0, insertAt)}\n${childIndent}${indented}${source.slice(insertAt)}`;
200
176
  }