From a526066b61f18b082adf6faeaf3e19f3d13cd77d Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 2 Mar 2026 16:36:10 +0100 Subject: feat: wire ui to have keyframes for: opacity, text color, and all background properties --- .../editor/panels/properties/sections/blending.tsx | 72 +++++++++++++++------- 1 file changed, 49 insertions(+), 23 deletions(-) (limited to 'apps/web/src/components/editor/panels/properties/sections') diff --git a/apps/web/src/components/editor/panels/properties/sections/blending.tsx b/apps/web/src/components/editor/panels/properties/sections/blending.tsx index 66fe820f..a50e84e7 100644 --- a/apps/web/src/components/editor/panels/properties/sections/blending.tsx +++ b/apps/web/src/components/editor/panels/properties/sections/blending.tsx @@ -1,5 +1,4 @@ import { useEditor } from "@/hooks/use-editor"; -import { usePropertyDraft } from "../hooks/use-property-draft"; import { clamp } from "@/utils/math"; import { NumberField } from "@/components/ui/number-field"; import { @@ -19,14 +18,23 @@ import { } from "@/components/ui/select"; import type { BlendMode } from "@/types/rendering"; import type { ElementType } from "@/types/timeline"; +import type { ElementAnimations } from "@/types/animation"; import { HugeiconsIcon } from "@hugeicons/react"; import { RainDropIcon } from "@hugeicons/core-free-icons"; +import { KeyframeToggle } from "../keyframe-toggle"; +import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; +import { useElementPlayhead } from "../hooks/use-element-playhead"; +import { resolveOpacityAtTime } from "@/lib/animation"; +import { isPropertyAtDefault } from "./transform"; type BlendingElement = { id: string; opacity: number; type: ElementType; blendMode?: BlendMode; + startTime: number; + duration: number; + animations?: ElementAnimations; }; const BLEND_MODE_GROUPS = [ @@ -105,20 +113,31 @@ export function BlendingSection({ } }; - const opacity = usePropertyDraft({ - displayValue: Math.round(element.opacity * 100).toString(), + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const resolvedOpacity = resolveOpacityAtTime({ + baseOpacity: element.opacity, + animations: element.animations, + localTime, + }); + + const opacity = useKeyframedNumberProperty({ + trackId, + elementId: element.id, + animations: element.animations, + propertyPath: "opacity", + localTime, + isPlayheadWithinElementRange, + displayValue: Math.round(resolvedOpacity * 100).toString(), parse: (input) => { const parsed = parseFloat(input); if (Number.isNaN(parsed)) return null; return clamp({ value: parsed, min: 0, max: 100 }) / 100; }, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { opacity: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), + valueAtPlayhead: resolvedOpacity, + buildBaseUpdates: ({ value }) => ({ opacity: value }), }); return ( @@ -126,7 +145,18 @@ export function BlendingSection({ Blending
- + + } + > - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - updates: { opacity: DEFAULT_OPACITY }, - }, - ], - }) - } - isDefault={element.opacity === DEFAULT_OPACITY} + onReset={() => opacity.commitValue({ value: DEFAULT_OPACITY })} + isDefault={isPropertyAtDefault({ + hasAnimatedKeyframes: opacity.hasAnimatedKeyframes, + isPlayheadWithinElementRange, + resolvedValue: resolvedOpacity, + staticValue: element.opacity, + defaultValue: DEFAULT_OPACITY, + })} dragSensitivity="slow" /> -- cgit v1.3.1