mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): show inspector selection bounds after click (#789)
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
|
import React, { act } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { Window } from "happy-dom";
|
import { Window } from "happy-dom";
|
||||||
import {
|
import {
|
||||||
|
DomEditOverlay,
|
||||||
filterNestedDomEditGroupItems,
|
filterNestedDomEditGroupItems,
|
||||||
focusDomEditOverlayElement,
|
focusDomEditOverlayElement,
|
||||||
hasDomEditRotationChanged,
|
hasDomEditRotationChanged,
|
||||||
@@ -9,6 +14,75 @@ import {
|
|||||||
resolveDomEditResizeGesture,
|
resolveDomEditResizeGesture,
|
||||||
resolveDomEditRotationGesture,
|
resolveDomEditRotationGesture,
|
||||||
} from "./DomEditOverlay";
|
} from "./DomEditOverlay";
|
||||||
|
import type { DomEditSelection } from "./domEditing";
|
||||||
|
|
||||||
|
// React 19 warns unless the test environment opts into act().
|
||||||
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
vi.mock("./useDomEditOverlayGestures", () => ({
|
||||||
|
createDomEditOverlayGestureHandlers: () => ({
|
||||||
|
startGesture: () => true,
|
||||||
|
startGroupDrag: () => {},
|
||||||
|
onPointerMove: () => {},
|
||||||
|
onPointerUp: () => {},
|
||||||
|
clearPointerState: () => {},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./useDomEditOverlayRects", async () => {
|
||||||
|
const React = await import("react");
|
||||||
|
const { rectsEqual } = await import("./domEditOverlayGeometry");
|
||||||
|
|
||||||
|
return {
|
||||||
|
useDomEditOverlayRects: () => {
|
||||||
|
const [overlayRect, setOverlayRectState] = React.useState(null);
|
||||||
|
const overlayRectRef = React.useRef(null);
|
||||||
|
const [groupOverlayItems, setGroupOverlayItemsState] = React.useState([]);
|
||||||
|
const groupOverlayItemsRef = React.useRef([]);
|
||||||
|
|
||||||
|
const setOverlayRect = (next: unknown) => {
|
||||||
|
if (rectsEqual(overlayRectRef.current, next)) return;
|
||||||
|
overlayRectRef.current = next;
|
||||||
|
setOverlayRectState(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setGroupOverlayItems = (next: unknown[]) => {
|
||||||
|
groupOverlayItemsRef.current = next;
|
||||||
|
setGroupOverlayItemsState(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
overlayRect,
|
||||||
|
overlayRectRef,
|
||||||
|
setOverlayRect,
|
||||||
|
hoverRect: null,
|
||||||
|
hoverRectRef: { current: null },
|
||||||
|
setHoverRect: () => {},
|
||||||
|
groupOverlayItems,
|
||||||
|
groupOverlayItemsRef,
|
||||||
|
setGroupOverlayItems,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("./domEditOverlayGeometry", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("./domEditOverlayGeometry")>(
|
||||||
|
"./domEditOverlayGeometry",
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
toOverlayRect: () => ({
|
||||||
|
left: 24,
|
||||||
|
top: 36,
|
||||||
|
width: 180,
|
||||||
|
height: 72,
|
||||||
|
editScaleX: 1,
|
||||||
|
editScaleY: 1,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe("focusDomEditOverlayElement", () => {
|
describe("focusDomEditOverlayElement", () => {
|
||||||
it("focuses the canvas overlay without scrolling", () => {
|
it("focuses the canvas overlay without scrolling", () => {
|
||||||
@@ -21,6 +95,96 @@ describe("focusDomEditOverlayElement", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("DomEditOverlay", () => {
|
||||||
|
it("renders selected bounds right after clicking a movable selection", () => {
|
||||||
|
const host = document.createElement("div");
|
||||||
|
document.body.append(host);
|
||||||
|
const root = createRoot(host);
|
||||||
|
const selection: DomEditSelection = {
|
||||||
|
element: document.createElement("div"),
|
||||||
|
id: "hero-title",
|
||||||
|
selector: ".hero-title",
|
||||||
|
selectorIndex: 0,
|
||||||
|
sourceFile: "index.html",
|
||||||
|
tagName: "div",
|
||||||
|
label: "Hero Title",
|
||||||
|
textContent: "Hello",
|
||||||
|
textFields: [],
|
||||||
|
capabilities: {
|
||||||
|
canEditText: true,
|
||||||
|
canEditLayout: true,
|
||||||
|
canApplyManualOffset: true,
|
||||||
|
canApplyManualSize: false,
|
||||||
|
canApplyManualRotation: false,
|
||||||
|
canAdjustOpacity: true,
|
||||||
|
canAdjustFill: true,
|
||||||
|
canAdjustBorderRadius: true,
|
||||||
|
canAdjustStroke: true,
|
||||||
|
canAdjustShadow: true,
|
||||||
|
canAdjustZIndex: true,
|
||||||
|
},
|
||||||
|
computedStyle: {
|
||||||
|
display: "block",
|
||||||
|
position: "absolute",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentSelection: DomEditSelection | null = null;
|
||||||
|
const iframeRef = { current: document.createElement("iframe") as HTMLIFrameElement | null };
|
||||||
|
const originalPointerCapture = HTMLDivElement.prototype.setPointerCapture;
|
||||||
|
HTMLDivElement.prototype.setPointerCapture = () => {};
|
||||||
|
|
||||||
|
function Harness() {
|
||||||
|
const [selected, setSelected] = React.useState<DomEditSelection | null>(null);
|
||||||
|
currentSelection = selected;
|
||||||
|
|
||||||
|
return React.createElement(DomEditOverlay, {
|
||||||
|
iframeRef,
|
||||||
|
activeCompositionPath: null,
|
||||||
|
selection: selected,
|
||||||
|
hoverSelection: null,
|
||||||
|
groupSelections: [],
|
||||||
|
onCanvasMouseDown: () => {},
|
||||||
|
onCanvasPointerMove: () => selection,
|
||||||
|
onCanvasPointerLeave: () => {},
|
||||||
|
onSelectionChange: (next: DomEditSelection) => setSelected(next),
|
||||||
|
onBlockedMove: () => {},
|
||||||
|
onPathOffsetCommit: () => {},
|
||||||
|
onGroupPathOffsetCommit: () => {},
|
||||||
|
onBoxSizeCommit: () => {},
|
||||||
|
onRotationCommit: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(React.createElement(Harness));
|
||||||
|
});
|
||||||
|
|
||||||
|
const overlay = host.querySelector('[aria-label="Composition canvas"]') as HTMLDivElement;
|
||||||
|
expect(overlay).toBeTruthy();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
overlay.dispatchEvent(
|
||||||
|
new PointerEvent("pointerdown", {
|
||||||
|
bubbles: true,
|
||||||
|
button: 0,
|
||||||
|
clientX: 120,
|
||||||
|
clientY: 80,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(currentSelection).toBe(selection);
|
||||||
|
expect(host.querySelector('[data-dom-edit-selection-box="true"]')).toBeTruthy();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.unmount();
|
||||||
|
});
|
||||||
|
HTMLDivElement.prototype.setPointerCapture = originalPointerCapture;
|
||||||
|
host.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("resolveDomEditCoordinateScale", () => {
|
describe("resolveDomEditCoordinateScale", () => {
|
||||||
it("uses the top-level preview scale when no source boundary dimensions are available", () => {
|
it("uses the top-level preview scale when no source boundary dimensions are available", () => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -223,7 +223,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|||||||
|
|
||||||
suppressNextOverlayMouseDownRef.current = true;
|
suppressNextOverlayMouseDownRef.current = true;
|
||||||
selectionRef.current = candidate;
|
selectionRef.current = candidate;
|
||||||
overlayRectRef.current = candidateRect;
|
|
||||||
setOverlayRect(candidateRect);
|
setOverlayRect(candidateRect);
|
||||||
const didStartGesture = gestures.startGesture("drag", event, {
|
const didStartGesture = gestures.startGesture("drag", event, {
|
||||||
selection: candidate,
|
selection: candidate,
|
||||||
|
|||||||
@@ -23,12 +23,7 @@ import {
|
|||||||
restoreStudioPathOffset,
|
restoreStudioPathOffset,
|
||||||
restoreStudioRotation,
|
restoreStudioRotation,
|
||||||
} from "./manualEdits";
|
} from "./manualEdits";
|
||||||
import {
|
import { type GroupOverlayItem, type OverlayRect } from "./domEditOverlayGeometry";
|
||||||
type GroupOverlayItem,
|
|
||||||
type OverlayRect,
|
|
||||||
groupOverlayItemsEqual,
|
|
||||||
rectsEqual,
|
|
||||||
} from "./domEditOverlayGeometry";
|
|
||||||
import {
|
import {
|
||||||
BLOCKED_MOVE_THRESHOLD_PX,
|
BLOCKED_MOVE_THRESHOLD_PX,
|
||||||
type BlockedMoveState,
|
type BlockedMoveState,
|
||||||
@@ -87,8 +82,6 @@ export type UseDomEditOverlayGesturesOptions = {
|
|||||||
|
|
||||||
export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGesturesOptions) {
|
export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGesturesOptions) {
|
||||||
const setDraftOverlayRect = (next: OverlayRect) => {
|
const setDraftOverlayRect = (next: OverlayRect) => {
|
||||||
if (rectsEqual(opts.overlayRectRef.current, next)) return;
|
|
||||||
opts.overlayRectRef.current = next;
|
|
||||||
opts.setOverlayRect(next);
|
opts.setOverlayRect(next);
|
||||||
};
|
};
|
||||||
const restoreGestureOverlayRect = (g: GestureState) => {
|
const restoreGestureOverlayRect = (g: GestureState) => {
|
||||||
@@ -102,8 +95,6 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const setDraftGroupOverlayItems = (next: GroupOverlayItem[]) => {
|
const setDraftGroupOverlayItems = (next: GroupOverlayItem[]) => {
|
||||||
if (groupOverlayItemsEqual(opts.groupOverlayItemsRef.current, next)) return;
|
|
||||||
opts.groupOverlayItemsRef.current = next;
|
|
||||||
opts.setGroupOverlayItems(next);
|
opts.setGroupOverlayItems(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user