fix: make presentation figures inspectable

This commit is contained in:
lda
2026-07-08 22:56:38 +07:00 Verified
parent 0216f8db4e
commit 7ddca743a3
12 changed files with 138 additions and 174 deletions
@@ -186,6 +186,24 @@ describe("InteractiveFigure", () => {
expect(figure.querySelector(".interactive-figure__canvas")).toBeInTheDocument();
});
it("keeps root stage figures in presentation mode", () => {
renderFigure({ focusPath: [], size: "stage" });
expect(screen.getByRole("group", { name: /architecture/i })).toHaveAttribute(
"data-pan-zoom",
"disabled",
);
});
it("enables pan and zoom inspection for focused stage figures", () => {
renderFigure({ focusPath: ["runtime"], size: "stage" });
expect(screen.getByRole("group", { name: /runtime detail/i })).toHaveAttribute(
"data-pan-zoom",
"enabled",
);
});
it("resets roving focus when focusPath changes", () => {
const { rerender } = render(
<InteractiveFigure
@@ -83,9 +83,8 @@ const FitViewOnLayoutChange = ({ layoutKey }: { layoutKey: string }) => {
const { fitView } = useReactFlow();
useEffect(() => {
void fitView({ padding: 0.15, duration: 0 });
// React Flow can measure before the scaled presentation canvas and
// breadcrumb row settle. Fit again on the next frame to align edges with
// the final node boxes without adding a larger measurement framework.
// React Flow can measure before the breadcrumb row and stage grid settle.
// Fit again on the next frame to align edges with the final node boxes.
const frame = window.requestAnimationFrame(() => {
void fitView({ padding: 0.15, duration: 0 });
});
@@ -111,6 +110,7 @@ const InteractiveFigureInner = ({
const initialFocusedNodeId = activeNodeId ?? focus.figure.nodes[0]?.id ?? "";
const [focusedNodeId, setFocusedNodeId] = useState(initialFocusedNodeId);
const focusedNodeIdRef = useRef(initialFocusedNodeId);
const graphInspectionEnabled = size === "stage" && focus.path.length > 0;
const fallbackFocusedNodeId = activeNodeId ?? focus.figure.nodes[0]?.id ?? "";
useEffect(() => {
@@ -230,6 +230,7 @@ const InteractiveFigureInner = ({
data-motion={motionDisabled ? "disabled" : "enabled"}
data-figure-id={focus.figure.id}
data-figure-size={size}
data-pan-zoom={graphInspectionEnabled ? "enabled" : "disabled"}
onKeyDown={handleKeyDown}
>
<FigureBreadcrumbs
@@ -248,11 +249,13 @@ const InteractiveFigureInner = ({
nodesFocusable={false}
edgesFocusable={false}
elementsSelectable={false}
panOnDrag={false}
zoomOnScroll={false}
zoomOnPinch={false}
zoomOnDoubleClick={false}
preventScrolling={false}
minZoom={0.35}
maxZoom={2.2}
panOnDrag={graphInspectionEnabled}
zoomOnScroll={graphInspectionEnabled}
zoomOnPinch={graphInspectionEnabled}
zoomOnDoubleClick={graphInspectionEnabled}
preventScrolling={graphInspectionEnabled}
onNodeClick={handleNodeClick}
>
<FitViewOnLayoutChange layoutKey={focus.figure.id} />
@@ -32,10 +32,23 @@
background: color-mix(in oklch, var(--color-editorial-surface, oklch(0.96 0.012 82)) 92%, white);
}
.interactive-figure .react-flow__pane {
/*
Presentation overview figures should not steal normal slide navigation
gestures. Focused stage figures opt into React Flow inspection, where the pane
needs pointer events for drag-pan and wheel zoom.
*/
.interactive-figure[data-pan-zoom="disabled"] .react-flow__pane {
pointer-events: none;
}
.interactive-figure[data-pan-zoom="enabled"] .react-flow__pane {
cursor: grab;
}
.interactive-figure[data-pan-zoom="enabled"] .react-flow__pane:active {
cursor: grabbing;
}
.interactive-figure .react-flow__node {
cursor: default;
}
@@ -133,8 +146,10 @@
}
.interactive-figure[data-figure-size="stage"] .interactive-figure__canvas {
min-width: 1440px;
min-height: 470px;
position: relative;
min-width: 100%;
min-height: 0;
height: 100%;
}
.interactive-figure[data-figure-size="stage"] .react-flow {
@@ -23,4 +23,16 @@ describe("interactive-figure CSS", () => {
// Only the stage variant should have gradients.
expect(totalGradientSelectors).toBe(stageGradients?.length ?? 0);
});
it("does not force stage figures wider than the visible canvas", () => {
expect(css).not.toContain("min-width: 1440px");
expect(css).toContain('data-figure-size="stage"] .interactive-figure__canvas');
expect(css).toContain("min-width: 100%");
});
it("lets stage figures shrink to the available scene height", () => {
expect(css).not.toContain("min-height: 470px");
expect(css).toContain("min-height: 0");
expect(css).toContain("height: 100%");
});
});