@hyperframes/studio 0.6.122-alpha.0 → 0.7.0
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.
- package/dist/assets/index-CbNg8Ghm.js +374 -0
- package/dist/assets/{index-DSLrl2tB.js → index-CccCJ2B6.js} +24 -24
- package/dist/assets/{index-i-MLqhmm.js → index-DsxCdV8b.js} +1 -1
- package/dist/assets/index-DzWIinxk.css +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.html +3 -3
- package/dist/index.js +3757 -2232
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/App.tsx +1 -0
- package/src/components/StudioHeader.tsx +2 -3
- package/src/components/StudioPreviewArea.tsx +12 -0
- package/src/components/TimelineToolbar.tsx +29 -16
- package/src/components/editor/DomEditOverlay.test.ts +0 -13
- package/src/components/editor/DomEditOverlay.tsx +3 -10
- package/src/components/editor/KeyframeNavigation.test.ts +36 -0
- package/src/components/editor/KeyframeNavigation.tsx +34 -4
- package/src/components/editor/MotionPathNode.tsx +98 -0
- package/src/components/editor/MotionPathOverlay.tsx +481 -0
- package/src/components/editor/SnapToolbar.tsx +28 -3
- package/src/components/editor/SourceEditor.tsx +21 -9
- package/src/components/editor/domEditOverlayGeometry.ts +12 -0
- package/src/components/editor/domEditOverlayGestures.ts +5 -1
- package/src/components/editor/domEditOverlayStartGesture.ts +5 -1
- package/src/components/editor/domEditingDom.ts +24 -9
- package/src/components/editor/manualEditingAvailability.test.ts +0 -12
- package/src/components/editor/manualEditingAvailability.ts +1 -11
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsDom.ts +68 -39
- package/src/components/editor/manualEditsDomGsap.test.ts +82 -0
- package/src/components/editor/manualOffsetDrag.test.ts +12 -0
- package/src/components/editor/manualOffsetDrag.ts +79 -4
- package/src/components/editor/motionPathCommit.test.ts +130 -0
- package/src/components/editor/motionPathCommit.ts +83 -0
- package/src/components/editor/motionPathGeometry.test.ts +127 -0
- package/src/components/editor/motionPathGeometry.ts +116 -0
- package/src/components/editor/motionPathSelection.ts +33 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +36 -18
- package/src/components/editor/useDomEditOverlayRects.ts +6 -0
- package/src/components/editor/useMotionPathData.ts +135 -0
- package/src/components/nle/NLELayout.tsx +17 -6
- package/src/components/panels/SlideshowPanel.tsx +2 -1
- package/src/components/panels/SlideshowSubPanels.tsx +2 -1
- package/src/components/renders/RenderQueue.tsx +1 -1
- package/src/components/renders/useRenderQueue.ts +3 -2
- package/src/hooks/draggedGsapPosition.ts +47 -0
- package/src/hooks/gsapDragCommit.test.ts +435 -0
- package/src/hooks/gsapDragCommit.ts +278 -290
- package/src/hooks/gsapDragPositionCommit.ts +410 -0
- package/src/hooks/gsapRuntimeBridge.test.ts +147 -0
- package/src/hooks/gsapRuntimeBridge.ts +86 -69
- package/src/hooks/gsapRuntimeKeyframes.test.ts +129 -1
- package/src/hooks/gsapRuntimeKeyframes.ts +183 -11
- package/src/hooks/gsapRuntimePatch.test.ts +462 -0
- package/src/hooks/gsapRuntimePatch.ts +159 -0
- package/src/hooks/gsapScriptCommitTypes.ts +11 -1
- package/src/hooks/gsapShared.test.ts +58 -0
- package/src/hooks/gsapShared.ts +28 -10
- package/src/hooks/gsapTargetCache.ts +0 -3
- package/src/hooks/useDomEditSession.ts +0 -4
- package/src/hooks/useDomSelection.ts +12 -5
- package/src/hooks/useEnableKeyframes.test.ts +130 -0
- package/src/hooks/useEnableKeyframes.ts +298 -45
- package/src/hooks/useGestureCommit.ts +121 -53
- package/src/hooks/useGestureRecording.ts +61 -42
- package/src/hooks/useGsapAnimationFetchFallback.test.ts +36 -0
- package/src/hooks/useGsapAnimationFetchFallback.ts +65 -9
- package/src/hooks/useGsapAnimationOps.ts +1 -1
- package/src/hooks/useGsapAwareEditing.ts +38 -40
- package/src/hooks/useGsapScriptCommits.test.tsx +366 -0
- package/src/hooks/useGsapScriptCommits.ts +102 -6
- package/src/hooks/useGsapTweenCache.ts +7 -1
- package/src/hooks/useRazorSplit.ts +21 -2
- package/src/hooks/useSafeGsapCommitMutation.test.tsx +86 -0
- package/src/hooks/useSafeGsapCommitMutation.ts +8 -4
- package/src/hooks/useServerConnection.ts +4 -1
- package/src/hooks/useStudioContextValue.ts +3 -2
- package/src/hooks/useStudioUrlState.ts +97 -37
- package/src/player/components/ShortcutsPanel.tsx +8 -0
- package/src/player/components/TimelineClipDiamonds.tsx +10 -12
- package/src/player/hooks/useExpandedTimelineElements.test.ts +34 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +20 -2
- package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -1
- package/src/player/lib/mediaProbe.ts +7 -1
- package/src/player/store/playerStore.ts +38 -1
- package/src/telemetry/config.ts +3 -2
- package/src/utils/editDebugLog.ts +16 -0
- package/src/utils/generateId.ts +7 -0
- package/src/utils/gsapSoftReload.test.ts +217 -13
- package/src/utils/gsapSoftReload.ts +228 -50
- package/src/utils/studioHelpers.test.ts +31 -1
- package/src/utils/studioHelpers.ts +24 -0
- package/src/utils/studioPreviewHelpers.test.ts +26 -1
- package/src/utils/studioPreviewHelpers.ts +30 -6
- package/src/utils/studioTelemetry.ts +3 -1
- package/dist/assets/index-Baz2T-9U.js +0 -269
- package/dist/assets/index-BvEhf7SS.css +0 -1
- 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
|
|
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 =
|
|
34
|
+
distinctId = generateId();
|
|
33
35
|
try {
|
|
34
36
|
localStorage.setItem("hf-studio-anon-id", distinctId);
|
|
35
37
|
} catch {
|