fix(studio): correct gesture commits for scaled and graded elements

- resize on a scale-driven element commits per-axis scale (scaleX/scaleY
  for non-uniform drags) with keyframe normalization to the longhands, and
  clears the width/height draft so size can't double-apply; the intercept
  moves to gsapResizeIntercept.ts
- the drop frame applies the corrected position synchronously in the same
  microtask chain as the soft reload (no network-window jump), and the
  draft pins the anchor through accumulated moves on scaled elements
- gesture size/position math divides by the element's own content scale
- convert-to-keyframes resolves current values through the property-group
  filter for ALL capture passes (opacity/rotationX from unrelated tweens
  no longer leak into a rotation commit), and a grading-hidden source's
  opacity is read from its canvas, not the inline hide
- canvas pointer-down confirms the hover target with a synchronous
  hit-test before starting a marquee (stale-hover race lost selections)
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-11 04:07:06 -04:00
parent 5cc14c2221
commit 3525c7ff52
10 changed files with 703 additions and 257 deletions
@@ -535,6 +535,47 @@ describe("resolveDomEditResizeGesture", () => {
});
});
it("divides the cursor delta by the element's content scale (rescaled element)", () => {
// Element renders at 2x via a GSAP scale: a 30px cursor delta must grow the
// CSS box by only 15px so the RENDERED box tracks the pointer 1:1.
const next = resolveDomEditResizeGesture({
originWidth: 480, // 240 css x 2 content scale (overlay px at editScale 1)
originHeight: 240,
actualWidth: 240,
actualHeight: 120,
scaleX: 1,
scaleY: 1,
contentScaleX: 2,
contentScaleY: 2,
dx: 30,
dy: 12,
uniform: false,
});
expect(next.width).toBe(255);
expect(next.height).toBe(126);
// The overlay box keeps tracking the raw cursor.
expect(next.overlayWidth).toBe(510);
expect(next.overlayHeight).toBe(252);
});
it("treats a missing/invalid content scale as 1 (unscaled element)", () => {
const next = resolveDomEditResizeGesture({
originWidth: 240,
originHeight: 120,
actualWidth: 240,
actualHeight: 120,
scaleX: 1,
scaleY: 1,
contentScaleX: 0,
contentScaleY: Number.NaN,
dx: 30,
dy: 12,
uniform: false,
});
expect(next.width).toBe(270);
expect(next.height).toBe(132);
});
it("snaps width and height to the same value when Shift is held", () => {
expect(
resolveDomEditResizeGesture({