mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): surface fromTo from-state in GSAP design panel (#1122)
* fix(studio): surface fromTo from-state in GSAP design panel Closes #1121. The core already parsed, serialized, and mutated fromProperties end to end (gsapParser.ts, applyUpdatesToCall, buildTweenStatementCode). The panel never wired it in — AnimationCard only read animation.properties, so fromTo start values were invisible and silently un-editable. Changes: - files.ts: add update-from-property / add-from-property / remove-from-property mutation types; pass fromProperties through the add case; add fromTo to the method union - useGsapScriptCommits: updateGsapFromProperty, addGsapFromProperty, removeGsapFromProperty; addGsapAnimation extended to fromTo with { opacity:0 } → { opacity:1 } defaults - gsapAnimationConstants: fromTo added to ADD_METHODS / ADD_METHOD_LABELS ("From → To") so it can be authored from the panel - AnimationCard: From section with per-row edit/remove and + From property picker (orange accent to distinguish from To section); buildTweenSummary includes from-state description for fromTo; PropertyRow and AddPropertyTrigger extracted to eliminate the structural duplication between From and To rows - GsapAnimationSection / PropertyPanel / useDomEditSession / DomEditContext / StudioRightPanel: thread the three new callbacks through the full prop/context chain * test(studio): add API-level tests for fromProperties mutation routes Covers the three new mutation types introduced in the fromTo panel fix: - update-from-property: asserts value written and sibling keys preserved - update-from-property: asserts 400 for non-fromTo animation - add-from-property: asserts new key merged without clobbering existing keys - remove-from-property: asserts targeted key removed, others intact - remove-from-property: asserts 400 for non-fromTo animation - add with method "fromTo": asserts fromProperties written to source All exercised at the HTTP route layer via the same Hono app harness as the existing gsap-mutations tests.
This commit is contained in:
@@ -87,6 +87,9 @@ export function StudioRightPanel({
|
||||
handleGsapAddAnimation,
|
||||
handleGsapAddProperty,
|
||||
handleGsapRemoveProperty,
|
||||
handleGsapUpdateFromProperty,
|
||||
handleGsapAddFromProperty,
|
||||
handleGsapRemoveFromProperty,
|
||||
} = useDomEditContext();
|
||||
|
||||
const { assets, fontAssets, projectDir, handleImportFiles, handleImportFonts } =
|
||||
@@ -215,6 +218,9 @@ export function StudioRightPanel({
|
||||
onDeleteGsapAnimation={handleGsapDeleteAnimation}
|
||||
onAddGsapProperty={handleGsapAddProperty}
|
||||
onRemoveGsapProperty={handleGsapRemoveProperty}
|
||||
onUpdateGsapFromProperty={handleGsapUpdateFromProperty}
|
||||
onAddGsapFromProperty={handleGsapAddFromProperty}
|
||||
onRemoveGsapFromProperty={handleGsapRemoveFromProperty}
|
||||
onAddGsapAnimation={handleGsapAddAnimation}
|
||||
/>
|
||||
) : motionPanelActive ? (
|
||||
|
||||
@@ -19,6 +19,112 @@ function isPercentProp(prop: string): boolean {
|
||||
return PERCENT_PROPS.has(prop);
|
||||
}
|
||||
|
||||
function displayValue(prop: string, val: number | string): string {
|
||||
return isPercentProp(prop) ? String(Math.round(Number(val) * 100)) : String(val);
|
||||
}
|
||||
|
||||
function adjustedValue(prop: string, raw: string): string {
|
||||
return isPercentProp(prop) ? String(Number(raw) / 100) : raw;
|
||||
}
|
||||
|
||||
function PropertyRow({
|
||||
prop,
|
||||
val,
|
||||
onCommit,
|
||||
onRemove,
|
||||
removeTitle,
|
||||
}: {
|
||||
prop: string;
|
||||
val: number | string;
|
||||
onCommit: (adjusted: string) => void;
|
||||
onRemove: () => void;
|
||||
removeTitle: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="min-w-0 flex-1">
|
||||
<MetricField
|
||||
label={PROP_LABELS[prop] ?? prop}
|
||||
value={displayValue(prop, val)}
|
||||
suffix={PROP_UNITS[prop]}
|
||||
tooltip={PROP_TOOLTIPS[prop]}
|
||||
scrub
|
||||
liveCommit
|
||||
onCommit={(raw) => onCommit(adjustedValue(prop, raw))}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
className="flex-shrink-0 rounded p-0.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-red-400"
|
||||
title={removeTitle}
|
||||
>
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
>
|
||||
<path d="M3 3l6 6M9 3l-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AddPropertyTrigger({
|
||||
adding,
|
||||
available,
|
||||
addLabel,
|
||||
addTitle,
|
||||
onAdd,
|
||||
onOpen,
|
||||
onClose,
|
||||
buttonClassName,
|
||||
}: {
|
||||
adding: boolean;
|
||||
available: string[];
|
||||
addLabel: string;
|
||||
addTitle: string;
|
||||
onAdd: (prop: string) => void;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
buttonClassName: string;
|
||||
}) {
|
||||
if (adding && available.length > 0) {
|
||||
return (
|
||||
<select
|
||||
autoFocus
|
||||
className="min-w-0 rounded-lg border border-neutral-700 bg-neutral-900 px-2 py-1 text-[11px] text-neutral-100 outline-none"
|
||||
defaultValue=""
|
||||
onChange={(e) => {
|
||||
if (e.target.value) onAdd(e.target.value);
|
||||
onClose();
|
||||
}}
|
||||
onBlur={onClose}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Choose property…
|
||||
</option>
|
||||
{available.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{PROP_LABELS[p] ?? p}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
if (available.length === 0) return null;
|
||||
return (
|
||||
<button type="button" onClick={onOpen} className={buttonClassName} title={addTitle}>
|
||||
{addLabel}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
function buildTweenSummary(animation: GsapAnimation): string {
|
||||
const easeName = animation.ease ?? "none";
|
||||
const ease = EASE_LABELS[easeName] ?? easeName;
|
||||
@@ -35,6 +141,16 @@ function buildTweenSummary(animation: GsapAnimation): string {
|
||||
if (animation.method === "set") return `At ${pos}s, instantly set ${target}'s ${propText}.`;
|
||||
if (animation.method === "from")
|
||||
return `Starting at ${pos}s, over ${dur}s, ${target} enters from ${propText} using a ${ease.toLowerCase()} curve.`;
|
||||
if (animation.method === "fromTo") {
|
||||
const fromProps = Object.entries(animation.fromProperties ?? {});
|
||||
const fromDescs = fromProps.map(([p, v]) => {
|
||||
const label = (PROP_LABELS[p] ?? p).toLowerCase();
|
||||
const unit = PROP_UNITS[p] ?? "";
|
||||
return `${label} ${v}${unit}`;
|
||||
});
|
||||
const fromText = fromDescs.length > 0 ? fromDescs.join(", ") : "—";
|
||||
return `Starting at ${pos}s, over ${dur}s, ${target} animates from [${fromText}] to [${propText}] using a ${ease.toLowerCase()} curve.`;
|
||||
}
|
||||
return `Starting at ${pos}s, over ${dur}s, animate ${target}'s ${propText} using a ${ease.toLowerCase()} curve.`;
|
||||
}
|
||||
|
||||
@@ -54,6 +170,9 @@ interface AnimationCardProps {
|
||||
onDeleteAnimation: (animationId: string) => void;
|
||||
onAddProperty: (animationId: string, property: string) => void;
|
||||
onRemoveProperty: (animationId: string, property: string) => void;
|
||||
onUpdateFromProperty?: (animationId: string, property: string, value: number | string) => void;
|
||||
onAddFromProperty?: (animationId: string, property: string) => void;
|
||||
onRemoveFromProperty?: (animationId: string, property: string) => void;
|
||||
onLivePreview?: (property: string, value: number | string) => void;
|
||||
onLivePreviewEnd?: () => void;
|
||||
}
|
||||
@@ -67,11 +186,15 @@ export const AnimationCard = memo(function AnimationCard({
|
||||
onDeleteAnimation,
|
||||
onAddProperty,
|
||||
onRemoveProperty,
|
||||
onUpdateFromProperty,
|
||||
onAddFromProperty,
|
||||
onRemoveFromProperty,
|
||||
onLivePreview,
|
||||
onLivePreviewEnd,
|
||||
}: AnimationCardProps) {
|
||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||
const [addingProp, setAddingProp] = useState(false);
|
||||
const [addingFromProp, setAddingFromProp] = useState(false);
|
||||
|
||||
const usedProps = useMemo(
|
||||
() => new Set(Object.keys(animation.properties)),
|
||||
@@ -82,6 +205,15 @@ export const AnimationCard = memo(function AnimationCard({
|
||||
[usedProps],
|
||||
);
|
||||
|
||||
const usedFromProps = useMemo(
|
||||
() => new Set(Object.keys(animation.fromProperties ?? {})),
|
||||
[animation.fromProperties],
|
||||
);
|
||||
const availableFromProps = useMemo(
|
||||
() => SUPPORTED_PROPS.filter((p) => !usedFromProps.has(p)),
|
||||
[usedFromProps],
|
||||
);
|
||||
|
||||
const commitProperty = useCallback(
|
||||
(prop: string, raw: string) => {
|
||||
const value = parseNumericOrString(raw);
|
||||
@@ -98,6 +230,15 @@ export const AnimationCard = memo(function AnimationCard({
|
||||
[onLivePreview],
|
||||
);
|
||||
|
||||
const commitFromProperty = useCallback(
|
||||
(prop: string, raw: string) => {
|
||||
const value = parseNumericOrString(raw);
|
||||
onUpdateFromProperty?.(animation.id, prop, value);
|
||||
onLivePreviewEnd?.();
|
||||
},
|
||||
[animation.id, onUpdateFromProperty, onLivePreviewEnd],
|
||||
);
|
||||
|
||||
const commitDuration = useCallback(
|
||||
(raw: string) => {
|
||||
const num = Number(raw);
|
||||
@@ -232,82 +373,73 @@ export const AnimationCard = memo(function AnimationCard({
|
||||
</>
|
||||
)}
|
||||
|
||||
{animation.method === "fromTo" && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-[9px] font-semibold uppercase tracking-wider text-orange-400/70">
|
||||
From
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{Object.entries(animation.fromProperties ?? {}).map(([prop, val]) => (
|
||||
<PropertyRow
|
||||
key={prop}
|
||||
prop={prop}
|
||||
val={val}
|
||||
onCommit={(adjusted) => commitFromProperty(prop, adjusted)}
|
||||
onRemove={() => onRemoveFromProperty?.(animation.id, prop)}
|
||||
removeTitle={`Remove from-${PROP_LABELS[prop] ?? prop}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="pt-0.5">
|
||||
<AddPropertyTrigger
|
||||
adding={addingFromProp}
|
||||
available={availableFromProps}
|
||||
addLabel="+ From property"
|
||||
addTitle="Add a from-state property"
|
||||
onAdd={(prop) => onAddFromProperty?.(animation.id, prop)}
|
||||
onOpen={() => setAddingFromProp(true)}
|
||||
onClose={() => setAddingFromProp(false)}
|
||||
buttonClassName="text-[11px] font-medium text-orange-400/70 transition-colors hover:text-orange-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{animation.method === "fromTo" && Object.keys(animation.properties).length > 0 && (
|
||||
<p className="text-[9px] font-semibold uppercase tracking-wider text-emerald-400/70">
|
||||
To
|
||||
</p>
|
||||
)}
|
||||
|
||||
{Object.keys(animation.properties).length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
{Object.entries(animation.properties).map(([prop, val]) => (
|
||||
<div key={prop} className="flex items-center gap-1">
|
||||
<div className="min-w-0 flex-1">
|
||||
<MetricField
|
||||
label={PROP_LABELS[prop] ?? prop}
|
||||
value={
|
||||
isPercentProp(prop) ? String(Math.round(Number(val) * 100)) : String(val)
|
||||
}
|
||||
suffix={PROP_UNITS[prop]}
|
||||
tooltip={PROP_TOOLTIPS[prop]}
|
||||
scrub
|
||||
liveCommit
|
||||
onCommit={(raw) => {
|
||||
const adjusted = isPercentProp(prop) ? String(Number(raw) / 100) : raw;
|
||||
scrubProperty(prop, adjusted);
|
||||
commitProperty(prop, adjusted);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRemoveProperty(animation.id, prop)}
|
||||
className="flex-shrink-0 rounded p-0.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-red-400"
|
||||
title={`Remove ${PROP_LABELS[prop] ?? prop}`}
|
||||
>
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
>
|
||||
<path d="M3 3l6 6M9 3l-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<PropertyRow
|
||||
key={prop}
|
||||
prop={prop}
|
||||
val={val}
|
||||
onCommit={(adjusted) => {
|
||||
scrubProperty(prop, adjusted);
|
||||
commitProperty(prop, adjusted);
|
||||
}}
|
||||
onRemove={() => onRemoveProperty(animation.id, prop)}
|
||||
removeTitle={`Remove ${PROP_LABELS[prop] ?? prop}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
{addingProp && availableProps.length > 0 ? (
|
||||
<select
|
||||
autoFocus
|
||||
className="min-w-0 rounded-lg border border-neutral-700 bg-neutral-900 px-2 py-1 text-[11px] text-neutral-100 outline-none"
|
||||
defaultValue=""
|
||||
onChange={(e) => {
|
||||
if (e.target.value) onAddProperty(animation.id, e.target.value);
|
||||
setAddingProp(false);
|
||||
}}
|
||||
onBlur={() => setAddingProp(false)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Choose effect…
|
||||
</option>
|
||||
{availableProps.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{PROP_LABELS[p] ?? p}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
availableProps.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAddingProp(true)}
|
||||
className="text-[11px] font-medium text-neutral-400 transition-colors hover:text-neutral-200"
|
||||
title="Add another animated property to this effect"
|
||||
>
|
||||
+ Effect
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
<AddPropertyTrigger
|
||||
adding={addingProp}
|
||||
available={availableProps}
|
||||
addLabel="+ Effect"
|
||||
addTitle="Add another animated property to this effect"
|
||||
onAdd={(prop) => onAddProperty(animation.id, prop)}
|
||||
onOpen={() => setAddingProp(true)}
|
||||
onClose={() => setAddingProp(false)}
|
||||
buttonClassName="text-[11px] font-medium text-neutral-400 transition-colors hover:text-neutral-200"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteAnimation(animation.id)}
|
||||
|
||||
@@ -17,7 +17,10 @@ interface GsapAnimationSectionProps {
|
||||
onDeleteAnimation: (animationId: string) => void;
|
||||
onAddProperty: (animationId: string, property: string) => void;
|
||||
onRemoveProperty: (animationId: string, property: string) => void;
|
||||
onAddAnimation: (method: "to" | "from" | "set") => void;
|
||||
onUpdateFromProperty?: (animationId: string, property: string, value: number | string) => void;
|
||||
onAddFromProperty?: (animationId: string, property: string) => void;
|
||||
onRemoveFromProperty?: (animationId: string, property: string) => void;
|
||||
onAddAnimation: (method: "to" | "from" | "set" | "fromTo") => void;
|
||||
onLivePreview?: (property: string, value: number | string) => void;
|
||||
onLivePreviewEnd?: () => void;
|
||||
}
|
||||
@@ -31,6 +34,9 @@ export const GsapAnimationSection = memo(function GsapAnimationSection({
|
||||
onDeleteAnimation,
|
||||
onAddProperty,
|
||||
onRemoveProperty,
|
||||
onUpdateFromProperty,
|
||||
onAddFromProperty,
|
||||
onRemoveFromProperty,
|
||||
onAddAnimation,
|
||||
onLivePreview,
|
||||
onLivePreviewEnd,
|
||||
@@ -64,6 +70,9 @@ export const GsapAnimationSection = memo(function GsapAnimationSection({
|
||||
onDeleteAnimation={onDeleteAnimation}
|
||||
onAddProperty={onAddProperty}
|
||||
onRemoveProperty={onRemoveProperty}
|
||||
onUpdateFromProperty={onUpdateFromProperty}
|
||||
onAddFromProperty={onAddFromProperty}
|
||||
onRemoveFromProperty={onRemoveFromProperty}
|
||||
onLivePreview={onLivePreview}
|
||||
onLivePreviewEnd={onLivePreviewEnd}
|
||||
/>
|
||||
|
||||
@@ -61,7 +61,10 @@ interface PropertyPanelProps {
|
||||
onDeleteGsapAnimation?: (animId: string) => void;
|
||||
onAddGsapProperty?: (animId: string, prop: string) => void;
|
||||
onRemoveGsapProperty?: (animId: string, prop: string) => void;
|
||||
onAddGsapAnimation?: (method: "to" | "from" | "set") => void;
|
||||
onUpdateGsapFromProperty?: (animId: string, prop: string, value: number | string) => void;
|
||||
onAddGsapFromProperty?: (animId: string, prop: string) => void;
|
||||
onRemoveGsapFromProperty?: (animId: string, prop: string) => void;
|
||||
onAddGsapAnimation?: (method: "to" | "from" | "set" | "fromTo") => void;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -162,6 +165,9 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
onDeleteGsapAnimation,
|
||||
onAddGsapProperty,
|
||||
onRemoveGsapProperty,
|
||||
onUpdateGsapFromProperty,
|
||||
onAddGsapFromProperty,
|
||||
onRemoveGsapFromProperty,
|
||||
onAddGsapAnimation,
|
||||
}: PropertyPanelProps) {
|
||||
const styles = element?.computedStyles ?? EMPTY_STYLES;
|
||||
@@ -368,6 +374,9 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
onDeleteAnimation={onDeleteGsapAnimation}
|
||||
onAddProperty={onAddGsapProperty}
|
||||
onRemoveProperty={onRemoveGsapProperty ?? (() => {})}
|
||||
onUpdateFromProperty={onUpdateGsapFromProperty}
|
||||
onAddFromProperty={onAddGsapFromProperty}
|
||||
onRemoveFromProperty={onRemoveGsapFromProperty}
|
||||
onAddAnimation={onAddGsapAnimation}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -121,10 +121,11 @@ export function parseCustomEaseFromString(ease: string): {
|
||||
return { x1: nums[2], y1: nums[3], x2: nums[4], y2: nums[5] };
|
||||
}
|
||||
|
||||
export const ADD_METHODS = ["to", "from", "set"] as const;
|
||||
export const ADD_METHODS = ["to", "from", "fromTo", "set"] as const;
|
||||
|
||||
export const ADD_METHOD_LABELS: Record<string, string> = {
|
||||
to: "Animate",
|
||||
from: "Animate In",
|
||||
fromTo: "From → To",
|
||||
set: "Set Instantly",
|
||||
};
|
||||
|
||||
@@ -62,6 +62,9 @@ export function DomEditProvider({
|
||||
handleGsapAddAnimation,
|
||||
handleGsapAddProperty,
|
||||
handleGsapRemoveProperty,
|
||||
handleGsapUpdateFromProperty,
|
||||
handleGsapAddFromProperty,
|
||||
handleGsapRemoveFromProperty,
|
||||
},
|
||||
children,
|
||||
}: {
|
||||
@@ -119,6 +122,9 @@ export function DomEditProvider({
|
||||
handleGsapAddAnimation,
|
||||
handleGsapAddProperty,
|
||||
handleGsapRemoveProperty,
|
||||
handleGsapUpdateFromProperty,
|
||||
handleGsapAddFromProperty,
|
||||
handleGsapRemoveFromProperty,
|
||||
}),
|
||||
[
|
||||
domEditSelection,
|
||||
@@ -170,6 +176,9 @@ export function DomEditProvider({
|
||||
handleGsapAddAnimation,
|
||||
handleGsapAddProperty,
|
||||
handleGsapRemoveProperty,
|
||||
handleGsapUpdateFromProperty,
|
||||
handleGsapAddFromProperty,
|
||||
handleGsapRemoveFromProperty,
|
||||
],
|
||||
);
|
||||
return <DomEditContext value={stable}>{children}</DomEditContext>;
|
||||
|
||||
@@ -215,6 +215,9 @@ export function useDomEditSession({
|
||||
addGsapAnimation,
|
||||
addGsapProperty,
|
||||
removeGsapProperty,
|
||||
updateGsapFromProperty,
|
||||
addGsapFromProperty,
|
||||
removeGsapFromProperty,
|
||||
} = useGsapScriptCommits({
|
||||
projectIdRef,
|
||||
activeCompPath,
|
||||
@@ -288,7 +291,7 @@ export function useDomEditSession({
|
||||
);
|
||||
|
||||
const handleGsapAddAnimation = useCallback(
|
||||
(method: "to" | "from" | "set") => {
|
||||
(method: "to" | "from" | "set" | "fromTo") => {
|
||||
if (!domEditSelection) return;
|
||||
addGsapAnimation(domEditSelection, method, currentTime);
|
||||
},
|
||||
@@ -311,6 +314,30 @@ export function useDomEditSession({
|
||||
[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],
|
||||
);
|
||||
|
||||
// Sync selection from preview document on load / refresh
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
@@ -443,5 +470,8 @@ export function useDomEditSession({
|
||||
handleGsapAddAnimation,
|
||||
handleGsapAddProperty,
|
||||
handleGsapRemoveProperty,
|
||||
handleGsapUpdateFromProperty,
|
||||
handleGsapAddFromProperty,
|
||||
handleGsapRemoveFromProperty,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,7 +210,11 @@ export function useGsapScriptCommits({
|
||||
);
|
||||
|
||||
const addGsapAnimation = useCallback(
|
||||
async (selection: DomEditSelection, method: "to" | "from" | "set", currentTime?: number) => {
|
||||
async (
|
||||
selection: DomEditSelection,
|
||||
method: "to" | "from" | "set" | "fromTo",
|
||||
currentTime?: number,
|
||||
) => {
|
||||
const { selector, autoId } = ensureElementAddressable(selection);
|
||||
|
||||
if (autoId) {
|
||||
@@ -238,10 +242,11 @@ export function useGsapScriptCommits({
|
||||
}
|
||||
|
||||
const start = currentTime ?? (Number.parseFloat(selection.dataAttributes.start ?? "0") || 0);
|
||||
const defaults: Record<string, Record<string, number>> = {
|
||||
const toDefaults: Record<string, Record<string, number>> = {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 1 },
|
||||
set: { opacity: 1 },
|
||||
fromTo: { opacity: 1 },
|
||||
};
|
||||
|
||||
await commitMutation(
|
||||
@@ -253,7 +258,8 @@ export function useGsapScriptCommits({
|
||||
position: start,
|
||||
duration: method === "set" ? undefined : 0.5,
|
||||
ease: method === "set" ? undefined : "power2.out",
|
||||
properties: defaults[method] ?? { opacity: 1 },
|
||||
properties: toDefaults[method] ?? { opacity: 1 },
|
||||
fromProperties: method === "fromTo" ? { opacity: 0 } : undefined,
|
||||
},
|
||||
{ label: `Add GSAP ${method} animation` },
|
||||
);
|
||||
@@ -292,6 +298,48 @@ export function useGsapScriptCommits({
|
||||
[commitMutation],
|
||||
);
|
||||
|
||||
const updateGsapFromProperty = useCallback(
|
||||
(
|
||||
selection: DomEditSelection,
|
||||
animationId: string,
|
||||
property: string,
|
||||
value: number | string,
|
||||
) => {
|
||||
void commitMutation(
|
||||
selection,
|
||||
{ type: "update-from-property", animationId, property, value },
|
||||
{
|
||||
label: `Edit GSAP from-${property}`,
|
||||
coalesceKey: `gsap:${animationId}:from:${property}`,
|
||||
},
|
||||
);
|
||||
},
|
||||
[commitMutation],
|
||||
);
|
||||
|
||||
const addGsapFromProperty = useCallback(
|
||||
(selection: DomEditSelection, animationId: string, property: string) => {
|
||||
const defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
|
||||
void commitMutation(
|
||||
selection,
|
||||
{ type: "add-from-property", animationId, property, defaultValue },
|
||||
{ label: `Add GSAP from-${property}` },
|
||||
);
|
||||
},
|
||||
[commitMutation],
|
||||
);
|
||||
|
||||
const removeGsapFromProperty = useCallback(
|
||||
(selection: DomEditSelection, animationId: string, property: string) => {
|
||||
void commitMutation(
|
||||
selection,
|
||||
{ type: "remove-from-property", animationId, property },
|
||||
{ label: `Remove GSAP from-${property}` },
|
||||
);
|
||||
},
|
||||
[commitMutation],
|
||||
);
|
||||
|
||||
return {
|
||||
updateGsapProperty,
|
||||
updateGsapMeta,
|
||||
@@ -299,5 +347,8 @@ export function useGsapScriptCommits({
|
||||
addGsapAnimation,
|
||||
addGsapProperty,
|
||||
removeGsapProperty,
|
||||
updateGsapFromProperty,
|
||||
addGsapFromProperty,
|
||||
removeGsapFromProperty,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user