import { useCallback, useRef } from "react"; import type { DomEditSelection } from "../components/editor/domEditing"; import { usePlayerStore } from "../player"; import { trackStudioSaveFailure } from "../utils/studioSaveDiagnostics"; /** * Thin useCallback wrappers that guard on `domEditSelection` before * delegating to the underlying GSAP script-commit functions. Extracted * from useDomEditSession to keep that file under the 600-line limit. */ // fallow-ignore-next-line complexity export function useGsapSelectionHandlers({ domEditSelection, updateGsapProperty, updateGsapMeta, deleteGsapAnimation, deleteAllForSelector, addGsapAnimation, addGsapProperty, removeGsapProperty, updateGsapFromProperty, addGsapFromProperty, removeGsapFromProperty, addKeyframe, addKeyframeBatch, removeKeyframe, convertToKeyframes, removeAllKeyframes, handleDomManualEditsReset, selectedGsapAnimations, }: { domEditSelection: DomEditSelection | null; updateGsapProperty: ( sel: DomEditSelection, animId: string, prop: string, value: number | string, ) => void; updateGsapMeta: ( sel: DomEditSelection, animId: string, updates: { duration?: number; ease?: string; position?: number }, ) => void; deleteGsapAnimation: (sel: DomEditSelection, animId: string) => void; deleteAllForSelector: (sel: DomEditSelection, targetSelector: string) => void; addGsapAnimation: ( sel: DomEditSelection, method: "to" | "from" | "set" | "fromTo", time: number, ) => Promise; addGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void; removeGsapProperty: (sel: DomEditSelection, animId: string, prop: string) => void; updateGsapFromProperty: ( sel: DomEditSelection, animId: string, prop: string, value: number | string, ) => void; addGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void; removeGsapFromProperty: (sel: DomEditSelection, animId: string, prop: string) => void; addKeyframe: ( sel: DomEditSelection, animId: string, percentage: number, property: string, value: number | string, ) => void; addKeyframeBatch: ( sel: DomEditSelection, animId: string, percentage: number, properties: Record, ) => Promise; removeKeyframe: (sel: DomEditSelection, animId: string, percentage: number) => void; convertToKeyframes: ( sel: DomEditSelection, animId: string, resolvedFromValues?: Record, ) => Promise; removeAllKeyframes: (sel: DomEditSelection, animId: string) => void; handleDomManualEditsReset: (sel: DomEditSelection) => void; selectedGsapAnimations: { id: string; keyframes?: unknown }[]; }) { const lastSelectionRef = useRef(null); if (domEditSelection) lastSelectionRef.current = domEditSelection; const trackGsapHandlerFailure = useCallback( (error: unknown, selection: DomEditSelection, mutationType: string, label: string) => { trackStudioSaveFailure({ source: "gsap_commit", error, filePath: selection.sourceFile ?? undefined, mutationType, label, targetId: selection.id, targetSelector: selection.selector, targetSourceFile: selection.sourceFile, }); }, [], ); const handleGsapUpdateProperty = useCallback( (animId: string, prop: string, value: number | string) => { if (!domEditSelection) return; updateGsapProperty(domEditSelection, animId, prop, value); }, [domEditSelection, updateGsapProperty], ); const handleGsapUpdateMeta = useCallback( ( animId: string, updates: { duration?: number; ease?: string; position?: number }, selectionOverride?: DomEditSelection | null, ) => { const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; if (!sel) return; updateGsapMeta(sel, animId, updates); }, [domEditSelection, updateGsapMeta], ); const handleGsapDeleteAnimation = useCallback( (animId: string) => { const sel = domEditSelection ?? lastSelectionRef.current; if (!sel) return; deleteGsapAnimation(sel, animId); }, [domEditSelection, deleteGsapAnimation], ); const handleGsapDeleteAllForElement = useCallback( (targetSelector: string) => { const sel = domEditSelection ?? lastSelectionRef.current; if (!sel) return; deleteAllForSelector(sel, targetSelector); }, [domEditSelection, deleteAllForSelector], ); const handleGsapAddAnimation = useCallback( (method: "to" | "from" | "set" | "fromTo") => { if (!domEditSelection) return; void addGsapAnimation(domEditSelection, method, usePlayerStore.getState().currentTime).catch( (error) => { trackGsapHandlerFailure(error, domEditSelection, "add", `Add GSAP ${method} animation`); }, ); if (domEditSelection.element.hasAttribute("data-hf-studio-path-offset")) { handleDomManualEditsReset(domEditSelection); } }, [domEditSelection, addGsapAnimation, handleDomManualEditsReset, trackGsapHandlerFailure], ); const handleGsapAddProperty = useCallback( (animId: string, prop: string) => { if (!domEditSelection) return; addGsapProperty(domEditSelection, animId, prop); }, [domEditSelection, addGsapProperty], ); const handleGsapRemoveProperty = useCallback( (animId: string, prop: string) => { if (!domEditSelection) return; removeGsapProperty(domEditSelection, animId, prop); }, [domEditSelection, removeGsapProperty], ); const handleGsapUpdateFromProperty = useCallback( (animId: string, prop: string, value: number | string) => { if (!domEditSelection) return; updateGsapFromProperty(domEditSelection, animId, prop, value); }, [domEditSelection, updateGsapFromProperty], ); const handleGsapAddFromProperty = useCallback( (animId: string, prop: string) => { if (!domEditSelection) return; addGsapFromProperty(domEditSelection, animId, prop); }, [domEditSelection, addGsapFromProperty], ); const handleGsapRemoveFromProperty = useCallback( (animId: string, prop: string) => { if (!domEditSelection) return; removeGsapFromProperty(domEditSelection, animId, prop); }, [domEditSelection, removeGsapFromProperty], ); const handleGsapAddKeyframe = useCallback( ( animId: string, percentage: number, property: string, value: number | string, selectionOverride?: DomEditSelection | null, ) => { const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; if (!sel) return; addKeyframe(sel, animId, percentage, property, value); }, [domEditSelection, addKeyframe], ); const handleGsapAddKeyframeBatch = useCallback( (animId: string, percentage: number, properties: Record) => { if (!domEditSelection) return Promise.resolve(); return addKeyframeBatch(domEditSelection, animId, percentage, properties).catch((error) => { trackGsapHandlerFailure(error, domEditSelection, "add-keyframe", "Add keyframe"); }); }, [domEditSelection, addKeyframeBatch, trackGsapHandlerFailure], ); const handleGsapRemoveKeyframe = useCallback( (animId: string, percentage: number, selectionOverride?: DomEditSelection | null) => { const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; if (!sel) return; removeKeyframe(sel, animId, percentage); }, [domEditSelection, removeKeyframe], ); const handleGsapConvertToKeyframes = useCallback( (animId: string, resolvedFromValues?: Record) => { if (!domEditSelection) return Promise.resolve(); return convertToKeyframes(domEditSelection, animId, resolvedFromValues).catch((error) => { trackGsapHandlerFailure( error, domEditSelection, "convert-to-keyframes", "Convert to keyframes", ); }); }, [domEditSelection, convertToKeyframes, trackGsapHandlerFailure], ); const handleGsapRemoveAllKeyframes = useCallback( (animId: string) => { if (!domEditSelection) return; removeAllKeyframes(domEditSelection, animId); }, [domEditSelection, removeAllKeyframes], ); const handleResetSelectedElementKeyframes = useCallback((): boolean => { if (!domEditSelection) return false; const withKeyframes = selectedGsapAnimations.find((a) => a.keyframes); if (!withKeyframes) return false; removeAllKeyframes(domEditSelection, withKeyframes.id); return true; }, [domEditSelection, selectedGsapAnimations, removeAllKeyframes]); return { handleGsapUpdateProperty, handleGsapUpdateMeta, handleGsapDeleteAnimation, handleGsapDeleteAllForElement, handleGsapAddAnimation, handleGsapAddProperty, handleGsapRemoveProperty, handleGsapUpdateFromProperty, handleGsapAddFromProperty, handleGsapRemoveFromProperty, handleGsapAddKeyframe, handleGsapAddKeyframeBatch, handleGsapRemoveKeyframe, handleGsapConvertToKeyframes, handleGsapRemoveAllKeyframes, handleResetSelectedElementKeyframes, }; }