@hyperframes/studio 0.6.93 → 0.6.95
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-BkwsVKGA.js → index-CAANLw9Q.js} +1 -1
- package/dist/assets/{index-DYRWmfMX.js → index-DujOjou6.js} +111 -111
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/components/StudioPreviewArea.tsx +0 -9
- package/src/components/editor/DomEditOverlay.tsx +0 -63
- package/src/components/editor/useOffScreenIndicators.ts +0 -197
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-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DujOjou6.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.
|
|
3
|
+
"version": "0.6.95",
|
|
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.
|
|
35
|
-
"@hyperframes/player": "0.6.
|
|
34
|
+
"@hyperframes/core": "0.6.95",
|
|
35
|
+
"@hyperframes/player": "0.6.95"
|
|
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.
|
|
49
|
+
"@hyperframes/producer": "0.6.95"
|
|
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
|
-
}
|