mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +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 { createRoot } from "react-dom/client";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
FlatLayoutSection,
|
||||||
LayoutFlexBlock,
|
LayoutFlexBlock,
|
||||||
LayoutGeometryRows,
|
LayoutGeometryRows,
|
||||||
LayoutTransform3DBlock,
|
LayoutTransform3DBlock,
|
||||||
@@ -36,6 +37,7 @@ function getFlatRowInput(host: HTMLElement, label: string): HTMLInputElement {
|
|||||||
|
|
||||||
function baseGeometryProps(overrides: Partial<Parameters<typeof LayoutGeometryRows>[0]> = {}) {
|
function baseGeometryProps(overrides: Partial<Parameters<typeof LayoutGeometryRows>[0]> = {}) {
|
||||||
return {
|
return {
|
||||||
|
element: {} as never,
|
||||||
displayX: 0,
|
displayX: 0,
|
||||||
displayY: -24,
|
displayY: -24,
|
||||||
displayW: 257.4,
|
displayW: 257.4,
|
||||||
@@ -111,6 +113,32 @@ describe("LayoutGeometryRows", () => {
|
|||||||
expect(full.length).toBeGreaterThan(0);
|
expect(full.length).toBeGreaterThan(0);
|
||||||
act(() => root.unmount());
|
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", () => {
|
describe("LayoutZIndexRow", () => {
|
||||||
@@ -189,3 +217,77 @@ describe("LayoutTransform3DBlock", () => {
|
|||||||
act(() => root.unmount());
|
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;
|
}> | null;
|
||||||
|
|
||||||
interface GeometryRowsProps {
|
interface GeometryRowsProps {
|
||||||
|
element: DomEditSelection;
|
||||||
displayX: number;
|
displayX: number;
|
||||||
displayY: number;
|
displayY: number;
|
||||||
displayW: number;
|
displayW: number;
|
||||||
@@ -31,15 +32,16 @@ interface GeometryRowsProps {
|
|||||||
seekFromKfPct: (pct: number) => void;
|
seekFromKfPct: (pct: number) => void;
|
||||||
animIdForProp: (prop: string) => string;
|
animIdForProp: (prop: string) => string;
|
||||||
onCommitAnimatedProperty?: (
|
onCommitAnimatedProperty?: (
|
||||||
element: unknown,
|
element: DomEditSelection,
|
||||||
property: string,
|
property: string,
|
||||||
value: number,
|
value: number,
|
||||||
) => void | Promise<void>;
|
) => Promise<void>;
|
||||||
onRemoveKeyframe?: (animId: string, pct: number) => void;
|
onRemoveKeyframe?: (animId: string, pct: number) => void;
|
||||||
onConvertToKeyframes?: (animId: string) => void;
|
onConvertToKeyframes?: (animId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function KeyframeGutter({
|
function KeyframeGutter({
|
||||||
|
element,
|
||||||
property,
|
property,
|
||||||
displayValue,
|
displayValue,
|
||||||
gsapAnimId,
|
gsapAnimId,
|
||||||
@@ -55,6 +57,7 @@ function KeyframeGutter({
|
|||||||
displayValue: number;
|
displayValue: number;
|
||||||
} & Pick<
|
} & Pick<
|
||||||
GeometryRowsProps,
|
GeometryRowsProps,
|
||||||
|
| "element"
|
||||||
| "gsapAnimId"
|
| "gsapAnimId"
|
||||||
| "navKeyframes"
|
| "navKeyframes"
|
||||||
| "currentPct"
|
| "currentPct"
|
||||||
@@ -74,7 +77,7 @@ function KeyframeGutter({
|
|||||||
currentPercentage={currentPct}
|
currentPercentage={currentPct}
|
||||||
onSeek={seekFromKfPct}
|
onSeek={seekFromKfPct}
|
||||||
onAddKeyframe={() =>
|
onAddKeyframe={() =>
|
||||||
onCommitAnimatedProperty && void onCommitAnimatedProperty(null, property, displayValue)
|
onCommitAnimatedProperty && void onCommitAnimatedProperty(element, property, displayValue)
|
||||||
}
|
}
|
||||||
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp(property), pct)}
|
onRemoveKeyframe={(pct) => onRemoveKeyframe?.(animIdForProp(property), pct)}
|
||||||
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp(property))}
|
onConvertToKeyframes={() => onConvertToKeyframes?.(animIdForProp(property))}
|
||||||
@@ -84,6 +87,7 @@ function KeyframeGutter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LayoutGeometryRows({
|
export function LayoutGeometryRows({
|
||||||
|
element,
|
||||||
displayX,
|
displayX,
|
||||||
displayY,
|
displayY,
|
||||||
displayW,
|
displayW,
|
||||||
@@ -105,6 +109,7 @@ export function LayoutGeometryRows({
|
|||||||
onConvertToKeyframes,
|
onConvertToKeyframes,
|
||||||
}: GeometryRowsProps) {
|
}: GeometryRowsProps) {
|
||||||
const gutterProps = {
|
const gutterProps = {
|
||||||
|
element,
|
||||||
gsapAnimId,
|
gsapAnimId,
|
||||||
navKeyframes,
|
navKeyframes,
|
||||||
currentPct,
|
currentPct,
|
||||||
@@ -304,3 +309,63 @@ export function LayoutTransform3DBlock({
|
|||||||
</div>
|
</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