diff --git a/packages/studio/src/components/editor/PropertyPanel.tsx b/packages/studio/src/components/editor/PropertyPanel.tsx index bf77e7c71..07d7da520 100644 --- a/packages/studio/src/components/editor/PropertyPanel.tsx +++ b/packages/studio/src/components/editor/PropertyPanel.tsx @@ -11,6 +11,7 @@ import { readGsapBorderRadiusForPanel, } from "./propertyPanelHelpers"; import { MetricField, Section } from "./propertyPanelPrimitives"; +import { createTransformCommitHandlers } from "./propertyPanelTransformCommit"; import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser"; import { isMediaElement, MediaSection } from "./propertyPanelMediaSection"; import { TextSection, StyleSections } from "./propertyPanelSections"; @@ -158,66 +159,7 @@ export const PropertyPanel = memo(function PropertyPanel({ ? manualSize.height : (parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height); - const commitManualOffset = (axis: "x" | "y", nextValue: string) => { - const parsed = parsePxMetricValue(nextValue); - if (parsed == null) return; - if (onCommitAnimatedProperty && hasGsapAnimation) { - void onCommitAnimatedProperty(element, axis, parsed); - return; - } - if (gsapKeyframes && gsapAnimId && onAddKeyframe) { - const pct = Math.max(0, Math.min(100, Math.round(currentPct * 10) / 10)); - onAddKeyframe(gsapAnimId, pct, axis, parsed); - return; - } - if (hasGsapAnimation) { - showToast?.("Cannot edit position — animation callbacks not available"); - return; - } - const current = readStudioPathOffset(element.element); - void Promise.resolve( - onSetManualOffset(element, { - x: axis === "x" ? parsed : current.x, - y: axis === "y" ? parsed : current.y, - }), - ).catch(() => undefined); - }; - - // fallow-ignore-next-line complexity - const commitManualSize = (axis: "width" | "height", nextValue: string) => { - const parsed = parsePxMetricValue(nextValue); - if (parsed == null || parsed <= 0) return; - if (onCommitAnimatedProperty && hasGsapAnimation) { - void onCommitAnimatedProperty(element, axis, parsed); - return; - } - if (hasGsapAnimation) { - showToast?.("Cannot edit size — animation callbacks not available"); - return; - } - const current = readStudioBoxSize(element.element); - const width = - current.width > 0 - ? current.width - : (parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width); - const height = - current.height > 0 - ? current.height - : (parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height); - void Promise.resolve( - onSetManualSize(element, { - width: axis === "width" ? parsed : width, - height: axis === "height" ? parsed : height, - }), - ).catch(() => undefined); - }; - const manualRotation = readStudioRotation(element.element); - const commitManualRotation = (nextValue: string) => { - const parsed = Number.parseFloat(nextValue); - if (!Number.isFinite(parsed)) return; - void Promise.resolve(onSetManualRotation(element, { angle: parsed })).catch(() => undefined); - }; const elStart = Number.parseFloat(element?.dataAttributes?.start ?? "0") || 0; const elDuration = Number.parseFloat(element?.dataAttributes?.duration ?? "1") || 0; @@ -227,6 +169,21 @@ export const PropertyPanel = memo(function PropertyPanel({ const gsapKeyframes = gsapKfAnim?.keyframes?.keyframes ?? null; const gsapAnimId = gsapKfAnim?.id ?? gsapAnimations?.[0]?.id ?? null; const hasGsapAnimation = !!(gsapAnimId || gsapAnimations.length > 0); + const { commitManualOffset, commitManualSize, commitManualRotation } = + createTransformCommitHandlers({ + element, + styles, + hasGsapAnimation, + gsapAnimId, + gsapKeyframes, + currentPct, + onCommitAnimatedProperty, + onAddKeyframe, + onSetManualOffset, + onSetManualSize, + onSetManualRotation, + showToast, + }); const navKeyframes = cacheEntry?.keyframes ?? gsapKeyframes; const seekFromKfPct = (pct: number) => onSeekToTime?.(elStart + (pct / 100) * elDuration); diff --git a/packages/studio/src/components/editor/propertyPanelTransformCommit.ts b/packages/studio/src/components/editor/propertyPanelTransformCommit.ts new file mode 100644 index 000000000..ec4a948ab --- /dev/null +++ b/packages/studio/src/components/editor/propertyPanelTransformCommit.ts @@ -0,0 +1,129 @@ +import type { DomEditSelection } from "./domEditingTypes"; +import { readStudioBoxSize, readStudioPathOffset } from "./manualEdits"; +import { parsePxMetricValue, type PropertyPanelProps } from "./propertyPanelHelpers"; + +interface TransformCommitDeps { + element: DomEditSelection; + styles: Record; + hasGsapAnimation: boolean; + gsapAnimId: string | null; + gsapKeyframes: unknown; + currentPct: number; + onCommitAnimatedProperty: PropertyPanelProps["onCommitAnimatedProperty"]; + onAddKeyframe: PropertyPanelProps["onAddKeyframe"]; + onSetManualOffset: PropertyPanelProps["onSetManualOffset"]; + onSetManualSize: PropertyPanelProps["onSetManualSize"]; + onSetManualRotation: PropertyPanelProps["onSetManualRotation"]; + showToast?: (message: string, tone?: "error" | "info") => void; +} + +/** + * Build the X/Y, W/H and rotation field commit handlers for the property panel. + * Each handler routes the value into the GSAP animation when the element is + * animated (matching the drag gesture and keyframe buttons), and otherwise + * falls through to the manual transform setter. + */ +// fallow-ignore-next-line unit-size +export function createTransformCommitHandlers({ + element, + styles, + hasGsapAnimation, + gsapAnimId, + gsapKeyframes, + currentPct, + onCommitAnimatedProperty, + onAddKeyframe, + onSetManualOffset, + onSetManualSize, + onSetManualRotation, + showToast, +}: TransformCommitDeps) { + // Route a transform value into the GSAP animation (or a new keyframe) when the + // element is animated. Returns true when handled, so callers fall through to + // the manual-transform path only for non-animated elements. + const commitAnimatedTransformValue = ( + property: string, + value: number, + noCallbacksMessage: string, + ): boolean => { + if (onCommitAnimatedProperty && hasGsapAnimation) { + void onCommitAnimatedProperty(element, property, value); + return true; + } + if (gsapKeyframes && gsapAnimId && onAddKeyframe) { + const pct = Math.max(0, Math.min(100, Math.round(currentPct * 10) / 10)); + onAddKeyframe(gsapAnimId, pct, property, value); + return true; + } + if (hasGsapAnimation) { + showToast?.(noCallbacksMessage); + return true; + } + return false; + }; + + const commitManualOffset = (axis: "x" | "y", nextValue: string) => { + const parsed = parsePxMetricValue(nextValue); + if (parsed == null) return; + if ( + commitAnimatedTransformValue( + axis, + parsed, + "Cannot edit position — animation callbacks not available", + ) + ) + return; + const current = readStudioPathOffset(element.element); + void Promise.resolve( + onSetManualOffset(element, { + x: axis === "x" ? parsed : current.x, + y: axis === "y" ? parsed : current.y, + }), + ).catch(() => undefined); + }; + + // fallow-ignore-next-line complexity + const commitManualSize = (axis: "width" | "height", nextValue: string) => { + const parsed = parsePxMetricValue(nextValue); + if (parsed == null || parsed <= 0) return; + if (onCommitAnimatedProperty && hasGsapAnimation) { + void onCommitAnimatedProperty(element, axis, parsed); + return; + } + if (hasGsapAnimation) { + showToast?.("Cannot edit size — animation callbacks not available"); + return; + } + const current = readStudioBoxSize(element.element); + const width = + current.width > 0 + ? current.width + : (parsePxMetricValue(styles.width ?? "") ?? element.boundingBox.width); + const height = + current.height > 0 + ? current.height + : (parsePxMetricValue(styles.height ?? "") ?? element.boundingBox.height); + void Promise.resolve( + onSetManualSize(element, { + width: axis === "width" ? parsed : width, + height: axis === "height" ? parsed : height, + }), + ).catch(() => undefined); + }; + + const commitManualRotation = (nextValue: string) => { + const parsed = Number.parseFloat(nextValue); + if (!Number.isFinite(parsed)) return; + if ( + commitAnimatedTransformValue( + "rotation", + parsed, + "Cannot edit rotation — animation callbacks not available", + ) + ) + return; + void Promise.resolve(onSetManualRotation(element, { angle: parsed })).catch(() => undefined); + }; + + return { commitManualOffset, commitManualSize, commitManualRotation }; +}