From ea463cdf8fdc5924bb384ad066214d9f9cb1f7b6 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 9 Jul 2026 14:11:17 -0700 Subject: [PATCH] feat(studio): add fit/position rows to FlatMediaSection --- .../propertyPanelFlatMediaSection.test.tsx | 67 +++++++++++++++++++ .../editor/propertyPanelFlatMediaSection.tsx | 32 ++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/packages/studio/src/components/editor/propertyPanelFlatMediaSection.test.tsx b/packages/studio/src/components/editor/propertyPanelFlatMediaSection.test.tsx index d77472a88..cce0af420 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatMediaSection.test.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatMediaSection.test.tsx @@ -364,3 +364,70 @@ describe("FlatMediaSection — loop/muted/has-audio", () => { act(() => root.unmount()); }); }); + +describe("FlatMediaSection — fit/position", () => { + it("commits object-fit and object-position changes", () => { + const onSetStyle = vi.fn(); + const { host, root } = (() => { + const element = makeVideoElement(); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + act(() => { + root.render( + , + ); + }); + return { host, root }; + })(); + const selects = host.querySelectorAll("select"); + const fitSelect = Array.from(selects).find((s) => s.value === "cover"); + expect(fitSelect).not.toBeUndefined(); + act(() => { + if (fitSelect) { + fitSelect.value = "contain"; + fitSelect.dispatchEvent(new Event("change", { bubbles: true })); + } + }); + expect(onSetStyle).toHaveBeenCalledWith("object-fit", "contain"); + act(() => root.unmount()); + }); + + it("commits an object-position change", () => { + const onSetStyle = vi.fn(); + const element = makeVideoElement(); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + act(() => { + root.render( + , + ); + }); + const selects = host.querySelectorAll("select"); + const positionSelect = Array.from(selects).find((s) => s.value === "center"); + expect(positionSelect).not.toBeUndefined(); + act(() => { + if (positionSelect) { + positionSelect.value = "left top"; + positionSelect.dispatchEvent(new Event("change", { bubbles: true })); + } + }); + expect(onSetStyle).toHaveBeenCalledWith("object-position", "left top"); + act(() => root.unmount()); + }); +}); diff --git a/packages/studio/src/components/editor/propertyPanelFlatMediaSection.tsx b/packages/studio/src/components/editor/propertyPanelFlatMediaSection.tsx index 37f7a0c71..b3bcca833 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatMediaSection.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatMediaSection.tsx @@ -15,9 +15,7 @@ import { FlatSelectRow, FlatSlider, FlatToggle } from "./propertyPanelFlatPrimit export function FlatMediaSection({ projectDir, element, - // oxlint-disable-next-line no-unused-vars -- wired into the Fit/Position rows in Task 6 styles, - // oxlint-disable-next-line no-unused-vars -- wired into the Fit/Position rows in Task 6 onSetStyle, onSetAttribute, onSetHtmlAttribute, @@ -59,6 +57,8 @@ export function FlatMediaSection({ const hasLoop = el.hasAttribute("loop"); const hasMuted = el.hasAttribute("muted"); const hasAudio = element.dataAttributes["has-audio"] === "true"; + const objectFit = styles["object-fit"] || "contain"; + const objectPosition = styles["object-position"] || "center"; const srcAttr = el.getAttribute("src") ?? ""; const [copied, setCopied] = useState(false); @@ -248,6 +248,34 @@ export function FlatMediaSection({ )} )} + {isVisualMedia && ( + <> + void onSetStyle("object-fit", next)} + /> + void onSetStyle("object-position", next)} + /> + + )} ); }