diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx
index 533fb5f8c7ff..9fead6612cd2 100644
--- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx
+++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.spec.browser2.tsx
@@ -3124,6 +3124,96 @@ describe('Double click on resize edge', () => {
expect(div.style.width).toEqual(MaxContent)
expect(div.style.height).toEqual('445px')
})
+
+ describe('grids', () => {
+ const project = ({
+ rows: rows,
+ columns: columns,
+ }: {
+ rows: string
+ columns: string
+ }) => `import * as React from 'react'
+import { Scene, Storyboard } from 'utopia-api'
+
+export var storyboard = (
+
+
+
+
+
+
+
+)
+`
+ it('removes width when right edge is clicked', async () => {
+ const editor = await renderTestEditorWithCode(
+ project({ rows: '66px 66px 66px 66px', columns: '50px 81px 96px 85px' }),
+ 'await-first-dom-report',
+ )
+ const div = await doDblClickTest(editor, edgeResizeControlTestId(EdgePositionRight))
+ expect(div.style.width).toEqual('') // width is removed
+ expect(div.style.height).toEqual('400px')
+ })
+ it('removes height when bottom edge is clicked', async () => {
+ const editor = await renderTestEditorWithCode(
+ project({ rows: '66px 66px 66px 66px', columns: '50px 81px 96px 85px' }),
+ 'await-first-dom-report',
+ )
+ const div = await doDblClickTest(editor, edgeResizeControlTestId(EdgePositionBottom))
+ expect(div.style.width).toEqual('400px')
+ expect(div.style.height).toEqual('') // height is removed
+ })
+ it("isn't applicable when the selected grid uses fr along the affected axis", async () => {
+ const editor = await renderTestEditorWithCode(
+ project({ rows: '66px 1fr 66px 66px', columns: '50px 81px 96px 85px' }),
+ 'await-first-dom-report',
+ )
+ const div = await doDblClickTest(editor, edgeResizeControlTestId(EdgePositionBottom))
+ expect(div.style.width).toEqual('400px')
+ expect(div.style.height).toEqual('400px')
+ })
+ })
})
describe('double click on resize corner', () => {
diff --git a/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts b/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts
index aed823bad9bc..af2fc721ea09 100644
--- a/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts
+++ b/editor/src/components/common/shared-strategies/convert-to-flex-strategy.ts
@@ -50,7 +50,7 @@ import {
onlyChildIsSpan,
sizeToVisualDimensions,
} from '../../inspector/inspector-common'
-import { setHugContentForAxis } from '../../inspector/inspector-strategies/hug-contents-basic-strategy'
+import { setHugContentForAxis } from '../../inspector/inspector-strategies/hug-contents-strategy'
type FlexDirectionRowColumn = 'row' | 'column' // a limited subset as we won't never guess row-reverse or column-reverse
type FlexAlignItems = 'center' | 'flex-end'
diff --git a/editor/src/components/inspector/inspector-common.ts b/editor/src/components/inspector/inspector-common.ts
index 3c5fbdacfb8a..c9163ded2cef 100644
--- a/editor/src/components/inspector/inspector-common.ts
+++ b/editor/src/components/inspector/inspector-common.ts
@@ -75,7 +75,7 @@ import {
import { fixedSizeDimensionHandlingText } from '../text-editor/text-handling'
import { convertToAbsolute } from '../canvas/commands/convert-to-absolute-command'
import { hugPropertiesFromStyleMap } from '../../core/shared/dom-utils'
-import { setHugContentForAxis } from './inspector-strategies/hug-contents-basic-strategy'
+import { setHugContentForAxis } from './inspector-strategies/hug-contents-strategy'
export type StartCenterEnd = 'flex-start' | 'center' | 'flex-end'
@@ -255,12 +255,12 @@ export function detectAreElementsFlexContainers(
export const isFlexColumn = (flexDirection: FlexDirection): boolean =>
flexDirection.startsWith('column')
-export const hugContentsApplicableForContainer = (
+export const basicHugContentsApplicableForContainer = (
metadata: ElementInstanceMetadataMap,
pathTrees: ElementPathTrees,
elementPath: ElementPath,
): boolean => {
- return (
+ const isNonFixStickOrAbsolute =
mapDropNulls(
(path) => MetadataUtils.findElementByElementPath(metadata, path),
MetadataUtils.getChildrenPathsOrdered(metadata, pathTrees, elementPath),
@@ -272,7 +272,12 @@ export const hugContentsApplicableForContainer = (
MetadataUtils.isPositionAbsolute(element)
),
).length > 0
+
+ const isGrid = MetadataUtils.isGridLayoutedContainer(
+ MetadataUtils.findElementByElementPath(metadata, elementPath),
)
+
+ return isNonFixStickOrAbsolute && !isGrid
}
export const hugContentsApplicableForText = (
@@ -999,7 +1004,7 @@ export function getFixedFillHugOptionsForElement(
isGroup ? 'hug-group' : null,
'fixed',
hugContentsApplicableForText(metadata, selectedView) ||
- (!isGroup && hugContentsApplicableForContainer(metadata, pathTrees, selectedView))
+ (!isGroup && basicHugContentsApplicableForContainer(metadata, pathTrees, selectedView))
? 'hug'
: null,
fillContainerApplicable(metadata, selectedView) ? 'fill' : null,
diff --git a/editor/src/components/inspector/inspector-strategies/hug-contents-basic-strategy.ts b/editor/src/components/inspector/inspector-strategies/hug-contents-strategy.ts
similarity index 66%
rename from editor/src/components/inspector/inspector-strategies/hug-contents-basic-strategy.ts
rename to editor/src/components/inspector/inspector-strategies/hug-contents-strategy.ts
index 70ca1829f219..8e6395c3cf24 100644
--- a/editor/src/components/inspector/inspector-strategies/hug-contents-basic-strategy.ts
+++ b/editor/src/components/inspector/inspector-strategies/hug-contents-strategy.ts
@@ -1,5 +1,6 @@
import type { ElementPathTrees } from '../../../core/shared/element-path-tree'
import { MetadataUtils } from '../../../core/model/element-metadata-utils'
+import type { GridTemplate } from '../../../core/shared/element-template'
import { type ElementInstanceMetadataMap } from '../../../core/shared/element-template'
import type { ElementPath } from '../../../core/shared/project-file-types'
import * as PP from '../../../core/shared/property-path'
@@ -15,7 +16,7 @@ import { cssKeyword } from '../common/css-utils'
import type { Axis } from '../inspector-common'
import {
detectFillHugFixedState,
- hugContentsApplicableForContainer,
+ basicHugContentsApplicableForContainer,
hugContentsApplicableForText,
MaxContent,
nukeSizingPropsForAxisCommand,
@@ -28,6 +29,8 @@ import { queueTrueUpElement } from '../../canvas/commands/queue-true-up-command'
import { trueUpGroupElementChanged } from '../../../components/editor/store/editor-state'
import type { AllElementProps } from '../../../components/editor/store/editor-state'
import { convertSizelessDivToFrameCommands } from '../../canvas/canvas-strategies/strategies/group-conversion-helpers'
+import { deleteProperties } from '../../canvas/commands/delete-properties-command'
+import { assertNever } from '../../../core/shared/utils'
const CHILDREN_CONVERTED_TOAST_ID = 'CHILDREN_CONVERTED_TOAST_ID'
@@ -87,6 +90,70 @@ function hugContentsSingleElement(
]
}
+function gridTemplateUsesFr(template: GridTemplate | null) {
+ return (
+ template != null &&
+ template.type === 'DIMENSIONS' &&
+ template.dimensions.some((d) => d.unit === 'fr')
+ )
+}
+
+function elementUsesFrAlongAxis(
+ axis: Axis,
+ metadata: ElementInstanceMetadataMap,
+ elementPath: ElementPath,
+) {
+ const instance = MetadataUtils.findElementByElementPath(metadata, elementPath)
+ if (instance == null) {
+ return false
+ }
+ const { containerGridProperties, containerGridPropertiesFromProps } =
+ instance.specialSizeMeasurements
+
+ switch (axis) {
+ case 'horizontal':
+ return (
+ gridTemplateUsesFr(containerGridProperties.gridTemplateColumns) ||
+ gridTemplateUsesFr(containerGridPropertiesFromProps.gridTemplateColumns)
+ )
+ case 'vertical':
+ return (
+ gridTemplateUsesFr(containerGridProperties.gridTemplateRows) ||
+ gridTemplateUsesFr(containerGridPropertiesFromProps.gridTemplateRows)
+ )
+ default:
+ assertNever(axis)
+ }
+}
+
+export const hugContentsGridStrategy = (
+ metadata: ElementInstanceMetadataMap,
+ elementPaths: ElementPath[],
+ axis: Axis,
+): InspectorStrategy => ({
+ name: 'Set Grid to Hug',
+ strategy: () => {
+ // only run the strategy if all selected elements are grids
+ const allSelectedElementsGrids = elementPaths.every((e) =>
+ MetadataUtils.isGridLayoutedContainer(MetadataUtils.findElementByElementPath(metadata, e)),
+ )
+
+ // only run the strategy if no selected element uses fr along the affected
+ // axis, because that implies that the container needs to be sized
+ const anyElementUsesFrAlongAxis = elementPaths.some((e) =>
+ elementUsesFrAlongAxis(axis, metadata, e),
+ )
+
+ if (!allSelectedElementsGrids || anyElementUsesFrAlongAxis) {
+ return null
+ }
+
+ return elementPaths.flatMap((elementPath) =>
+ deleteProperties('always', elementPath, [PP.create('style', widthHeightFromAxis(axis))]),
+ )
+ },
+})
+
export const hugContentsBasicStrategy = (
metadata: ElementInstanceMetadataMap,
elementPaths: ElementPath[],
@@ -97,7 +164,7 @@ export const hugContentsBasicStrategy = (
strategy: () => {
const elements = elementPaths.filter(
(path) =>
- hugContentsApplicableForContainer(metadata, pathTrees, path) ||
+ basicHugContentsApplicableForContainer(metadata, pathTrees, path) ||
hugContentsApplicableForText(metadata, path),
)
diff --git a/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts b/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts
index 82550696f489..471bba6fa6b3 100644
--- a/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts
+++ b/editor/src/components/inspector/inspector-strategies/inspector-strategies.ts
@@ -16,7 +16,8 @@ import type { WhenToRun } from '../../../components/canvas/commands/commands'
import {
hugContentsAbsoluteStrategy,
hugContentsBasicStrategy,
-} from './hug-contents-basic-strategy'
+ hugContentsGridStrategy,
+} from './hug-contents-strategy'
import {
fillContainerStrategyFlexParent,
fillContainerStrategyFlow,
@@ -204,7 +205,10 @@ export const setPropHugStrategies = (
elementPaths: ElementPath[],
pathTrees: ElementPathTrees,
axis: Axis,
-): Array => [hugContentsBasicStrategy(metadata, elementPaths, pathTrees, axis)]
+): Array => [
+ hugContentsGridStrategy(metadata, elementPaths, axis),
+ hugContentsBasicStrategy(metadata, elementPaths, pathTrees, axis),
+]
export const setPropHugAbsoluteStrategies = (
metadata: ElementInstanceMetadataMap,