mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): close the review findings in this PR instead of at the stack tip
The R1/R3 residuals on this PR were fixed at the top of the stack, so they only cleared once every branch above landed. They belong here, next to the code they correct: - `idFromSelector` inverts `idSelector` for both regex readers, so the post-commit cache refresh stops skipping the CSS-unsafe ids `idSelector` exists to support. - `deduplicateKeyframes` drops `ease` when it is ambiguous; the flag was the only honest answer and the last-writer-wins curve belonged to an arbitrary colliding tween. - `isStaticPositionHold` is now the single owner of the hold skip. The `sourceAnimations` filter and the `allKeyframes` filter had diverged on whether `immediateRender` counts as a property. - The keyframe-cache setters no-op when the write changes nothing, instead of handing every subscriber a fresh Map. - `reset()` clears `focusedEaseSegment`. - The test hook `delete`s its window key rather than setting it to undefined, so feature detection still works. - The `toClipKeyframes` fixture uses `as unknown as T` with the justification CONTRIBUTING.md asks for.
This commit is contained in:
@@ -5,6 +5,23 @@ import type {
|
||||
} from "@hyperframes/core/gsap-parser";
|
||||
import { PROPERTY_DEFAULTS } from "./gsapShared";
|
||||
|
||||
/**
|
||||
* A static position hold (only x/y, no real motion) is a `set`, not a keyframe —
|
||||
* it must not synthesize a diamond. Covers both `tl.set(...)` and the
|
||||
* `tl.to({ duration: 0, immediateRender: true })` hold that remove-all-keyframes
|
||||
* collapses to (otherwise shown as a stray 0% keyframe).
|
||||
*
|
||||
* Single owner: the collapsed keyframe cache and the expanded property lanes'
|
||||
* `gsapAnimations` map MUST agree on it, or a hold draws a phantom expanded lane
|
||||
* with no matching collapsed diamond.
|
||||
*/
|
||||
export function isStaticPositionHold(anim: GsapAnimation): boolean {
|
||||
if (anim.keyframes) return false;
|
||||
if (anim.method !== "set" && (anim.duration ?? 0) !== 0) return false;
|
||||
const propKeys = Object.keys(anim.properties).filter((k) => k !== "immediateRender");
|
||||
return propKeys.length > 0 && propKeys.every((k) => k === "x" || k === "y");
|
||||
}
|
||||
|
||||
export function deduplicateKeyframes<
|
||||
T extends GsapPercentageKeyframe & { animationId?: string; easeAmbiguous?: boolean },
|
||||
>(keyframes: T[]): T[] {
|
||||
@@ -25,7 +42,14 @@ export function deduplicateKeyframes<
|
||||
) {
|
||||
existing.easeAmbiguous = true;
|
||||
}
|
||||
if (kf.ease) existing.ease = kf.ease;
|
||||
// Whichever tween iterated last used to win `ease`, so the merged
|
||||
// keyframe carried an arbitrary one of the colliding curves. Readers that
|
||||
// do not check easeAmbiguous (drag readouts, lane hints) then showed a
|
||||
// curve belonging to a different animation than the one an edit targets.
|
||||
// Drop it instead: ambiguous means "no single ease", and the flag is the
|
||||
// only honest answer.
|
||||
if (existing.easeAmbiguous) delete existing.ease;
|
||||
else if (kf.ease) existing.ease = kf.ease;
|
||||
} else {
|
||||
byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user