mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
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.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
||||
import { Plus, X } from "../../icons/SystemIcons";
|
||||
import { isTextEditableSelection, type DomEditSelection } from "./domEditing";
|
||||
@@ -257,10 +257,12 @@ export function FlatTextSection({
|
||||
const [activeFieldKey, setActiveFieldKey] = useState<string | null>(
|
||||
element.textFields[0]?.key ?? null,
|
||||
);
|
||||
// Armed by the add handler, read and cleared by the render that first shows
|
||||
// the new field. A ref rather than state: the marker only has to survive one
|
||||
// render, and clearing it afterwards would be a state-syncing effect.
|
||||
const autoFocusFieldKeyRef = useRef<string | null>(null);
|
||||
// Armed by the add handler so the newly added field mounts focused. State, not
|
||||
// a ref cleared during render: Strict Mode renders twice, so the first pass
|
||||
// would eat the marker and the second would mount the field unfocused. Nothing
|
||||
// clears it on read either — `autoFocus` is a mount-only DOM prop and the
|
||||
// editor is keyed on the field, so it can only fire once per added field.
|
||||
const [autoFocusFieldKey, setAutoFocusFieldKey] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const nextFields = element.textFields;
|
||||
@@ -275,8 +277,7 @@ export function FlatTextSection({
|
||||
const activeField = textFields.find((field) => field.key === activeFieldKey) ?? textFields[0];
|
||||
if (!activeField) return null;
|
||||
|
||||
const autoFocusActiveField = autoFocusFieldKeyRef.current === activeField.key;
|
||||
if (autoFocusActiveField) autoFocusFieldKeyRef.current = null;
|
||||
const autoFocusActiveField = autoFocusFieldKey === activeField.key;
|
||||
|
||||
if (textFields.length > 1) {
|
||||
return (
|
||||
@@ -286,13 +287,13 @@ export function FlatTextSection({
|
||||
activeFieldKey={activeField.key}
|
||||
styles={styles}
|
||||
onSelect={(fieldKey) => {
|
||||
autoFocusFieldKeyRef.current = null;
|
||||
setAutoFocusFieldKey(null);
|
||||
setActiveFieldKey(fieldKey);
|
||||
}}
|
||||
onAdd={() =>
|
||||
void Promise.resolve(onAddTextField(activeField.key)).then((nextKey) => {
|
||||
if (!nextKey) return;
|
||||
autoFocusFieldKeyRef.current = nextKey;
|
||||
setAutoFocusFieldKey(nextKey);
|
||||
setActiveFieldKey(nextKey);
|
||||
})
|
||||
}
|
||||
@@ -330,7 +331,7 @@ export function FlatTextSection({
|
||||
track("button", "Add text field");
|
||||
void Promise.resolve(onAddTextField(activeField.key)).then((nextKey) => {
|
||||
if (!nextKey) return;
|
||||
autoFocusFieldKeyRef.current = nextKey;
|
||||
setAutoFocusFieldKey(nextKey);
|
||||
setActiveFieldKey(nextKey);
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -512,10 +512,14 @@ describe("useTimelineEditCallbacks — flat tween keyframe lanes", () => {
|
||||
),
|
||||
).resolves.toBe(true);
|
||||
|
||||
// The whole point of the clip basis: the drop lands at 10.94 + 0.40 * 16.26 =
|
||||
// 17.444s, and the duration-less tween borrows the clip's 16.26s window from
|
||||
// its 3.2s start, so 17.444 - 3.2 over 16.26 is 87.601%. Any other basis (a
|
||||
// zero-length tween, or the clip's own 0-100 %) produces a different number.
|
||||
expect(mocks.actions.handleGsapMoveKeyframe).toHaveBeenCalledWith(
|
||||
durationless.id,
|
||||
50,
|
||||
expect.any(Number),
|
||||
expect.closeTo(87.601, 3),
|
||||
mocks.selection,
|
||||
);
|
||||
expect(mocks.actions.handleGsapResizeKeyframedTween).not.toHaveBeenCalled();
|
||||
|
||||
@@ -301,20 +301,6 @@ export function useTimelineEditCallbacks({
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
onChangeKeyframeEase: (elId: string, _pct: number, ease: string) => {
|
||||
// The edited element's own animations + selection, not the selection's:
|
||||
// an ease change on a non-selected lane otherwise rewrote whichever
|
||||
// element happened to be selected, in whichever file it lives.
|
||||
const animations = resolveElementAnimations(elId);
|
||||
const element = usePlayerStore.getState().elements.find((el) => (el.key ?? el.id) === elId);
|
||||
if (!element) return;
|
||||
void buildDomSelectionForTimelineElement(element).then((selection) => {
|
||||
if (!selection) return;
|
||||
for (const anim of animations) {
|
||||
if (anim.keyframes) handleGsapUpdateMeta(anim.id, { ease }, selection);
|
||||
}
|
||||
});
|
||||
},
|
||||
// fallow-ignore-next-line complexity
|
||||
onToggleKeyframeAtPlayhead: (el: TimelineElement) => {
|
||||
const currentTime = usePlayerStore.getState().currentTime;
|
||||
|
||||
Reference in New Issue
Block a user