feat(studio): add fit/position rows to FlatMediaSection

This commit is contained in:
Vance Ingalls
2026-07-14 15:50:45 -07:00
parent 79fdff8701
commit ea463cdf8f
2 changed files with 97 additions and 2 deletions
@@ -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(
<FlatMediaSection
projectDir={null}
element={element}
styles={{ "object-fit": "cover", "object-position": "center" }}
onSetStyle={onSetStyle}
onSetAttribute={vi.fn()}
onSetHtmlAttribute={vi.fn()}
/>,
);
});
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(
<FlatMediaSection
projectDir={null}
element={element}
styles={{ "object-fit": "cover", "object-position": "center" }}
onSetStyle={onSetStyle}
onSetAttribute={vi.fn()}
onSetHtmlAttribute={vi.fn()}
/>,
);
});
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());
});
});
@@ -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 && (
<>
<FlatSelectRow
label="Fit"
value={objectFit}
options={["contain", "cover", "fill", "none", "scale-down"]}
tier={objectFit === "contain" ? "default" : "explicitCustom"}
onChange={(next) => void onSetStyle("object-fit", next)}
/>
<FlatSelectRow
label="Position"
value={objectPosition}
options={[
"center",
"top",
"bottom",
"left",
"right",
"left top",
"right top",
"left bottom",
"right bottom",
]}
tier={objectPosition === "center" ? "default" : "explicitCustom"}
onChange={(next) => void onSetStyle("object-position", next)}
/>
</>
)}
</div>
);
}