@hyperframes/studio 0.7.76 → 0.7.78

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 (138) hide show
  1. package/dist/assets/{hyperframes-player-DsRdxz7A.js → hyperframes-player-uiAAPKvw.js} +1 -1
  2. package/dist/assets/index-BSZrK0bx.js +428 -0
  3. package/dist/assets/{index-Du1RoLiB.js → index-CDSTMtUs.js} +1 -1
  4. package/dist/assets/index-DpkO2Hvw.css +1 -0
  5. package/dist/assets/{index-BLICKger.js → index-r1tKKlU7.js} +1 -1
  6. package/dist/index.d.ts +64 -17
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +25030 -23214
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/src/App.tsx +16 -17
  12. package/src/components/EditorShell.tsx +3 -0
  13. package/src/components/TimelineToolbar.test.tsx +48 -3
  14. package/src/components/TimelineToolbar.tsx +89 -36
  15. package/src/components/editor/AnimationCard.test.tsx +18 -75
  16. package/src/components/editor/AnimationCard.tsx +26 -2
  17. package/src/components/editor/GsapAddAnimationControl.tsx +68 -0
  18. package/src/components/editor/GsapAnimationSection.tsx +18 -157
  19. package/src/components/editor/KeyframeEaseList.tsx +5 -1
  20. package/src/components/editor/KeyframeNavigation.tsx +46 -14
  21. package/src/components/editor/KeyframeNavigationDiamond.test.tsx +59 -0
  22. package/src/components/editor/MotionPathOverlay.tsx +32 -10
  23. package/src/components/editor/PropertyPanel.tsx +15 -5
  24. package/src/components/editor/PropertyPanelFlat.tsx +48 -23
  25. package/src/components/editor/gsapAnimationCallbacks.test.ts +122 -0
  26. package/src/components/editor/gsapAnimationCallbacks.ts +108 -4
  27. package/src/components/editor/keyframeRetime.test.ts +25 -22
  28. package/src/components/editor/keyframeRetime.ts +1 -2
  29. package/src/components/editor/propertyPanelFlatLayoutSection.tsx +2 -2
  30. package/src/components/editor/propertyPanelFlatMotionSection.tsx +25 -139
  31. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +32 -16
  32. package/src/components/editor/propertyPanelFlatTextSection.tsx +21 -4
  33. package/src/components/nle/PreviewOverlays.tsx +3 -1
  34. package/src/components/nle/useTimelineEditCallbacks.test.ts +30 -0
  35. package/src/components/nle/useTimelineEditCallbacks.test.tsx +693 -0
  36. package/src/components/nle/useTimelineEditCallbacks.ts +197 -57
  37. package/src/components/sidebar/CompositionsTab.drag.test.tsx +41 -0
  38. package/src/components/sidebar/CompositionsTab.tsx +66 -32
  39. package/src/contexts/TimelineEditContext.tsx +1 -1
  40. package/src/hooks/deleteSelectedKeyframes.ts +24 -8
  41. package/src/hooks/gsapDragCommit.ts +2 -2
  42. package/src/hooks/gsapDragPositionCommit.ts +84 -5
  43. package/src/hooks/gsapKeyframeCacheHelpers.test.ts +176 -1
  44. package/src/hooks/gsapKeyframeCacheHelpers.ts +93 -52
  45. package/src/hooks/gsapRuntimeBridge.test.ts +100 -0
  46. package/src/hooks/gsapScriptCommitHelpers.ts +3 -2
  47. package/src/hooks/gsapScriptCommitTypes.ts +2 -0
  48. package/src/hooks/gsapShared.test.ts +109 -1
  49. package/src/hooks/gsapShared.ts +142 -17
  50. package/src/hooks/gsapTweenSynth.test.ts +30 -1
  51. package/src/hooks/gsapTweenSynth.ts +61 -14
  52. package/src/hooks/keyframeCacheAstLoad.ts +181 -0
  53. package/src/hooks/timelineEditingHelpers.test.ts +80 -0
  54. package/src/hooks/timelineMoveAdapter.ts +9 -2
  55. package/src/hooks/useDomEditWiring.ts +2 -2
  56. package/src/hooks/useDomSelection.ts +4 -0
  57. package/src/hooks/useEnableKeyframes.test.ts +122 -0
  58. package/src/hooks/useEnableKeyframes.ts +116 -56
  59. package/src/hooks/useGestureCommit.ts +2 -2
  60. package/src/hooks/useGsapAnimationFetchFallback.ts +1 -1
  61. package/src/hooks/useGsapKeyframeOps.test.tsx +92 -13
  62. package/src/hooks/useGsapKeyframeOps.ts +30 -13
  63. package/src/hooks/useGsapScriptCommits.test.tsx +21 -0
  64. package/src/hooks/useGsapScriptCommits.ts +2 -0
  65. package/src/hooks/useGsapSelectionHandlers.test.tsx +69 -3
  66. package/src/hooks/useGsapSelectionHandlers.ts +90 -65
  67. package/src/hooks/useGsapTweenCache.test.ts +16 -0
  68. package/src/hooks/useGsapTweenCache.ts +64 -178
  69. package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +68 -0
  70. package/src/hooks/useStudioContextValue.test.ts +93 -0
  71. package/src/hooks/useStudioContextValue.ts +17 -3
  72. package/src/hooks/useStudioTestHooks.ts +63 -0
  73. package/src/hooks/useTimelineEditing.test.tsx +187 -22
  74. package/src/hooks/useTimelineEditing.ts +43 -32
  75. package/src/hooks/useTimelineEditingTypes.ts +2 -0
  76. package/src/hooks/useTimelineGroupEditing.ts +106 -82
  77. package/src/player/components/KeyframeDiamondContextMenu.test.tsx +69 -0
  78. package/src/player/components/KeyframeDiamondContextMenu.tsx +19 -8
  79. package/src/player/components/LayerDisclosureRow.tsx +76 -0
  80. package/src/player/components/Player.test.ts +143 -12
  81. package/src/player/components/Player.tsx +84 -43
  82. package/src/player/components/Timeline.test.ts +420 -90
  83. package/src/player/components/Timeline.tsx +80 -78
  84. package/src/player/components/TimelineCanvas.tsx +33 -21
  85. package/src/player/components/TimelineClip.tsx +3 -1
  86. package/src/player/components/TimelineClipDiamonds.test.tsx +540 -16
  87. package/src/player/components/TimelineClipDiamonds.tsx +320 -117
  88. package/src/player/components/TimelineDiamondConnectors.tsx +145 -0
  89. package/src/player/components/TimelineLanes.tsx +153 -79
  90. package/src/player/components/TimelineOverlays.tsx +4 -18
  91. package/src/player/components/TimelinePropertyLanes.test.tsx +450 -0
  92. package/src/player/components/TimelinePropertyLanes.tsx +191 -0
  93. package/src/player/components/TimelineRazorInteraction.tsx +60 -0
  94. package/src/player/components/TimelineRuler.tsx +11 -21
  95. package/src/player/components/TimelineTrackHeader.test.tsx +346 -0
  96. package/src/player/components/TimelineTrackHeader.tsx +363 -0
  97. package/src/player/components/TrackClipCount.tsx +17 -0
  98. package/src/player/components/timelineCallbacks.ts +21 -6
  99. package/src/player/components/timelineClipDragPreview.test.ts +50 -3
  100. package/src/player/components/timelineClipDragPreview.ts +33 -15
  101. package/src/player/components/timelineCollision.ts +4 -13
  102. package/src/player/components/timelineDiamondTypes.ts +120 -0
  103. package/src/player/components/timelineDragDrop.ts +8 -3
  104. package/src/player/components/timelineEditing.test.ts +19 -27
  105. package/src/player/components/timelineEditing.ts +7 -8
  106. package/src/player/components/timelineKeyframeIdentity.test.ts +49 -0
  107. package/src/player/components/timelineKeyframeIdentity.ts +66 -0
  108. package/src/player/components/timelineLayout.test.ts +162 -9
  109. package/src/player/components/timelineLayout.ts +198 -38
  110. package/src/player/components/timelineMarquee.test.ts +76 -21
  111. package/src/player/components/timelineMarquee.ts +17 -14
  112. package/src/player/components/trackHeaderLaneState.ts +121 -0
  113. package/src/player/components/trackHeaderLaneValues.ts +139 -0
  114. package/src/player/components/useAutoExpandKeyframedClips.test.tsx +75 -0
  115. package/src/player/components/useAutoExpandKeyframedClips.ts +50 -0
  116. package/src/player/components/useTimelineClipDrag.ts +4 -1
  117. package/src/player/components/useTimelineGeometry.ts +4 -1
  118. package/src/player/components/useTimelineKeyframeHandlers.test.tsx +108 -0
  119. package/src/player/components/useTimelineKeyframeHandlers.ts +50 -15
  120. package/src/player/components/useTimelinePlayhead.ts +21 -12
  121. package/src/player/components/useTimelineRangeSelection.ts +35 -14
  122. package/src/player/components/useTimelineTrackLayout.test.ts +53 -0
  123. package/src/player/components/useTimelineTrackLayout.ts +162 -0
  124. package/src/player/hooks/useExpandedTimelineElements.test.ts +43 -0
  125. package/src/player/hooks/useExpandedTimelineElements.ts +21 -12
  126. package/src/player/lib/timelineElementHelpers.ts +26 -11
  127. package/src/player/store/keyframeSlice.ts +122 -0
  128. package/src/player/store/playerStore.test.ts +25 -0
  129. package/src/player/store/playerStore.ts +7 -43
  130. package/src/telemetry/events.test.ts +9 -3
  131. package/src/telemetry/events.ts +16 -7
  132. package/src/utils/globalTimeCompiler.test.ts +10 -0
  133. package/src/utils/globalTimeCompiler.ts +3 -3
  134. package/dist/assets/index-B37rWXo4.css +0 -1
  135. package/dist/assets/index-BdOfFsR8.js +0 -428
  136. package/src/player/components/TimelineDragGhost.tsx +0 -59
  137. package/src/utils/keyframeSelection.test.ts +0 -45
  138. package/src/utils/keyframeSelection.ts +0 -29
@@ -299,22 +299,17 @@ describe("FlatTextFieldEditor controls", () => {
299
299
 
300
300
  describe("FlatTextSection — multi-field", () => {
301
301
  it("shows the layer list, switches the active field's rows on selection, and has no doubled heading (this component never renders its own heading — the parent FlatGroup does)", () => {
302
- const host = document.createElement("div");
303
- document.body.append(host);
304
- const root = createRoot(host);
305
- act(() => {
306
- root.render(
307
- <FlatTextSection
308
- element={makeMultiFieldElement()}
309
- styles={{}}
310
- fontAssets={[]}
311
- onSetText={vi.fn()}
312
- onSetTextFieldStyle={vi.fn()}
313
- onAddTextField={vi.fn()}
314
- onRemoveTextField={vi.fn()}
315
- />,
316
- );
317
- });
302
+ const { host, root } = renderInto(
303
+ <FlatTextSection
304
+ element={makeMultiFieldElement()}
305
+ styles={{}}
306
+ fontAssets={[]}
307
+ onSetText={vi.fn()}
308
+ onSetTextFieldStyle={vi.fn()}
309
+ onAddTextField={vi.fn()}
310
+ onRemoveTextField={vi.fn()}
311
+ />,
312
+ );
318
313
  expect(host.textContent).toContain("Headline");
319
314
  expect(host.textContent).toContain("Subhead");
320
315
  // Active field's editor rows are visible (Font/Weight/etc. from FlatTextFieldEditor).
@@ -408,6 +403,27 @@ describe("FlatTextSection — multi-field", () => {
408
403
  act(() => root.unmount());
409
404
  });
410
405
 
406
+ it("does not steal canvas focus when a multi-field element is selected", () => {
407
+ const focusOwner = document.createElement("button");
408
+ document.body.append(focusOwner);
409
+ focusOwner.focus();
410
+
411
+ const { root } = renderInto(
412
+ <FlatTextSection
413
+ element={makeMultiFieldElement()}
414
+ styles={{}}
415
+ fontAssets={[]}
416
+ onSetText={vi.fn()}
417
+ onSetTextFieldStyle={vi.fn()}
418
+ onAddTextField={vi.fn()}
419
+ onRemoveTextField={vi.fn()}
420
+ />,
421
+ );
422
+
423
+ expect(document.activeElement).toBe(focusOwner);
424
+ act(() => root.unmount());
425
+ });
426
+
411
427
  it("auto-focuses the Content textarea when a new text field is added", async () => {
412
428
  let addResolved = false;
413
429
 
@@ -257,6 +257,12 @@ export function FlatTextSection({
257
257
  const [activeFieldKey, setActiveFieldKey] = useState<string | null>(
258
258
  element.textFields[0]?.key ?? null,
259
259
  );
260
+ // Armed by the add handler so the newly added field mounts focused. State, not
261
+ // a ref cleared during render: Strict Mode renders twice, so the first pass
262
+ // would eat the marker and the second would mount the field unfocused. Nothing
263
+ // clears it on read either — `autoFocus` is a mount-only DOM prop and the
264
+ // editor is keyed on the field, so it can only fire once per added field.
265
+ const [autoFocusFieldKey, setAutoFocusFieldKey] = useState<string | null>(null);
260
266
 
261
267
  useEffect(() => {
262
268
  const nextFields = element.textFields;
@@ -271,6 +277,8 @@ export function FlatTextSection({
271
277
  const activeField = textFields.find((field) => field.key === activeFieldKey) ?? textFields[0];
272
278
  if (!activeField) return null;
273
279
 
280
+ const autoFocusActiveField = autoFocusFieldKey === activeField.key;
281
+
274
282
  if (textFields.length > 1) {
275
283
  return (
276
284
  <div className="space-y-2.5">
@@ -278,10 +286,15 @@ export function FlatTextSection({
278
286
  fields={textFields}
279
287
  activeFieldKey={activeField.key}
280
288
  styles={styles}
281
- onSelect={setActiveFieldKey}
289
+ onSelect={(fieldKey) => {
290
+ setAutoFocusFieldKey(null);
291
+ setActiveFieldKey(fieldKey);
292
+ }}
282
293
  onAdd={() =>
283
294
  void Promise.resolve(onAddTextField(activeField.key)).then((nextKey) => {
284
- if (nextKey) setActiveFieldKey(nextKey);
295
+ if (!nextKey) return;
296
+ setAutoFocusFieldKey(nextKey);
297
+ setActiveFieldKey(nextKey);
285
298
  })
286
299
  }
287
300
  onRemove={onRemoveTextField}
@@ -295,7 +308,7 @@ export function FlatTextSection({
295
308
  onSetText={onSetText}
296
309
  onSetTextFieldStyle={onSetTextFieldStyle}
297
310
  onPreviewTextFieldStyle={onPreviewTextFieldStyle}
298
- autoFocus
311
+ autoFocus={autoFocusActiveField}
299
312
  />
300
313
  </div>
301
314
  );
@@ -316,7 +329,11 @@ export function FlatTextSection({
316
329
  type="button"
317
330
  onClick={() => {
318
331
  track("button", "Add text field");
319
- void onAddTextField(activeField.key);
332
+ void Promise.resolve(onAddTextField(activeField.key)).then((nextKey) => {
333
+ if (!nextKey) return;
334
+ setAutoFocusFieldKey(nextKey);
335
+ setActiveFieldKey(nextKey);
336
+ });
320
337
  }}
321
338
  className="mt-0.5 flex items-center gap-[5px] text-[10px] text-panel-text-4 hover:text-panel-text-2"
322
339
  >
@@ -27,6 +27,7 @@ import type { GestureRecordingState } from "../editor/GestureRecordControl";
27
27
  import type { ReactNode } from "react";
28
28
 
29
29
  export interface PreviewOverlaysProps {
30
+ shouldShowMotionPath: boolean;
30
31
  shouldShowSelectedDomBounds: boolean;
31
32
  blockPreview?: BlockPreviewInfo | null;
32
33
  isGestureRecording?: boolean;
@@ -132,6 +133,7 @@ export function resolveZIndexEntries(
132
133
 
133
134
  // fallow-ignore-next-line complexity
134
135
  export function PreviewOverlays({
136
+ shouldShowMotionPath,
135
137
  shouldShowSelectedDomBounds,
136
138
  blockPreview,
137
139
  isGestureRecording,
@@ -274,7 +276,7 @@ export function PreviewOverlays({
274
276
  {STUDIO_KEYFRAMES_ENABLED && (
275
277
  <MotionPathOverlay
276
278
  iframeRef={previewIframeRef}
277
- selection={shouldShowSelectedDomBounds ? domEditSelection : null}
279
+ selection={shouldShowMotionPath ? domEditSelection : null}
278
280
  compositionSize={compositionDimensions}
279
281
  isPlaying={isPlaying}
280
282
  />
@@ -40,6 +40,36 @@ describe("resolveTimelineKeyframeTarget", () => {
40
40
  ).toBeNull();
41
41
  });
42
42
 
43
+ it("uses the rendered animation identity to resolve a same-group collision", () => {
44
+ expect(
45
+ resolveTimelineKeyframeTarget(
46
+ 50,
47
+ [
48
+ {
49
+ percentage: 50,
50
+ tweenPercentage: 25,
51
+ propertyGroup: "position",
52
+ animationId: "position-b",
53
+ },
54
+ ],
55
+ [
56
+ { id: "position-a", propertyGroup: "position" },
57
+ { id: "position-b", propertyGroup: "position" },
58
+ ],
59
+ ),
60
+ ).toEqual({ animId: "position-b", tweenPct: 25 });
61
+ });
62
+
63
+ it("rejects a rendered animation identity absent from the element", () => {
64
+ expect(
65
+ resolveTimelineKeyframeTarget(
66
+ 50,
67
+ [{ percentage: 50, animationId: "stale-position" }],
68
+ [{ id: "position", propertyGroup: "position" }],
69
+ ),
70
+ ).toBeNull();
71
+ });
72
+
43
73
  it("keeps a keyframed and flat tween in the same property group unresolved", () => {
44
74
  expect(
45
75
  resolveTimelineKeyframeTarget(