@hyperframes/studio 0.6.0-alpha.9 → 0.6.1
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/hyperframes-player-CzwFysqv.js +418 -0
- package/dist/assets/index-D1JDq7Gg.css +1 -0
- package/dist/assets/index-hYc4aP7M.js +117 -0
- package/dist/favicon.svg +14 -0
- package/dist/index.html +3 -2
- package/package.json +9 -9
- package/src/App.tsx +421 -4303
- package/src/captions/components/CaptionOverlay.tsx +13 -246
- package/src/captions/components/CaptionOverlayUtils.ts +221 -0
- package/src/components/AskAgentModal.tsx +120 -0
- package/src/components/StudioHeader.tsx +133 -0
- package/src/components/StudioLeftSidebar.tsx +125 -0
- package/src/components/StudioPreviewArea.tsx +167 -0
- package/src/components/StudioRightPanel.tsx +198 -0
- package/src/components/TimelineToolbar.tsx +89 -0
- package/src/components/editor/DomEditOverlay.tsx +88 -993
- package/src/components/editor/EaseCurveEditor.tsx +221 -0
- package/src/components/editor/FileTree.tsx +13 -621
- package/src/components/editor/FileTreeIcons.tsx +128 -0
- package/src/components/editor/FileTreeNodes.tsx +496 -0
- package/src/components/editor/MotionPanel.tsx +16 -390
- package/src/components/editor/MotionPanelFields.tsx +185 -0
- package/src/components/editor/PropertyPanel.test.ts +0 -49
- package/src/components/editor/PropertyPanel.tsx +132 -2763
- package/src/components/editor/domEditOverlayGeometry.ts +211 -0
- package/src/components/editor/domEditOverlayGestures.ts +138 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
- package/src/components/editor/domEditing.ts +44 -1117
- package/src/components/editor/domEditingAgentPrompt.ts +97 -0
- package/src/components/editor/domEditingDom.ts +266 -0
- package/src/components/editor/domEditingElement.ts +329 -0
- package/src/components/editor/domEditingLayers.ts +460 -0
- package/src/components/editor/domEditingTypes.ts +125 -0
- package/src/components/editor/manualEditingAvailability.test.ts +2 -2
- package/src/components/editor/manualEditingAvailability.ts +1 -1
- package/src/components/editor/manualEdits.ts +84 -1049
- package/src/components/editor/manualEditsDom.ts +436 -0
- package/src/components/editor/manualEditsParsing.ts +280 -0
- package/src/components/editor/manualEditsSnapshot.ts +333 -0
- package/src/components/editor/manualEditsTypes.ts +141 -0
- package/src/components/editor/propertyPanelColor.tsx +371 -0
- package/src/components/editor/propertyPanelFill.tsx +421 -0
- package/src/components/editor/propertyPanelFont.tsx +455 -0
- package/src/components/editor/propertyPanelHelpers.ts +401 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
- package/src/components/editor/propertyPanelSections.tsx +453 -0
- package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
- package/src/components/editor/studioMotion.ts +47 -434
- package/src/components/editor/studioMotionOps.ts +299 -0
- package/src/components/editor/studioMotionTypes.ts +168 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
- package/src/components/editor/useDomEditOverlayRects.ts +207 -0
- package/src/components/nle/NLELayout.tsx +68 -155
- package/src/components/nle/NLEPreview.tsx +3 -0
- package/src/components/nle/useCompositionStack.ts +126 -0
- package/src/components/renders/RenderQueue.tsx +102 -31
- package/src/components/renders/useRenderQueue.ts +8 -2
- package/src/components/sidebar/LeftSidebar.tsx +186 -186
- package/src/contexts/DomEditContext.tsx +137 -0
- package/src/contexts/FileManagerContext.tsx +110 -0
- package/src/contexts/PanelLayoutContext.tsx +68 -0
- package/src/contexts/StudioContext.tsx +135 -0
- package/src/hooks/useAppHotkeys.ts +326 -0
- package/src/hooks/useAskAgentModal.ts +162 -0
- package/src/hooks/useCaptionDetection.ts +132 -0
- package/src/hooks/useCompositionDimensions.ts +25 -0
- package/src/hooks/useConsoleErrorCapture.ts +60 -0
- package/src/hooks/useDomEditCommits.ts +437 -0
- package/src/hooks/useDomEditSession.ts +342 -0
- package/src/hooks/useDomEditTextCommits.ts +330 -0
- package/src/hooks/useDomSelection.ts +398 -0
- package/src/hooks/useFileManager.ts +431 -0
- package/src/hooks/useFrameCapture.ts +77 -0
- package/src/hooks/useLintModal.ts +35 -0
- package/src/hooks/useManifestPersistence.ts +492 -0
- package/src/hooks/usePanelLayout.ts +68 -0
- package/src/hooks/usePreviewInteraction.ts +153 -0
- package/src/hooks/useRenderClipContent.ts +124 -0
- package/src/hooks/useTimelineEditing.ts +472 -0
- package/src/hooks/useToast.ts +20 -0
- package/src/player/components/Player.tsx +33 -2
- package/src/player/components/Timeline.test.ts +0 -8
- package/src/player/components/Timeline.tsx +196 -1518
- package/src/player/components/TimelineCanvas.tsx +434 -0
- package/src/player/components/TimelineClip.tsx +9 -244
- package/src/player/components/TimelineEmptyState.tsx +102 -0
- package/src/player/components/TimelineRuler.tsx +90 -0
- package/src/player/components/timelineIcons.tsx +49 -0
- package/src/player/components/timelineLayout.ts +215 -0
- package/src/player/components/timelineUtils.ts +211 -0
- package/src/player/components/useTimelineClipDrag.ts +388 -0
- package/src/player/components/useTimelinePlayhead.ts +200 -0
- package/src/player/components/useTimelineRangeSelection.ts +135 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
- package/src/player/hooks/useTimelinePlayer.ts +105 -1371
- package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
- package/src/player/lib/playbackAdapter.ts +145 -0
- package/src/player/lib/playbackShortcuts.ts +68 -0
- package/src/player/lib/playbackTypes.ts +60 -0
- package/src/player/lib/timelineDOM.ts +373 -0
- package/src/player/lib/timelineElementHelpers.ts +303 -0
- package/src/player/lib/timelineIframeHelpers.ts +269 -0
- package/src/utils/domEditHelpers.ts +50 -0
- package/src/utils/studioFontHelpers.ts +83 -0
- package/src/utils/studioHelpers.ts +214 -0
- package/src/utils/studioPreviewHelpers.ts +185 -0
- package/src/utils/timelineDiscovery.ts +1 -1
- package/dist/assets/hyperframes-player-DjsVzYFP.js +0 -418
- package/dist/assets/index-14zH9lqh.css +0 -1
- package/dist/assets/index-DYCiFGWQ.js +0 -108
- package/src/player/components/TimelineClip.test.ts +0 -92
|
@@ -1,50 +1,28 @@
|
|
|
1
|
-
import { memo, useMemo, useRef,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { memo, useMemo, useRef, type RefObject } from "react";
|
|
2
|
+
import { type DomEditSelection } from "./domEditing";
|
|
3
|
+
import { resolveDomEditGroupOverlayRect, toOverlayRect } from "./domEditOverlayGeometry";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
restoreStudioPathOffset,
|
|
27
|
-
restoreStudioRotation,
|
|
28
|
-
type StudioBoxSizeSnapshot,
|
|
29
|
-
type StudioPathOffsetSnapshot,
|
|
30
|
-
type StudioRotationSnapshot,
|
|
31
|
-
} from "./manualEdits";
|
|
32
|
-
|
|
33
|
-
interface OverlayRect {
|
|
34
|
-
left: number;
|
|
35
|
-
top: number;
|
|
36
|
-
width: number;
|
|
37
|
-
height: number;
|
|
38
|
-
editScaleX: number;
|
|
39
|
-
editScaleY: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface GroupOverlayItem {
|
|
43
|
-
key: string;
|
|
44
|
-
selection: DomEditSelection;
|
|
45
|
-
element: HTMLElement;
|
|
46
|
-
rect: OverlayRect;
|
|
47
|
-
}
|
|
5
|
+
type BlockedMoveState,
|
|
6
|
+
type FocusableDomEditOverlay,
|
|
7
|
+
type GestureState,
|
|
8
|
+
type GroupGestureState,
|
|
9
|
+
focusDomEditOverlayElement,
|
|
10
|
+
} from "./domEditOverlayGestures";
|
|
11
|
+
import { useDomEditOverlayRects } from "./useDomEditOverlayRects";
|
|
12
|
+
import { createDomEditOverlayGestureHandlers } from "./useDomEditOverlayGestures";
|
|
13
|
+
|
|
14
|
+
// Re-exports for external consumers — preserving existing import paths.
|
|
15
|
+
export {
|
|
16
|
+
filterNestedDomEditGroupItems,
|
|
17
|
+
resolveDomEditCoordinateScale,
|
|
18
|
+
resolveDomEditGroupOverlayRect,
|
|
19
|
+
} from "./domEditOverlayGeometry";
|
|
20
|
+
export {
|
|
21
|
+
focusDomEditOverlayElement,
|
|
22
|
+
hasDomEditRotationChanged,
|
|
23
|
+
resolveDomEditResizeGesture,
|
|
24
|
+
resolveDomEditRotationGesture,
|
|
25
|
+
} from "./domEditOverlayGestures";
|
|
48
26
|
|
|
49
27
|
export interface DomEditGroupPathOffsetCommit {
|
|
50
28
|
selection: DomEditSelection;
|
|
@@ -85,297 +63,6 @@ interface DomEditOverlayProps {
|
|
|
85
63
|
onRotationCommit: (selection: DomEditSelection, next: { angle: number }) => Promise<void> | void;
|
|
86
64
|
}
|
|
87
65
|
|
|
88
|
-
function toOverlayRect(
|
|
89
|
-
overlayEl: HTMLDivElement,
|
|
90
|
-
iframe: HTMLIFrameElement,
|
|
91
|
-
element: HTMLElement,
|
|
92
|
-
): OverlayRect | null {
|
|
93
|
-
const iframeRect = iframe.getBoundingClientRect();
|
|
94
|
-
const overlayRect = overlayEl.getBoundingClientRect();
|
|
95
|
-
const doc = iframe.contentDocument;
|
|
96
|
-
const root =
|
|
97
|
-
doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
|
|
98
|
-
const rootRect = root?.getBoundingClientRect();
|
|
99
|
-
const rootWidth = rootRect?.width;
|
|
100
|
-
const rootHeight = rootRect?.height;
|
|
101
|
-
if (!rootWidth || !rootHeight) return null;
|
|
102
|
-
|
|
103
|
-
const elementRect = element.getBoundingClientRect();
|
|
104
|
-
const rootScaleX = iframeRect.width / rootWidth;
|
|
105
|
-
const rootScaleY = iframeRect.height / rootHeight;
|
|
106
|
-
const sourceBoundary = findSourceBoundary(element);
|
|
107
|
-
const sourceBoundaryRect = sourceBoundary?.getBoundingClientRect();
|
|
108
|
-
const editScale = resolveDomEditCoordinateScale({
|
|
109
|
-
rootScaleX,
|
|
110
|
-
rootScaleY,
|
|
111
|
-
sourceRectWidth: sourceBoundaryRect?.width,
|
|
112
|
-
sourceRectHeight: sourceBoundaryRect?.height,
|
|
113
|
-
sourceWidth: readPositiveDimension(sourceBoundary?.getAttribute("data-width") ?? null),
|
|
114
|
-
sourceHeight: readPositiveDimension(sourceBoundary?.getAttribute("data-height") ?? null),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
left: iframeRect.left - overlayRect.left + (elementRect.left - rootRect.left) * rootScaleX,
|
|
119
|
-
top: iframeRect.top - overlayRect.top + (elementRect.top - rootRect.top) * rootScaleY,
|
|
120
|
-
width: elementRect.width * rootScaleX,
|
|
121
|
-
height: elementRect.height * rootScaleY,
|
|
122
|
-
editScaleX: editScale.scaleX,
|
|
123
|
-
editScaleY: editScale.scaleY,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function readPositiveDimension(value: string | null): number | null {
|
|
128
|
-
if (!value) return null;
|
|
129
|
-
const parsed = Number.parseFloat(value);
|
|
130
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function findSourceBoundary(element: HTMLElement): HTMLElement | null {
|
|
134
|
-
let current: HTMLElement | null = element;
|
|
135
|
-
while (current) {
|
|
136
|
-
if (
|
|
137
|
-
current.hasAttribute("data-composition-file") ||
|
|
138
|
-
current.hasAttribute("data-composition-src")
|
|
139
|
-
) {
|
|
140
|
-
return current;
|
|
141
|
-
}
|
|
142
|
-
current = current.parentElement;
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function resolveDomEditCoordinateScale(input: {
|
|
148
|
-
rootScaleX: number;
|
|
149
|
-
rootScaleY: number;
|
|
150
|
-
sourceRectWidth?: number;
|
|
151
|
-
sourceRectHeight?: number;
|
|
152
|
-
sourceWidth?: number | null;
|
|
153
|
-
sourceHeight?: number | null;
|
|
154
|
-
}): { scaleX: number; scaleY: number } {
|
|
155
|
-
const rootScaleX = input.rootScaleX > 0 ? input.rootScaleX : 1;
|
|
156
|
-
const rootScaleY = input.rootScaleY > 0 ? input.rootScaleY : 1;
|
|
157
|
-
const sourceScaleX =
|
|
158
|
-
input.sourceRectWidth && input.sourceRectWidth > 0 && input.sourceWidth && input.sourceWidth > 0
|
|
159
|
-
? (input.sourceRectWidth * rootScaleX) / input.sourceWidth
|
|
160
|
-
: rootScaleX;
|
|
161
|
-
const sourceScaleY =
|
|
162
|
-
input.sourceRectHeight &&
|
|
163
|
-
input.sourceRectHeight > 0 &&
|
|
164
|
-
input.sourceHeight &&
|
|
165
|
-
input.sourceHeight > 0
|
|
166
|
-
? (input.sourceRectHeight * rootScaleY) / input.sourceHeight
|
|
167
|
-
: rootScaleY;
|
|
168
|
-
return {
|
|
169
|
-
scaleX: sourceScaleX > 0 ? sourceScaleX : rootScaleX,
|
|
170
|
-
scaleY: sourceScaleY > 0 ? sourceScaleY : rootScaleY,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
type GestureKind = "drag" | "resize" | "rotate";
|
|
175
|
-
const BLOCKED_MOVE_THRESHOLD_PX = 4;
|
|
176
|
-
const MIN_RESIZE_EDGE_PX = 20;
|
|
177
|
-
const OVERLAY_RECT_EPSILON_PX = 0.5;
|
|
178
|
-
const ROTATION_COMMIT_EPSILON_DEGREES = 0.05;
|
|
179
|
-
const ROTATION_SNAP_DEGREES = 15;
|
|
180
|
-
|
|
181
|
-
function rectsEqual(a: OverlayRect | null, b: OverlayRect | null): boolean {
|
|
182
|
-
if (a === b) return true;
|
|
183
|
-
if (!a || !b) return false;
|
|
184
|
-
return (
|
|
185
|
-
Math.abs(a.left - b.left) < OVERLAY_RECT_EPSILON_PX &&
|
|
186
|
-
Math.abs(a.top - b.top) < OVERLAY_RECT_EPSILON_PX &&
|
|
187
|
-
Math.abs(a.width - b.width) < OVERLAY_RECT_EPSILON_PX &&
|
|
188
|
-
Math.abs(a.height - b.height) < OVERLAY_RECT_EPSILON_PX &&
|
|
189
|
-
Math.abs(a.editScaleX - b.editScaleX) < 0.001 &&
|
|
190
|
-
Math.abs(a.editScaleY - b.editScaleY) < 0.001
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function groupOverlayItemsEqual(a: GroupOverlayItem[], b: GroupOverlayItem[]): boolean {
|
|
195
|
-
if (a === b) return true;
|
|
196
|
-
if (a.length !== b.length) return false;
|
|
197
|
-
return a.every((item, index) => {
|
|
198
|
-
const other = b[index];
|
|
199
|
-
return Boolean(
|
|
200
|
-
other &&
|
|
201
|
-
item.key === other.key &&
|
|
202
|
-
item.element === other.element &&
|
|
203
|
-
item.selection === other.selection &&
|
|
204
|
-
rectsEqual(item.rect, other.rect),
|
|
205
|
-
);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function resolveDomEditGroupOverlayRect(rects: OverlayRect[]): OverlayRect | null {
|
|
210
|
-
const first = rects[0];
|
|
211
|
-
if (!first) return null;
|
|
212
|
-
|
|
213
|
-
let left = first.left;
|
|
214
|
-
let top = first.top;
|
|
215
|
-
let right = first.left + first.width;
|
|
216
|
-
let bottom = first.top + first.height;
|
|
217
|
-
|
|
218
|
-
for (const rect of rects.slice(1)) {
|
|
219
|
-
left = Math.min(left, rect.left);
|
|
220
|
-
top = Math.min(top, rect.top);
|
|
221
|
-
right = Math.max(right, rect.left + rect.width);
|
|
222
|
-
bottom = Math.max(bottom, rect.top + rect.height);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
left,
|
|
227
|
-
top,
|
|
228
|
-
width: right - left,
|
|
229
|
-
height: bottom - top,
|
|
230
|
-
editScaleX: 1,
|
|
231
|
-
editScaleY: 1,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function filterNestedDomEditGroupItems<T extends { element: HTMLElement }>(items: T[]): T[] {
|
|
236
|
-
return items.filter(
|
|
237
|
-
(item) => !items.some((other) => other !== item && other.element.contains(item.element)),
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function selectionCacheKey(
|
|
242
|
-
selection: Pick<DomEditSelection, "id" | "selector" | "selectorIndex" | "sourceFile">,
|
|
243
|
-
): string {
|
|
244
|
-
return [
|
|
245
|
-
selection.sourceFile ?? "",
|
|
246
|
-
selection.id ?? "",
|
|
247
|
-
selection.selector ?? "",
|
|
248
|
-
selection.selectorIndex ?? "",
|
|
249
|
-
].join("|");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
type FocusableDomEditOverlay = {
|
|
253
|
-
focus(options?: FocusOptions): void;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
export function focusDomEditOverlayElement(element: FocusableDomEditOverlay | null): void {
|
|
257
|
-
element?.focus({ preventScroll: true });
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function resolveDomEditResizeGesture(input: {
|
|
261
|
-
originWidth: number;
|
|
262
|
-
originHeight: number;
|
|
263
|
-
actualWidth: number;
|
|
264
|
-
actualHeight: number;
|
|
265
|
-
scaleX: number;
|
|
266
|
-
scaleY: number;
|
|
267
|
-
dx: number;
|
|
268
|
-
dy: number;
|
|
269
|
-
uniform: boolean;
|
|
270
|
-
}): { overlayWidth: number; overlayHeight: number; width: number; height: number } {
|
|
271
|
-
const scaleX = input.scaleX > 0 ? input.scaleX : 1;
|
|
272
|
-
const scaleY = input.scaleY > 0 ? input.scaleY : 1;
|
|
273
|
-
|
|
274
|
-
if (input.uniform) {
|
|
275
|
-
const deltaX = input.dx / scaleX;
|
|
276
|
-
const deltaY = input.dy / scaleY;
|
|
277
|
-
const delta = Math.abs(deltaX) >= Math.abs(deltaY) ? deltaX : deltaY;
|
|
278
|
-
const side = Math.max(1, Math.max(input.actualWidth, input.actualHeight) + delta);
|
|
279
|
-
return {
|
|
280
|
-
overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, side * scaleX),
|
|
281
|
-
overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, side * scaleY),
|
|
282
|
-
width: side,
|
|
283
|
-
height: side,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
overlayWidth: Math.max(MIN_RESIZE_EDGE_PX, input.originWidth + input.dx),
|
|
289
|
-
overlayHeight: Math.max(MIN_RESIZE_EDGE_PX, input.originHeight + input.dy),
|
|
290
|
-
width: Math.max(1, input.actualWidth + input.dx / scaleX),
|
|
291
|
-
height: Math.max(1, input.actualHeight + input.dy / scaleY),
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function pointerAngleDegrees(centerX: number, centerY: number, x: number, y: number): number {
|
|
296
|
-
return (Math.atan2(y - centerY, x - centerX) * 180) / Math.PI;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function normalizeAngleDelta(delta: number): number {
|
|
300
|
-
return ((((delta + 180) % 360) + 360) % 360) - 180;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function roundAngle(angle: number): number {
|
|
304
|
-
return Math.round(angle * 10) / 10;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function resolveDomEditRotationGesture(input: {
|
|
308
|
-
centerX: number;
|
|
309
|
-
centerY: number;
|
|
310
|
-
startX: number;
|
|
311
|
-
startY: number;
|
|
312
|
-
currentX: number;
|
|
313
|
-
currentY: number;
|
|
314
|
-
actualAngle: number;
|
|
315
|
-
snap: boolean;
|
|
316
|
-
}): { angle: number } {
|
|
317
|
-
const startAngle = pointerAngleDegrees(input.centerX, input.centerY, input.startX, input.startY);
|
|
318
|
-
const currentAngle = pointerAngleDegrees(
|
|
319
|
-
input.centerX,
|
|
320
|
-
input.centerY,
|
|
321
|
-
input.currentX,
|
|
322
|
-
input.currentY,
|
|
323
|
-
);
|
|
324
|
-
const delta = normalizeAngleDelta(currentAngle - startAngle);
|
|
325
|
-
const angle = input.actualAngle + delta;
|
|
326
|
-
return {
|
|
327
|
-
angle: input.snap
|
|
328
|
-
? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES
|
|
329
|
-
: roundAngle(angle),
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export function hasDomEditRotationChanged(initialAngle: number, nextAngle: number): boolean {
|
|
334
|
-
return Math.abs(nextAngle - initialAngle) >= ROTATION_COMMIT_EPSILON_DEGREES;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
interface GestureState {
|
|
338
|
-
kind: GestureKind;
|
|
339
|
-
mode: "path-offset" | "box-size" | "rotation";
|
|
340
|
-
selection: DomEditSelection;
|
|
341
|
-
startX: number;
|
|
342
|
-
startY: number;
|
|
343
|
-
centerX: number;
|
|
344
|
-
centerY: number;
|
|
345
|
-
initialPathOffset: StudioPathOffsetSnapshot;
|
|
346
|
-
initialRotation: StudioRotationSnapshot;
|
|
347
|
-
initialBoxSize: StudioBoxSizeSnapshot;
|
|
348
|
-
pathOffsetMember?: ManualOffsetDragMember;
|
|
349
|
-
originLeft: number;
|
|
350
|
-
originTop: number;
|
|
351
|
-
originWidth: number;
|
|
352
|
-
originHeight: number;
|
|
353
|
-
actualWidth: number;
|
|
354
|
-
actualHeight: number;
|
|
355
|
-
actualRotation: number;
|
|
356
|
-
editScaleX: number;
|
|
357
|
-
editScaleY: number;
|
|
358
|
-
manualEditDragToken?: string;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
interface GroupGestureState {
|
|
362
|
-
startX: number;
|
|
363
|
-
startY: number;
|
|
364
|
-
originItems: GroupOverlayItem[];
|
|
365
|
-
members: ManualOffsetDragMember[];
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
interface BlockedMoveState {
|
|
369
|
-
pointerId: number;
|
|
370
|
-
startX: number;
|
|
371
|
-
startY: number;
|
|
372
|
-
notified: boolean;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
type ResolvedElementRef = {
|
|
376
|
-
current: { key: string; element: HTMLElement } | null;
|
|
377
|
-
};
|
|
378
|
-
|
|
379
66
|
export const DomEditOverlay = memo(function DomEditOverlay({
|
|
380
67
|
iframeRef,
|
|
381
68
|
activeCompositionPath,
|
|
@@ -396,9 +83,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
396
83
|
}: DomEditOverlayProps) {
|
|
397
84
|
const overlayRef = useRef<HTMLDivElement | null>(null);
|
|
398
85
|
const boxRef = useRef<HTMLDivElement | null>(null);
|
|
399
|
-
const [overlayRect, setOverlayRect] = useState<OverlayRect | null>(null);
|
|
400
|
-
const [hoverRect, setHoverRect] = useState<OverlayRect | null>(null);
|
|
401
|
-
const [groupOverlayItems, setGroupOverlayItems] = useState<GroupOverlayItem[]>([]);
|
|
402
86
|
const gestureRef = useRef<GestureState | null>(null);
|
|
403
87
|
const groupGestureRef = useRef<GroupGestureState | null>(null);
|
|
404
88
|
const blockedMoveRef = useRef<BlockedMoveState | null>(null);
|
|
@@ -406,9 +90,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
406
90
|
const suppressNextBoxMouseDownRef = useRef(false);
|
|
407
91
|
const suppressNextOverlayMouseDownRef = useRef(false);
|
|
408
92
|
const rafPausedRef = useRef(false);
|
|
409
|
-
const resolvedElementRef = useRef<{ key: string; element: HTMLElement } | null>(null);
|
|
410
|
-
const resolvedHoverElementRef = useRef<{ key: string; element: HTMLElement } | null>(null);
|
|
411
|
-
const resolvedGroupElementRef = useRef<Map<string, HTMLElement>>(new Map());
|
|
412
93
|
|
|
413
94
|
const selectionRef = useRef(selection);
|
|
414
95
|
selectionRef.current = selection;
|
|
@@ -418,12 +99,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
418
99
|
groupSelectionsRef.current = groupSelections;
|
|
419
100
|
const hoverSelectionRef = useRef(hoverSelection);
|
|
420
101
|
hoverSelectionRef.current = hoverSelection;
|
|
421
|
-
const overlayRectRef = useRef(overlayRect);
|
|
422
|
-
overlayRectRef.current = overlayRect;
|
|
423
|
-
const hoverRectRef = useRef(hoverRect);
|
|
424
|
-
hoverRectRef.current = hoverRect;
|
|
425
|
-
const groupOverlayItemsRef = useRef(groupOverlayItems);
|
|
426
|
-
groupOverlayItemsRef.current = groupOverlayItems;
|
|
427
102
|
const onPathOffsetCommitRef = useRef(onPathOffsetCommit);
|
|
428
103
|
onPathOffsetCommitRef.current = onPathOffsetCommit;
|
|
429
104
|
const onGroupPathOffsetCommitRef = useRef(onGroupPathOffsetCommit);
|
|
@@ -443,159 +118,50 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
443
118
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
|
444
119
|
onSelectionChangeRef.current = onSelectionChange;
|
|
445
120
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const setNextHoverRect = (next: OverlayRect | null) => {
|
|
464
|
-
if (rectsEqual(hoverRectRef.current, next)) return;
|
|
465
|
-
hoverRectRef.current = next;
|
|
466
|
-
setHoverRect(next);
|
|
467
|
-
};
|
|
468
|
-
const clearGroupOverlayItems = () => {
|
|
469
|
-
if (groupOverlayItemsRef.current.length === 0) return;
|
|
470
|
-
groupOverlayItemsRef.current = [];
|
|
471
|
-
setGroupOverlayItems([]);
|
|
472
|
-
};
|
|
473
|
-
const setNextGroupOverlayItems = (next: GroupOverlayItem[]) => {
|
|
474
|
-
if (groupOverlayItemsEqual(groupOverlayItemsRef.current, next)) return;
|
|
475
|
-
groupOverlayItemsRef.current = next;
|
|
476
|
-
setGroupOverlayItems(next);
|
|
477
|
-
};
|
|
478
|
-
const resolveElement = (doc: Document, sel: DomEditSelection, cacheRef: ResolvedElementRef) => {
|
|
479
|
-
const key = selectionCacheKey(sel);
|
|
480
|
-
const cached = cacheRef.current;
|
|
481
|
-
if (
|
|
482
|
-
cached?.key === key &&
|
|
483
|
-
cached.element.isConnected &&
|
|
484
|
-
cached.element.ownerDocument === doc
|
|
485
|
-
) {
|
|
486
|
-
return cached.element;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const next = findElementForSelection(doc, sel, activeCompositionPathRef.current);
|
|
490
|
-
cacheRef.current = next ? { key, element: next } : null;
|
|
491
|
-
return next;
|
|
492
|
-
};
|
|
493
|
-
const resolveGroupElement = (doc: Document, sel: DomEditSelection) => {
|
|
494
|
-
const key = selectionCacheKey(sel);
|
|
495
|
-
const cached = resolvedGroupElementRef.current.get(key);
|
|
496
|
-
if (cached?.isConnected && cached.ownerDocument === doc) return cached;
|
|
497
|
-
|
|
498
|
-
const next = findElementForSelection(doc, sel, activeCompositionPathRef.current);
|
|
499
|
-
if (next) {
|
|
500
|
-
resolvedGroupElementRef.current.set(key, next);
|
|
501
|
-
} else {
|
|
502
|
-
resolvedGroupElementRef.current.delete(key);
|
|
503
|
-
}
|
|
504
|
-
return next;
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
const update = () => {
|
|
508
|
-
frame = requestAnimationFrame(update);
|
|
509
|
-
if (rafPausedRef.current) return;
|
|
510
|
-
|
|
511
|
-
const sel = selectionRef.current;
|
|
512
|
-
const iframe = iframeRef.current;
|
|
513
|
-
const overlayEl = overlayRef.current;
|
|
514
|
-
if (!iframe || !overlayEl) {
|
|
515
|
-
resolvedElementRef.current = null;
|
|
516
|
-
resolvedHoverElementRef.current = null;
|
|
517
|
-
resolvedGroupElementRef.current.clear();
|
|
518
|
-
clearOverlayRect();
|
|
519
|
-
clearHoverRect();
|
|
520
|
-
clearGroupOverlayItems();
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const doc = iframe.contentDocument;
|
|
525
|
-
if (!doc) {
|
|
526
|
-
resolvedElementRef.current = null;
|
|
527
|
-
resolvedHoverElementRef.current = null;
|
|
528
|
-
resolvedGroupElementRef.current.clear();
|
|
529
|
-
clearOverlayRect();
|
|
530
|
-
clearHoverRect();
|
|
531
|
-
clearGroupOverlayItems();
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (sel) {
|
|
536
|
-
const el = resolveElement(doc, sel, resolvedElementRef);
|
|
537
|
-
if (el) {
|
|
538
|
-
setNextOverlayRect(toOverlayRect(overlayEl, iframe, el));
|
|
539
|
-
} else {
|
|
540
|
-
clearOverlayRect();
|
|
541
|
-
}
|
|
542
|
-
} else {
|
|
543
|
-
resolvedElementRef.current = null;
|
|
544
|
-
clearOverlayRect();
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const group = groupSelectionsRef.current;
|
|
548
|
-
if (group.length > 0) {
|
|
549
|
-
const nextGroupItems: GroupOverlayItem[] = [];
|
|
550
|
-
const liveGroupKeys = new Set<string>();
|
|
551
|
-
for (const groupSelection of group) {
|
|
552
|
-
const key = selectionCacheKey(groupSelection);
|
|
553
|
-
liveGroupKeys.add(key);
|
|
554
|
-
const el = resolveGroupElement(doc, groupSelection);
|
|
555
|
-
const rect = el ? toOverlayRect(overlayEl, iframe, el) : null;
|
|
556
|
-
if (el && rect)
|
|
557
|
-
nextGroupItems.push({ key, selection: groupSelection, element: el, rect });
|
|
558
|
-
}
|
|
559
|
-
for (const key of resolvedGroupElementRef.current.keys()) {
|
|
560
|
-
if (!liveGroupKeys.has(key)) resolvedGroupElementRef.current.delete(key);
|
|
561
|
-
}
|
|
562
|
-
setNextGroupOverlayItems(nextGroupItems);
|
|
563
|
-
} else {
|
|
564
|
-
resolvedGroupElementRef.current.clear();
|
|
565
|
-
clearGroupOverlayItems();
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const hoverSel = hoverSelectionRef.current;
|
|
569
|
-
const hoverMatchesSelection = Boolean(
|
|
570
|
-
sel && hoverSel && selectionCacheKey(sel) === selectionCacheKey(hoverSel),
|
|
571
|
-
);
|
|
572
|
-
const hoverMatchesGroup = Boolean(
|
|
573
|
-
hoverSel && group.some((entry) => selectionCacheKey(entry) === selectionCacheKey(hoverSel)),
|
|
574
|
-
);
|
|
575
|
-
if (!hoverSel || hoverMatchesSelection || hoverMatchesGroup) {
|
|
576
|
-
resolvedHoverElementRef.current = null;
|
|
577
|
-
clearHoverRect();
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const hoverEl = resolveElement(doc, hoverSel, resolvedHoverElementRef);
|
|
582
|
-
if (!hoverEl) {
|
|
583
|
-
clearHoverRect();
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
setNextHoverRect(toOverlayRect(overlayEl, iframe, hoverEl));
|
|
588
|
-
};
|
|
121
|
+
const {
|
|
122
|
+
overlayRect,
|
|
123
|
+
overlayRectRef,
|
|
124
|
+
setOverlayRect,
|
|
125
|
+
hoverRect,
|
|
126
|
+
groupOverlayItems,
|
|
127
|
+
groupOverlayItemsRef,
|
|
128
|
+
setGroupOverlayItems,
|
|
129
|
+
} = useDomEditOverlayRects({
|
|
130
|
+
iframeRef,
|
|
131
|
+
overlayRef,
|
|
132
|
+
selectionRef,
|
|
133
|
+
activeCompositionPathRef,
|
|
134
|
+
groupSelectionsRef,
|
|
135
|
+
hoverSelectionRef,
|
|
136
|
+
rafPausedRef,
|
|
137
|
+
});
|
|
589
138
|
|
|
590
|
-
|
|
591
|
-
|
|
139
|
+
const gestures = createDomEditOverlayGestureHandlers({
|
|
140
|
+
overlayRef,
|
|
141
|
+
boxRef,
|
|
142
|
+
selectionRef,
|
|
143
|
+
overlayRectRef,
|
|
144
|
+
groupOverlayItemsRef,
|
|
145
|
+
gestureRef,
|
|
146
|
+
groupGestureRef,
|
|
147
|
+
blockedMoveRef,
|
|
148
|
+
rafPausedRef,
|
|
149
|
+
suppressNextBoxClickRef,
|
|
150
|
+
setOverlayRect,
|
|
151
|
+
setGroupOverlayItems,
|
|
152
|
+
onBlockedMoveRef,
|
|
153
|
+
onManualDragStartRef,
|
|
154
|
+
onPathOffsetCommitRef,
|
|
155
|
+
onGroupPathOffsetCommitRef,
|
|
156
|
+
onBoxSizeCommitRef,
|
|
157
|
+
onRotationCommitRef,
|
|
158
|
+
onCanvasPointerMoveRef,
|
|
159
|
+
onCanvasMouseDown,
|
|
592
160
|
});
|
|
593
161
|
|
|
594
162
|
const selectionKey = useMemo(() => {
|
|
595
163
|
if (!selection) return "none";
|
|
596
|
-
return `${selection.sourceFile}:${selection.id ?? selection.selector ?? selection.label}:${
|
|
597
|
-
selection.selectorIndex ?? 0
|
|
598
|
-
}`;
|
|
164
|
+
return `${selection.sourceFile}:${selection.id ?? selection.selector ?? selection.label}:${selection.selectorIndex ?? 0}`;
|
|
599
165
|
}, [selection]);
|
|
600
166
|
const groupBounds = useMemo(
|
|
601
167
|
() => resolveDomEditGroupOverlayRect(groupOverlayItems.map((item) => item.rect)),
|
|
@@ -607,440 +173,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
607
173
|
groupOverlayItems.length > 1 &&
|
|
608
174
|
groupOverlayItems.every((item) => item.selection.capabilities.canApplyManualOffset);
|
|
609
175
|
|
|
610
|
-
const setDraftOverlayRect = (next: OverlayRect) => {
|
|
611
|
-
if (rectsEqual(overlayRectRef.current, next)) return;
|
|
612
|
-
overlayRectRef.current = next;
|
|
613
|
-
setOverlayRect(next);
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const restoreGestureOverlayRect = (g: GestureState) => {
|
|
617
|
-
setDraftOverlayRect({
|
|
618
|
-
left: g.originLeft,
|
|
619
|
-
top: g.originTop,
|
|
620
|
-
width: g.originWidth,
|
|
621
|
-
height: g.originHeight,
|
|
622
|
-
editScaleX: g.editScaleX,
|
|
623
|
-
editScaleY: g.editScaleY,
|
|
624
|
-
});
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
const setDraftGroupOverlayItems = (next: GroupOverlayItem[]) => {
|
|
628
|
-
if (groupOverlayItemsEqual(groupOverlayItemsRef.current, next)) return;
|
|
629
|
-
groupOverlayItemsRef.current = next;
|
|
630
|
-
setGroupOverlayItems(next);
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
const restoreGroupGestureOverlayItems = (g: GroupGestureState) => {
|
|
634
|
-
setDraftGroupOverlayItems(g.originItems);
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
const startGroupDrag = (e: React.PointerEvent<HTMLElement>) => {
|
|
638
|
-
const items = groupOverlayItemsRef.current;
|
|
639
|
-
if (items.length <= 1) return false;
|
|
640
|
-
|
|
641
|
-
const blockedSelection = items.find(
|
|
642
|
-
(item) => !item.selection.capabilities.canApplyManualOffset,
|
|
643
|
-
)?.selection;
|
|
644
|
-
if (blockedSelection) {
|
|
645
|
-
e.preventDefault();
|
|
646
|
-
e.stopPropagation();
|
|
647
|
-
onBlockedMoveRef.current(blockedSelection);
|
|
648
|
-
return false;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
onManualDragStartRef.current?.();
|
|
652
|
-
|
|
653
|
-
const dragItems = filterNestedDomEditGroupItems(items);
|
|
654
|
-
|
|
655
|
-
const members: ManualOffsetDragMember[] = [];
|
|
656
|
-
for (const item of dragItems) {
|
|
657
|
-
const result = createManualOffsetDragMember({
|
|
658
|
-
key: item.key,
|
|
659
|
-
selection: item.selection,
|
|
660
|
-
element: item.element,
|
|
661
|
-
rect: item.rect,
|
|
662
|
-
});
|
|
663
|
-
if (!result.ok) {
|
|
664
|
-
restoreManualOffsetDragMembers(members);
|
|
665
|
-
e.preventDefault();
|
|
666
|
-
e.stopPropagation();
|
|
667
|
-
onBlockedMoveRef.current(result.selection);
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
members.push(result.member);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
e.preventDefault();
|
|
674
|
-
e.stopPropagation();
|
|
675
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
676
|
-
|
|
677
|
-
rafPausedRef.current = true;
|
|
678
|
-
groupGestureRef.current = {
|
|
679
|
-
startX: e.clientX,
|
|
680
|
-
startY: e.clientY,
|
|
681
|
-
originItems: items,
|
|
682
|
-
members,
|
|
683
|
-
};
|
|
684
|
-
return true;
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const restoreGroupPathOffsets = (g: GroupGestureState) => {
|
|
688
|
-
restoreManualOffsetDragMembers(g.members);
|
|
689
|
-
restoreGroupGestureOverlayItems(g);
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
const startGesture = (
|
|
693
|
-
kind: GestureKind,
|
|
694
|
-
e: React.PointerEvent<HTMLElement>,
|
|
695
|
-
options?: { selection?: DomEditSelection; rect?: OverlayRect | null },
|
|
696
|
-
) => {
|
|
697
|
-
const sel = options?.selection ?? selectionRef.current;
|
|
698
|
-
const rect = options?.rect ?? overlayRectRef.current;
|
|
699
|
-
const box = boxRef.current;
|
|
700
|
-
const overlayEl = overlayRef.current;
|
|
701
|
-
if (!sel || !rect) return false;
|
|
702
|
-
if (kind !== "drag" && !box) return false;
|
|
703
|
-
const mode: GestureState["mode"] =
|
|
704
|
-
kind === "rotate" ? "rotation" : kind === "drag" ? "path-offset" : "box-size";
|
|
705
|
-
if (kind === "drag" && !sel.capabilities.canApplyManualOffset) return false;
|
|
706
|
-
if (kind === "resize" && !sel.capabilities.canApplyManualSize) return false;
|
|
707
|
-
if (kind === "rotate" && !sel.capabilities.canApplyManualRotation) return false;
|
|
708
|
-
if (kind === "resize" && (!Number.isFinite(rect.width) || !Number.isFinite(rect.height))) {
|
|
709
|
-
return false;
|
|
710
|
-
}
|
|
711
|
-
const size = readStudioBoxSize(sel.element);
|
|
712
|
-
const rotation = readStudioRotation(sel.element);
|
|
713
|
-
const actualWidth = size.width > 0 ? size.width : rect.width / rect.editScaleX;
|
|
714
|
-
const actualHeight = size.height > 0 ? size.height : rect.height / rect.editScaleY;
|
|
715
|
-
let initialPathOffset = captureStudioPathOffset(sel.element);
|
|
716
|
-
let manualEditDragToken: string | undefined;
|
|
717
|
-
let pathOffsetMember: ManualOffsetDragMember | undefined;
|
|
718
|
-
if (kind === "drag") {
|
|
719
|
-
onManualDragStartRef.current?.();
|
|
720
|
-
const result = createManualOffsetDragMember({
|
|
721
|
-
key: selectionCacheKey(sel),
|
|
722
|
-
selection: sel,
|
|
723
|
-
element: sel.element,
|
|
724
|
-
rect,
|
|
725
|
-
});
|
|
726
|
-
if (!result.ok) {
|
|
727
|
-
onBlockedMoveRef.current(result.selection);
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
pathOffsetMember = result.member;
|
|
731
|
-
initialPathOffset = result.member.initialPathOffset;
|
|
732
|
-
manualEditDragToken = result.member.gestureToken;
|
|
733
|
-
} else {
|
|
734
|
-
manualEditDragToken = beginStudioManualEditGesture(sel.element);
|
|
735
|
-
}
|
|
736
|
-
const overlayBounds = overlayEl?.getBoundingClientRect();
|
|
737
|
-
const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
|
|
738
|
-
const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
|
|
739
|
-
|
|
740
|
-
e.preventDefault();
|
|
741
|
-
e.stopPropagation();
|
|
742
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
743
|
-
|
|
744
|
-
rafPausedRef.current = true;
|
|
745
|
-
|
|
746
|
-
gestureRef.current = {
|
|
747
|
-
kind,
|
|
748
|
-
mode,
|
|
749
|
-
selection: sel,
|
|
750
|
-
startX: e.clientX,
|
|
751
|
-
startY: e.clientY,
|
|
752
|
-
centerX,
|
|
753
|
-
centerY,
|
|
754
|
-
initialPathOffset,
|
|
755
|
-
initialRotation: captureStudioRotation(sel.element),
|
|
756
|
-
initialBoxSize: captureStudioBoxSize(sel.element),
|
|
757
|
-
pathOffsetMember,
|
|
758
|
-
originLeft: rect.left,
|
|
759
|
-
originTop: rect.top,
|
|
760
|
-
originWidth: rect.width,
|
|
761
|
-
originHeight: rect.height,
|
|
762
|
-
actualWidth,
|
|
763
|
-
actualHeight,
|
|
764
|
-
actualRotation: rotation.angle,
|
|
765
|
-
editScaleX: rect.editScaleX,
|
|
766
|
-
editScaleY: rect.editScaleY,
|
|
767
|
-
manualEditDragToken,
|
|
768
|
-
};
|
|
769
|
-
return true;
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
773
|
-
const g = gestureRef.current;
|
|
774
|
-
const groupG = groupGestureRef.current;
|
|
775
|
-
const sel = g?.selection ?? selectionRef.current;
|
|
776
|
-
const box = boxRef.current;
|
|
777
|
-
const blockedMove = blockedMoveRef.current;
|
|
778
|
-
if (!blockedMove && !g && !groupG) {
|
|
779
|
-
onCanvasPointerMoveRef.current(e, { preferClipAncestor: false });
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (blockedMove && sel) {
|
|
783
|
-
const dx = e.clientX - blockedMove.startX;
|
|
784
|
-
const dy = e.clientY - blockedMove.startY;
|
|
785
|
-
if (!blockedMove.notified && Math.hypot(dx, dy) >= BLOCKED_MOVE_THRESHOLD_PX) {
|
|
786
|
-
blockedMove.notified = true;
|
|
787
|
-
suppressNextBoxClickRef.current = true;
|
|
788
|
-
onBlockedMoveRef.current(sel);
|
|
789
|
-
}
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
if (groupG) {
|
|
794
|
-
const dx = e.clientX - groupG.startX;
|
|
795
|
-
const dy = e.clientY - groupG.startY;
|
|
796
|
-
setDraftGroupOverlayItems(
|
|
797
|
-
groupG.originItems.map((item) => ({
|
|
798
|
-
...item,
|
|
799
|
-
rect: {
|
|
800
|
-
...item.rect,
|
|
801
|
-
left: item.rect.left + dx,
|
|
802
|
-
top: item.rect.top + dy,
|
|
803
|
-
},
|
|
804
|
-
})),
|
|
805
|
-
);
|
|
806
|
-
for (const member of groupG.members) {
|
|
807
|
-
applyManualOffsetDragDraft(member, dx, dy);
|
|
808
|
-
}
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
if (!g || !sel) return;
|
|
813
|
-
|
|
814
|
-
const dx = e.clientX - g.startX;
|
|
815
|
-
const dy = e.clientY - g.startY;
|
|
816
|
-
|
|
817
|
-
if (g.kind === "rotate") {
|
|
818
|
-
const nextRotation = resolveDomEditRotationGesture({
|
|
819
|
-
centerX: g.centerX,
|
|
820
|
-
centerY: g.centerY,
|
|
821
|
-
startX: g.startX,
|
|
822
|
-
startY: g.startY,
|
|
823
|
-
currentX: e.clientX,
|
|
824
|
-
currentY: e.clientY,
|
|
825
|
-
actualAngle: g.actualRotation,
|
|
826
|
-
snap: e.shiftKey,
|
|
827
|
-
});
|
|
828
|
-
applyStudioRotationDraft(sel.element, nextRotation);
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (g.kind === "drag") {
|
|
833
|
-
const nextBoxLeft = g.originLeft + dx;
|
|
834
|
-
const nextBoxTop = g.originTop + dy;
|
|
835
|
-
setDraftOverlayRect({
|
|
836
|
-
left: nextBoxLeft,
|
|
837
|
-
top: nextBoxTop,
|
|
838
|
-
width: g.originWidth,
|
|
839
|
-
height: g.originHeight,
|
|
840
|
-
editScaleX: g.editScaleX,
|
|
841
|
-
editScaleY: g.editScaleY,
|
|
842
|
-
});
|
|
843
|
-
if (box) {
|
|
844
|
-
box.style.left = `${nextBoxLeft}px`;
|
|
845
|
-
box.style.top = `${nextBoxTop}px`;
|
|
846
|
-
}
|
|
847
|
-
if (g.pathOffsetMember) applyManualOffsetDragDraft(g.pathOffsetMember, dx, dy);
|
|
848
|
-
} else {
|
|
849
|
-
if (!box) return;
|
|
850
|
-
const nextSize = resolveDomEditResizeGesture({
|
|
851
|
-
originWidth: g.originWidth,
|
|
852
|
-
originHeight: g.originHeight,
|
|
853
|
-
actualWidth: g.actualWidth,
|
|
854
|
-
actualHeight: g.actualHeight,
|
|
855
|
-
scaleX: g.editScaleX,
|
|
856
|
-
scaleY: g.editScaleY,
|
|
857
|
-
dx,
|
|
858
|
-
dy,
|
|
859
|
-
uniform: e.shiftKey,
|
|
860
|
-
});
|
|
861
|
-
setDraftOverlayRect({
|
|
862
|
-
left: g.originLeft,
|
|
863
|
-
top: g.originTop,
|
|
864
|
-
width: nextSize.overlayWidth,
|
|
865
|
-
height: nextSize.overlayHeight,
|
|
866
|
-
editScaleX: g.editScaleX,
|
|
867
|
-
editScaleY: g.editScaleY,
|
|
868
|
-
});
|
|
869
|
-
box.style.width = `${nextSize.overlayWidth}px`;
|
|
870
|
-
box.style.height = `${nextSize.overlayHeight}px`;
|
|
871
|
-
applyStudioBoxSizeDraft(sel.element, nextSize);
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
|
|
875
|
-
const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
876
|
-
const g = gestureRef.current;
|
|
877
|
-
const groupG = groupGestureRef.current;
|
|
878
|
-
const sel = g?.selection ?? selectionRef.current;
|
|
879
|
-
const box = boxRef.current;
|
|
880
|
-
blockedMoveRef.current = null;
|
|
881
|
-
|
|
882
|
-
if (groupG) {
|
|
883
|
-
groupGestureRef.current = null;
|
|
884
|
-
rafPausedRef.current = false;
|
|
885
|
-
|
|
886
|
-
const dx = e.clientX - groupG.startX;
|
|
887
|
-
const dy = e.clientY - groupG.startY;
|
|
888
|
-
const movedDistance = Math.hypot(e.clientX - groupG.startX, e.clientY - groupG.startY);
|
|
889
|
-
if (movedDistance < BLOCKED_MOVE_THRESHOLD_PX) {
|
|
890
|
-
restoreGroupPathOffsets(groupG);
|
|
891
|
-
suppressNextBoxClickRef.current = true;
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
setDraftGroupOverlayItems(
|
|
896
|
-
groupG.originItems.map((item) => ({
|
|
897
|
-
...item,
|
|
898
|
-
rect: {
|
|
899
|
-
...item.rect,
|
|
900
|
-
left: item.rect.left + dx,
|
|
901
|
-
top: item.rect.top + dy,
|
|
902
|
-
},
|
|
903
|
-
})),
|
|
904
|
-
);
|
|
905
|
-
const updates = groupG.members.map((member) => {
|
|
906
|
-
const finalOffset = applyManualOffsetDragCommit(member, dx, dy);
|
|
907
|
-
return { selection: member.selection, next: finalOffset };
|
|
908
|
-
});
|
|
909
|
-
void Promise.resolve(onGroupPathOffsetCommitRef.current(updates))
|
|
910
|
-
.catch(() => {
|
|
911
|
-
for (const member of groupG.members) {
|
|
912
|
-
if (
|
|
913
|
-
member.gestureToken &&
|
|
914
|
-
isStudioManualEditGestureCurrent(member.element, member.gestureToken)
|
|
915
|
-
) {
|
|
916
|
-
restoreStudioPathOffset(member.element, member.initialPathOffset);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
})
|
|
920
|
-
.finally(() => {
|
|
921
|
-
endManualOffsetDragMembers(groupG.members);
|
|
922
|
-
});
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (!g || !sel) {
|
|
927
|
-
gestureRef.current = null;
|
|
928
|
-
rafPausedRef.current = false;
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
gestureRef.current = null;
|
|
933
|
-
rafPausedRef.current = false;
|
|
934
|
-
|
|
935
|
-
const movedDistance = Math.hypot(e.clientX - g.startX, e.clientY - g.startY);
|
|
936
|
-
if (g.kind === "drag" && movedDistance < BLOCKED_MOVE_THRESHOLD_PX) {
|
|
937
|
-
restoreStudioPathOffset(sel.element, g.initialPathOffset);
|
|
938
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
939
|
-
if (box) {
|
|
940
|
-
box.style.left = `${g.originLeft}px`;
|
|
941
|
-
box.style.top = `${g.originTop}px`;
|
|
942
|
-
}
|
|
943
|
-
restoreGestureOverlayRect(g);
|
|
944
|
-
suppressNextBoxClickRef.current = true;
|
|
945
|
-
onCanvasMouseDown(e as unknown as React.MouseEvent<HTMLDivElement>, {
|
|
946
|
-
preferClipAncestor: false,
|
|
947
|
-
});
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (g.kind === "resize" && movedDistance < BLOCKED_MOVE_THRESHOLD_PX) {
|
|
952
|
-
restoreStudioBoxSize(sel.element, g.initialBoxSize);
|
|
953
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
954
|
-
if (box) {
|
|
955
|
-
box.style.width = `${g.originWidth}px`;
|
|
956
|
-
box.style.height = `${g.originHeight}px`;
|
|
957
|
-
}
|
|
958
|
-
restoreGestureOverlayRect(g);
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (g.kind === "rotate") {
|
|
963
|
-
const finalRotation = resolveDomEditRotationGesture({
|
|
964
|
-
centerX: g.centerX,
|
|
965
|
-
centerY: g.centerY,
|
|
966
|
-
startX: g.startX,
|
|
967
|
-
startY: g.startY,
|
|
968
|
-
currentX: e.clientX,
|
|
969
|
-
currentY: e.clientY,
|
|
970
|
-
actualAngle: g.actualRotation,
|
|
971
|
-
snap: e.shiftKey,
|
|
972
|
-
});
|
|
973
|
-
if (!hasDomEditRotationChanged(g.actualRotation, finalRotation.angle)) {
|
|
974
|
-
restoreStudioRotation(sel.element, g.initialRotation);
|
|
975
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
applyStudioRotation(sel.element, finalRotation);
|
|
979
|
-
void Promise.resolve(onRotationCommitRef.current(sel, finalRotation))
|
|
980
|
-
.catch(() => {
|
|
981
|
-
if (
|
|
982
|
-
g.manualEditDragToken &&
|
|
983
|
-
isStudioManualEditGestureCurrent(sel.element, g.manualEditDragToken)
|
|
984
|
-
) {
|
|
985
|
-
restoreStudioRotation(sel.element, g.initialRotation);
|
|
986
|
-
}
|
|
987
|
-
})
|
|
988
|
-
.finally(() => {
|
|
989
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
990
|
-
});
|
|
991
|
-
} else if (g.kind === "drag") {
|
|
992
|
-
const dx = e.clientX - g.startX;
|
|
993
|
-
const dy = e.clientY - g.startY;
|
|
994
|
-
if (!g.pathOffsetMember) return;
|
|
995
|
-
const finalOffset = applyManualOffsetDragCommit(g.pathOffsetMember, dx, dy);
|
|
996
|
-
const nextBoxLeft = g.originLeft + dx;
|
|
997
|
-
const nextBoxTop = g.originTop + dy;
|
|
998
|
-
setDraftOverlayRect({
|
|
999
|
-
left: nextBoxLeft,
|
|
1000
|
-
top: nextBoxTop,
|
|
1001
|
-
width: g.originWidth,
|
|
1002
|
-
height: g.originHeight,
|
|
1003
|
-
editScaleX: g.editScaleX,
|
|
1004
|
-
editScaleY: g.editScaleY,
|
|
1005
|
-
});
|
|
1006
|
-
if (box) {
|
|
1007
|
-
box.style.left = `${nextBoxLeft}px`;
|
|
1008
|
-
box.style.top = `${nextBoxTop}px`;
|
|
1009
|
-
}
|
|
1010
|
-
void Promise.resolve(onPathOffsetCommitRef.current(sel, finalOffset))
|
|
1011
|
-
.catch(() => {
|
|
1012
|
-
if (
|
|
1013
|
-
g.pathOffsetMember?.gestureToken &&
|
|
1014
|
-
isStudioManualEditGestureCurrent(sel.element, g.pathOffsetMember.gestureToken)
|
|
1015
|
-
) {
|
|
1016
|
-
restoreStudioPathOffset(sel.element, g.initialPathOffset);
|
|
1017
|
-
}
|
|
1018
|
-
})
|
|
1019
|
-
.finally(() => {
|
|
1020
|
-
if (g.pathOffsetMember) endManualOffsetDragMembers([g.pathOffsetMember]);
|
|
1021
|
-
});
|
|
1022
|
-
} else {
|
|
1023
|
-
const finalSize = readStudioBoxSize(sel.element);
|
|
1024
|
-
applyStudioBoxSize(sel.element, finalSize);
|
|
1025
|
-
void Promise.resolve(onBoxSizeCommitRef.current(sel, finalSize))
|
|
1026
|
-
.catch(() => {
|
|
1027
|
-
if (
|
|
1028
|
-
g.manualEditDragToken &&
|
|
1029
|
-
isStudioManualEditGestureCurrent(sel.element, g.manualEditDragToken)
|
|
1030
|
-
) {
|
|
1031
|
-
restoreStudioBoxSize(sel.element, g.initialBoxSize);
|
|
1032
|
-
}
|
|
1033
|
-
})
|
|
1034
|
-
.finally(() => {
|
|
1035
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
};
|
|
1039
|
-
|
|
1040
|
-
// Click on overlay background → select whatever is under the pointer in the iframe.
|
|
1041
|
-
// This handles clicking children inside an already-selected parent: the selection
|
|
1042
|
-
// box stops propagation for drag gestures, but clicks on the transparent overlay
|
|
1043
|
-
// area outside the box pass through to the iframe pick logic.
|
|
1044
176
|
const handleOverlayMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
1045
177
|
if (suppressNextOverlayMouseDownRef.current) {
|
|
1046
178
|
suppressNextOverlayMouseDownRef.current = false;
|
|
@@ -1063,11 +195,9 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1063
195
|
if (!allowCanvasMovement || event.button !== 0) return;
|
|
1064
196
|
if (event.shiftKey) {
|
|
1065
197
|
const candidate =
|
|
1066
|
-
onCanvasPointerMoveRef.current(event, {
|
|
1067
|
-
|
|
1068
|
-
}) ?? hoverSelectionRef.current;
|
|
198
|
+
onCanvasPointerMoveRef.current(event, { preferClipAncestor: false }) ??
|
|
199
|
+
hoverSelectionRef.current;
|
|
1069
200
|
if (!candidate) return;
|
|
1070
|
-
|
|
1071
201
|
event.preventDefault();
|
|
1072
202
|
event.stopPropagation();
|
|
1073
203
|
suppressNextOverlayMouseDownRef.current = true;
|
|
@@ -1080,10 +210,9 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1080
210
|
const target = event.target as HTMLElement | null;
|
|
1081
211
|
if (target?.closest('[data-dom-edit-selection-box="true"]')) return;
|
|
1082
212
|
|
|
1083
|
-
const
|
|
1084
|
-
preferClipAncestor: false
|
|
1085
|
-
|
|
1086
|
-
const candidate = hoverCandidate ?? hoverSelectionRef.current;
|
|
213
|
+
const candidate =
|
|
214
|
+
onCanvasPointerMoveRef.current(event, { preferClipAncestor: false }) ??
|
|
215
|
+
hoverSelectionRef.current;
|
|
1087
216
|
if (!candidate?.capabilities.canApplyManualOffset) return;
|
|
1088
217
|
|
|
1089
218
|
const overlayEl = overlayRef.current;
|
|
@@ -1095,10 +224,8 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1095
224
|
suppressNextOverlayMouseDownRef.current = true;
|
|
1096
225
|
selectionRef.current = candidate;
|
|
1097
226
|
overlayRectRef.current = candidateRect;
|
|
1098
|
-
hoverRectRef.current = null;
|
|
1099
227
|
setOverlayRect(candidateRect);
|
|
1100
|
-
|
|
1101
|
-
const didStartGesture = startGesture("drag", event, {
|
|
228
|
+
const didStartGesture = gestures.startGesture("drag", event, {
|
|
1102
229
|
selection: candidate,
|
|
1103
230
|
rect: candidateRect,
|
|
1104
231
|
});
|
|
@@ -1109,9 +236,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1109
236
|
onSelectionChangeRef.current(candidate);
|
|
1110
237
|
};
|
|
1111
238
|
|
|
1112
|
-
// Click on the selection box itself → re-pick the element under the pointer.
|
|
1113
|
-
// This lets you click a child element even when a parent is selected, because
|
|
1114
|
-
// the click coordinates are forwarded to the iframe's element picker.
|
|
1115
239
|
const handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
1116
240
|
if (gestureRef.current || groupGestureRef.current) return;
|
|
1117
241
|
if (suppressNextBoxClickRef.current) {
|
|
@@ -1122,29 +246,11 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1122
246
|
onCanvasMouseDown(event, { preferClipAncestor: false });
|
|
1123
247
|
};
|
|
1124
248
|
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
if (g?.mode === "path-offset" && sel) {
|
|
1131
|
-
restoreStudioPathOffset(sel.element, g.initialPathOffset);
|
|
1132
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
1133
|
-
restoreGestureOverlayRect(g);
|
|
1134
|
-
}
|
|
1135
|
-
if (g?.mode === "box-size" && sel) {
|
|
1136
|
-
restoreStudioBoxSize(sel.element, g.initialBoxSize);
|
|
1137
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
1138
|
-
restoreGestureOverlayRect(g);
|
|
1139
|
-
}
|
|
1140
|
-
if (g?.mode === "rotation" && sel) {
|
|
1141
|
-
restoreStudioRotation(sel.element, g.initialRotation);
|
|
1142
|
-
endStudioManualEditGesture(sel.element, g.manualEditDragToken);
|
|
1143
|
-
}
|
|
1144
|
-
blockedMoveRef.current = null;
|
|
1145
|
-
groupGestureRef.current = null;
|
|
1146
|
-
gestureRef.current = null;
|
|
1147
|
-
rafPausedRef.current = false;
|
|
249
|
+
const suppressBoxMouseDown = (e: React.MouseEvent) => {
|
|
250
|
+
if (!suppressNextBoxMouseDownRef.current) return;
|
|
251
|
+
suppressNextBoxMouseDownRef.current = false;
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
e.stopPropagation();
|
|
1148
254
|
};
|
|
1149
255
|
|
|
1150
256
|
return (
|
|
@@ -1153,13 +259,15 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1153
259
|
className="absolute inset-0 z-10 pointer-events-auto outline-none"
|
|
1154
260
|
tabIndex={-1}
|
|
1155
261
|
aria-label="Composition canvas"
|
|
1156
|
-
onPointerDownCapture={(event) =>
|
|
262
|
+
onPointerDownCapture={(event) =>
|
|
263
|
+
focusDomEditOverlayElement(event.currentTarget as FocusableDomEditOverlay)
|
|
264
|
+
}
|
|
1157
265
|
onPointerDown={handleOverlayPointerDown}
|
|
1158
266
|
onMouseDown={handleOverlayMouseDown}
|
|
1159
|
-
onPointerMove={onPointerMove}
|
|
267
|
+
onPointerMove={gestures.onPointerMove}
|
|
1160
268
|
onPointerLeave={() => onCanvasPointerLeaveRef.current()}
|
|
1161
|
-
onPointerUp={onPointerUp}
|
|
1162
|
-
onPointerCancel={clearPointerState}
|
|
269
|
+
onPointerUp={gestures.onPointerUp}
|
|
270
|
+
onPointerCancel={() => gestures.clearPointerState(selectionRef)}
|
|
1163
271
|
>
|
|
1164
272
|
{hoverSelection && hoverRect && (
|
|
1165
273
|
<div
|
|
@@ -1200,16 +308,10 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1200
308
|
cursor: allowCanvasMovement && groupCanMove ? "move" : "default",
|
|
1201
309
|
}}
|
|
1202
310
|
onPointerDown={(e) => {
|
|
1203
|
-
if (!allowCanvasMovement) return;
|
|
1204
|
-
|
|
1205
|
-
startGroupDrag(e);
|
|
1206
|
-
}}
|
|
1207
|
-
onMouseDown={(e) => {
|
|
1208
|
-
if (!suppressNextBoxMouseDownRef.current) return;
|
|
1209
|
-
suppressNextBoxMouseDownRef.current = false;
|
|
1210
|
-
e.preventDefault();
|
|
1211
|
-
e.stopPropagation();
|
|
311
|
+
if (!allowCanvasMovement || e.shiftKey) return;
|
|
312
|
+
gestures.startGroupDrag(e);
|
|
1212
313
|
}}
|
|
314
|
+
onMouseDown={suppressBoxMouseDown}
|
|
1213
315
|
onClick={handleBoxClick}
|
|
1214
316
|
/>
|
|
1215
317
|
</>
|
|
@@ -1236,7 +338,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1236
338
|
aria-label="Rotate selection"
|
|
1237
339
|
onPointerDown={(e) => {
|
|
1238
340
|
e.stopPropagation();
|
|
1239
|
-
startGesture("rotate", e);
|
|
341
|
+
gestures.startGesture("rotate", e);
|
|
1240
342
|
}}
|
|
1241
343
|
/>
|
|
1242
344
|
</div>
|
|
@@ -1257,10 +359,9 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1257
359
|
: "default",
|
|
1258
360
|
}}
|
|
1259
361
|
onPointerDown={(e) => {
|
|
1260
|
-
if (!allowCanvasMovement) return;
|
|
1261
|
-
if (e.shiftKey) return;
|
|
362
|
+
if (!allowCanvasMovement || e.shiftKey) return;
|
|
1262
363
|
if (selection.capabilities.canApplyManualOffset) {
|
|
1263
|
-
startGesture("drag", e);
|
|
364
|
+
gestures.startGesture("drag", e);
|
|
1264
365
|
return;
|
|
1265
366
|
}
|
|
1266
367
|
e.preventDefault();
|
|
@@ -1273,22 +374,16 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
1273
374
|
notified: false,
|
|
1274
375
|
};
|
|
1275
376
|
}}
|
|
1276
|
-
onMouseDown={
|
|
1277
|
-
if (!suppressNextBoxMouseDownRef.current) return;
|
|
1278
|
-
suppressNextBoxMouseDownRef.current = false;
|
|
1279
|
-
e.preventDefault();
|
|
1280
|
-
e.stopPropagation();
|
|
1281
|
-
}}
|
|
377
|
+
onMouseDown={suppressBoxMouseDown}
|
|
1282
378
|
onClick={handleBoxClick}
|
|
1283
379
|
>
|
|
1284
|
-
{/* Resize handle — bottom-right corner */}
|
|
1285
380
|
{allowCanvasMovement && selection.capabilities.canApplyManualSize && (
|
|
1286
381
|
<div
|
|
1287
382
|
className="absolute -right-1.5 -bottom-1.5 w-3 h-3 rounded-sm bg-studio-accent border border-studio-accent/60"
|
|
1288
383
|
style={{ cursor: "se-resize", touchAction: "none" }}
|
|
1289
384
|
onPointerDown={(e) => {
|
|
1290
385
|
e.stopPropagation();
|
|
1291
|
-
startGesture("resize", e);
|
|
386
|
+
gestures.startGesture("resize", e);
|
|
1292
387
|
}}
|
|
1293
388
|
/>
|
|
1294
389
|
)}
|