mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 07:09:59 +00:00
fix(studio): stabilize manual drag targets (#1393)
## Problem Studio manual drag had two bad target paths in `hf-keyframes-test`: - Drag could start from a cached hover selection instead of the selected overlay, so moving `#cta` could write an offset to the wrong element while the overlay appeared correct. - `#stage` is the visual 1920x1080 canvas, but the composition metadata lives on `<html data-composition-id ...>`. Studio treated `#stage` as a normal movable layer, so dragging it moved the coordinate basis the overlay depends on and left Undo with no reliable saved edit to reverse. ## Fix - Manual drag now starts only from the selected overlay bounds; canvas pointer-down no longer starts movement from `hoverSelectionRef`. - Added a structural root-layer heuristic: a direct body child matching the composition root's declared `data-width` / `data-height` is selectable and style-editable, but cannot receive manual offset/size/rotation edits. - Manual geometry commits now return/rethrow the save promise so failed writes roll back optimistic DOM changes instead of silently sticking. - GSAP-targeted CSS fallback edits are blocked before applying manual geometry when the GSAP drag intercept is unavailable. ## Tests - New regression coverage for stale-hover drag start and full-canvas root-stage capability resolution. - Focused DOM-edit suite: 119/119 pass. - Full Studio suite: 800 pass, 18 todo. - Studio typecheck clean; oxlint/oxfmt clean; lefthook pre-commit clean. - Studio build passes with the existing Vite chunk-size warning. - Browser verified with `agent-browser`: attempted `#stage` drag writes no offset and keeps Undo disabled; `#cta` drag writes exactly one offset to `#cta`; Undo returns the fixture to zero offsets. Local browser artifacts: `artifacts/studio-cta-drag/stage-cta-drag-fix.webm`, `artifacts/studio-cta-drag/stage-cta-drag-fix-final.png`.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { memo, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import { useMountEffect } from "../../hooks/useMountEffect";
|
||||
import { type DomEditSelection } from "./domEditing";
|
||||
import { resolveDomEditGroupOverlayRect, toOverlayRect } from "./domEditOverlayGeometry";
|
||||
import { resolveDomEditGroupOverlayRect } from "./domEditOverlayGeometry";
|
||||
import {
|
||||
type BlockedMoveState,
|
||||
type FocusableDomEditOverlay,
|
||||
@@ -304,28 +304,6 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
||||
|
||||
const target = event.target as HTMLElement | null;
|
||||
if (target?.closest('[data-dom-edit-selection-box="true"]')) return;
|
||||
|
||||
const candidate = hoverSelectionRef.current;
|
||||
if (!candidate?.capabilities.canApplyManualOffset) return;
|
||||
|
||||
const overlayEl = overlayRef.current;
|
||||
const iframe = iframeRef.current;
|
||||
const candidateRect =
|
||||
overlayEl && iframe ? toOverlayRect(overlayEl, iframe, candidate.element) : null;
|
||||
if (!candidateRect) return;
|
||||
|
||||
suppressNextOverlayMouseDownRef.current = true;
|
||||
selectionRef.current = candidate;
|
||||
setOverlayRect(candidateRect);
|
||||
const didStartGesture = gestures.startGesture("drag", event, {
|
||||
selection: candidate,
|
||||
rect: candidateRect,
|
||||
});
|
||||
if (!didStartGesture) {
|
||||
suppressNextOverlayMouseDownRef.current = false;
|
||||
return;
|
||||
}
|
||||
onSelectionChangeRef.current(candidate);
|
||||
};
|
||||
|
||||
const handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
|
||||
Reference in New Issue
Block a user