@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.
Files changed (40) hide show
  1. package/dist/assets/{index-B_NnmU6q.js → index-B2YXvFxf.js} +1 -1
  2. package/dist/assets/index-BSkUuN8g.css +1 -0
  3. package/dist/assets/{index-ysftPins.js → index-BeRh2hMe.js} +195 -194
  4. package/dist/assets/{index-DsBhGFPe.js → index-BoASKOeE.js} +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.html +2 -2
  7. package/dist/index.js +1765 -1193
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/components/StudioRightPanel.tsx +2 -0
  11. package/src/components/editor/AnimationCard.tsx +6 -0
  12. package/src/components/editor/EaseCurveSection.tsx +175 -99
  13. package/src/components/editor/GsapAnimationSection.tsx +2 -0
  14. package/src/components/editor/KeyframeEaseList.tsx +67 -3
  15. package/src/components/editor/PropertyPanel.tsx +11 -2
  16. package/src/components/editor/gsapAnimationCallbacks.ts +2 -0
  17. package/src/components/editor/gsapAnimationConstants.ts +6 -35
  18. package/src/components/editor/gsapAnimationHelpers.test.ts +1 -1
  19. package/src/components/editor/propertyPanelHelpers.ts +6 -0
  20. package/src/components/editor/propertyPanelTimingSection.tsx +35 -2
  21. package/src/components/editor/useMotionPathData.ts +2 -1
  22. package/src/components/sidebar/AssetContextMenu.tsx +97 -0
  23. package/src/components/sidebar/AssetsTab.tsx +542 -254
  24. package/src/components/sidebar/assetHelpers.ts +40 -0
  25. package/src/contexts/DomEditContext.tsx +4 -0
  26. package/src/hooks/gsapDragCommit.ts +101 -0
  27. package/src/hooks/gsapRuntimeBridge.ts +22 -0
  28. package/src/hooks/gsapRuntimeKeyframes.test.ts +47 -0
  29. package/src/hooks/gsapRuntimeKeyframes.ts +13 -0
  30. package/src/hooks/useDomEditSession.ts +20 -0
  31. package/src/hooks/useDomEditWiring.ts +3 -0
  32. package/src/hooks/useGestureCommit.ts +0 -4
  33. package/src/hooks/useGsapAwareEditing.ts +0 -4
  34. package/src/hooks/useGsapScriptCommits.ts +0 -12
  35. package/src/hooks/useGsapTweenCache.test.ts +45 -1
  36. package/src/hooks/useGsapTweenCache.ts +119 -38
  37. package/src/hooks/useMusicBeatAnalysis.ts +36 -21
  38. package/src/hooks/useRazorSplit.ts +0 -3
  39. package/dist/assets/index-wJZf6FK3.css +0 -1
  40. 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 start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
20
- const duration =
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
- const read = readRuntimeKeyframes(iframeRef.current, selector);
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
+ }