diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.spec.browser2.tsx
new file mode 100644
index 000000000000..9712f1d1fb76
--- /dev/null
+++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.spec.browser2.tsx
@@ -0,0 +1,261 @@
+import { matchInlineSnapshotBrowser } from '../../../../../test/karma-snapshots'
+import { runDOMWalker } from '../../../editor/actions/action-creators'
+import { makeTestProjectCodeWithSnippet, renderTestEditorWithCode } from '../../ui-jsx.test-utils'
+import { getGlobalFramesOfGridCellsFromMetadata } from './grid-helpers'
+
+describe('Grids', () => {
+ it('can calculate global frames of grid cells', async () => {
+ const editor = await renderTestEditorWithCode(
+ makeTestProjectCodeWithSnippet(`
`),
+ 'await-first-dom-report',
+ )
+
+ await editor.dispatch([runDOMWalker()], true)
+
+ // non-grids don't have cell measurements:
+ expect(
+ getGlobalFramesOfGridCellsFromMetadata(
+ editor.getEditorState().editor.jsxMetadata[
+ 'utopia-storyboard-uid/scene-aaa/app-entity:grid/child'
+ ],
+ ),
+ ).toBeNull()
+
+ // grids have cell measurements:
+ matchInlineSnapshotBrowser(
+ getGlobalFramesOfGridCellsFromMetadata(
+ editor.getEditorState().editor.jsxMetadata[
+ 'utopia-storyboard-uid/scene-aaa/app-entity:grid'
+ ],
+ ),
+ `Array [
+ Array [
+ Object {
+ \"height\": 50,
+ \"width\": 150,
+ \"x\": 10,
+ \"y\": 10,
+ },
+ Object {
+ \"height\": 50,
+ \"width\": 80,
+ \"x\": 170,
+ \"y\": 10,
+ },
+ Object {
+ \"height\": 50,
+ \"width\": 119.5,
+ \"x\": 260,
+ \"y\": 10,
+ },
+ Object {
+ \"height\": 50,
+ \"width\": 119.5,
+ \"x\": 389.5,
+ \"y\": 10,
+ },
+ ],
+ Array [
+ Object {
+ \"height\": 83.25,
+ \"width\": 150,
+ \"x\": 10,
+ \"y\": 70,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 80,
+ \"x\": 170,
+ \"y\": 70,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 260,
+ \"y\": 70,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 389.5,
+ \"y\": 70,
+ },
+ ],
+ Array [
+ Object {
+ \"height\": 83.25,
+ \"width\": 150,
+ \"x\": 10,
+ \"y\": 163.25,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 80,
+ \"x\": 170,
+ \"y\": 163.25,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 260,
+ \"y\": 163.25,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 389.5,
+ \"y\": 163.25,
+ },
+ ],
+ Array [
+ Object {
+ \"height\": 83.25,
+ \"width\": 150,
+ \"x\": 10,
+ \"y\": 256.5,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 80,
+ \"x\": 170,
+ \"y\": 256.5,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 260,
+ \"y\": 256.5,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 389.5,
+ \"y\": 256.5,
+ },
+ ],
+ Array [
+ Object {
+ \"height\": 83.25,
+ \"width\": 150,
+ \"x\": 10,
+ \"y\": 349.75,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 80,
+ \"x\": 170,
+ \"y\": 349.75,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 260,
+ \"y\": 349.75,
+ },
+ Object {
+ \"height\": 83.25,
+ \"width\": 119.5,
+ \"x\": 389.5,
+ \"y\": 349.75,
+ },
+ ],
+]`,
+ )
+ })
+})
diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts
index 437329d52b4b..caff66fee4bd 100644
--- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts
+++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts
@@ -4,6 +4,7 @@ import * as EP from '../../../../core/shared/element-path'
import type {
ElementInstanceMetadataMap,
GridPositionValue,
+ GridTemplate,
} from '../../../../core/shared/element-template'
import {
gridPositionValue,
@@ -12,9 +13,14 @@ import {
type GridElementProperties,
type GridPosition,
} from '../../../../core/shared/element-template'
-import type { CanvasVector, WindowRectangle } from '../../../../core/shared/math-utils'
+import type {
+ CanvasRectangle,
+ CanvasVector,
+ WindowRectangle,
+} from '../../../../core/shared/math-utils'
import {
canvasPoint,
+ canvasRectangle,
isInfinityRectangle,
offsetPoint,
scaleVector,
@@ -40,6 +46,7 @@ import {
getGridCellUnderMouseRecursive,
gridCellCoordinates,
} from './grid-cell-bounds'
+import type { Sides } from 'utopia-api/core'
export function runGridRearrangeMove(
targetElement: ElementPath,
@@ -604,3 +611,62 @@ function getGridPositionIndex(props: {
}): number {
return (props.row - 1) * props.gridTemplateColumns + props.column - 1
}
+
+export function getGlobalFramesOfGridCellsFromMetadata(
+ metadata: ElementInstanceMetadata,
+): Array> | null {
+ return getGlobalFramesOfGridCells(metadata.specialSizeMeasurements)
+}
+
+export function getGlobalFramesOfGridCells({
+ containerGridProperties,
+ rowGap,
+ columnGap,
+ padding,
+}: {
+ containerGridProperties: GridContainerProperties
+ rowGap: number | null
+ columnGap: number | null
+ padding: Sides
+}): Array> | null {
+ const columnWidths = gridTemplateToNumbers(containerGridProperties.gridTemplateColumns)
+
+ const rowHeights = gridTemplateToNumbers(containerGridProperties.gridTemplateRows)
+
+ if (columnWidths == null || rowHeights == null) {
+ return null
+ }
+
+ const cellRects: Array> = []
+ let yOffset = padding.top ?? 0
+ rowHeights.forEach((height) => {
+ let xOffset = padding.left ?? 0
+ const rowRects: CanvasRectangle[] = []
+ columnWidths.forEach((width) => {
+ const rect = canvasRectangle({ x: xOffset, y: yOffset, width: width, height: height })
+ rowRects.push(rect)
+ xOffset += width + (columnGap ?? 0)
+ })
+ cellRects.push(rowRects)
+ yOffset += height + (rowGap ?? 0)
+ })
+
+ return cellRects
+}
+
+function gridTemplateToNumbers(gridTemplate: GridTemplate | null): Array | null {
+ if (gridTemplate?.type !== 'DIMENSIONS') {
+ return null
+ }
+
+ const result: Array = []
+
+ for (const dimension of gridTemplate.dimensions) {
+ if (dimension.type !== 'NUMBER') {
+ return null
+ }
+ result.push(dimension.value.value)
+ }
+
+ return result
+}