fix(studio): break all 7 circular dependency cycles and fix rules-of-hooks violation (#1422)

This commit is contained in:
Miguel Ángel
2026-06-13 19:23:05 -04:00
committed by GitHub
parent a0d7295367
commit a241f2591e
17 changed files with 149 additions and 85 deletions
@@ -4,6 +4,7 @@ import { type DomEditSelection } from "./domEditing";
import { resolveDomEditGroupOverlayRect } from "./domEditOverlayGeometry";
import {
type BlockedMoveState,
type DomEditGroupPathOffsetCommit,
type FocusableDomEditOverlay,
type GestureState,
type GroupGestureState,
@@ -27,11 +28,7 @@ export {
resolveDomEditResizeGesture,
resolveDomEditRotationGesture,
} from "./domEditOverlayGestures";
export interface DomEditGroupPathOffsetCommit {
selection: DomEditSelection;
next: { x: number; y: number };
}
export type { DomEditGroupPathOffsetCommit } from "./domEditOverlayGestures";
interface DomEditOverlayProps {
iframeRef: RefObject<HTMLIFrameElement | null>;
@@ -1,3 +1,4 @@
import type { RefObject } from "react";
import type { DomEditSelection } from "./domEditing";
import type {
StudioBoxSizeSnapshot,
@@ -5,8 +6,9 @@ import type {
StudioRotationSnapshot,
} from "./manualEdits";
import type { ManualOffsetDragMember } from "./manualOffsetDrag";
import type { GroupOverlayItem } from "./domEditOverlayGeometry";
import type { GroupOverlayItem, OverlayRect } from "./domEditOverlayGeometry";
import type { SnapContext } from "./snapTargetCollection";
import type { SnapGuidesState } from "./SnapGuideOverlay";
export type GestureKind = "drag" | "resize" | "rotate";
@@ -143,3 +145,54 @@ export function resolveDomEditRotationGesture(input: {
export function hasDomEditRotationChanged(initialAngle: number, nextAngle: number): boolean {
return Math.abs(nextAngle - initialAngle) >= ROTATION_COMMIT_EPSILON_DEGREES;
}
// ── Shared types for DomEditOverlay gesture wiring ──
// These live here (rather than in DomEditOverlay.tsx or useDomEditOverlayGestures.ts)
// to break circular imports between those files.
export interface DomEditGroupPathOffsetCommit {
selection: DomEditSelection;
next: { x: number; y: number };
}
// Refs are stable across renders; values are read via .current.
export type UseDomEditOverlayGesturesOptions = {
overlayRef: RefObject<HTMLDivElement | null>;
iframeRef: RefObject<HTMLIFrameElement | null>;
boxRef: RefObject<HTMLDivElement | null>;
selectionRef: RefObject<DomEditSelection | null>;
overlayRectRef: RefObject<OverlayRect | null>;
groupOverlayItemsRef: RefObject<GroupOverlayItem[]>;
gestureRef: RefObject<GestureState | null>;
groupGestureRef: RefObject<GroupGestureState | null>;
blockedMoveRef: RefObject<BlockedMoveState | null>;
rafPausedRef: RefObject<boolean>;
suppressNextBoxClickRef: RefObject<boolean>;
setOverlayRect: (next: OverlayRect | null) => void;
setGroupOverlayItems: (next: GroupOverlayItem[]) => void;
onBlockedMoveRef: RefObject<(selection: DomEditSelection) => void>;
onManualDragStartRef: RefObject<(() => void) | undefined>;
onPathOffsetCommitRef: RefObject<
(s: DomEditSelection, n: { x: number; y: number }) => Promise<void> | void
>;
onGroupPathOffsetCommitRef: RefObject<
(updates: DomEditGroupPathOffsetCommit[]) => Promise<void> | void
>;
onBoxSizeCommitRef: RefObject<
(s: DomEditSelection, n: { width: number; height: number }) => Promise<void> | void
>;
onRotationCommitRef: RefObject<
(s: DomEditSelection, n: { angle: number }) => Promise<void> | void
>;
onCanvasPointerMoveRef: RefObject<
(
e: React.PointerEvent<HTMLDivElement>,
o?: { preferClipAncestor?: boolean },
) => Promise<DomEditSelection | null>
>;
onCanvasMouseDown: (
e: React.MouseEvent<HTMLDivElement>,
o?: { preferClipAncestor?: boolean },
) => void;
snapGuidesRef: RefObject<SnapGuidesState | null>;
};
@@ -21,8 +21,11 @@ import {
filterNestedDomEditGroupItems,
selectionCacheKey,
} from "./domEditOverlayGeometry";
import { type GestureKind, type GestureState } from "./domEditOverlayGestures";
import type { UseDomEditOverlayGesturesOptions } from "./useDomEditOverlayGestures";
import {
type GestureKind,
type GestureState,
type UseDomEditOverlayGesturesOptions,
} from "./domEditOverlayGestures";
import { collectSnapContext, buildExcludeElements } from "./snapTargetCollection";
export function startGroupDrag(
@@ -33,15 +33,14 @@ import {
} from "./domEditOverlayGeometry";
import {
BLOCKED_MOVE_THRESHOLD_PX,
type BlockedMoveState,
type GestureKind,
type GestureState,
type GroupGestureState,
type UseDomEditOverlayGesturesOptions,
hasDomEditRotationChanged,
resolveDomEditResizeGesture,
resolveDomEditRotationGesture,
} from "./domEditOverlayGestures";
import type { DomEditGroupPathOffsetCommit } from "./DomEditOverlay";
import {
startGesture as _startGesture,
startGroupDrag as _startGroupDrag,
@@ -52,50 +51,6 @@ import {
resolveEquidistanceGuides,
SNAP_THRESHOLD_PX,
} from "./snapEngine";
import type { SnapGuidesState } from "./SnapGuideOverlay";
// Refs are stable across renders; values are read via .current.
export type UseDomEditOverlayGesturesOptions = {
overlayRef: RefObject<HTMLDivElement | null>;
iframeRef: RefObject<HTMLIFrameElement | null>;
boxRef: RefObject<HTMLDivElement | null>;
selectionRef: RefObject<DomEditSelection | null>;
overlayRectRef: RefObject<OverlayRect | null>;
groupOverlayItemsRef: RefObject<GroupOverlayItem[]>;
gestureRef: RefObject<GestureState | null>;
groupGestureRef: RefObject<GroupGestureState | null>;
blockedMoveRef: RefObject<BlockedMoveState | null>;
rafPausedRef: RefObject<boolean>;
suppressNextBoxClickRef: RefObject<boolean>;
setOverlayRect: (next: OverlayRect | null) => void;
setGroupOverlayItems: (next: GroupOverlayItem[]) => void;
onBlockedMoveRef: RefObject<(selection: DomEditSelection) => void>;
onManualDragStartRef: RefObject<(() => void) | undefined>;
onPathOffsetCommitRef: RefObject<
(s: DomEditSelection, n: { x: number; y: number }) => Promise<void> | void
>;
onGroupPathOffsetCommitRef: RefObject<
(updates: DomEditGroupPathOffsetCommit[]) => Promise<void> | void
>;
onBoxSizeCommitRef: RefObject<
(s: DomEditSelection, n: { width: number; height: number }) => Promise<void> | void
>;
onRotationCommitRef: RefObject<
(s: DomEditSelection, n: { angle: number }) => Promise<void> | void
>;
onCanvasPointerMoveRef: RefObject<
(
e: React.PointerEvent<HTMLDivElement>,
o?: { preferClipAncestor?: boolean },
) => Promise<DomEditSelection | null>
>;
onCanvasMouseDown: (
e: React.MouseEvent<HTMLDivElement>,
o?: { preferClipAncestor?: boolean },
) => void;
snapGuidesRef: RefObject<SnapGuidesState | null>;
};
export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGesturesOptions) {
const setDraftOverlayRect = (next: OverlayRect) => {
opts.setOverlayRect(next);