mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
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.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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({
|
||||
<div
|
||||
data-property-group={lane.group}
|
||||
data-timeline-lane-top={getTimelineLaneTop(laneIndex)}
|
||||
className="absolute left-0 flex items-center gap-1 px-1.5 text-[10px] text-white/65"
|
||||
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-white/65"
|
||||
style={{
|
||||
top: getTimelineLaneTop(laneIndex),
|
||||
width: LABEL_COL_W,
|
||||
// The header column narrows to contentOrigin whenever that is under
|
||||
// LABEL_COL_W; a lane row pinned to LABEL_COL_W then hangs its value
|
||||
// readout over the canvas, on top of the clips it is labelling.
|
||||
width: columnWidth,
|
||||
height: LANE_H,
|
||||
background: gutterBackground,
|
||||
}}
|
||||
@@ -339,6 +344,7 @@ export function TimelineTrackHeader({
|
||||
currentTime={currentTime}
|
||||
clipPercentage={clipPercentage}
|
||||
gutterBackground={theme.gutterBackground}
|
||||
columnWidth={showTrackLabel ? LABEL_COL_W : contentOrigin}
|
||||
onTogglePropertyGroupKeyframe={onTogglePropertyGroupKeyframe}
|
||||
onSeek={onSeek}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user