diff --git a/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx b/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx index 02b7126991e..955fe6c44e1 100644 --- a/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx +++ b/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx @@ -53,6 +53,7 @@ export interface ToolpadBridge { canvasCommands: Commands<{ getViewCoordinates(clientX: number, clientY: number): { x: number; y: number } | null; getPageViewState(): PageViewState; + scrollComponent(nodeId: string): void; isReady(): boolean; invalidateQueries(): void; }>; @@ -73,6 +74,9 @@ const bridge: ToolpadBridge | null = isRenderedInCanvas getPageViewState: () => { throw new Error('Not implemented'); }, + scrollComponent: () => { + throw new Error('Not Implemented'); + }, getViewCoordinates: () => { throw new Error('Not implemented'); }, diff --git a/packages/toolpad-studio/src/canvas/index.tsx b/packages/toolpad-studio/src/canvas/index.tsx index 06fcbdb2b69..d1de1ca27dc 100644 --- a/packages/toolpad-studio/src/canvas/index.tsx +++ b/packages/toolpad-studio/src/canvas/index.tsx @@ -142,7 +142,6 @@ export default function AppCanvas({ basename, state }: AppCanvasProps) { setCommandHandler(bridge.canvasCommands, 'getPageViewState', () => { invariant(appRootRef.current, 'App root not found'); - let nodes = viewState.current.nodes; for (const [nodeId, nodeInfo] of Object.entries(nodes)) { @@ -154,6 +153,15 @@ export default function AppCanvas({ basename, state }: AppCanvasProps) { return { nodes }; }); + setCommandHandler(bridge.canvasCommands, 'scrollComponent', (nodeId) => { + if (!nodeId) { + return; + } + invariant(appRootRef.current, 'App root not found'); + const canvasNode = appRootRef.current.querySelector(`[data-node-id='${nodeId}']`); + canvasNode?.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'end' }); + }); + setCommandHandler(bridge.canvasCommands, 'getViewCoordinates', (clientX, clientY) => { if (!appRootRef.current) { return null; diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx index 6f1933c1c9f..46b7806d5db 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx @@ -7,6 +7,7 @@ import { AppHostProvider, useAppHost, queryClient, + NodeId, } from '@toolpad/studio-runtime'; import createCache from '@emotion/cache'; import { CacheProvider } from '@emotion/react'; @@ -182,6 +183,13 @@ export default function EditorCanvasHost({ queryClient.invalidateQueries(); }, update: () => {}, + scrollComponent: (nodeId: NodeId) => { + if (!appRoot) { + return; + } + const node = appRoot.querySelector(`[data-node-id='${nodeId}']`); + node?.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'end' }); + }, }), } satisfies ToolpadBridge; diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderOverlay.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderOverlay.tsx index c523c31be04..a97c838d457 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderOverlay.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderOverlay.tsx @@ -1160,6 +1160,17 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) { }; }, [handleNodeDragEnd]); + const scrollSelectedNode = React.useCallback(() => { + if (!selectedNode) { + return; + } + bridge?.canvasCommands.scrollComponent(selectedNode.id as string); + }, [bridge?.canvasCommands, selectedNode]); + + React.useEffect(() => { + scrollSelectedNode(); + }, [scrollSelectedNode]); + const resizePreviewElementRef = React.useRef(null); const overlayGridRef = React.useRef({ diff --git a/test/visual/components/index.spec.ts b/test/visual/components/index.spec.ts index ed6606a99d8..9eda5fac777 100644 --- a/test/visual/components/index.spec.ts +++ b/test/visual/components/index.spec.ts @@ -40,6 +40,9 @@ test('rendering components in the app editor', async ({ page, argosScreenshot }) await clickCenter(page, image); await argosScreenshot('with-selection'); + + await page.getByRole('treeitem', { name: 'typography1' }).click(); + await argosScreenshot('with-selection-scroll'); }); test('building layouts', async ({ page, argosScreenshot }) => {