Skip to content

Commit 20e711a

Browse files
committed
fix(studio): commit lane edits through the edited element's own selection
An explicit null selection override now aborts the write instead of falling back to domEditSelection: a caller that resolved a selection for its own element and found none was committing onto whichever element happened to be selected. Ease changes and the playhead keyframe toggle resolve the edited element's animations and selection instead of the current selection's, and lane header rows follow the real label-column width so a narrowed column no longer hangs its value readout over the canvas.
1 parent 284f48f commit 20e711a

4 files changed

Lines changed: 28 additions & 5 deletions

File tree

packages/studio/src/components/nle/useTimelineEditCallbacks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ export function useTimelineEditCallbacks({
327327
: 0;
328328
// Same frame for read and write: the toggled element's animations decide
329329
// add-vs-remove, and its selection is what the mutation commits through.
330-
const animations = resolveElementAnimations(el.key ?? el.id);
330+
const animations = resolveElementAnimations(getTimelineElementIdentity(el));
331331
void buildDomSelectionForTimelineElement(el).then((selection) => {
332332
if (!selection) return;
333333
const anim = animations.find((a) => a.keyframes);

packages/studio/src/hooks/useGsapSelectionHandlers.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,20 @@ describe("useGsapSelectionHandlers retime settlement", () => {
163163
withSelection.unmount();
164164
});
165165
});
166+
167+
describe("useGsapSelectionHandlers selection override", () => {
168+
it("aborts on an explicit null override instead of writing to the current selection", () => {
169+
const removeKeyframe = vi.fn();
170+
const rendered = renderHandlers(makeParams({ removeKeyframe }));
171+
172+
// Explicit null: the caller resolved a selection for its own element and
173+
// found none, so the write must not land on the selected element.
174+
rendered.handlers().handleGsapRemoveKeyframe("anim-1", 50, undefined, null);
175+
expect(removeKeyframe).not.toHaveBeenCalled();
176+
177+
// Omitted override: falls back to the current selection as before.
178+
rendered.handlers().handleGsapRemoveKeyframe("anim-1", 50);
179+
expect(removeKeyframe).toHaveBeenCalledOnce();
180+
rendered.unmount();
181+
});
182+
});

packages/studio/src/hooks/useGsapSelectionHandlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ export function useGsapSelectionHandlers({
418418

419419
const handleGsapRemoveAllKeyframes = useCallback(
420420
(animId: string, selectionOverride?: DomEditSelection | null) => {
421-
const selection = selectionOverride ?? domEditSelection ?? lastSelectionRef.current;
421+
const selection = resolveWriteSelection(selectionOverride);
422422
if (!selection) return;
423423
observeGsapMutation(
424424
removeAllKeyframes(selection, animId),
@@ -427,7 +427,7 @@ export function useGsapSelectionHandlers({
427427
"Remove all keyframes",
428428
);
429429
},
430-
[domEditSelection, observeGsapMutation, removeAllKeyframes],
430+
[resolveWriteSelection, observeGsapMutation, removeAllKeyframes],
431431
);
432432

433433
const handleResetSelectedElementKeyframes = useCallback((): boolean => {

packages/studio/src/player/components/TimelineTrackHeader.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ function PropertyGroupHeaderRow({
178178
currentTime,
179179
clipPercentage,
180180
gutterBackground,
181+
columnWidth,
181182
onTogglePropertyGroupKeyframe,
182183
onSeek,
183184
}: {
@@ -188,6 +189,7 @@ function PropertyGroupHeaderRow({
188189
currentTime: number;
189190
clipPercentage: number;
190191
gutterBackground: string;
192+
columnWidth: number;
191193
onTogglePropertyGroupKeyframe?: TimelineEditCallbacks["onTogglePropertyGroupKeyframe"];
192194
onSeek?: (time: number) => void;
193195
}) {
@@ -201,10 +203,13 @@ function PropertyGroupHeaderRow({
201203
<div
202204
data-property-group={lane.group}
203205
data-timeline-lane-top={getTimelineLaneTop(laneIndex)}
204-
className="absolute left-0 flex items-center gap-1 px-1.5 text-[10px] text-white/65"
206+
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-white/65"
205207
style={{
206208
top: getTimelineLaneTop(laneIndex),
207-
width: LABEL_COL_W,
209+
// The header column narrows to contentOrigin whenever that is under
210+
// LABEL_COL_W; a lane row pinned to LABEL_COL_W then hangs its value
211+
// readout over the canvas, on top of the clips it is labelling.
212+
width: columnWidth,
208213
height: LANE_H,
209214
background: gutterBackground,
210215
}}
@@ -339,6 +344,7 @@ export function TimelineTrackHeader({
339344
currentTime={currentTime}
340345
clipPercentage={clipPercentage}
341346
gutterBackground={theme.gutterBackground}
347+
columnWidth={showTrackLabel ? LABEL_COL_W : contentOrigin}
342348
onTogglePropertyGroupKeyframe={onTogglePropertyGroupKeyframe}
343349
onSeek={onSeek}
344350
/>

0 commit comments

Comments
 (0)