feat: wire ui to have keyframes for: opacity, text color, and all background properties

This commit is contained in:
Maze Winther
2026-03-02 16:36:10 +01:00
parent b0165da764
commit a526066b61
13 changed files with 673 additions and 272 deletions
+1
View File
@@ -18,6 +18,7 @@ export {
export {
getElementLocalTime,
resolveColorAtTime,
resolveNumberAtTime,
resolveOpacityAtTime,
resolveTransformAtTime,
resolveVolumeAtTime,
@@ -7,6 +7,11 @@ import type {
} from "@/types/animation";
import type { TimelineElement } from "@/types/timeline";
import { MIN_TRANSFORM_SCALE } from "@/constants/animation-constants";
import {
CORNER_RADIUS_MAX,
CORNER_RADIUS_MIN,
DEFAULT_TEXT_BACKGROUND,
} from "@/constants/text-constants";
import { isVisualElement } from "@/lib/timeline/element-utils";
interface NumericRange {
@@ -144,6 +149,89 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
}
: element,
},
"background.paddingX": {
valueKind: "number",
defaultInterpolation: "linear",
numericRange: { min: 0 },
supportsElement: ({ element }) => element.type === "text",
getValue: ({ element }) =>
element.type === "text"
? (element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX)
: null,
setValue: ({ element, value }) =>
element.type === "text"
? {
...element,
background: { ...element.background, paddingX: value as number },
}
: element,
},
"background.paddingY": {
valueKind: "number",
defaultInterpolation: "linear",
numericRange: { min: 0 },
supportsElement: ({ element }) => element.type === "text",
getValue: ({ element }) =>
element.type === "text"
? (element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY)
: null,
setValue: ({ element, value }) =>
element.type === "text"
? {
...element,
background: { ...element.background, paddingY: value as number },
}
: element,
},
"background.offsetX": {
valueKind: "number",
defaultInterpolation: "linear",
supportsElement: ({ element }) => element.type === "text",
getValue: ({ element }) =>
element.type === "text"
? (element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX)
: null,
setValue: ({ element, value }) =>
element.type === "text"
? {
...element,
background: { ...element.background, offsetX: value as number },
}
: element,
},
"background.offsetY": {
valueKind: "number",
defaultInterpolation: "linear",
supportsElement: ({ element }) => element.type === "text",
getValue: ({ element }) =>
element.type === "text"
? (element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY)
: null,
setValue: ({ element, value }) =>
element.type === "text"
? {
...element,
background: { ...element.background, offsetY: value as number },
}
: element,
},
"background.cornerRadius": {
valueKind: "number",
defaultInterpolation: "linear",
numericRange: { min: CORNER_RADIUS_MIN, max: CORNER_RADIUS_MAX },
supportsElement: ({ element }) => element.type === "text",
getValue: ({ element }) =>
element.type === "text"
? (element.background.cornerRadius ?? CORNER_RADIUS_MIN)
: null,
setValue: ({ element, value }) =>
element.type === "text"
? {
...element,
background: { ...element.background, cornerRadius: value as number },
}
: element,
},
};
export function isAnimationPropertyPath({
+18
View File
@@ -92,6 +92,24 @@ export function resolveOpacityAtTime({
});
}
export function resolveNumberAtTime({
baseValue,
animations,
propertyPath,
localTime,
}: {
baseValue: number;
animations: ElementAnimations | undefined;
propertyPath: AnimationPropertyPath;
localTime: number;
}): number {
return getNumberChannelValueAtTime({
channel: getNumberChannelForPath({ animations, propertyPath }),
time: Math.max(0, localTime),
fallbackValue: baseValue,
});
}
export function resolveColorAtTime({
baseColor,
animations,
+38 -2
View File
@@ -4,10 +4,15 @@ import { isMainTrack } from "@/lib/timeline";
import {
DEFAULT_TEXT_ELEMENT,
DEFAULT_LINE_HEIGHT,
DEFAULT_TEXT_BACKGROUND,
FONT_SIZE_SCALE_REFERENCE,
} from "@/constants/text-constants";
import { getTextVisualRect, measureTextBlock } from "@/lib/text/layout";
import { getElementLocalTime, resolveTransformAtTime } from "@/lib/animation";
import {
getElementLocalTime,
resolveTransformAtTime,
resolveNumberAtTime,
} from "@/lib/animation";
export interface ElementBounds {
cx: number;
@@ -147,10 +152,41 @@ export function getElementBounds({
fallbackFontSize: scaledFontSize,
});
const fontSizeRatio = element.fontSize / DEFAULT_TEXT_ELEMENT.fontSize;
const resolvedBackground = {
...element.background,
paddingX: resolveNumberAtTime({
baseValue:
element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,
animations: element.animations,
propertyPath: "background.paddingX",
localTime,
}),
paddingY: resolveNumberAtTime({
baseValue:
element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,
animations: element.animations,
propertyPath: "background.paddingY",
localTime,
}),
offsetX: resolveNumberAtTime({
baseValue:
element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,
animations: element.animations,
propertyPath: "background.offsetX",
localTime,
}),
offsetY: resolveNumberAtTime({
baseValue:
element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,
animations: element.animations,
propertyPath: "background.offsetY",
localTime,
}),
};
const visualRect = getTextVisualRect({
textAlign: element.textAlign,
block,
background: element.background,
background: resolvedBackground,
fontSizeRatio,
});
measuredWidth = visualRect.width;