diff --git a/packages/studio/src/components/nle/useTimelineEditCallbacks.ts b/packages/studio/src/components/nle/useTimelineEditCallbacks.ts index 7ca47e0bf..635f31675 100644 --- a/packages/studio/src/components/nle/useTimelineEditCallbacks.ts +++ b/packages/studio/src/components/nle/useTimelineEditCallbacks.ts @@ -313,7 +313,7 @@ export function useTimelineEditCallbacks({ : 0; // Same frame for read and write: the toggled element's animations decide // add-vs-remove, and its selection is what the mutation commits through. - const animations = resolveElementAnimations(el.key ?? el.id); + const animations = resolveElementAnimations(getTimelineElementIdentity(el)); void buildDomSelectionForTimelineElement(el).then((selection) => { if (!selection) return; const anim = animations.find((a) => a.keyframes); diff --git a/packages/studio/src/hooks/useGsapSelectionHandlers.test.tsx b/packages/studio/src/hooks/useGsapSelectionHandlers.test.tsx index e2bd9628a..6dc247b36 100644 --- a/packages/studio/src/hooks/useGsapSelectionHandlers.test.tsx +++ b/packages/studio/src/hooks/useGsapSelectionHandlers.test.tsx @@ -163,3 +163,20 @@ describe("useGsapSelectionHandlers retime settlement", () => { withSelection.unmount(); }); }); + +describe("useGsapSelectionHandlers selection override", () => { + it("aborts on an explicit null override instead of writing to the current selection", () => { + const removeKeyframe = vi.fn(); + const rendered = renderHandlers(makeParams({ removeKeyframe })); + + // Explicit null: the caller resolved a selection for its own element and + // found none, so the write must not land on the selected element. + rendered.handlers().handleGsapRemoveKeyframe("anim-1", 50, undefined, null); + expect(removeKeyframe).not.toHaveBeenCalled(); + + // Omitted override: falls back to the current selection as before. + rendered.handlers().handleGsapRemoveKeyframe("anim-1", 50); + expect(removeKeyframe).toHaveBeenCalledOnce(); + rendered.unmount(); + }); +}); diff --git a/packages/studio/src/hooks/useGsapSelectionHandlers.ts b/packages/studio/src/hooks/useGsapSelectionHandlers.ts index 857cfdaac..7247433d4 100644 --- a/packages/studio/src/hooks/useGsapSelectionHandlers.ts +++ b/packages/studio/src/hooks/useGsapSelectionHandlers.ts @@ -418,7 +418,7 @@ export function useGsapSelectionHandlers({ const handleGsapRemoveAllKeyframes = useCallback( (animId: string, selectionOverride?: DomEditSelection | null) => { - const selection = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; + const selection = resolveWriteSelection(selectionOverride); if (!selection) return; observeGsapMutation( removeAllKeyframes(selection, animId), @@ -427,7 +427,7 @@ export function useGsapSelectionHandlers({ "Remove all keyframes", ); }, - [domEditSelection, observeGsapMutation, removeAllKeyframes], + [resolveWriteSelection, observeGsapMutation, removeAllKeyframes], ); const handleResetSelectedElementKeyframes = useCallback((): boolean => { diff --git a/packages/studio/src/player/components/TimelineTrackHeader.tsx b/packages/studio/src/player/components/TimelineTrackHeader.tsx index 205203901..6b2c0fb37 100644 --- a/packages/studio/src/player/components/TimelineTrackHeader.tsx +++ b/packages/studio/src/player/components/TimelineTrackHeader.tsx @@ -178,6 +178,7 @@ function PropertyGroupHeaderRow({ currentTime, clipPercentage, gutterBackground, + columnWidth, onTogglePropertyGroupKeyframe, onSeek, }: { @@ -188,6 +189,7 @@ function PropertyGroupHeaderRow({ currentTime: number; clipPercentage: number; gutterBackground: string; + columnWidth: number; onTogglePropertyGroupKeyframe?: TimelineEditCallbacks["onTogglePropertyGroupKeyframe"]; onSeek?: (time: number) => void; }) { @@ -201,10 +203,13 @@ function PropertyGroupHeaderRow({