diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts index 54a9bd9f785a..1adc1f4f739d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-change-element-location-strategy.ts @@ -1,12 +1,18 @@ import type { ElementPath } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' +import * as PP from '../../../../core/shared/property-path' import type { ElementInstanceMetadata, ElementInstanceMetadataMap, GridContainerProperties, + GridElementProperties, +} from '../../../../core/shared/element-template' +import { + getJSXAttribute, + gridPositionValue, + isJSXElement, } from '../../../../core/shared/element-template' -import { gridPositionValue } from '../../../../core/shared/element-template' import { isInfinityRectangle } from '../../../../core/shared/math-utils' import { absolute } from '../../../../utils/utils' import type { CanvasCommand } from '../../commands/commands' @@ -32,6 +38,13 @@ import { sortElementsByGridPosition, } from './grid-helpers' import { getTargetGridCellData } from '../../../inspector/grid-helpers' +import { forEachOf } from '../../../../core/shared/optics/optic-utilities' +import { + eitherRight, + fromField, + fromTypeGuard, +} from '../../../../core/shared/optics/optic-creators' +import { getJSXAttributesAtPath } from '../../../..//core/shared/jsx-attribute-utils' export const gridChangeElementLocationStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -215,11 +228,53 @@ export function runGridChangeElementLocation( } const { targetCellCoords, targetRootCell } = targetGridCellData - const gridCellMoveCommands = setGridPropsCommands(pathForCommands, gridTemplate, { + const gridProps: Partial = { gridColumnStart: gridPositionValue(targetRootCell.column), gridColumnEnd: gridPositionValue(targetRootCell.column + originalCellBounds.height), gridRowStart: gridPositionValue(targetRootCell.row), gridRowEnd: gridPositionValue(targetRootCell.row + originalCellBounds.width), + } + + // TODO: Remove this logic once there is a fix for the handling of the track end fields. + let keepGridColumnEnd: boolean = true + let keepGridRowEnd: boolean = true + forEachOf( + fromField('element') + .compose(eitherRight()) + .compose(fromTypeGuard(isJSXElement)) + .compose(fromField('props')), + selectedElementMetadata, + (elementProperties) => { + const gridColumnEnd = getJSXAttributesAtPath( + elementProperties, + PP.create('style', 'gridColumnEnd'), + ) + if (gridColumnEnd.attribute.type === 'ATTRIBUTE_NOT_FOUND') { + keepGridColumnEnd = false + } + const gridRowEnd = getJSXAttributesAtPath(elementProperties, PP.create('style', 'gridRowEnd')) + if (gridRowEnd.attribute.type === 'ATTRIBUTE_NOT_FOUND') { + keepGridRowEnd = false + } + }, + ) + + const gridCellMoveCommands = setGridPropsCommands( + pathForCommands, + gridTemplate, + gridProps, + ).filter((command) => { + if (command.type === 'SET_PROPERTY') { + if (PP.pathsEqual(command.property, PP.create('style', 'gridColumnEnd'))) { + return keepGridColumnEnd + } else if (PP.pathsEqual(command.property, PP.create('style', 'gridRowEnd'))) { + return keepGridRowEnd + } else { + return true + } + } else { + return true + } }) // The siblings of the grid element being moved diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx index 1be76d1cf3a9..d1a11cc8377a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx @@ -504,10 +504,13 @@ export var storyboard = ( await mouseUpAtPoint(dragTarget, endPoint) { - const { top, left, gridColumn, gridRow } = child.style - expect({ top, left, gridColumn, gridRow }).toEqual({ - gridColumn: '1', - gridRow: '1', + const { top, left, gridColumnStart, gridColumnEnd, gridRowStart, gridRowEnd } = + child.style + expect({ top, left, gridColumnStart, gridColumnEnd, gridRowStart, gridRowEnd }).toEqual({ + gridColumnStart: '1', + gridColumnEnd: '', + gridRowStart: '1', + gridRowEnd: '', left: '59px', top: '59.5px', }) diff --git a/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx index 1cc724bb5556..e76428030af7 100644 --- a/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx +++ b/editor/src/components/canvas/controls/grid-controls-for-strategies.tsx @@ -222,6 +222,8 @@ export const GridMeasurementHelpersKey = (gridPath: ElementPath) => `grid-measurement-helpers-${EP.toString(gridPath)}` export const GridMeasurementHelperKey = (gridPath: ElementPath) => `grid-measurement-helper-${EP.toString(gridPath)}` +export const GridElementContainingBlockKey = (gridPath: ElementPath) => + `grid-measurement-containing-block-${EP.toString(gridPath)}` export interface GridControlProps { grid: GridData diff --git a/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx b/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx new file mode 100644 index 000000000000..be1be9264dd2 --- /dev/null +++ b/editor/src/components/canvas/controls/grid-controls.spec.browser2.tsx @@ -0,0 +1,618 @@ +import type { CSSProperties } from 'react' +import { renderTestEditorWithCode } from '../ui-jsx.test-utils' +import type { SimpleRectangle } from '../../..//core/shared/math-utils' +import { selectComponentsForTest } from '../../../utils/utils.test-utils' +import * as EP from '../../../core/shared/element-path' + +interface TestGridOutlinesResult { + top?: SimpleRectangle + left?: SimpleRectangle + bottom?: SimpleRectangle + right?: SimpleRectangle +} + +async function testGridOutlines( + targetPath: string, + attributes: CSSProperties, +): Promise { + const fullAttributes: CSSProperties = { backgroundColor: 'pink', ...attributes } + let fullAttributesText: string = '{\n' + for (const [key, value] of Object.entries(fullAttributes)) { + fullAttributesText += `${key}: ${JSON.stringify(value)},\n` + } + fullAttributesText += '}' + + const projectCode = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+
+
+ + +) +` + const editor = await renderTestEditorWithCode(projectCode, 'await-first-dom-report') + await selectComponentsForTest(editor, [EP.fromString(targetPath)]) + + const topPinLine = editor.renderedDOM.queryByTestId('pin-line-top') + const leftPinLine = editor.renderedDOM.queryByTestId('pin-line-left') + const bottomPinLine = editor.renderedDOM.queryByTestId('pin-line-bottom') + const rightPinLine = editor.renderedDOM.queryByTestId('pin-line-right') + + function toBoundingRect(element: HTMLElement): SimpleRectangle { + const domRect = element.getBoundingClientRect() + return { + x: domRect.x, + y: domRect.y, + width: domRect.width, + height: domRect.height, + } + } + + let result: TestGridOutlinesResult = {} + if (topPinLine != null) { + result.top = toBoundingRect(topPinLine) + } + if (leftPinLine != null) { + result.left = toBoundingRect(leftPinLine) + } + if (bottomPinLine != null) { + result.bottom = toBoundingRect(bottomPinLine) + } + if (rightPinLine != null) { + result.right = toBoundingRect(rightPinLine) + } + return result +} + +describe('Grid Pin Outlines', () => { + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 315.5, width: 12, height: 1 }, + top: { x: 725.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 315.5, width: 12, height: 1 }, + top: { x: 751.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 725.5, y: 381.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 777.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 751.5, y: 381.5, width: 1, height: 5 }, + }) + }) + + it('pinned top and left, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 315.5, width: 12, height: 1 }, + top: { x: 725.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 807.5, y: 315.5, width: 12, height: 1 }, + top: { x: 781.5, y: 285.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 688.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 725.5, y: 381.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid column end set to auto and not absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid column end set to auto and absolutely positioned', async () => { + const result = await testGridOutlines('storyboard/scene/grid/grid-child', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 807.5, y: 355.5, width: 12, height: 1 }, + bottom: { x: 781.5, y: 381.5, width: 1, height: 5 }, + }) + }) + + it('pinned top and left, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1455.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1781.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1455.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid rows and columns fully specified and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid rows and columns fully specified and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 3, + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1781.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned top and left, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and left, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1455.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned top and right, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned top and right, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + top: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 190.5, width: 12, height: 1 }, + top: { x: 1781.5, y: 160.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and left, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and left, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + left: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + left: { x: 1418.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1455.5, y: 555.5, width: 1, height: 5 }, + }) + }) + it('pinned bottom and right, grid column end set to auto and not absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({}) + }) + it('pinned bottom and right, grid column end set to auto and absolutely positioned in non-absolute grid', async () => { + const result = await testGridOutlines('storyboard/scene/grid-2-outer/grid-2/grid-child-2', { + position: 'absolute', + bottom: 5, + right: 12, + gridRowStart: 2, + gridColumnStart: 3, + gridRowEnd: 2, + gridColumnEnd: 'auto', + width: 50, + height: 50, + }) + expect(result).toEqual({ + right: { x: 1806.5, y: 530.5, width: 12, height: 1 }, + bottom: { x: 1781.5, y: 555.5, width: 1, height: 5 }, + }) + }) +}) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 29bad8d7eb4d..b708bead4b9f 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -1,5 +1,6 @@ /** @jsxRuntime classic */ /** @jsx jsx */ +/** @jsxFrag React.Fragment */ import { jsx } from '@emotion/react' import type { AnimationControls } from 'framer-motion' import { motion, useAnimationControls } from 'framer-motion' @@ -7,9 +8,10 @@ import type { CSSProperties } from 'react' import React from 'react' import type { Sides } from 'utopia-api/core' import type { ElementPath } from 'utopia-shared/src/types' +import type { PropsOrJSXAttributes } from '../../../core/model/element-metadata-utils' import { MetadataUtils } from '../../../core/model/element-metadata-utils' import { mapDropNulls, range, stripNulls, uniqBy } from '../../../core/shared/array-utils' -import { defaultEither } from '../../../core/shared/either' +import { defaultEither, eitherToMaybe } from '../../../core/shared/either' import * as EP from '../../../core/shared/element-path' import type { ElementInstanceMetadataMap, @@ -19,7 +21,7 @@ import { isGridAutoOrTemplateDimensions, type GridAutoOrTemplateBase, } from '../../../core/shared/element-template' -import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils' +import type { CanvasPoint, CanvasRectangle, LocalRectangle } from '../../../core/shared/math-utils' import { canvasPoint, isFiniteRectangle, @@ -28,6 +30,7 @@ import { pointsEqual, scaleRect, windowPoint, + zeroCanvasRect, zeroRectIfNullOrInfinity, } from '../../../core/shared/math-utils' import { @@ -44,12 +47,23 @@ import { Modifier } from '../../../utils/modifiers' import { when } from '../../../utils/react-conditionals' import { useColorTheme, UtopiaStyles } from '../../../uuiui' import { useDispatch } from '../../editor/store/dispatch-context' -import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook' -import type { GridDimension, GridDiscreteDimension } from '../../inspector/common/css-utils' import { + Substores, + useEditorState, + useRefEditorState, + useSelectorWithCallback, +} from '../../editor/store/store-hook' +import type { + CSSNumber, + GridDimension, + GridDiscreteDimension, +} from '../../inspector/common/css-utils' +import { + cssNumberToString, isCSSKeyword, isDynamicGridRepeat, isGridCSSRepeat, + printCSSNumberWithDefaultUnit, printGridCSSNumber, } from '../../inspector/common/css-utils' import CanvasActions from '../canvas-actions' @@ -66,6 +80,7 @@ import { getGlobalFrameOfGridCellFromMetadata, getGridRelatedIndexes, getGridElementPinState, + gridPositionToValue, } from '../canvas-strategies/strategies/grid-helpers' import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' @@ -85,6 +100,7 @@ import { GridCellTestId, GridControlKey, gridEdgeToEdgePosition, + GridElementContainingBlockKey, GridMeasurementHelperKey, GridMeasurementHelpersKey, useGridData, @@ -94,6 +110,10 @@ import { useMaybeHighlightElement } from './select-mode/select-mode-hooks' import { useResizeEdges } from './select-mode/use-resize-edges' import { getGridHelperStyleMatchingTargetGrid } from './grid-controls-helpers' import { isFillOrStretchModeAppliedOnSpecificSide } from '../../inspector/inspector-common' +import type { PinOutlineProps } from './position-outline' +import { PinOutline, PositionOutline, usePropsOrJSXAttributes } from './position-outline' +import { getLayoutProperty } from '../../../core/layout/getLayoutProperty' +import { styleStringInArray } from '../../../utils/common-constants' const CELL_ANIMATION_DURATION = 0.15 // seconds @@ -1783,6 +1803,235 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) ) } +function collectGridPinOutlines( + attributes: PropsOrJSXAttributes, + frame: LocalRectangle, + scale: number, +): PinOutlineProps[] { + const pinLeft = eitherToMaybe(getLayoutProperty('left', attributes, styleStringInArray)) + const pinTop = eitherToMaybe(getLayoutProperty('top', attributes, styleStringInArray)) + const pinRight = eitherToMaybe(getLayoutProperty('right', attributes, styleStringInArray)) + const pinBottom = eitherToMaybe(getLayoutProperty('bottom', attributes, styleStringInArray)) + let pins: PinOutlineProps[] = [] + if (pinLeft != null) { + let startY: string | number | undefined = undefined + let endY: string | number | undefined = undefined + if (pinTop != null) { + startY = `calc(${printCSSNumberWithDefaultUnit(pinTop, 'px')} + ${frame.height / 2}px)` + } else if (pinBottom != null) { + endY = `calc(${printCSSNumberWithDefaultUnit(pinBottom, 'px')} + ${frame.height / 2}px)` + } else { + startY = frame.height / 2 + } + pins.push({ + name: 'left', + isHorizontalLine: true, + size: printCSSNumberWithDefaultUnit(pinLeft, 'px'), + startX: 0, + startY: startY, + endY: endY, + scale: scale, + }) + } + if (pinTop != null) { + let startX: string | number | undefined = undefined + let endX: string | number | undefined = undefined + if (pinLeft != null) { + startX = `calc(${printCSSNumberWithDefaultUnit(pinLeft, 'px')} + ${frame.width / 2}px)` + } else if (pinRight != null) { + endX = `calc(${printCSSNumberWithDefaultUnit(pinRight, 'px')} + ${frame.width / 2}px)` + } else { + startX = frame.width / 2 + } + pins.push({ + name: 'top', + isHorizontalLine: false, + size: printCSSNumberWithDefaultUnit(pinTop, 'px'), + startX: startX, + endX: endX, + startY: 0, + scale: scale, + }) + } + if (pinRight != null) { + let startY: string | number | undefined = undefined + let endY: string | number | undefined = undefined + if (pinTop != null) { + startY = `calc(${printCSSNumberWithDefaultUnit(pinTop, 'px')} + ${frame.height / 2}px)` + } else if (pinBottom != null) { + endY = `calc(${printCSSNumberWithDefaultUnit(pinBottom, 'px')} + ${frame.height / 2}px)` + } else { + endY = frame.height / 2 + } + pins.push({ + name: 'right', + isHorizontalLine: true, + size: printCSSNumberWithDefaultUnit(pinRight, 'px'), + startY: startY, + endX: 0, + endY: endY, + scale: scale, + }) + } + if (pinBottom != null) { + let startX: string | number | undefined = undefined + let endX: string | number | undefined = undefined + if (pinLeft != null) { + startX = `calc(${printCSSNumberWithDefaultUnit(pinLeft, 'px')} + ${frame.width / 2}px)` + } else if (pinRight != null) { + endX = `calc(${printCSSNumberWithDefaultUnit(pinRight, 'px')} + ${frame.width / 2}px)` + } else { + endX = frame.width / 2 + } + pins.push({ + name: 'bottom', + isHorizontalLine: false, + size: printCSSNumberWithDefaultUnit(pinBottom, 'px'), + startX: startX, + endX: endX, + endY: 0, + scale: scale, + }) + } + return pins +} + +export interface GridElementContainingBlockProps { + gridPath: ElementPath + gridChild: ElementPath +} + +const GridElementContainingBlock = React.memo((props) => { + const gridData = useGridMeasurentHelperData(props.gridPath) + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridElementContainingBlock scale', + ) + const position = useEditorState( + Substores.metadata, + (store) => { + const childMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + props.gridChild, + ) + return childMetadata?.specialSizeMeasurements.position + }, + 'GridElementContainingBlock position', + ) + const gridChildStyle: CSSProperties = useEditorState( + Substores.metadata, + (store) => { + const childMetadata = MetadataUtils.findElementByElementPath( + store.editor.jsxMetadata, + props.gridChild, + ) + if (childMetadata == null) { + return {} + } + const gridFromProps = childMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + const gridComputed = childMetadata.specialSizeMeasurements.elementGridProperties + return { + gridColumnStart: + gridPositionToValue(gridFromProps.gridColumnStart ?? gridComputed.gridColumnStart) ?? + undefined, + gridColumnEnd: + gridPositionToValue(gridFromProps.gridColumnEnd ?? gridComputed.gridColumnEnd) ?? + undefined, + gridRowStart: + gridPositionToValue(gridFromProps.gridRowStart ?? gridComputed.gridRowStart) ?? undefined, + gridRowEnd: + gridPositionToValue(gridFromProps.gridRowEnd ?? gridComputed.gridRowEnd) ?? undefined, + position: childMetadata.specialSizeMeasurements.position ?? undefined, + } + }, + 'GridElementContainingBlock gridChildStyle', + ) + const gridChildFrame = useEditorState( + Substores.metadata, + (store) => { + return MetadataUtils.getLocalFrame(props.gridChild, store.editor.jsxMetadata, null) + }, + 'GridElementContainingBlock gridChildFrame', + ) + const attributes = usePropsOrJSXAttributes(props.gridChild) + + if (gridChildFrame == null || isInfinityRectangle(gridChildFrame)) { + return null + } + + if (gridData == null) { + return null + } + + if (position !== 'absolute') { + return null + } + + const style: CSSProperties = { + ...getGridHelperStyleMatchingTargetGrid(gridData), + opacity: 1, + } + + const pins: Array = collectGridPinOutlines(attributes, gridChildFrame, scale) + + return ( +
+
+ {pins.map((pin) => ( + + ))} +
+
+ ) +}) +GridElementContainingBlock.displayName = 'GridElementContainingBlock' + +export interface GridElementContainingBlocksProps {} + +export const GridElementContainingBlocks = React.memo((props) => { + const selectedGridChildElements = useEditorState( + Substores.metadata, + (store) => { + return store.editor.selectedViews.filter((selectedView) => { + return MetadataUtils.isGridItemWithLayoutProvidingGridParent( + store.editor.jsxMetadata, + selectedView, + ) + }) + }, + 'GridElementContainingBlocks selectedViews', + ) + + return ( + + {selectedGridChildElements.map((selectedGridChildElement) => { + return ( + + ) + })} + + ) +}) + function gridEdgeToCSSCursor(edge: GridResizeEdge): CSSCursor { switch (edge) { case 'column-end': diff --git a/editor/src/components/canvas/controls/new-canvas-controls.tsx b/editor/src/components/canvas/controls/new-canvas-controls.tsx index bd1863cff4c7..6e31c6e04816 100644 --- a/editor/src/components/canvas/controls/new-canvas-controls.tsx +++ b/editor/src/components/canvas/controls/new-canvas-controls.tsx @@ -75,7 +75,7 @@ import { NO_OP } from '../../../core/shared/utils' import { useIsMyProject } from '../../editor/store/collaborative-editing' import { MultiplayerWrapper } from '../../../utils/multiplayer-wrapper' import { MultiplayerPresence } from '../multiplayer-presence' -import { GridMeasurementHelpers } from './grid-controls' +import { GridElementContainingBlocks, GridMeasurementHelpers } from './grid-controls' export const CanvasControlsContainerID = 'new-canvas-controls-container' @@ -619,6 +619,10 @@ const NewCanvasControlsInner = (props: NewCanvasControlsInnerProps) => { isSelectMode(editorMode) || isInsertMode(editorMode), , )} + {when( + isSelectMode(editorMode) || isInsertMode(editorMode), + , + )} {middleStrategyControls.map((c) => ( { return mapDropNulls((path) => { const element = MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, path) const isAbsolute = MetadataUtils.isPositionAbsolute(element) + const isGridItem = MetadataUtils.isGridItemWithLayoutProvidingGridParent( + store.editor.jsxMetadata, + path, + ) const frame = element?.globalFrame - if (isAbsolute && frame != null && isFiniteRectangle(frame)) { + if (isAbsolute && !isGridItem && frame != null && isFiniteRectangle(frame)) { return { path: path, frame: frame, @@ -78,26 +82,26 @@ interface PositionOutlineProps { export const PositionOutline = React.memo((props: PositionOutlineProps) => { const containingFrame = useContainingFrameForElement(props.path) const attributes = usePropsOrJSXAttributes(props.path) - if (containingFrame != null) { - let pins: PinOutlineProps[] = collectPinOutlines( - attributes, - props.frame, - containingFrame, - props.scale, - ) - return ( - - {pins.map((pin) => ( - - ))} - - ) - } else { + if (containingFrame == null) { return null } + + const pins: PinOutlineProps[] = collectPinOutlines( + attributes, + props.frame, + containingFrame, + props.scale, + ) + return ( + + {pins.map((pin) => ( + + ))} + + ) }) -const usePropsOrJSXAttributes = (path: ElementPath): PropsOrJSXAttributes => { +export const usePropsOrJSXAttributes = (path: ElementPath): PropsOrJSXAttributes => { return useEditorState( Substores.metadata, (store) => { @@ -128,7 +132,7 @@ const useContainingFrameForElement = (path: ElementPath): CanvasRectangle | null ) } -const collectPinOutlines = ( +export const collectPinOutlines = ( attributes: PropsOrJSXAttributes, frame: CanvasRectangle, containingFrame: CanvasRectangle, @@ -182,29 +186,57 @@ const collectPinOutlines = ( return pins } -interface PinOutlineProps { +export interface PinOutlineProps { name: string isHorizontalLine: boolean - startX: number - startY: number - size: number + startX?: number | string + endX?: number | string + startY?: number | string + endY?: number | string + size: number | string scale: number } const PinOutlineUnscaledSize = 1 -const PinOutline = React.memo((props: PinOutlineProps): JSX.Element => { +export const PinOutline = React.memo((props: PinOutlineProps): JSX.Element => { const colorTheme = useColorTheme() - const width = props.isHorizontalLine ? props.size : 0 - const height = props.isHorizontalLine ? 0 : props.size + function numberOrStringToSize(value: number | string): string { + return typeof value === 'number' ? `${value}px` : value + } + const width = numberOrStringToSize(props.isHorizontalLine ? props.size : 0) + const height = numberOrStringToSize(props.isHorizontalLine ? 0 : props.size) const borderTop = props.isHorizontalLine ? `${PinOutlineUnscaledSize / props.scale}px dashed ${colorTheme.primary.value}` : 'none' const borderLeft = props.isHorizontalLine ? 'none' : `${PinOutlineUnscaledSize / props.scale}px dashed ${colorTheme.primary.value}` - const lineLeft = props.startX - PinOutlineUnscaledSize / 2 / props.scale - const lineTop = props.startY - PinOutlineUnscaledSize / 2 / props.scale + + function lineStart(startValue?: number | string): string | undefined { + if (startValue == null) { + return undefined + } else { + return `calc(${numberOrStringToSize(startValue)} - ${ + PinOutlineUnscaledSize / 2 / props.scale + }px)` + } + } + + function lineEnd(endValue?: number | string): string | undefined { + if (endValue == null) { + return undefined + } else { + return `calc(${numberOrStringToSize(endValue)} - ${ + PinOutlineUnscaledSize / 2 / props.scale + }px)` + } + } + + const lineLeft = lineStart(props.startX) + const lineTop = lineStart(props.startY) + const lineRight = lineEnd(props.endX) + const lineBottom = lineEnd(props.endY) return (
{ position: 'absolute', left: lineLeft, top: lineTop, + right: lineRight, + bottom: lineBottom, width: width, height: height, borderTop: borderTop, diff --git a/editor/src/components/canvas/dom-walker.ts b/editor/src/components/canvas/dom-walker.ts index 56d1f6aaa894..d18f48e5d0cc 100644 --- a/editor/src/components/canvas/dom-walker.ts +++ b/editor/src/components/canvas/dom-walker.ts @@ -703,9 +703,9 @@ function getSpecialMeasurements( containerRectLazy: CanvasPoint | (() => CanvasPoint), elementCanvasRectangleCache: ElementCanvasRectangleCache, ): SpecialSizeMeasurements { - const elementStyle = window.getComputedStyle(element) - const layoutSystemForChildren = elementLayoutSystem(elementStyle) - const position = getPosition(elementStyle) + const computedStyle = window.getComputedStyle(element) + const layoutSystemForChildren = elementLayoutSystem(computedStyle) + const position = getPosition(computedStyle) const offset = { x: roundToNearestHalf(element.offsetLeft), @@ -740,7 +740,7 @@ function getSpecialMeasurements( element.parentElement == null ? null : window.getComputedStyle(element.parentElement) const isParentNonStatic = isElementNonStatic(parentElementStyle) - const providesBoundsForAbsoluteChildren = isElementAContainingBlockForAbsolute(elementStyle) + const providesBoundsForAbsoluteChildren = isElementAContainingBlockForAbsolute(computedStyle) const parentLayoutSystem = elementLayoutSystem(parentElementStyle) const parentProvidesLayout = element.parentElement === element.offsetParent @@ -755,29 +755,29 @@ function getSpecialMeasurements( element.parentElement != null && element.parentElement.style[sizeMainAxis] === MaxContent - const flexDirection = eitherToMaybe(parseFlexDirection(elementStyle.flexDirection, null)) + const flexDirection = eitherToMaybe(parseFlexDirection(computedStyle.flexDirection, null)) const parentTextDirection = eitherToMaybe(parseDirection(parentElementStyle?.direction, null)) - const justifyContent = getFlexJustifyContent(elementStyle.justifyContent) - const alignContent = getAlignContent(elementStyle.alignContent) - const alignItems = getFlexAlignment(elementStyle.alignItems) - const alignSelf = getSelfAlignment(elementStyle.alignSelf) - const justifySelf = getSelfAlignment(elementStyle.justifySelf) + const justifyContent = getFlexJustifyContent(computedStyle.justifyContent) + const alignContent = getAlignContent(computedStyle.alignContent) + const alignItems = getFlexAlignment(computedStyle.alignItems) + const alignSelf = getSelfAlignment(computedStyle.alignSelf) + const justifySelf = getSelfAlignment(computedStyle.justifySelf) const margin = applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.marginTop), - parseCSSLength(elementStyle.marginRight), - parseCSSLength(elementStyle.marginBottom), - parseCSSLength(elementStyle.marginLeft), + parseCSSLength(computedStyle.marginTop), + parseCSSLength(computedStyle.marginRight), + parseCSSLength(computedStyle.marginBottom), + parseCSSLength(computedStyle.marginLeft), ) const padding = applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.paddingTop), - parseCSSLength(elementStyle.paddingRight), - parseCSSLength(elementStyle.paddingBottom), - parseCSSLength(elementStyle.paddingLeft), + parseCSSLength(computedStyle.paddingTop), + parseCSSLength(computedStyle.paddingRight), + parseCSSLength(computedStyle.paddingBottom), + parseCSSLength(computedStyle.paddingLeft), ) const parentPadding = applicative4Either( @@ -800,10 +800,10 @@ function getSpecialMeasurements( const childrenCount = element.childElementCount - const borderTopWidth = parseCSSLength(elementStyle.borderTopWidth) - const borderRightWidth = parseCSSLength(elementStyle.borderRightWidth) - const borderBottomWidth = parseCSSLength(elementStyle.borderBottomWidth) - const borderLeftWidth = parseCSSLength(elementStyle.borderLeftWidth) + const borderTopWidth = parseCSSLength(computedStyle.borderTopWidth) + const borderRightWidth = parseCSSLength(computedStyle.borderRightWidth) + const borderBottomWidth = parseCSSLength(computedStyle.borderBottomWidth) + const borderLeftWidth = parseCSSLength(computedStyle.borderLeftWidth) const border: BorderWidths = { top: isRight(borderTopWidth) ? borderTopWidth.value.value : 0, right: isRight(borderRightWidth) ? borderRightWidth.value.value : 0, @@ -845,25 +845,25 @@ function getSpecialMeasurements( } const hasPositionOffset = - !positionValueIsDefault(elementStyle.top) || - !positionValueIsDefault(elementStyle.right) || - !positionValueIsDefault(elementStyle.bottom) || - !positionValueIsDefault(elementStyle.left) - const hasTransform = elementStyle.transform !== 'none' + !positionValueIsDefault(computedStyle.top) || + !positionValueIsDefault(computedStyle.right) || + !positionValueIsDefault(computedStyle.bottom) || + !positionValueIsDefault(computedStyle.left) + const hasTransform = computedStyle.transform !== 'none' const gap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.gap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.gap)), ) const rowGap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.rowGap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.rowGap)), ) const columnGap = defaultEither( null, - mapEither((n) => n.value, parseCSSLength(elementStyle.columnGap)), + mapEither((n) => n.value, parseCSSLength(computedStyle.columnGap)), ) const flexGapValue = parseCSSLength(parentElementStyle?.gap) @@ -873,17 +873,17 @@ function getSpecialMeasurements( null, applicative4Either( applicativeSidesPxTransform, - parseCSSLength(elementStyle.borderTopLeftRadius), - parseCSSLength(elementStyle.borderTopRightRadius), - parseCSSLength(elementStyle.borderBottomLeftRadius), - parseCSSLength(elementStyle.borderBottomRightRadius), + parseCSSLength(computedStyle.borderTopLeftRadius), + parseCSSLength(computedStyle.borderTopRightRadius), + parseCSSLength(computedStyle.borderBottomLeftRadius), + parseCSSLength(computedStyle.borderBottomRightRadius), ), ) - const fontSize = elementStyle.fontSize - const fontWeight = elementStyle.fontWeight - const fontStyle = elementStyle.fontStyle - const textDecorationLine = elementStyle.textDecorationLine + const fontSize = computedStyle.fontSize + const fontWeight = computedStyle.fontWeight + const fontStyle = computedStyle.fontStyle + const textDecorationLine = computedStyle.textDecorationLine const textBounds = elementContainsOnlyText(element) ? stretchRect( @@ -896,15 +896,15 @@ function getSpecialMeasurements( ), { w: - maybeValueFromComputedStyle(elementStyle.paddingLeft) + - maybeValueFromComputedStyle(elementStyle.paddingRight) + - maybeValueFromComputedStyle(elementStyle.marginLeft) + - maybeValueFromComputedStyle(elementStyle.marginRight), + maybeValueFromComputedStyle(computedStyle.paddingLeft) + + maybeValueFromComputedStyle(computedStyle.paddingRight) + + maybeValueFromComputedStyle(computedStyle.marginLeft) + + maybeValueFromComputedStyle(computedStyle.marginRight), h: - maybeValueFromComputedStyle(elementStyle.paddingTop) + - maybeValueFromComputedStyle(elementStyle.paddingBottom) + - maybeValueFromComputedStyle(elementStyle.marginTop) + - maybeValueFromComputedStyle(elementStyle.marginBottom), + maybeValueFromComputedStyle(computedStyle.paddingTop) + + maybeValueFromComputedStyle(computedStyle.paddingBottom) + + maybeValueFromComputedStyle(computedStyle.marginTop) + + maybeValueFromComputedStyle(computedStyle.marginBottom), }, ) : null @@ -944,7 +944,7 @@ function getSpecialMeasurements( const containerGridPropertiesFromProps = getGridContainerProperties(element.style) const parentContainerGridPropertiesFromProps = getGridContainerProperties(parentElementStyle) - const containerGridProperties = getGridContainerProperties(elementStyle, { + const containerGridProperties = getGridContainerProperties(computedStyle, { dynamicCols: isDynamicGridTemplate(containerGridPropertiesFromProps.gridTemplateColumns), dynamicRows: isDynamicGridTemplate(containerGridPropertiesFromProps.gridTemplateRows), }) @@ -955,7 +955,7 @@ function getSpecialMeasurements( ) const containerElementProperties = getGridElementProperties( parentContainerGridProperties, - elementStyle, + computedStyle, ) return specialSizeMeasurements( @@ -970,7 +970,7 @@ function getSpecialMeasurements( layoutSystemForChildren, false, // layoutSystemForChildrenInherited providesBoundsForAbsoluteChildren, - elementStyle.display, + computedStyle.display, position, isRight(margin) ? margin.value : sides(undefined, undefined, undefined, undefined), paddingValue, @@ -993,7 +993,7 @@ function getSpecialMeasurements( element.localName, childrenCount, globalContentBoxForChildren, - elementStyle.float, + computedStyle.float, hasPositionOffset, parentTextDirection, hasTransform, diff --git a/editor/src/components/inspector/common/css-utils.ts b/editor/src/components/inspector/common/css-utils.ts index a275e06c50ec..0e7d0c444c54 100644 --- a/editor/src/components/inspector/common/css-utils.ts +++ b/editor/src/components/inspector/common/css-utils.ts @@ -1208,9 +1208,7 @@ export function parseGridRange( const startParsed = parseGridPosition(container, axis, 'start', null, input) return mapEither((start) => { const end = - !isCSSKeyword(start) && start.numericalPosition != null - ? gridPositionValue(start.numericalPosition + 1) - : null + !isCSSKeyword(start) && start.numericalPosition != null ? cssKeyword('auto') : null return gridRange(start, end) }, startParsed) } diff --git a/editor/src/core/model/element-metadata-utils.ts b/editor/src/core/model/element-metadata-utils.ts index 1e8209a245ec..e55959495b93 100644 --- a/editor/src/core/model/element-metadata-utils.ts +++ b/editor/src/core/model/element-metadata-utils.ts @@ -385,6 +385,16 @@ export const MetadataUtils = { const elementMetadata = MetadataUtils.findElementByElementPath(metadata, path) return elementMetadata?.specialSizeMeasurements.parentLayoutSystem === 'grid' }, + isGridItemWithLayoutProvidingGridParent( + metadata: ElementInstanceMetadataMap, + path: ElementPath, + ): boolean { + const elementMetadata = MetadataUtils.findElementByElementPath(metadata, EP.parentPath(path)) + return ( + elementMetadata?.specialSizeMeasurements.layoutSystemForChildren === 'grid' && + elementMetadata?.specialSizeMeasurements.providesBoundsForAbsoluteChildren + ) + }, isGridItemWithPositioning(metadata: ElementInstanceMetadataMap, path: ElementPath): boolean { const element = MetadataUtils.findElementByElementPath(metadata, path) return ( diff --git a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap index 1e4ec99abbfc..85a45178b8d7 100644 --- a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap +++ b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap @@ -36,6 +36,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -823,6 +824,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -1499,6 +1501,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -1790,6 +1793,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -2395,6 +2399,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -2504,6 +2509,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -2624,6 +2630,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -2903,6 +2910,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -3384,6 +3392,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", @@ -3435,6 +3444,7 @@ Array [ "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)(GridMeasurementHelpers)", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", + "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/UtopiaSpiedExoticType(Symbol(react.fragment))", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", "/div/div/UtopiaSpiedFunctionComponent(NewCanvasControlsInner)/Symbol(react.memo)()", diff --git a/editor/src/core/performance/performance-regression-tests.spec.tsx b/editor/src/core/performance/performance-regression-tests.spec.tsx index 57938fa23a2c..313173838176 100644 --- a/editor/src/core/performance/performance-regression-tests.spec.tsx +++ b/editor/src/core/performance/performance-regression-tests.spec.tsx @@ -65,7 +65,7 @@ describe('React Render Count Tests -', () => { const renderCountAfter = renderResult.getNumberOfRenders() // if this breaks, GREAT NEWS but update the test please :) - expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`868`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`872`) expect(renderResult.getRenderInfo()).toMatchSnapshot() }) @@ -127,7 +127,7 @@ describe('React Render Count Tests -', () => { const renderCountAfter = renderResult.getNumberOfRenders() // if this breaks, GREAT NEWS but update the test please :) - expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`1120`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`1124`) expect(renderResult.getRenderInfo()).toMatchSnapshot() }) @@ -183,7 +183,7 @@ describe('React Render Count Tests -', () => { const renderCountAfter = renderResult.getNumberOfRenders() // if this breaks, GREAT NEWS but update the test please :) - expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`657`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`658`) expect(renderResult.getRenderInfo()).toMatchSnapshot() }) @@ -249,7 +249,7 @@ describe('React Render Count Tests -', () => { const renderCountAfter = renderResult.getNumberOfRenders() // if this breaks, GREAT NEWS but update the test please :) - expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`782`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`783`) expect(renderResult.getRenderInfo()).toMatchSnapshot() }) })