mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
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:
@@ -3,9 +3,33 @@
|
||||
*/
|
||||
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
||||
import { classifyPropertyGroup, type PropertyGroupName } from "@hyperframes/core/gsap-parser";
|
||||
import { getIframeGsap, queryIframeElement } from "./gsapShared";
|
||||
import {
|
||||
COLOR_GRADING_SOURCE_HIDDEN_ATTR,
|
||||
HF_COLOR_GRADING_CANVAS_ID_PREFIX,
|
||||
} from "@hyperframes/core/color-grading";
|
||||
import { getIframeGsap, queryIframeElement, type IframeGsap } from "./gsapShared";
|
||||
import { roundTo3 } from "../utils/rounding";
|
||||
|
||||
/**
|
||||
* The element's live value for `prop` as GSAP drives it. Opacity on a
|
||||
* color-grading-hidden source needs a detour: the runtime hides the source
|
||||
* with inline `opacity: 0 !important`, so computed opacity is the hide, not
|
||||
* the animated value. The grading canvas mirrors the source's effective
|
||||
* opacity every frame, so it is the truth for that one property — reading the
|
||||
* raw 0 here is what bakes `opacity: 0` into committed keyframes.
|
||||
*/
|
||||
function readLiveGsapValue(gsap: IframeGsap, el: Element, prop: string): number {
|
||||
if (prop === "opacity" && el.getAttribute(COLOR_GRADING_SOURCE_HIDDEN_ATTR) != null && el.id) {
|
||||
const canvas = el.ownerDocument.getElementById(HF_COLOR_GRADING_CANVAS_ID_PREFIX + el.id);
|
||||
const win = el.ownerDocument.defaultView;
|
||||
if (canvas && win) {
|
||||
const val = Number(win.getComputedStyle(canvas).opacity);
|
||||
if (Number.isFinite(val)) return val;
|
||||
}
|
||||
}
|
||||
return Number(gsap.getProperty(el, prop));
|
||||
}
|
||||
|
||||
export function readGsapProperty(
|
||||
iframe: HTMLIFrameElement | null,
|
||||
selector: string | null,
|
||||
@@ -17,7 +41,7 @@ export function readGsapProperty(
|
||||
const el = queryIframeElement(iframe, selector);
|
||||
if (!el) return null;
|
||||
try {
|
||||
const val = Number(gsap.getProperty(el, prop));
|
||||
const val = readLiveGsapValue(gsap, el, prop);
|
||||
if (!Number.isFinite(val)) return null;
|
||||
return POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
|
||||
} catch {
|
||||
@@ -77,15 +101,17 @@ export function readAllAnimatedProperties(
|
||||
for (const p of Object.keys(anim.properties)) propKeys.add(p);
|
||||
}
|
||||
|
||||
// When a group filter is specified, only keep properties belonging to that group.
|
||||
if (group) {
|
||||
for (const p of propKeys) {
|
||||
if (classifyPropertyGroup(p) !== group) propKeys.delete(p);
|
||||
}
|
||||
// When a group filter is specified, only properties belonging to that group
|
||||
// may enter the result — including the baseline passes below. The whole
|
||||
// point of property-group tweens is that a rotation commit never carries
|
||||
// opacity/rotationX/etc. captured from unrelated tweens on the element.
|
||||
const inGroup = (p: string) => !group || classifyPropertyGroup(p) === group;
|
||||
for (const p of propKeys) {
|
||||
if (!inGroup(p)) propKeys.delete(p);
|
||||
}
|
||||
|
||||
for (const prop of propKeys) {
|
||||
const val = Number(gsap.getProperty(el, prop));
|
||||
const val = readLiveGsapValue(gsap, el, prop);
|
||||
if (Number.isFinite(val)) {
|
||||
result[prop] = POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
|
||||
}
|
||||
@@ -110,7 +136,7 @@ export function readAllAnimatedProperties(
|
||||
const vars = child.vars;
|
||||
if (!vars) continue;
|
||||
for (const k of Object.keys(vars)) {
|
||||
if (!GSAP_CONFIG_KEYS.has(k)) otherTweenProps.add(k);
|
||||
if (!GSAP_CONFIG_KEYS.has(k) && inGroup(k)) otherTweenProps.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +178,7 @@ export function readAllAnimatedProperties(
|
||||
for (const [prop, defaultVal] of Object.entries(UNIVERSAL_BASELINE)) {
|
||||
if (prop in result) continue;
|
||||
if (!allTweenedProps.has(prop)) continue;
|
||||
const val = Number(gsap.getProperty(el, prop));
|
||||
const val = readLiveGsapValue(gsap, el, prop);
|
||||
if (Number.isFinite(val) && Math.round(val * 1000) !== Math.round(defaultVal * 1000)) {
|
||||
result[prop] = roundTo3(val);
|
||||
}
|
||||
@@ -184,6 +210,7 @@ export function readAllAnimatedProperties(
|
||||
} catch {}
|
||||
for (const prop of COMPUTED_BASELINE) {
|
||||
if (prop in result) continue;
|
||||
if (!inGroup(prop)) continue;
|
||||
if (otherTweenProps.has(prop)) continue;
|
||||
const gsapVal = Number(gsap.getProperty(el, prop));
|
||||
if (!Number.isFinite(gsapVal)) continue;
|
||||
|
||||
Reference in New Issue
Block a user