mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
feat(studio): compose FlatLayoutSection from geometry, z-index, flex, and 3D blocks
This commit is contained in:
@@ -4,6 +4,7 @@ import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
FlatLayoutSection,
|
||||
LayoutFlexBlock,
|
||||
LayoutGeometryRows,
|
||||
LayoutTransform3DBlock,
|
||||
@@ -36,6 +37,7 @@ function getFlatRowInput(host: HTMLElement, label: string): HTMLInputElement {
|
||||
|
||||
function baseGeometryProps(overrides: Partial<Parameters<typeof LayoutGeometryRows>[0]> = {}) {
|
||||
return {
|
||||
element: {} as never,
|
||||
displayX: 0,
|
||||
displayY: -24,
|
||||
displayW: 257.4,
|
||||
@@ -111,6 +113,32 @@ describe("LayoutGeometryRows", () => {
|
||||
expect(full.length).toBeGreaterThan(0);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("passes the real element/selection (not null) to onCommitAnimatedProperty when adding a keyframe", () => {
|
||||
const onCommitAnimatedProperty = vi.fn();
|
||||
const element = { id: "el-1" } as unknown as Parameters<
|
||||
typeof LayoutGeometryRows
|
||||
>[0]["element"];
|
||||
const { host, root } = renderInto(
|
||||
<LayoutGeometryRows
|
||||
{...baseGeometryProps({
|
||||
element,
|
||||
gsapAnimId: "anim-1",
|
||||
navKeyframes: [{ percentage: 50, properties: { x: 5 } }],
|
||||
currentPct: 0,
|
||||
onCommitAnimatedProperty,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const addButton = host.querySelector('[title="Add x keyframe"]');
|
||||
if (!addButton) throw new Error("expected an Add x keyframe button");
|
||||
act(() => {
|
||||
(addButton as HTMLElement).dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
expect(onCommitAnimatedProperty).toHaveBeenCalledWith(element, "x", 0);
|
||||
expect(onCommitAnimatedProperty).not.toHaveBeenCalledWith(null, "x", 0);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
||||
describe("LayoutZIndexRow", () => {
|
||||
@@ -189,3 +217,77 @@ describe("LayoutTransform3DBlock", () => {
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
||||
describe("FlatLayoutSection", () => {
|
||||
it("renders geometry rows, z-index, flex (when applicable), and the 3D transform block in order", () => {
|
||||
const { host, root } = renderInto(
|
||||
<FlatLayoutSection
|
||||
element={{} as never}
|
||||
styles={{ display: "flex", "flex-direction": "row" }}
|
||||
onSetStyle={vi.fn()}
|
||||
disabled={false}
|
||||
displayX={0}
|
||||
displayY={0}
|
||||
displayW={100}
|
||||
displayH={100}
|
||||
displayR={0}
|
||||
manualOffsetEditingDisabled={false}
|
||||
manualSizeEditingDisabled={false}
|
||||
manualRotationEditingDisabled={false}
|
||||
commitManualOffset={vi.fn()}
|
||||
commitManualSize={vi.fn()}
|
||||
commitManualRotation={vi.fn()}
|
||||
gsapAnimId={null}
|
||||
navKeyframes={null}
|
||||
currentPct={0}
|
||||
seekFromKfPct={vi.fn()}
|
||||
animIdForProp={(p) => p}
|
||||
gsapRuntimeValues={{}}
|
||||
gsapKeyframes={null}
|
||||
elStart={0}
|
||||
elDuration={0}
|
||||
onSeekToTime={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
const text = host.textContent ?? "";
|
||||
expect(text).toContain("X");
|
||||
expect(text).toContain("Z-index");
|
||||
expect(text).toContain("Flex");
|
||||
expect(text).toContain("3D Transform");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("omits the Flex block for a non-flex element", () => {
|
||||
const { host, root } = renderInto(
|
||||
<FlatLayoutSection
|
||||
element={{} as never}
|
||||
styles={{ display: "block" }}
|
||||
onSetStyle={vi.fn()}
|
||||
disabled={false}
|
||||
displayX={0}
|
||||
displayY={0}
|
||||
displayW={100}
|
||||
displayH={100}
|
||||
displayR={0}
|
||||
manualOffsetEditingDisabled={false}
|
||||
manualSizeEditingDisabled={false}
|
||||
manualRotationEditingDisabled={false}
|
||||
commitManualOffset={vi.fn()}
|
||||
commitManualSize={vi.fn()}
|
||||
commitManualRotation={vi.fn()}
|
||||
gsapAnimId={null}
|
||||
navKeyframes={null}
|
||||
currentPct={0}
|
||||
seekFromKfPct={vi.fn()}
|
||||
animIdForProp={(p) => p}
|
||||
gsapRuntimeValues={{}}
|
||||
gsapKeyframes={null}
|
||||
elStart={0}
|
||||
elDuration={0}
|
||||
onSeekToTime={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(host.textContent).not.toContain("Flex");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ type KeyframeEntry = Array<{
|
||||
}> | null;
|
||||
|
||||
interface GeometryRowsProps {
|
||||
element: DomEditSelection;
|
||||
displayX: number;
|
||||
displayY: number;
|
||||
displayW: number;
|
||||
@@ -31,15 +32,16 @@ interface GeometryRowsProps {
|
||||
seekFromKfPct: (pct: number) => void;
|
||||
animIdForProp: (prop: string) => string;
|
||||
onCommitAnimatedProperty?: (
|
||||
element: unknown,
|
||||
element: DomEditSelection,
|
||||
property: string,
|
||||
value: number,
|
||||
) => void | Promise<void>;
|
||||
) => Promise<void>;
|
||||
onRemoveKeyframe?: (animId: string, pct: number) => void;
|
||||
onConvertToKeyframes?: (animId: string) => void;
|
||||
}
|
||||
|
||||
function KeyframeGutter({
|
||||
element,
|
||||
property,
|
||||
displayValue,
|
||||
gsapAnimId,
|
||||
@@ -55,6 +57,7 @@ function KeyframeGutter({
|
||||
displayValue: number;
|
||||
} & Pick<
|
||||
GeometryRowsProps,
|
||||
| "element"
|
||||
| "gsapAnimId"
|
||||
| "navKeyframes"
|
||||
| "currentPct"
|
||||
@@ -74,7 +77,7 @@ function KeyframeGutter({
|
||||
currentPercentage={currentPct}
|
||||
onSeek={seekFromKfPct}
|
||||
onAddKeyframe={() =>
|
||||
onCommitAnimatedProperty && void onCommitAnimatedProperty(null, property, displayValue)
|
||||
onCommitAnimatedProperty && void onCommitAnimatedProperty(element, property, displayValue)
|
||||
}
|
||||
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp(property), pct)}
|
||||
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp(property))}
|
||||
@@ -84,6 +87,7 @@ function KeyframeGutter({
|
||||
}
|
||||
|
||||
export function LayoutGeometryRows({
|
||||
element,
|
||||
displayX,
|
||||
displayY,
|
||||
displayW,
|
||||
@@ -105,6 +109,7 @@ export function LayoutGeometryRows({
|
||||
onConvertToKeyframes,
|
||||
}: GeometryRowsProps) {
|
||||
const gutterProps = {
|
||||
element,
|
||||
gsapAnimId,
|
||||
navKeyframes,
|
||||
currentPct,
|
||||
@@ -304,3 +309,63 @@ export function LayoutTransform3DBlock({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FlatLayoutSectionProps
|
||||
extends
|
||||
Omit<GeometryRowsProps, never>,
|
||||
Pick<
|
||||
Parameters<typeof LayoutTransform3DBlock>[0],
|
||||
| "gsapRuntimeValues"
|
||||
| "resolveAnimIdForProp"
|
||||
| "gsapKeyframes"
|
||||
| "elStart"
|
||||
| "elDuration"
|
||||
| "onCommitAnimatedProperties"
|
||||
| "onSeekToTime"
|
||||
| "onLivePreviewProps"
|
||||
> {
|
||||
element: DomEditSelection;
|
||||
styles: Record<string, string>;
|
||||
onSetStyle: (prop: string, value: string) => void | Promise<void>;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function FlatLayoutSection({
|
||||
element,
|
||||
styles,
|
||||
onSetStyle,
|
||||
disabled,
|
||||
gsapRuntimeValues,
|
||||
resolveAnimIdForProp,
|
||||
gsapKeyframes,
|
||||
elStart,
|
||||
elDuration,
|
||||
onCommitAnimatedProperties,
|
||||
onSeekToTime,
|
||||
onLivePreviewProps,
|
||||
...geometry
|
||||
}: FlatLayoutSectionProps) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<LayoutGeometryRows element={element} {...geometry} />
|
||||
<LayoutZIndexRow styles={styles} onSetStyle={onSetStyle} />
|
||||
<LayoutFlexBlock styles={styles} onSetStyle={onSetStyle} disabled={disabled} />
|
||||
<LayoutTransform3DBlock
|
||||
gsapRuntimeValues={gsapRuntimeValues}
|
||||
gsapAnimId={geometry.gsapAnimId}
|
||||
resolveAnimIdForProp={resolveAnimIdForProp}
|
||||
gsapKeyframes={gsapKeyframes}
|
||||
currentPct={geometry.currentPct}
|
||||
elStart={elStart}
|
||||
elDuration={elDuration}
|
||||
element={element}
|
||||
onCommitAnimatedProperty={geometry.onCommitAnimatedProperty}
|
||||
onCommitAnimatedProperties={onCommitAnimatedProperties}
|
||||
onSeekToTime={onSeekToTime}
|
||||
onRemoveKeyframe={geometry.onRemoveKeyframe}
|
||||
onConvertToKeyframes={geometry.onConvertToKeyframes}
|
||||
onLivePreviewProps={onLivePreviewProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user