@hyperframes/studio 0.7.6 → 0.7.7
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-B_NnmU6q.js → index-B2YXvFxf.js} +1 -1
- package/dist/assets/index-BSkUuN8g.css +1 -0
- package/dist/assets/{index-ysftPins.js → index-BeRh2hMe.js} +195 -194
- package/dist/assets/{index-DsBhGFPe.js → index-BoASKOeE.js} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.html +2 -2
- package/dist/index.js +1765 -1193
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/components/StudioRightPanel.tsx +2 -0
- package/src/components/editor/AnimationCard.tsx +6 -0
- package/src/components/editor/EaseCurveSection.tsx +175 -99
- package/src/components/editor/GsapAnimationSection.tsx +2 -0
- package/src/components/editor/KeyframeEaseList.tsx +67 -3
- package/src/components/editor/PropertyPanel.tsx +11 -2
- package/src/components/editor/gsapAnimationCallbacks.ts +2 -0
- package/src/components/editor/gsapAnimationConstants.ts +6 -35
- package/src/components/editor/gsapAnimationHelpers.test.ts +1 -1
- package/src/components/editor/propertyPanelHelpers.ts +6 -0
- package/src/components/editor/propertyPanelTimingSection.tsx +35 -2
- package/src/components/editor/useMotionPathData.ts +2 -1
- package/src/components/sidebar/AssetContextMenu.tsx +97 -0
- package/src/components/sidebar/AssetsTab.tsx +542 -254
- package/src/components/sidebar/assetHelpers.ts +40 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/hooks/gsapDragCommit.ts +101 -0
- package/src/hooks/gsapRuntimeBridge.ts +22 -0
- package/src/hooks/gsapRuntimeKeyframes.test.ts +47 -0
- package/src/hooks/gsapRuntimeKeyframes.ts +13 -0
- package/src/hooks/useDomEditSession.ts +20 -0
- package/src/hooks/useDomEditWiring.ts +3 -0
- package/src/hooks/useGestureCommit.ts +0 -4
- package/src/hooks/useGsapAwareEditing.ts +0 -4
- package/src/hooks/useGsapScriptCommits.ts +0 -12
- package/src/hooks/useGsapTweenCache.test.ts +45 -1
- package/src/hooks/useGsapTweenCache.ts +119 -38
- package/src/hooks/useMusicBeatAnalysis.ts +36 -21
- package/src/hooks/useRazorSplit.ts +0 -3
- package/dist/assets/index-wJZf6FK3.css +0 -1
- package/src/utils/editDebugLog.ts +0 -9
|
@@ -67,6 +67,7 @@ export interface PropertyPanelProps {
|
|
|
67
67
|
) => void;
|
|
68
68
|
onRemoveKeyframe?: (animationId: string, percentage: number) => void;
|
|
69
69
|
onUpdateKeyframeEase?: (animationId: string, percentage: number, ease: string) => void;
|
|
70
|
+
onSetAllKeyframeEases?: (animationId: string, ease: string) => void;
|
|
70
71
|
onConvertToKeyframes?: (animationId: string) => void;
|
|
71
72
|
onCommitAnimatedProperty?: (
|
|
72
73
|
selection: DomEditSelection,
|
|
@@ -211,7 +212,9 @@ export const LABEL = "text-[11px] font-medium text-panel-text-3";
|
|
|
211
212
|
export const RESPONSIVE_GRID = "grid grid-cols-[repeat(auto-fit,minmax(118px,1fr))] gap-3";
|
|
212
213
|
export const EMPTY_STYLES: Record<string, string> = {};
|
|
213
214
|
|
|
215
|
+
// fallow-ignore-next-line unused-exports -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
214
216
|
export const EMPTY_FILTER_VALUE = "none";
|
|
217
|
+
// fallow-ignore-next-line unused-exports -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
215
218
|
export const BOX_SHADOW_PRESETS = {
|
|
216
219
|
none: "none",
|
|
217
220
|
soft: "0 12px 36px rgba(0, 0, 0, 0.28)",
|
|
@@ -272,6 +275,7 @@ export function parsePxMetricValue(value: string): number | null {
|
|
|
272
275
|
return token.value;
|
|
273
276
|
}
|
|
274
277
|
|
|
278
|
+
// fallow-ignore-next-line unused-exports -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
275
279
|
export function clampPanelNumber(
|
|
276
280
|
value: number,
|
|
277
281
|
min: number,
|
|
@@ -320,6 +324,7 @@ export function normalizeTextMetricValue(
|
|
|
320
324
|
function splitCssFunctions(value: string): string[] {
|
|
321
325
|
const functions: string[] = [];
|
|
322
326
|
let current = "";
|
|
327
|
+
// fallow-ignore-next-line code-duplication -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
323
328
|
let depth = 0;
|
|
324
329
|
|
|
325
330
|
for (const char of value.trim()) {
|
|
@@ -485,6 +490,7 @@ export function extractBackgroundImageUrl(value: string | undefined): string {
|
|
|
485
490
|
|
|
486
491
|
// ── GSAP runtime value readers (used by PropertyPanel) ────────────────────
|
|
487
492
|
|
|
493
|
+
// fallow-ignore-next-line complexity -- pre-existing; surfaced in this file's diff by an unrelated line shift
|
|
488
494
|
export function readGsapRuntimeValuesForPanel(
|
|
489
495
|
gsapAnimId: string | null,
|
|
490
496
|
gsapAnimations: GsapAnimation[],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
1
2
|
import { Clock } from "../../icons/SystemIcons";
|
|
2
3
|
import type { DomEditSelection } from "./domEditing";
|
|
3
4
|
import { formatTimingValue, RESPONSIVE_GRID } from "./propertyPanelHelpers";
|
|
@@ -9,18 +10,45 @@ function parseTimingValue(input: string): number | null {
|
|
|
9
10
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Derive a time range from the element's GSAP tweens (earliest start → latest
|
|
15
|
+
* end) so an element animated purely by GSAP — with no `data-start` /
|
|
16
|
+
* `data-duration` — still shows a meaningful Timing range instead of 0s.
|
|
17
|
+
*/
|
|
18
|
+
function deriveTimingFromAnimations(
|
|
19
|
+
animations: GsapAnimation[],
|
|
20
|
+
): { start: number; duration: number } | null {
|
|
21
|
+
let lo = Infinity;
|
|
22
|
+
let hi = -Infinity;
|
|
23
|
+
for (const a of animations) {
|
|
24
|
+
const s = a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0);
|
|
25
|
+
const d = a.duration ?? 0;
|
|
26
|
+
lo = Math.min(lo, s);
|
|
27
|
+
hi = Math.max(hi, s + d);
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi) || hi <= lo) return null;
|
|
30
|
+
return { start: lo, duration: hi - lo };
|
|
31
|
+
}
|
|
32
|
+
|
|
12
33
|
export function TimingSection({
|
|
13
34
|
element,
|
|
35
|
+
animations = [],
|
|
14
36
|
onSetAttribute,
|
|
15
37
|
}: {
|
|
16
38
|
element: DomEditSelection;
|
|
39
|
+
animations?: GsapAnimation[];
|
|
17
40
|
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
|
18
41
|
}) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
42
|
+
const explicitStart = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
|
|
43
|
+
const explicitDuration =
|
|
21
44
|
Number.parseFloat(
|
|
22
45
|
element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
|
|
23
46
|
) || 0;
|
|
47
|
+
|
|
48
|
+
// No authored clip timing → infer the range from the element's animations.
|
|
49
|
+
const derived = explicitDuration > 0 ? null : deriveTimingFromAnimations(animations);
|
|
50
|
+
const start = derived ? derived.start : explicitStart;
|
|
51
|
+
const duration = derived ? derived.duration : explicitDuration;
|
|
24
52
|
const end = start + duration;
|
|
25
53
|
|
|
26
54
|
const commitStart = (nextValue: string) => {
|
|
@@ -54,6 +82,11 @@ export function TimingSection({
|
|
|
54
82
|
onCommit={commitDuration}
|
|
55
83
|
/>
|
|
56
84
|
</div>
|
|
85
|
+
{derived && (
|
|
86
|
+
<p className="mt-2 text-[10px] leading-snug text-neutral-500">
|
|
87
|
+
Inferred from this element’s animation — edit to pin an explicit clip range.
|
|
88
|
+
</p>
|
|
89
|
+
)}
|
|
57
90
|
</Section>
|
|
58
91
|
);
|
|
59
92
|
}
|
|
@@ -119,7 +119,8 @@ export function useMotionPathData(
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
const recompute = () => {
|
|
122
|
-
|
|
122
|
+
// Position-only: never let a co-located size/scale tween shadow the path.
|
|
123
|
+
const read = readRuntimeKeyframes(iframeRef.current, selector, undefined, ["x", "y"]);
|
|
123
124
|
const next = buildMotionPathGeometry(read);
|
|
124
125
|
setGeometry((prev) =>
|
|
125
126
|
prev?.points === next?.points && prev?.kind === next?.kind ? prev : next,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function ContextMenu({
|
|
2
|
+
x,
|
|
3
|
+
y,
|
|
4
|
+
asset,
|
|
5
|
+
onClose,
|
|
6
|
+
onCopy,
|
|
7
|
+
onDelete,
|
|
8
|
+
onRename,
|
|
9
|
+
}: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
asset: string;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
onCopy: (path: string) => void;
|
|
15
|
+
onDelete?: (path: string) => void;
|
|
16
|
+
onRename?: (oldPath: string, newPath: string) => void;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className="fixed inset-0 z-[200]"
|
|
21
|
+
onClick={onClose}
|
|
22
|
+
onContextMenu={(e) => {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
onClose();
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
className="absolute bg-neutral-900 border border-neutral-700 rounded-lg shadow-xl py-1 min-w-[140px] text-xs"
|
|
29
|
+
style={{ left: x, top: y }}
|
|
30
|
+
>
|
|
31
|
+
<button
|
|
32
|
+
onClick={(e) => {
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
onCopy(asset);
|
|
35
|
+
onClose();
|
|
36
|
+
}}
|
|
37
|
+
className="w-full text-left px-3 py-1.5 text-neutral-300 hover:bg-neutral-800 transition-colors"
|
|
38
|
+
>
|
|
39
|
+
Copy path
|
|
40
|
+
</button>
|
|
41
|
+
{onRename && (
|
|
42
|
+
<button
|
|
43
|
+
onClick={(e) => {
|
|
44
|
+
e.stopPropagation();
|
|
45
|
+
onClose();
|
|
46
|
+
}}
|
|
47
|
+
className="w-full text-left px-3 py-1.5 text-neutral-300 hover:bg-neutral-800 transition-colors"
|
|
48
|
+
>
|
|
49
|
+
Rename
|
|
50
|
+
</button>
|
|
51
|
+
)}
|
|
52
|
+
{onDelete && (
|
|
53
|
+
<button
|
|
54
|
+
onClick={(e) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
onDelete(asset);
|
|
57
|
+
onClose();
|
|
58
|
+
}}
|
|
59
|
+
className="w-full text-left px-3 py-1.5 text-red-400 hover:bg-neutral-800 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
Delete
|
|
62
|
+
</button>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function DeleteConfirm({
|
|
70
|
+
name,
|
|
71
|
+
onConfirm,
|
|
72
|
+
onCancel,
|
|
73
|
+
}: {
|
|
74
|
+
name: string;
|
|
75
|
+
onConfirm: () => void;
|
|
76
|
+
onCancel: () => void;
|
|
77
|
+
}) {
|
|
78
|
+
return (
|
|
79
|
+
<div className="px-2 py-1.5 bg-red-950/30 border-l-2 border-red-500 flex items-center justify-between gap-2">
|
|
80
|
+
<span className="text-[10px] text-red-400 truncate">Delete {name}?</span>
|
|
81
|
+
<div className="flex items-center gap-1 flex-shrink-0">
|
|
82
|
+
<button
|
|
83
|
+
onClick={onConfirm}
|
|
84
|
+
className="px-2 py-0.5 text-[10px] rounded bg-red-600 text-white hover:bg-red-500 transition-colors"
|
|
85
|
+
>
|
|
86
|
+
Delete
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
onClick={onCancel}
|
|
90
|
+
className="px-2 py-0.5 text-[10px] rounded text-neutral-400 hover:text-neutral-200 transition-colors"
|
|
91
|
+
>
|
|
92
|
+
Cancel
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|