Files
hyperframes/packages/studio/src/hooks/deleteSelectedKeyframes.ts
T
Miguel Angel Simon Sierra 6b58678d94 fix(studio): drop dead keyframe menu wiring and close the R1 review items
The diamond context menu still declared `onChangeEase` and `onCopyProperties`
props, and `TimelineOverlays` still threaded `onChangeKeyframeEase` plus a
`keyframeCache` it never read. Nothing on any timeline branch calls them, so
they are removed along with the `onChangeKeyframeEase` callback implementation.

Also from review:
- `deleteSelectedKeyframes` only falls back to the sole keyframed animation when
  there is exactly one. A collapsed selection key carries no animation id, so
  taking the first of several deleted an arbitrary tween's keyframe.
- The duration-less retime test asserts the real 87.601% instead of
  `expect.any(Number)`, so a wrong timing basis fails it.
- `Timeline` wires the keyframe handlers' `onSelectSegment` through to the
  diamonds; it was built and then dropped, so segment ease selection never fired.
- The flat text section arms auto-focus in state rather than reading and
  clearing a ref during render, which Strict Mode's double render swallowed.
2026-07-28 00:37:31 +02:00

52 lines
2.4 KiB
TypeScript

import { usePlayerStore } from "../player/store/playerStore";
import { timelineKeyframeTargetFromSelectionKey } from "../player/components/timelineKeyframeIdentity";
import type { CommitMutationOptions } from "./gsapScriptCommitTypes";
let deleteKeyframesCommitCounter = 0;
/**
* Remove the keyframes currently selected in the player store from the active
* element's GSAP animation. Reads selection lazily so it stays correct when
* invoked from a ref callback.
*/
export function deleteSelectedKeyframes(session: {
selectedGsapAnimations: readonly { id: string; keyframes?: unknown }[];
handleGsapRemoveKeyframe: (
animId: string,
pct: number,
options?: Partial<CommitMutationOptions>,
) => void;
}): void {
const { selectedKeyframes, selectedElementId } = usePlayerStore.getState();
if (!selectedElementId) return;
const keyframedAnimations = session.selectedGsapAnimations.filter((anim) => anim.keyframes);
// A collapsed selection key (an ungrouped animation) carries no animation id,
// so it only resolves when there is exactly one keyframed animation it could
// mean. Taking the first of several deletes an arbitrary tween's keyframe.
const fallbackAnimation = keyframedAnimations.length === 1 ? keyframedAnimations[0] : undefined;
const animationsById = new Map(keyframedAnimations.map((animation) => [animation.id, animation]));
const removals = new Map<string, { animationId: string; percentage: number }>();
for (const key of selectedKeyframes) {
const target = timelineKeyframeTargetFromSelectionKey(selectedElementId, key);
if (!target) continue;
const animation = target.animationId
? animationsById.get(target.animationId)
: fallbackAnimation;
if (!animation) continue;
const percentage = target.tweenPercentage ?? target.percentage;
removals.set(`${animation.id}\0${percentage}`, { animationId: animation.id, percentage });
}
const targets = [...removals.values()];
if (targets.length === 0) return;
const coalesceOptions = {
coalesceKey: `delete-keyframes:${++deleteKeyframesCommitCounter}`,
coalesceMs: Number.POSITIVE_INFINITY,
};
for (const [index, target] of targets.entries()) {
session.handleGsapRemoveKeyframe(target.animationId, target.percentage, {
...coalesceOptions,
...(index === targets.length - 1 ? { softReload: true } : { skipReload: true }),
});
}
}