@hyperframes/studio 0.8.90 → 0.8.91

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 (87) hide show
  1. package/dist/assets/{hyperframes-player-DDjER_ii.js → hyperframes-player-zT2Bdx_l.js} +1 -1
  2. package/dist/assets/{index-5SFeUXP8.js → index-DSOLcE8X.js} +1 -1
  3. package/dist/assets/{index-CVsCxnlz.js → index-DV-z19zB.js} +1 -1
  4. package/dist/assets/{index-BKE3_N4v.js → index-V-0rfhe6.js} +168 -168
  5. package/dist/{chunk-KU4F7QZ3.js → chunk-QPSOTK6M.js} +8 -1
  6. package/dist/chunk-QPSOTK6M.js.map +1 -0
  7. package/dist/index.d.ts +12 -3
  8. package/dist/index.html +1 -1
  9. package/dist/index.js +1298 -1003
  10. package/dist/index.js.map +1 -1
  11. package/dist/player.js +1 -1
  12. package/package.json +8 -8
  13. package/src/components/TimelineToolbar.test.tsx +12 -0
  14. package/src/components/editor/MotionPathOverlay.tsx +5 -1
  15. package/src/components/editor/commitPreflightHandles.test.tsx +7 -20
  16. package/src/components/editor/elementPositionOffset.test.ts +62 -0
  17. package/src/components/editor/elementPositionOffset.ts +64 -0
  18. package/src/components/editor/motionPathCommit.test.ts +25 -5
  19. package/src/components/editor/motionPathSelection.ts +10 -3
  20. package/src/components/editor/useLayerRevealOverride.test.ts +8 -0
  21. package/src/components/editor/useLayerRevealOverride.ts +2 -1
  22. package/src/components/nle/NLEContext.tsx +1 -1
  23. package/src/components/nle/useTimelineEditCallbacks.test.tsx +53 -0
  24. package/src/hooks/domEditPersistFailure.ts +8 -6
  25. package/src/hooks/elementOffsetStager.ts +65 -0
  26. package/src/hooks/gestureTransaction.ts +4 -2
  27. package/src/hooks/gsapDragCommit.ts +1 -0
  28. package/src/hooks/gsapEditOutcome.ts +2 -1
  29. package/src/hooks/gsapRuntimeBridge.sharedTween.test.ts +157 -0
  30. package/src/hooks/gsapRuntimeBridge.ts +50 -14
  31. package/src/hooks/gsapShared.test.ts +1 -1
  32. package/src/hooks/gsapShared.ts +16 -12
  33. package/src/hooks/gsapTweenReach.ts +30 -0
  34. package/src/hooks/timelineEditingHelpers.ts +2 -1
  35. package/src/hooks/useClipboard.source.test.ts +58 -0
  36. package/src/hooks/useClipboard.ts +12 -3
  37. package/src/hooks/useCommitPreflightCapabilities.ts +2 -2
  38. package/src/hooks/useDomEditCommits.ts +2 -0
  39. package/src/hooks/useDomEditPositionPatchCommit.ts +4 -1
  40. package/src/hooks/useDomEditSession.ts +2 -0
  41. package/src/hooks/useDomGeometryCommit.test.tsx +102 -0
  42. package/src/hooks/useDomGeometryCommit.ts +42 -1
  43. package/src/hooks/useDomGeometryCommits.test.tsx +27 -0
  44. package/src/hooks/useDomGeometryCommits.ts +14 -1
  45. package/src/hooks/useGsapAwareEditing.test.tsx +113 -5
  46. package/src/hooks/useGsapAwareEditing.ts +44 -7
  47. package/src/hooks/useGsapInteractionFailureTelemetry.test.tsx +2 -1
  48. package/src/hooks/useGsapInteractionFailureTelemetry.ts +8 -4
  49. package/src/hooks/useRazorSplit.test.ts +5 -5
  50. package/src/hooks/useTimelineEditing.test.tsx +384 -0
  51. package/src/hooks/useTimelineEditing.ts +12 -12
  52. package/src/hooks/useTimelineGroupEditing.ts +16 -7
  53. package/src/player/components/Timeline.rulerSeek.test.tsx +111 -0
  54. package/src/player/components/TimelinePropertyLanes.test.tsx +1 -1
  55. package/src/player/components/TimelineProvider.tsx +1 -0
  56. package/src/player/components/TimelineTrackHeader.test.tsx +1 -1
  57. package/src/player/components/TimelineTypes.ts +1 -1
  58. package/src/player/components/timelineClipDragCommit.test.ts +18 -8
  59. package/src/player/components/timelineClipDragCommit.ts +5 -0
  60. package/src/player/components/timelineClipDragPreview.test.ts +70 -2
  61. package/src/player/components/timelineClipDragPreview.ts +15 -12
  62. package/src/player/components/timelineEditing.ts +2 -1
  63. package/src/player/components/timelineGroupEditing.ts +7 -2
  64. package/src/player/components/timelineMultiDragPreview.test.ts +10 -8
  65. package/src/player/components/timelineMultiDragPreview.ts +8 -5
  66. package/src/player/components/timelineProviderStateBuilders.test.ts +1 -0
  67. package/src/player/components/timelineProviderStateBuilders.ts +7 -2
  68. package/src/player/components/timelineZMirror.test.ts +2 -2
  69. package/src/player/components/timelineZMirror.ts +1 -1
  70. package/src/player/components/useTimelineClipDrag.resize.test.tsx +8 -5
  71. package/src/player/components/useTimelineClipDrag.ts +3 -2
  72. package/src/player/components/useTimelinePlayhead.test.tsx +215 -26
  73. package/src/player/components/useTimelinePlayhead.ts +56 -27
  74. package/src/player/components/useTimelineProviderState.tsx +1 -0
  75. package/src/player/components/useTimelineZoom.ts +9 -1
  76. package/src/player/hooks/useTimelinePlayer.seek.test.ts +44 -0
  77. package/src/player/hooks/useTimelinePlayer.ts +7 -4
  78. package/src/player/lib/timelineDOM.test.ts +79 -0
  79. package/src/player/lib/timelineDOM.ts +16 -7
  80. package/src/player/lib/timelineIframeHelpers.test.ts +36 -0
  81. package/src/player/lib/timelineIframeHelpers.ts +12 -3
  82. package/src/player/store/liveTime.ts +6 -0
  83. package/src/player/store/playerStore.ts +2 -0
  84. package/src/player/store/timelineElement.ts +28 -3
  85. package/src/utils/razorSplitTransaction.test.ts +1 -1
  86. package/src/utils/razorSplitTransaction.ts +3 -3
  87. package/dist/chunk-KU4F7QZ3.js.map +0 -1
@@ -12,6 +12,7 @@ import type { TimelineElement } from "../store/playerStore";
12
12
  import type { ClipManifestClip, IframeWindow, TimelineLike } from "./playbackTypes";
13
13
  import { resolveCssStackingContextId } from "@hyperframes/core/runtime/stacking-context";
14
14
  import { readClipTiming } from "@hyperframes/core/composition-contract";
15
+ import { createRuntimeStartTimeResolver } from "@hyperframes/core/runtime/start-resolver";
15
16
  import { groupInfoFor } from "./timelineGroupInfo";
16
17
  import { transitionLabelsForDocument } from "./timelineTransitionMetadata";
17
18
  import {
@@ -228,10 +229,13 @@ export function parseTimelineFromDOM(
228
229
  const nodes = doc.querySelectorAll("[data-start]");
229
230
  const els: TimelineElement[] = [];
230
231
  let trackCounter = 0;
231
- const transitionLabels = transitionLabelsForDocument(
232
- doc,
233
- timelines ?? (doc.defaultView as IframeWindow | null)?.__timelines,
234
- );
232
+ const timelineRegistry = timelines ?? (doc.defaultView as IframeWindow | null)?.__timelines;
233
+ const transitionLabels = transitionLabelsForDocument(doc, timelineRegistry);
234
+ const masterStart = createRuntimeStartTimeResolver({
235
+ timelineRegistry,
236
+ includeAuthoredTimingAttrs: true,
237
+ documentRef: doc,
238
+ });
235
239
 
236
240
  // fallow-ignore-next-line complexity
237
241
  nodes.forEach((node) => {
@@ -239,11 +243,14 @@ export function parseTimelineFromDOM(
239
243
  if (isTimelineIgnoredElement(node)) return;
240
244
  const el = node as HTMLElement;
241
245
  const timing = readClipTiming(el);
242
- const start = timing.start;
243
- if (start == null) return;
246
+ if (timing.start == null) return;
247
+ const tagLower = el.tagName.toLowerCase();
248
+ const start =
249
+ tagLower === "video" || tagLower === "audio"
250
+ ? masterStart.resolveMediaStartForElement(el)
251
+ : masterStart.resolveStartForElement(el);
244
252
  if (Number.isFinite(rootDuration) && rootDuration > 0 && start >= rootDuration) return;
245
253
 
246
- const tagLower = el.tagName.toLowerCase();
247
254
  let dur = timing.duration ?? 0;
248
255
  if (dur <= 0) dur = Math.max(0, rootDuration - start);
249
256
  if (Number.isFinite(rootDuration) && rootDuration > 0) {
@@ -287,6 +294,8 @@ export function parseTimelineFromDOM(
287
294
  : "element",
288
295
  tag: tagLower,
289
296
  start,
297
+ parentCompositionStart: masterStart.resolveHostStartForElement(el),
298
+ ...(masterStart.isRootGlobalMediaStartForElement(el) && { authoredStartIsMasterTime: true }),
290
299
  duration: dur,
291
300
  track,
292
301
  domId: el.id || undefined,
@@ -103,6 +103,42 @@ describe("buildMissingCompositionElements — hfId (R7)", () => {
103
103
  });
104
104
  });
105
105
 
106
+ describe("buildMissingCompositionElements — nested master time", () => {
107
+ it("places hosts two and three levels deep at their master time", () => {
108
+ const doc = makeDoc(`
109
+ <div data-composition-id="main" data-start="0" data-duration="20">
110
+ <div id="intro" data-composition-id="intro" data-start="2" data-duration="10">
111
+ <div data-composition-id="intro">
112
+ <div id="logo" data-composition-id="logo" data-start="3" data-duration="5">
113
+ <div data-composition-id="logo">
114
+ <div id="badge" data-composition-id="badge" data-start="1" data-duration="2"></div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ `);
121
+ const { missing } = buildMissingCompositionElements(doc, window as IframeWindow, [], 20);
122
+ expect(missing.map((e) => [e.domId, e.start, e.duration, e.parentCompositionStart])).toEqual([
123
+ ["intro", 2, 10, 0],
124
+ ["logo", 5, 5, 2],
125
+ ["badge", 6, 2, 5],
126
+ ]);
127
+ });
128
+
129
+ it("starts a host after a referenced scene's authored length", () => {
130
+ const doc = makeDoc(`
131
+ <div data-composition-id="main" data-start="0" data-duration="20">
132
+ <div id="s1" data-composition-id="s1" data-start="0" data-hf-authored-duration="8"></div>
133
+ <div id="s2" data-composition-id="s2" data-start="s1 + 1" data-duration="4"></div>
134
+ </div>
135
+ `);
136
+ const win = { __timelines: { s1: { duration: () => 6 } } } as unknown as IframeWindow;
137
+ const { missing } = buildMissingCompositionElements(doc, win, [], 20);
138
+ expect(missing.find((e) => e.domId === "s2")?.start).toBe(9);
139
+ });
140
+ });
141
+
106
142
  describe("setPreviewMediaVolume", () => {
107
143
  it("sends a clamped runtime volume to a direct preview iframe", () => {
108
144
  const iframe = document.createElement("iframe");
@@ -13,6 +13,7 @@
13
13
  import type { TimelineElement } from "../store/playerStore";
14
14
  import type { IframeWindow } from "./playbackTypes";
15
15
  import { readClipTiming } from "@hyperframes/core/composition-contract";
16
+ import { createRuntimeStartTimeResolver } from "@hyperframes/core/runtime/start-resolver";
16
17
  import {
17
18
  getTimelineElementSelector,
18
19
  getTimelineElementSourceFile,
@@ -414,15 +415,16 @@ function buildMissingCompositionEntry(params: {
414
415
  rootDuration: number;
415
416
  fallbackIndex: number;
416
417
  resolveEnd: (refId: string, visiting: ReadonlySet<string>) => number | null;
418
+ masterTime: ReturnType<typeof createRuntimeStartTimeResolver>;
417
419
  }): TimelineElement | null {
418
- const { doc, iframeWin, element, compositionId, rootDuration, fallbackIndex, resolveEnd } =
419
- params;
420
+ const { doc, iframeWin, element, compositionId, rootDuration, fallbackIndex } = params;
421
+ const { resolveEnd, masterTime } = params;
420
422
  const transitionLabels = transitionLabelsForDocument(doc, iframeWin.__timelines);
421
423
  const timing = readClipTiming(element, {
422
424
  resolveReferenceEnd: (refId) => resolveEnd(refId, new Set([compositionId])),
423
425
  });
424
426
  const window = clampCompositionWindow(
425
- timing.start ?? 0,
427
+ masterTime.resolveStartForElement(element),
426
428
  timing.duration ?? timelineDuration(iframeWin, compositionId),
427
429
  rootDuration,
428
430
  );
@@ -456,6 +458,7 @@ function buildMissingCompositionEntry(params: {
456
458
  key: identity.key,
457
459
  tag: element.tagName.toLowerCase(),
458
460
  start: window.start,
461
+ parentCompositionStart: masterTime.resolveHostStartForElement(element),
459
462
  duration: window.duration,
460
463
  track: timing.trackIndex,
461
464
  authoredTrack: timing.trackIndex,
@@ -494,6 +497,11 @@ export function buildMissingCompositionElements(
494
497
  const missing: TimelineElement[] = [];
495
498
 
496
499
  const resolveEnd = createReferenceEndResolver(createTimedElementLookup(doc), iframeWin);
500
+ const masterTime = createRuntimeStartTimeResolver({
501
+ timelineRegistry: iframeWin.__timelines,
502
+ includeAuthoredTimingAttrs: true,
503
+ documentRef: doc,
504
+ });
497
505
 
498
506
  for (const host of hosts) {
499
507
  const el = host as HTMLElement;
@@ -508,6 +516,7 @@ export function buildMissingCompositionElements(
508
516
  rootDuration,
509
517
  fallbackIndex: missing.length,
510
518
  resolveEnd,
519
+ masterTime,
511
520
  });
512
521
  if (entry) missing.push(entry);
513
522
  }
@@ -2,12 +2,18 @@
2
2
  type TimeListener = (time: number) => void;
3
3
  const timeListeners = new Set<TimeListener>();
4
4
  let latestTime = 0;
5
+ let seekCount = 0;
5
6
 
6
7
  export const liveTime = {
7
8
  notify: (time: number) => {
8
9
  latestTime = time;
9
10
  timeListeners.forEach((listener) => listener(time));
10
11
  },
12
+ notifySeek: (time: number) => {
13
+ seekCount += 1;
14
+ liveTime.notify(time);
15
+ },
16
+ seekCount: () => seekCount,
11
17
  latest: () => latestTime,
12
18
  subscribe: (listener: TimeListener) => {
13
19
  timeListeners.add(listener);
@@ -83,6 +83,7 @@ interface PlayerState extends PlayerStoreSlices {
83
83
  zoomMode: ZoomMode;
84
84
  /** Timeline zoom percent relative to the fit width when in manual mode */
85
85
  manualZoomPercent: number;
86
+ userZoomCount: number;
86
87
  /**
87
88
  * Bumped on every live z-index edit (handleDomZIndexReorderCommit apply AND
88
89
  * rollback). Flashless z commits (skipReload) never reload the iframe or
@@ -279,6 +280,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
279
280
  loopEnabled: false,
280
281
  zoomMode: "fit",
281
282
  manualZoomPercent: 100,
283
+ userZoomCount: 0,
282
284
  zEditVersion: 0,
283
285
  timelinePps: 100,
284
286
  timelineFitPps: 100,
@@ -84,13 +84,38 @@ export interface TimelineElement {
84
84
  audioGroupFxChain?: string;
85
85
  audioGroupAutomation?: string;
86
86
  /**
87
- * Legacy source-coordinate marker for an inline sub-composition child. The
88
- * current timeline keeps sub-compositions as single rows.
87
+ * Master start of the composition this row runs in, which its tweens and its
88
+ * `data-start` are local to; 0 at the root. Writes go through toAuthoredStart.
89
89
  */
90
- expandedParentStart?: number;
90
+ parentCompositionStart?: number;
91
+ /** A legacy root-global media start: its `data-start` is already master time. */
92
+ authoredStartIsMasterTime?: boolean;
93
+ /** Legacy marker for an inline sub-composition child; current rows never set it. */
91
94
  expandedHostKey?: string;
92
95
  }
93
96
 
97
+ type RowClock = Pick<
98
+ TimelineElement,
99
+ "start" | "parentCompositionStart" | "authoredStartIsMasterTime"
100
+ >;
101
+ const authoredOffset = (element: RowClock) =>
102
+ element.authoredStartIsMasterTime ? 0 : (element.parentCompositionStart ?? 0);
103
+
104
+ /** The earliest master start this row can take: its host's start, or its own if already earlier. */
105
+ export function clampToHostStart(element: RowClock, masterTime: number): number {
106
+ return Math.max(Math.min(authoredOffset(element), element.start), masterTime);
107
+ }
108
+
109
+ /** A master-time position on this row, as the `data-start` its source file stores. */
110
+ export function toAuthoredStart(element: RowClock, masterTime: number): number {
111
+ return Math.max(0, clampToHostStart(element, masterTime) - authoredOffset(element));
112
+ }
113
+
114
+ /** A master-time position on the clock this row's tweens run on. */
115
+ export function toCompositionTime(element: RowClock, masterTime: number): number {
116
+ return masterTime - (element.parentCompositionStart ?? 0);
117
+ }
118
+
94
119
  /**
95
120
  * The fields `updateElement` may write.
96
121
  *
@@ -48,7 +48,7 @@ describe("buildAtomicCutIntents", () => {
48
48
 
49
49
  it("rebases each nested target into its own source-file coordinates", () => {
50
50
  const intents = buildAtomicCutIntents(
51
- [element({ start: 8, duration: 4, expandedParentStart: 6, sourceFile: "scene.html" })],
51
+ [element({ start: 8, duration: 4, parentCompositionStart: 6, sourceFile: "scene.html" })],
52
52
  10,
53
53
  "index.html",
54
54
  );
@@ -1,4 +1,5 @@
1
1
  import type { TimelineElement } from "../player";
2
+ import { toAuthoredStart } from "../player/store/timelineElement";
2
3
  import type { RecordEditInput } from "../hooks/timelineEditingHelpers";
3
4
  import { buildPatchTarget } from "./timelineElementSplit";
4
5
  import { serializeStudioFileMutations } from "./studioFileMutationCoordinator";
@@ -61,12 +62,11 @@ function buildCutTarget(
61
62
  target: CutTarget["target"],
62
63
  splitTime: number,
63
64
  ): CutTarget {
64
- const basis = element.expandedParentStart;
65
65
  return {
66
66
  target,
67
67
  ...(element.domId ? { originalId: element.domId } : {}),
68
- splitTime: basis === undefined ? splitTime : Math.max(0, splitTime - basis),
69
- elementStart: basis === undefined ? element.start : element.start - basis,
68
+ splitTime: Math.max(0, toAuthoredStart(element, splitTime)),
69
+ elementStart: toAuthoredStart(element, element.start),
70
70
  elementDuration: element.duration,
71
71
  ...(element.playbackStart != null ? { playbackStart: element.playbackStart } : {}),
72
72
  ...(element.playbackRate != null ? { playbackRate: element.playbackRate } : {}),