@hyperframes/studio 0.6.122-alpha.0 → 0.7.1

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 (100) hide show
  1. package/dist/assets/{index-i-MLqhmm.js → index-CnYOrmoA.js} +1 -1
  2. package/dist/assets/index-DzWIinxk.css +1 -0
  3. package/dist/assets/index-Z4bA3Fi0.js +374 -0
  4. package/dist/assets/{index-DSLrl2tB.js → index-wWkKTkIq.js} +24 -24
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.html +3 -3
  7. package/dist/index.js +3768 -2245
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/App.tsx +1 -0
  11. package/src/components/StudioHeader.tsx +2 -3
  12. package/src/components/StudioPreviewArea.tsx +12 -0
  13. package/src/components/TimelineToolbar.tsx +29 -16
  14. package/src/components/editor/DomEditOverlay.test.ts +0 -13
  15. package/src/components/editor/DomEditOverlay.tsx +3 -10
  16. package/src/components/editor/KeyframeNavigation.test.ts +36 -0
  17. package/src/components/editor/KeyframeNavigation.tsx +34 -4
  18. package/src/components/editor/MotionPathNode.tsx +98 -0
  19. package/src/components/editor/MotionPathOverlay.tsx +481 -0
  20. package/src/components/editor/SnapToolbar.tsx +28 -3
  21. package/src/components/editor/SourceEditor.tsx +21 -9
  22. package/src/components/editor/domEditOverlayGeometry.ts +12 -0
  23. package/src/components/editor/domEditOverlayGestures.ts +5 -1
  24. package/src/components/editor/domEditOverlayStartGesture.ts +5 -1
  25. package/src/components/editor/domEditingDom.ts +24 -9
  26. package/src/components/editor/manualEditingAvailability.test.ts +0 -12
  27. package/src/components/editor/manualEditingAvailability.ts +1 -11
  28. package/src/components/editor/manualEdits.ts +1 -0
  29. package/src/components/editor/manualEditsDom.ts +68 -41
  30. package/src/components/editor/manualEditsDomGsap.test.ts +82 -0
  31. package/src/components/editor/manualOffsetDrag.test.ts +12 -0
  32. package/src/components/editor/manualOffsetDrag.ts +79 -4
  33. package/src/components/editor/motionPathCommit.test.ts +130 -0
  34. package/src/components/editor/motionPathCommit.ts +83 -0
  35. package/src/components/editor/motionPathGeometry.test.ts +127 -0
  36. package/src/components/editor/motionPathGeometry.ts +116 -0
  37. package/src/components/editor/motionPathSelection.ts +33 -0
  38. package/src/components/editor/useDomEditOverlayGestures.ts +36 -18
  39. package/src/components/editor/useDomEditOverlayRects.ts +6 -0
  40. package/src/components/editor/useMotionPathData.ts +135 -0
  41. package/src/components/nle/NLELayout.tsx +17 -6
  42. package/src/components/panels/SlideshowPanel.tsx +2 -1
  43. package/src/components/panels/SlideshowSubPanels.tsx +2 -1
  44. package/src/components/renders/RenderQueue.tsx +1 -1
  45. package/src/components/renders/useRenderQueue.ts +3 -2
  46. package/src/hooks/draggedGsapPosition.ts +47 -0
  47. package/src/hooks/gsapDragCommit.test.ts +435 -0
  48. package/src/hooks/gsapDragCommit.ts +278 -290
  49. package/src/hooks/gsapDragPositionCommit.ts +410 -0
  50. package/src/hooks/gsapRuntimeBridge.test.ts +147 -0
  51. package/src/hooks/gsapRuntimeBridge.ts +86 -69
  52. package/src/hooks/gsapRuntimeKeyframes.test.ts +129 -1
  53. package/src/hooks/gsapRuntimeKeyframes.ts +183 -11
  54. package/src/hooks/gsapRuntimePatch.test.ts +462 -0
  55. package/src/hooks/gsapRuntimePatch.ts +159 -0
  56. package/src/hooks/gsapScriptCommitTypes.ts +11 -1
  57. package/src/hooks/gsapShared.test.ts +58 -0
  58. package/src/hooks/gsapShared.ts +28 -10
  59. package/src/hooks/gsapTargetCache.ts +0 -3
  60. package/src/hooks/useDomEditPreviewSync.ts +3 -0
  61. package/src/hooks/useDomEditSession.ts +0 -4
  62. package/src/hooks/useDomEditWiring.ts +1 -0
  63. package/src/hooks/useDomSelection.ts +12 -5
  64. package/src/hooks/useEnableKeyframes.test.ts +130 -0
  65. package/src/hooks/useEnableKeyframes.ts +298 -45
  66. package/src/hooks/useGestureCommit.ts +121 -53
  67. package/src/hooks/useGestureRecording.ts +61 -42
  68. package/src/hooks/useGsapAnimationFetchFallback.test.ts +36 -0
  69. package/src/hooks/useGsapAnimationFetchFallback.ts +65 -9
  70. package/src/hooks/useGsapAnimationOps.ts +1 -1
  71. package/src/hooks/useGsapAwareEditing.ts +38 -40
  72. package/src/hooks/useGsapScriptCommits.test.tsx +366 -0
  73. package/src/hooks/useGsapScriptCommits.ts +102 -6
  74. package/src/hooks/useGsapTweenCache.ts +12 -11
  75. package/src/hooks/useRazorSplit.ts +21 -2
  76. package/src/hooks/useSafeGsapCommitMutation.test.tsx +86 -0
  77. package/src/hooks/useSafeGsapCommitMutation.ts +8 -4
  78. package/src/hooks/useServerConnection.ts +4 -1
  79. package/src/hooks/useStudioContextValue.ts +3 -2
  80. package/src/hooks/useStudioUrlState.ts +97 -37
  81. package/src/player/components/ShortcutsPanel.tsx +8 -0
  82. package/src/player/components/TimelineClipDiamonds.tsx +10 -12
  83. package/src/player/hooks/useExpandedTimelineElements.test.ts +34 -0
  84. package/src/player/hooks/useExpandedTimelineElements.ts +20 -2
  85. package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -1
  86. package/src/player/lib/mediaProbe.ts +7 -1
  87. package/src/player/store/playerStore.ts +38 -1
  88. package/src/telemetry/config.ts +3 -2
  89. package/src/utils/editDebugLog.ts +16 -0
  90. package/src/utils/generateId.ts +7 -0
  91. package/src/utils/gsapSoftReload.test.ts +217 -13
  92. package/src/utils/gsapSoftReload.ts +228 -50
  93. package/src/utils/studioHelpers.test.ts +31 -1
  94. package/src/utils/studioHelpers.ts +24 -0
  95. package/src/utils/studioPreviewHelpers.test.ts +26 -1
  96. package/src/utils/studioPreviewHelpers.ts +30 -6
  97. package/src/utils/studioTelemetry.ts +3 -1
  98. package/dist/assets/index-Baz2T-9U.js +0 -269
  99. package/dist/assets/index-BvEhf7SS.css +0 -1
  100. package/src/player/components/TimelinePropertyRows.tsx +0 -126
@@ -185,6 +185,30 @@ export function findMatchingTimelineElementId(
185
185
  return null;
186
186
  }
187
187
 
188
+ /**
189
+ * A selected DOM node may be a static descendant of a clip (e.g. the `.num` text
190
+ * inside a `#stat1` card) — not a timeline element itself. Walk up to the nearest
191
+ * ancestor that IS a clip so the timeline still selects + inline-expands around it.
192
+ */
193
+ export function findTimelineIdByAncestor(
194
+ element: Element | null | undefined,
195
+ elements: TimelineElement[],
196
+ sourceFile: string,
197
+ ): string | null {
198
+ let ancestor = element?.parentElement ?? null;
199
+ while (ancestor) {
200
+ const id = ancestor.id;
201
+ if (id) {
202
+ const match = elements.find(
203
+ (el) => el.domId === id && (el.sourceFile ?? "index.html") === sourceFile,
204
+ );
205
+ if (match) return match.key ?? match.id;
206
+ }
207
+ ancestor = ancestor.parentElement;
208
+ }
209
+ return null;
210
+ }
211
+
188
212
  export function resolveTimelineSelectionSeekTime(
189
213
  currentTime: number,
190
214
  element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
@@ -1,5 +1,30 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
- import { pauseStudioPreviewPlayback } from "./studioPreviewHelpers";
2
+ import { coversComposition, pauseStudioPreviewPlayback } from "./studioPreviewHelpers";
3
+
4
+ describe("coversComposition (full-bleed canvas-pick exclusion)", () => {
5
+ const viewport = { width: 1920, height: 1080 };
6
+
7
+ it("treats a full-bleed scene wrapper as covering the composition", () => {
8
+ expect(coversComposition({ width: 1920, height: 1080 }, viewport)).toBe(true);
9
+ expect(coversComposition({ width: 1900, height: 1040 }, viewport)).toBe(true); // ~99%/96%
10
+ });
11
+
12
+ it("does NOT exclude inner content (a stat card, a heading)", () => {
13
+ expect(coversComposition({ width: 320, height: 180 }, viewport)).toBe(false);
14
+ expect(coversComposition({ width: 1900, height: 200 }, viewport)).toBe(false); // wide but short
15
+ expect(coversComposition({ width: 200, height: 1040 }, viewport)).toBe(false); // tall but narrow
16
+ });
17
+
18
+ it("needs BOTH axes near full-bleed (>=95%)", () => {
19
+ expect(coversComposition({ width: 1800, height: 1080 }, viewport)).toBe(false); // 93.75% wide
20
+ expect(coversComposition({ width: 1920, height: 1000 }, viewport)).toBe(false); // 92.6% tall
21
+ });
22
+
23
+ it("guards against a degenerate viewport", () => {
24
+ expect(coversComposition({ width: 100, height: 100 }, { width: 0, height: 0 })).toBe(false);
25
+ expect(coversComposition({ width: 100, height: 100 }, { width: 1, height: 1 })).toBe(false);
26
+ });
27
+ });
3
28
 
4
29
  describe("pauseStudioPreviewPlayback", () => {
5
30
  it("pauses through __player without pausing sibling timelines directly", () => {
@@ -1,5 +1,4 @@
1
1
  import type { DomEditViewport } from "../components/editor/domEditing";
2
- import { resolveVisualDomEditSelectionTarget } from "../components/editor/domEditing";
3
2
  import {
4
3
  getDomLayerPatchTarget,
5
4
  isElementComputedVisible,
@@ -13,6 +12,29 @@ interface PreviewLocalPointer {
13
12
  viewport: DomEditViewport;
14
13
  }
15
14
 
15
+ // An element is "full-bleed" when its box spans nearly the whole composition on
16
+ // BOTH axes. Such elements (scene wrappers, backdrops) are excluded from canvas
17
+ // click-picking so a click lands on inner content — or deselects on empty area —
18
+ // instead of grabbing the giant container. The Layers panel still selects them.
19
+ // ponytail: pure size heuristic; tighten the ratio if decorative full-bleed art
20
+ // should remain canvas-selectable.
21
+ const FULL_BLEED_RATIO = 0.95;
22
+
23
+ export function coversComposition(
24
+ elRect: { width: number; height: number },
25
+ viewport: DomEditViewport,
26
+ ): boolean {
27
+ if (viewport.width <= 1 || viewport.height <= 1) return false;
28
+ return (
29
+ elRect.width / viewport.width >= FULL_BLEED_RATIO &&
30
+ elRect.height / viewport.height >= FULL_BLEED_RATIO
31
+ );
32
+ }
33
+
34
+ function isFullBleedTarget(el: HTMLElement, viewport: DomEditViewport): boolean {
35
+ return coversComposition(el.getBoundingClientRect(), viewport);
36
+ }
37
+
16
38
  function resolvePreviewLocalPointer(
17
39
  iframe: HTMLIFrameElement,
18
40
  doc: Document,
@@ -82,18 +104,19 @@ export function getPreviewTargetFromPointer(
82
104
  const overrideStyle = forcePointerEventsAuto(doc);
83
105
  try {
84
106
  if (typeof doc.elementsFromPoint === "function") {
85
- const visualTarget = resolveVisualDomEditSelectionTarget(
107
+ const candidates = resolveAllVisualDomEditTargets(
86
108
  doc.elementsFromPoint(localPointer.x, localPointer.y),
87
- {
88
- activeCompositionPath,
89
- },
109
+ { activeCompositionPath },
90
110
  );
111
+ const visualTarget =
112
+ candidates.find((el) => !isFullBleedTarget(el, localPointer.viewport)) ?? null;
91
113
  if (visualTarget) return visualTarget;
92
114
  }
93
115
 
94
116
  const fallback = getEventTargetElement(doc.elementFromPoint(localPointer.x, localPointer.y));
95
117
  if (!fallback || !getDomLayerPatchTarget(fallback, activeCompositionPath)) return null;
96
118
  if (!isElementComputedVisible(fallback)) return null;
119
+ if (isFullBleedTarget(fallback, localPointer.viewport)) return null;
97
120
  return fallback;
98
121
  } finally {
99
122
  removePointerEventsOverride(overrideStyle);
@@ -125,11 +148,12 @@ export function getAllPreviewTargetsFromPointer(
125
148
  if (typeof doc.elementsFromPoint === "function") {
126
149
  return resolveAllVisualDomEditTargets(doc.elementsFromPoint(localPointer.x, localPointer.y), {
127
150
  activeCompositionPath,
128
- });
151
+ }).filter((el) => !isFullBleedTarget(el, localPointer.viewport));
129
152
  }
130
153
  const fallback = getEventTargetElement(doc.elementFromPoint(localPointer.x, localPointer.y));
131
154
  if (!fallback || !getDomLayerPatchTarget(fallback, activeCompositionPath)) return [];
132
155
  if (!isElementComputedVisible(fallback)) return [];
156
+ if (isFullBleedTarget(fallback, localPointer.viewport)) return [];
133
157
  return [fallback];
134
158
  } finally {
135
159
  removePointerEventsOverride(overrideStyle);
@@ -1,3 +1,5 @@
1
+ import { generateId } from "./generateId";
2
+
1
3
  // PostHog public ingest key — write-only, safe to ship in the client bundle
2
4
  const POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
3
5
  const POSTHOG_HOST = "https://us.i.posthog.com";
@@ -29,7 +31,7 @@ function getDistinctId(): string {
29
31
  } catch {
30
32
  // localStorage may be unavailable
31
33
  }
32
- distinctId = crypto.randomUUID();
34
+ distinctId = generateId();
33
35
  try {
34
36
  localStorage.setItem("hf-studio-anon-id", distinctId);
35
37
  } catch {