mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-12 20:47:30 +00:00
fix(studio): a no-GSAP drag falls back to the plain transform when the fold does not parse
This commit is contained in:
@@ -557,22 +557,9 @@ describe("resumeGsapTimelines", () => {
|
||||
describe("drag in a composition without GSAP", () => {
|
||||
// The commit writes `gsap.set(x, y)`; once GSAP loads it folds the CSS translate and the
|
||||
// transform's translation into x/y, so the drag base must already count them.
|
||||
function committedPosition(
|
||||
css: string,
|
||||
dx: number,
|
||||
dy: number,
|
||||
gsapBase?: { x: number; y: number },
|
||||
) {
|
||||
function committedPosition(css: string, dx: number, dy: number, setup?: (w: Window) => void) {
|
||||
const window = new Window();
|
||||
if (gsapBase) {
|
||||
const base: Record<string, number> = gsapBase;
|
||||
Object.assign(window, {
|
||||
gsap: {
|
||||
set: () => undefined,
|
||||
getProperty: (_el: Element, prop: string) => base[prop] ?? 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
setup?.(window);
|
||||
window.document.head.innerHTML = `<style>#title { ${css} }</style>`;
|
||||
const element = window.document.createElement("h1");
|
||||
element.id = "title";
|
||||
@@ -615,8 +602,41 @@ describe("drag in a composition without GSAP", () => {
|
||||
expect(newY).toBeCloseTo(130);
|
||||
});
|
||||
|
||||
it("scales the transform's translation by a CSS scale", () => {
|
||||
const { newX, newY } = committedPosition("scale: 2; transform: translate(10px, 0);", 40, 30);
|
||||
expect({ x: newX, y: newY }).toEqual({ x: 60, y: 30 });
|
||||
});
|
||||
|
||||
it("falls back to the plain transform, as GSAP does, when the fold does not parse", () => {
|
||||
// Chrome's DOMMatrix rejects an axis rotate inside a transform list; happy-dom's does not.
|
||||
const rejectAxisRotate = (w: Window) => {
|
||||
const Parsed = w.DOMMatrix;
|
||||
class ChromeLike extends Parsed {
|
||||
constructor(init?: string) {
|
||||
if (init?.includes("rotate(x ")) throw new SyntaxError(`Failed to parse '${init}'`);
|
||||
super(init);
|
||||
}
|
||||
}
|
||||
Object.assign(w, { DOMMatrix: ChromeLike });
|
||||
};
|
||||
const { newX, newY } = committedPosition(
|
||||
"rotate: x 30deg; translate: 0 -200px; transform: translate(-60px, 10px);",
|
||||
40,
|
||||
30,
|
||||
rejectAxisRotate,
|
||||
);
|
||||
expect({ x: newX, y: newY }).toEqual({ x: -20, y: 40 });
|
||||
});
|
||||
|
||||
it("reads the base from GSAP itself once the page has it", () => {
|
||||
const { newX, newY } = committedPosition("translate: 0 -200px;", 40, 30, { x: 7, y: 5 });
|
||||
const base: Record<string, number> = { x: 7, y: 5 };
|
||||
const gsap = {
|
||||
set: () => undefined,
|
||||
getProperty: (_el: Element, prop: string) => base[prop] ?? 0,
|
||||
};
|
||||
const { newX, newY } = committedPosition("translate: 0 -200px;", 40, 30, (w) =>
|
||||
Object.assign(w, { gsap }),
|
||||
);
|
||||
expect({ x: newX, y: newY }).toEqual({ x: 47, y: 35 });
|
||||
});
|
||||
|
||||
|
||||
@@ -30,18 +30,28 @@ const cssValue = (style: CSSStyleDeclaration, prop: string) => {
|
||||
return value === "none" ? "" : value;
|
||||
};
|
||||
|
||||
function foldedTransformTranslation(view: Window & typeof globalThis, style: CSSStyleDeclaration) {
|
||||
function transformTranslation(view: Window & typeof globalThis, list: string) {
|
||||
const matrix = list ? new view.DOMMatrix(list) : null;
|
||||
return { x: matrix?.m41 ?? 0, y: matrix?.m42 ?? 0 };
|
||||
}
|
||||
|
||||
// GSAP writes `translate rotate scale transform` as one inline transform; a value the browser rejects
|
||||
// (e.g. `rotate: x 30deg`) drops the whole string, leaving GSAP only the plain transform.
|
||||
function foldedTranslation(view: Window & typeof globalThis, style: CSSStyleDeclaration) {
|
||||
const [tx = "", ty = ""] = cssValue(style, "translate").split(/\s+/);
|
||||
const rotate = cssValue(style, "rotate");
|
||||
const scale = cssValue(style, "scale");
|
||||
const transform = cssValue(style, "transform");
|
||||
const folded = [
|
||||
rotate && `rotate(${rotate})`,
|
||||
scale && `scale(${scale.split(/\s+/).join(",")})`,
|
||||
cssValue(style, "transform"),
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
const matrix = folded ? new view.DOMMatrix(folded) : null;
|
||||
return { x: matrix?.m41 ?? 0, y: matrix?.m42 ?? 0 };
|
||||
transform,
|
||||
];
|
||||
try {
|
||||
return { tx, ty, ...transformTranslation(view, folded.join(" ").trim()) };
|
||||
} catch {
|
||||
return { tx: "", ty: "", ...transformTranslation(view, transform) };
|
||||
}
|
||||
}
|
||||
|
||||
function foldedAxis(translate: string, fromTransform: number, size: number): number {
|
||||
@@ -55,12 +65,10 @@ function foldedAxis(translate: string, fromTransform: number, size: number): num
|
||||
function readGsapFoldedCssPosition(element: HTMLElement): { x: number; y: number } {
|
||||
const view = element.ownerDocument.defaultView as (Window & typeof globalThis) | null;
|
||||
if (!view) return { x: 0, y: 0 };
|
||||
const style = view.getComputedStyle(element);
|
||||
const [tx = "", ty = ""] = cssValue(style, "translate").split(/\s+/);
|
||||
const moved = foldedTransformTranslation(view, style);
|
||||
const moved = foldedTranslation(view, view.getComputedStyle(element));
|
||||
return {
|
||||
x: foldedAxis(tx, moved.x, element.offsetWidth),
|
||||
y: foldedAxis(ty, moved.y, element.offsetHeight),
|
||||
x: foldedAxis(moved.tx, moved.x, element.offsetWidth),
|
||||
y: foldedAxis(moved.ty, moved.y, element.offsetHeight),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user