@hyperframes/studio 0.6.93 → 0.6.94

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.
@@ -1,4 +1,4 @@
1
- import{n as Qi}from"./index-DYRWmfMX.js";/*!
1
+ import{n as Qi}from"./index-DvlSlmGV.js";/*!
2
2
  * Copyright (c) 2026-present, Vanilagy and contributors
3
3
  *
4
4
  * This Source Code Form is subject to the terms of the Mozilla Public
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-DYRWmfMX.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DvlSlmGV.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-rm9tn9nH.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.93",
3
+ "version": "0.6.94",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "mediabunny": "^1.45.3",
34
- "@hyperframes/core": "0.6.93",
35
- "@hyperframes/player": "0.6.93"
34
+ "@hyperframes/core": "0.6.94",
35
+ "@hyperframes/player": "0.6.94"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/react": "19",
@@ -46,7 +46,7 @@
46
46
  "vite": "^6.4.2",
47
47
  "vitest": "^3.2.4",
48
48
  "zustand": "^5.0.0",
49
- "@hyperframes/producer": "0.6.93"
49
+ "@hyperframes/producer": "0.6.94"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "19",
@@ -108,7 +108,6 @@ export function StudioPreviewArea({
108
108
  handlePreviewCanvasPointerMove,
109
109
  handlePreviewCanvasPointerLeave,
110
110
  applyDomSelection,
111
- buildDomSelectionFromTarget,
112
111
  handleBlockedDomMove,
113
112
  handleDomManualDragStart,
114
113
  handleDomPathOffsetCommit,
@@ -291,14 +290,6 @@ export function StudioPreviewArea({
291
290
  onRotationCommit={handleDomRotationCommit}
292
291
  gridVisible={snapPrefs.gridVisible}
293
292
  gridSpacing={snapPrefs.gridSpacing}
294
- onSelectElementById={async (id) => {
295
- const iframe = previewIframeRef.current;
296
- const el = iframe?.contentDocument?.getElementById(id);
297
- if (!el) return null;
298
- const sel = await buildDomSelectionFromTarget(el);
299
- if (sel) applyDomSelection(sel, { revealPanel: true });
300
- return sel;
301
- }}
302
293
  />
303
294
  <SnapToolbar onSnapChange={setSnapPrefs} />
304
295
  {gestureOverlay}
@@ -13,7 +13,6 @@ import { useDomEditOverlayRects } from "./useDomEditOverlayRects";
13
13
  import { createDomEditOverlayGestureHandlers } from "./useDomEditOverlayGestures";
14
14
  import { SnapGuideOverlay, type SnapGuidesState } from "./SnapGuideOverlay";
15
15
  import { GridOverlay } from "./GridOverlay";
16
- import { useOffScreenIndicators } from "./useOffScreenIndicators";
17
16
 
18
17
  // Re-exports for external consumers — preserving existing import paths.
19
18
  export {
@@ -55,7 +54,6 @@ interface DomEditOverlayProps {
55
54
  ) => void;
56
55
  onBlockedMove: (selection: DomEditSelection) => void;
57
56
  onManualDragStart?: () => void;
58
- onSelectElementById?: (id: string) => Promise<DomEditSelection | null>;
59
57
  onPathOffsetCommit: (
60
58
  selection: DomEditSelection,
61
59
  next: { x: number; y: number },
@@ -85,7 +83,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
85
83
  gridVisible = false,
86
84
  gridSpacing = 50,
87
85
  onManualDragStart,
88
- onSelectElementById,
89
86
  onPathOffsetCommit,
90
87
  onGroupPathOffsetCommit,
91
88
  onBoxSizeCommit,
@@ -215,8 +212,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
215
212
  return () => cancelAnimationFrame(frame);
216
213
  });
217
214
 
218
- const offScreenIndicators = useOffScreenIndicators({ iframeRef, overlayRef, compRect });
219
-
220
215
  const gestures = createDomEditOverlayGestureHandlers({
221
216
  overlayRef,
222
217
  iframeRef,
@@ -521,64 +516,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
521
516
  }}
522
517
  />
523
518
  ))}
524
- {offScreenIndicators.length > 0 &&
525
- compRect.width > 0 &&
526
- offScreenIndicators.map((ind) => {
527
- const isSelected = selection?.id === ind.elementId;
528
- return (
529
- <div
530
- key={`offscreen-${ind.key}`}
531
- className={`absolute rounded-sm ${isSelected ? "pointer-events-none" : "cursor-grab"}`}
532
- style={{
533
- left: ind.left,
534
- top: ind.top,
535
- width: ind.width,
536
- height: ind.height,
537
- border: `1.5px dashed var(--panel-accent, #34d399)`,
538
- opacity: isSelected ? 0.3 : 0.5,
539
- zIndex: isSelected ? 1 : 5,
540
- }}
541
- onPointerDown={
542
- isSelected
543
- ? undefined
544
- : (e) => {
545
- if (e.button !== 0) return;
546
- e.stopPropagation();
547
- e.preventDefault();
548
- const startX = e.clientX;
549
- const startY = e.clientY;
550
- const el = e.currentTarget;
551
- el.setPointerCapture(e.pointerId);
552
- let deltaX = 0;
553
- let deltaY = 0;
554
- let moved = false;
555
- const onMove = (me: PointerEvent) => {
556
- deltaX = me.clientX - startX;
557
- deltaY = me.clientY - startY;
558
- if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) moved = true;
559
- if (moved) {
560
- el.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
561
- }
562
- };
563
- const onUp = async (ue: PointerEvent) => {
564
- el.releasePointerCapture(ue.pointerId);
565
- el.removeEventListener("pointermove", onMove);
566
- el.removeEventListener("pointerup", onUp);
567
- el.style.transform = "";
568
- const sel = await onSelectElementById?.(ind.elementId);
569
- if (moved && sel && onPathOffsetCommit) {
570
- const scale = compRect.scaleX || 1;
571
- onPathOffsetCommit(sel, { x: deltaX / scale, y: deltaY / scale });
572
- }
573
- };
574
- el.addEventListener("pointermove", onMove);
575
- el.addEventListener("pointerup", onUp);
576
- }
577
- }
578
- title={isSelected ? undefined : `Drag #${ind.elementId}`}
579
- />
580
- );
581
- })}
582
519
  <GridOverlay
583
520
  visible={gridVisible}
584
521
  spacing={gridSpacing}
@@ -1,197 +0,0 @@
1
- /**
2
- * Detects GSAP-animated elements whose center is outside the visible composition
3
- * area and returns edge-clamped indicator positions for each.
4
- */
5
- import { useRef, useState, type RefObject } from "react";
6
- import { useMountEffect } from "../../hooks/useMountEffect";
7
-
8
- export interface OffScreenIndicator {
9
- key: string;
10
- elementId: string;
11
- left: number;
12
- top: number;
13
- width: number;
14
- height: number;
15
- }
16
-
17
- interface CompRect {
18
- left: number;
19
- top: number;
20
- width: number;
21
- height: number;
22
- scaleX: number;
23
- scaleY: number;
24
- }
25
-
26
- type TimelineLike = { getChildren?: (nested: boolean) => Array<{ targets?: () => Element[] }> };
27
-
28
- function isHtmlElement(node: unknown): node is HTMLElement {
29
- return (
30
- typeof node === "object" &&
31
- node !== null &&
32
- typeof (node as HTMLElement).getBoundingClientRect === "function" &&
33
- typeof (node as HTMLElement).tagName === "string"
34
- );
35
- }
36
-
37
- function collectGsapTargetElements(iframe: HTMLIFrameElement): HTMLElement[] {
38
- const win = iframe.contentWindow as
39
- | (Window & { __timelines?: Record<string, TimelineLike> })
40
- | null;
41
- if (!win) return [];
42
-
43
- let timelines: Record<string, TimelineLike> | undefined;
44
- try {
45
- timelines = win.__timelines;
46
- } catch {
47
- return [];
48
- }
49
- if (!timelines) return [];
50
-
51
- const seen = new Set<HTMLElement>();
52
- for (const tl of Object.values(timelines)) {
53
- if (!tl?.getChildren) continue;
54
- try {
55
- for (const child of tl.getChildren(true)) {
56
- if (!child.targets) continue;
57
- for (const t of child.targets()) {
58
- if (isHtmlElement(t)) seen.add(t);
59
- }
60
- }
61
- } catch {
62
- // cross-origin or detached timeline — skip
63
- }
64
- }
65
- return Array.from(seen);
66
- }
67
-
68
- function indicatorsEqual(a: OffScreenIndicator[], b: OffScreenIndicator[]): boolean {
69
- if (a.length !== b.length) return false;
70
- for (let i = 0; i < a.length; i++) {
71
- const ai = a[i]!;
72
- const bi = b[i]!;
73
- if (
74
- ai.key !== bi.key ||
75
- Math.abs(ai.left - bi.left) > 0.5 ||
76
- Math.abs(ai.top - bi.top) > 0.5 ||
77
- Math.abs(ai.width - bi.width) > 0.5 ||
78
- Math.abs(ai.height - bi.height) > 0.5
79
- )
80
- return false;
81
- }
82
- return true;
83
- }
84
-
85
- export function useOffScreenIndicators({
86
- iframeRef,
87
- overlayRef,
88
- compRect,
89
- }: {
90
- iframeRef: RefObject<HTMLIFrameElement | null>;
91
- overlayRef: RefObject<HTMLDivElement | null>;
92
- compRect: CompRect;
93
- }): OffScreenIndicator[] {
94
- const [indicators, setIndicators] = useState<OffScreenIndicator[]>([]);
95
- const prevRef = useRef<OffScreenIndicator[]>([]);
96
- const compRectRef = useRef(compRect);
97
- compRectRef.current = compRect;
98
-
99
- useMountEffect(() => {
100
- let frame = 0;
101
-
102
- const update = () => {
103
- frame = requestAnimationFrame(update);
104
-
105
- const iframe = iframeRef.current;
106
- const overlayEl = overlayRef.current;
107
- const cr = compRectRef.current;
108
- if (!iframe || !overlayEl || cr.width <= 0 || cr.height <= 0) {
109
- if (prevRef.current.length > 0) {
110
- prevRef.current = [];
111
- setIndicators([]);
112
- }
113
- return;
114
- }
115
-
116
- const iframeRect = iframe.getBoundingClientRect();
117
- const overlayRect = overlayEl.getBoundingClientRect();
118
-
119
- const doc = iframe.contentDocument;
120
- const root =
121
- doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
122
- if (!root) return;
123
-
124
- const declaredWidth =
125
- Number.parseFloat(root.getAttribute("data-width") ?? "") || iframeRect.width;
126
- const declaredHeight =
127
- Number.parseFloat(root.getAttribute("data-height") ?? "") || iframeRect.height;
128
- const rootScaleX = iframeRect.width / declaredWidth;
129
- const rootScaleY = iframeRect.height / declaredHeight;
130
-
131
- const targets = collectGsapTargetElements(iframe);
132
- if (targets.length === 0) {
133
- if (prevRef.current.length > 0) {
134
- prevRef.current = [];
135
- setIndicators([]);
136
- }
137
- return;
138
- }
139
-
140
- // Composition bounds in overlay coordinates
141
- const compLeft = cr.left;
142
- const compTop = cr.top;
143
- const compRight = compLeft + cr.width;
144
- const compBottom = compTop + cr.height;
145
-
146
- const next: OffScreenIndicator[] = [];
147
- const keyCounts = new Map<string, number>();
148
-
149
- for (const el of targets) {
150
- if (!el.isConnected) continue;
151
-
152
- const elRect = el.getBoundingClientRect();
153
- if (elRect.width <= 0 && elRect.height <= 0) continue;
154
-
155
- // Element rect in overlay coordinates
156
- const elLeft = iframeRect.left - overlayRect.left + elRect.left * rootScaleX;
157
- const elTop = iframeRect.top - overlayRect.top + elRect.top * rootScaleY;
158
- const elW = elRect.width * rootScaleX;
159
- const elH = elRect.height * rootScaleY;
160
-
161
- // Check if the element is fully inside the composition
162
- if (
163
- elLeft >= compLeft &&
164
- elTop >= compTop &&
165
- elLeft + elW <= compRight &&
166
- elTop + elH <= compBottom
167
- ) {
168
- continue;
169
- }
170
-
171
- // Only elements with a real id attribute can be selected via getElementById
172
- if (!el.id) continue;
173
- const count = keyCounts.get(el.id) ?? 0;
174
- keyCounts.set(el.id, count + 1);
175
- const key = count > 0 ? `${el.id}:${count}` : el.id;
176
- next.push({
177
- key,
178
- elementId: el.id,
179
- left: elLeft,
180
- top: elTop,
181
- width: elW,
182
- height: elH,
183
- });
184
- }
185
-
186
- if (!indicatorsEqual(prevRef.current, next)) {
187
- prevRef.current = next;
188
- setIndicators(next);
189
- }
190
- };
191
-
192
- frame = requestAnimationFrame(update);
193
- return () => cancelAnimationFrame(frame);
194
- });
195
-
196
- return indicators;
197
- }