@hyperframes/studio 0.6.73 → 0.6.75

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 (63) hide show
  1. package/dist/assets/index-DcyZuBcU.css +1 -0
  2. package/dist/assets/index-uB_W2GDl.js +140 -0
  3. package/dist/index.html +2 -2
  4. package/package.json +4 -4
  5. package/src/App.tsx +30 -24
  6. package/src/components/StudioPreviewArea.tsx +101 -26
  7. package/src/components/StudioRightPanel.tsx +3 -0
  8. package/src/components/StudioToast.tsx +18 -0
  9. package/src/components/TimelineToolbar.tsx +230 -4
  10. package/src/components/editor/AnimationCard.tsx +68 -4
  11. package/src/components/editor/DomEditOverlay.tsx +70 -1
  12. package/src/components/editor/GridOverlay.tsx +50 -0
  13. package/src/components/editor/KeyframeDiamond.tsx +49 -0
  14. package/src/components/editor/KeyframeNavigation.tsx +139 -0
  15. package/src/components/editor/LayersPanel.test.ts +135 -0
  16. package/src/components/editor/LayersPanel.tsx +151 -15
  17. package/src/components/editor/PropertyPanel.tsx +293 -140
  18. package/src/components/editor/SnapGuideOverlay.tsx +166 -0
  19. package/src/components/editor/SnapToolbar.tsx +163 -0
  20. package/src/components/editor/SpringEaseEditor.tsx +256 -0
  21. package/src/components/editor/domEditOverlayGestures.ts +7 -0
  22. package/src/components/editor/domEditOverlayStartGesture.ts +28 -0
  23. package/src/components/editor/gsapAnimationConstants.ts +42 -0
  24. package/src/components/editor/gsapAnimationHelpers.ts +2 -1
  25. package/src/components/editor/manualEditingAvailability.ts +6 -0
  26. package/src/components/editor/manualEditsDom.ts +56 -2
  27. package/src/components/editor/manualOffsetDrag.ts +19 -3
  28. package/src/components/editor/propertyPanelHelpers.ts +90 -0
  29. package/src/components/editor/propertyPanelTimingSection.tsx +64 -0
  30. package/src/components/editor/snapEngine.test.ts +657 -0
  31. package/src/components/editor/snapEngine.ts +575 -0
  32. package/src/components/editor/snapTargetCollection.ts +147 -0
  33. package/src/components/editor/useDomEditOverlayGestures.ts +137 -10
  34. package/src/components/editor/useLayerDrag.ts +213 -0
  35. package/src/components/nle/NLELayout.tsx +18 -0
  36. package/src/contexts/DomEditContext.tsx +27 -0
  37. package/src/hooks/gsapRuntimeBridge.ts +585 -0
  38. package/src/hooks/gsapRuntimeKeyframes.ts +170 -0
  39. package/src/hooks/useAnimatedPropertyCommit.ts +131 -0
  40. package/src/hooks/useAppHotkeys.ts +63 -1
  41. package/src/hooks/useDomEditCommits.ts +88 -4
  42. package/src/hooks/useDomEditSession.ts +179 -65
  43. package/src/hooks/useGsapScriptCommits.ts +144 -7
  44. package/src/hooks/useGsapSelectionHandlers.ts +202 -0
  45. package/src/hooks/useGsapTweenCache.ts +174 -3
  46. package/src/hooks/useTimelineEditing.ts +93 -0
  47. package/src/icons/SystemIcons.tsx +2 -0
  48. package/src/player/components/ClipContextMenu.tsx +99 -0
  49. package/src/player/components/KeyframeDiamondContextMenu.tsx +164 -0
  50. package/src/player/components/Timeline.test.ts +2 -1
  51. package/src/player/components/Timeline.tsx +108 -68
  52. package/src/player/components/TimelineCanvas.tsx +47 -1
  53. package/src/player/components/TimelineClip.tsx +8 -3
  54. package/src/player/components/TimelineClipDiamonds.tsx +174 -0
  55. package/src/player/components/timelineDragDrop.ts +103 -0
  56. package/src/player/components/timelineLayout.ts +1 -1
  57. package/src/player/store/playerStore.ts +42 -0
  58. package/src/utils/editHistory.ts +1 -1
  59. package/src/utils/optimisticUpdate.test.ts +53 -0
  60. package/src/utils/optimisticUpdate.ts +18 -0
  61. package/src/utils/studioUiPreferences.ts +17 -0
  62. package/dist/assets/index-CrxThtSJ.css +0 -1
  63. package/dist/assets/index-Dc2HfqON.js +0 -140
@@ -236,9 +236,25 @@ export function createManualOffsetDragMember(input: {
236
236
  const gestureToken = beginStudioManualEditGesture(input.element);
237
237
  const measured = measureManualOffsetDragScreenToOffsetMatrix(input.element, initialOffset);
238
238
  if (!measured.ok) {
239
- restoreStudioPathOffset(input.element, initialPathOffset);
240
- endStudioManualEditGesture(input.element, gestureToken);
241
- return { ok: false, reason: measured.reason, selection: input.selection };
239
+ // Fallback: when GSAP transforms interfere with probe measurement, use
240
+ // the preview scale as an approximation. The commit path reads the actual
241
+ // GSAP position from the iframe runtime, so visual imprecision during
242
+ // drag is acceptable — the final committed position is always exact.
243
+ const scaleX = input.rect.editScaleX || 1;
244
+ const scaleY = input.rect.editScaleY || 1;
245
+ return {
246
+ ok: true,
247
+ member: {
248
+ key: input.key,
249
+ selection: input.selection,
250
+ element: input.element,
251
+ initialOffset,
252
+ initialPathOffset,
253
+ gestureToken,
254
+ screenToOffset: { a: 1 / scaleX, b: 0, c: 0, d: 1 / scaleY },
255
+ originRect: input.rect,
256
+ },
257
+ };
242
258
  }
243
259
 
244
260
  return {
@@ -1,5 +1,61 @@
1
1
  import { parseCssColor, type ParsedColor } from "./colorValue";
2
2
  import { COMMON_LOCAL_FONT_FAMILIES } from "./fontCatalog";
3
+ import type { DomEditSelection } from "./domEditing";
4
+ import type { ImportedFontAsset } from "./fontAssets";
5
+
6
+ export interface PropertyPanelProps {
7
+ projectId: string;
8
+ projectDir: string | null;
9
+ assets: string[];
10
+ element: DomEditSelection | null;
11
+ multiSelectCount?: number;
12
+ copiedAgentPrompt: boolean;
13
+ onClearSelection: () => void;
14
+ onSetStyle: (prop: string, value: string) => void | Promise<void>;
15
+ onSetAttribute: (attr: string, value: string) => void | Promise<void>;
16
+ onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
17
+ onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void;
18
+ onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void;
19
+ onSetManualRotation: (element: DomEditSelection, next: { angle: number }) => void;
20
+ onSetText: (value: string, fieldKey?: string) => void;
21
+ onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void;
22
+ onAddTextField: (afterFieldKey?: string) => string | Promise<string | null> | null;
23
+ onRemoveTextField: (fieldKey: string) => void;
24
+ onAskAgent: () => void;
25
+ onImportAssets?: (files: FileList) => Promise<string[]>;
26
+ fontAssets?: ImportedFontAsset[];
27
+ onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
28
+ previewIframeRef?: React.RefObject<HTMLIFrameElement | null>;
29
+ gsapAnimations?: import("@hyperframes/core/gsap-parser").GsapAnimation[];
30
+ gsapMultipleTimelines?: boolean;
31
+ gsapUnsupportedTimelinePattern?: boolean;
32
+ onUpdateGsapProperty?: (animId: string, prop: string, value: number | string) => void;
33
+ onUpdateGsapMeta?: (
34
+ animId: string,
35
+ updates: { duration?: number; ease?: string; position?: number },
36
+ ) => void;
37
+ onDeleteGsapAnimation?: (animId: string) => void;
38
+ onAddGsapProperty?: (animId: string, prop: string) => void;
39
+ onRemoveGsapProperty?: (animId: string, prop: string) => void;
40
+ onUpdateGsapFromProperty?: (animId: string, prop: string, value: number | string) => void;
41
+ onAddGsapFromProperty?: (animId: string, prop: string) => void;
42
+ onRemoveGsapFromProperty?: (animId: string, prop: string) => void;
43
+ onAddGsapAnimation?: (method: "to" | "from" | "set" | "fromTo") => void;
44
+ onAddKeyframe?: (
45
+ animationId: string,
46
+ percentage: number,
47
+ property: string,
48
+ value: number | string,
49
+ ) => void;
50
+ onRemoveKeyframe?: (animationId: string, percentage: number) => void;
51
+ onConvertToKeyframes?: (animationId: string) => void;
52
+ onCommitAnimatedProperty?: (
53
+ selection: DomEditSelection,
54
+ property: string,
55
+ value: number | string,
56
+ ) => Promise<void>;
57
+ onSeekToTime?: (time: number) => void;
58
+ }
3
59
 
4
60
  /* ------------------------------------------------------------------ */
5
61
  /* Font types & constants (shared by font and section modules) */
@@ -399,3 +455,37 @@ export function extractBackgroundImageUrl(value: string | undefined): string {
399
455
  if (endParen < index) return "";
400
456
  return value.slice(index, endParen).trim();
401
457
  }
458
+
459
+ // ── Fit to children ──────────────────────────────────────────────────
460
+
461
+ export function computeFitToChildrenSize(
462
+ element: DomEditSelection,
463
+ ): { width: number; height: number } | null {
464
+ const el = element.element;
465
+ const win = el.ownerDocument?.defaultView;
466
+ const children = Array.from(el.children).filter((c): c is HTMLElement => c.nodeType === 1);
467
+ if (children.length === 0) return null;
468
+ let minX = Infinity,
469
+ minY = Infinity,
470
+ maxX = -Infinity,
471
+ maxY = -Infinity;
472
+ for (const child of children) {
473
+ if (win) {
474
+ const cs = win.getComputedStyle(child);
475
+ if (cs.visibility === "hidden" || cs.display === "none") continue;
476
+ }
477
+ const r = child.getBoundingClientRect();
478
+ if (r.width === 0 && r.height === 0) continue;
479
+ minX = Math.min(minX, r.left);
480
+ minY = Math.min(minY, r.top);
481
+ maxX = Math.max(maxX, r.right);
482
+ maxY = Math.max(maxY, r.bottom);
483
+ }
484
+ if (!isFinite(minX)) return null;
485
+ const parentRect = el.getBoundingClientRect();
486
+ const scaleX = parentRect.width > 0 ? element.boundingBox.width / parentRect.width : 1;
487
+ const scaleY = parentRect.height > 0 ? element.boundingBox.height / parentRect.height : 1;
488
+ const width = Math.round((maxX - minX) * scaleX);
489
+ const height = Math.round((maxY - minY) * scaleY);
490
+ return width > 0 && height > 0 ? { width, height } : null;
491
+ }
@@ -0,0 +1,64 @@
1
+ import { Clock } from "../../icons/SystemIcons";
2
+ import type { DomEditSelection } from "./domEditing";
3
+ import { RESPONSIVE_GRID } from "./propertyPanelHelpers";
4
+ import { MetricField, Section } from "./propertyPanelPrimitives";
5
+
6
+ function formatTimingValue(seconds: number): string {
7
+ if (!Number.isFinite(seconds) || seconds < 0) return "0.00s";
8
+ return `${seconds.toFixed(2)}s`;
9
+ }
10
+
11
+ function parseTimingValue(input: string): number | null {
12
+ const cleaned = input.replace(/s$/i, "").trim();
13
+ const parsed = Number.parseFloat(cleaned);
14
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
15
+ }
16
+
17
+ export function TimingSection({
18
+ element,
19
+ onSetAttribute,
20
+ }: {
21
+ element: DomEditSelection;
22
+ onSetAttribute: (attr: string, value: string) => void | Promise<void>;
23
+ }) {
24
+ const start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
25
+ const duration =
26
+ Number.parseFloat(
27
+ element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
28
+ ) || 0;
29
+ const end = start + duration;
30
+
31
+ const commitStart = (nextValue: string) => {
32
+ const parsed = parseTimingValue(nextValue);
33
+ if (parsed == null) return;
34
+ void onSetAttribute("start", parsed.toFixed(2));
35
+ };
36
+
37
+ const commitDuration = (nextValue: string) => {
38
+ const parsed = parseTimingValue(nextValue);
39
+ if (parsed == null || parsed <= 0) return;
40
+ void onSetAttribute("duration", parsed.toFixed(2));
41
+ };
42
+
43
+ const commitEnd = (nextValue: string) => {
44
+ const parsed = parseTimingValue(nextValue);
45
+ if (parsed == null || parsed <= start) return;
46
+ void onSetAttribute("duration", (parsed - start).toFixed(2));
47
+ };
48
+
49
+ return (
50
+ <Section title="Timing" icon={<Clock size={15} />}>
51
+ <div className={RESPONSIVE_GRID}>
52
+ <MetricField label="Start" value={formatTimingValue(start)} onCommit={commitStart} />
53
+ <MetricField label="End" value={formatTimingValue(end)} onCommit={commitEnd} />
54
+ </div>
55
+ <div className="mt-3">
56
+ <MetricField
57
+ label="Duration"
58
+ value={formatTimingValue(duration)}
59
+ onCommit={commitDuration}
60
+ />
61
+ </div>
62
+ </Section>
63
+ );
64
+ }