diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx
index ce32f1cacebc..1f6367b241eb 100644
--- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx
+++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx
@@ -228,6 +228,7 @@ export function pickCanvasStateFromEditorState(
propertyControlsInfo: editorState.propertyControlsInfo,
styleInfoReader: activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
+ jsxMetadata: editorState.jsxMetadata,
}),
}
}
@@ -255,6 +256,7 @@ export function pickCanvasStateFromEditorStateWithMetadata(
propertyControlsInfo: editorState.propertyControlsInfo,
styleInfoReader: activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
+ jsxMetadata: editorState.jsxMetadata,
}),
}
}
diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts
index 80d553a97a9a..e27c7bdd404b 100644
--- a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts
+++ b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts
@@ -112,9 +112,11 @@ export function controlWithProps
(value: ControlWithProps
): ControlWithProp
export type StyleInfoReader = (elementPath: ElementPath) => StyleInfo | null
-export type StyleInfoFactory = (context: {
+export type StyleInfoContext = {
projectContents: ProjectContentTreeRoot
-}) => StyleInfoReader
+ jsxMetadata: ElementInstanceMetadataMap
+}
+export type StyleInfoFactory = (context: StyleInfoContext) => StyleInfoReader
export interface InteractionCanvasState {
interactionTarget: InteractionTarget
diff --git a/editor/src/components/canvas/commands/adjust-css-length-command.ts b/editor/src/components/canvas/commands/adjust-css-length-command.ts
index 128aeac077d6..df4b81ecd292 100644
--- a/editor/src/components/canvas/commands/adjust-css-length-command.ts
+++ b/editor/src/components/canvas/commands/adjust-css-length-command.ts
@@ -78,6 +78,7 @@ export const runAdjustCssLengthProperties = (
const styleInfoReader = getActivePlugin(withConflictingPropertiesRemoved).styleInfoFactory({
projectContents: withConflictingPropertiesRemoved.projectContents,
+ jsxMetadata: withConflictingPropertiesRemoved.jsxMetadata,
})
const styleInfo = styleInfoReader(command.target)
diff --git a/editor/src/components/canvas/commands/set-css-length-command.ts b/editor/src/components/canvas/commands/set-css-length-command.ts
index f69f66c328df..cc2a0998f8a2 100644
--- a/editor/src/components/canvas/commands/set-css-length-command.ts
+++ b/editor/src/components/canvas/commands/set-css-length-command.ts
@@ -87,6 +87,7 @@ export const runSetCssLengthProperty = (
const styleInfo = getActivePlugin(editorStateWithPropsDeleted).styleInfoFactory({
projectContents: editorStateWithPropsDeleted.projectContents,
+ jsxMetadata: editorStateWithPropsDeleted.jsxMetadata,
})(command.target)
if (styleInfo == null) {
diff --git a/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx b/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx
index eb0d98c4d493..7f01a640b7e8 100644
--- a/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/border-radius-control.tsx
@@ -63,6 +63,7 @@ const borderRadiusSelector = createCachedSelector(
(store: StyleInfoSubstate) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
+ jsxMetadata: store.editor.jsxMetadata,
}),
(_: MetadataSubstate, x: ElementPath) => x,
(metadata, styleInfoReader, selectedElement) => {
diff --git a/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx b/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx
index 672e28d2f801..6f0121923126 100644
--- a/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/flex-gap-control.tsx
@@ -139,6 +139,7 @@ export const FlexGapControl = controlForStrategyMemoized((p
maybeFlexGapData(
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
+ jsxMetadata: store.editor.jsxMetadata,
})(selectedElement),
MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement),
),
diff --git a/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx b/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx
index c9d8906e69ba..81827667dec2 100644
--- a/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/padding-resize-control.tsx
@@ -362,6 +362,7 @@ export const PaddingResizeControl = controlForStrategyMemoized((props: PaddingCo
const styleInfoReaderRef = useRefEditorState((store) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
+ jsxMetadata: store.editor.jsxMetadata,
}),
)
diff --git a/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx b/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx
index 539d082cc253..4e0091951593 100644
--- a/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx
+++ b/editor/src/components/canvas/controls/select-mode/subdued-flex-gap-controls.tsx
@@ -43,6 +43,7 @@ export const SubduedFlexGapControl = React.memo((pro
maybeFlexGapData(
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
+ jsxMetadata: store.editor.jsxMetadata,
})(selectedElement),
MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, selectedElement),
),
diff --git a/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx b/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx
index 766a180c9386..3fb67c29a496 100644
--- a/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx
+++ b/editor/src/components/canvas/controls/select-mode/subdued-padding-control.tsx
@@ -29,6 +29,7 @@ export const SubduedPaddingControl = React.memo((pro
const styleInfoReaderRef = useRefEditorState((store) =>
getActivePlugin(store.editor).styleInfoFactory({
projectContents: store.editor.projectContents,
+ jsxMetadata: store.editor.jsxMetadata,
}),
)
diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts
index 8ba0e9733b8c..e0e8029bce6f 100644
--- a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts
+++ b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts
@@ -110,6 +110,7 @@ function getStyleInfoFromInlineStyle(editor: EditorRenderResult) {
const styleInfoReader = InlineStylePlugin.styleInfoFactory({
projectContents: projectContents,
+ jsxMetadata: jsxMetadata,
})
const styleInfo = styleInfoReader(EP.fromString('sb/scene/div'))
return styleInfo
diff --git a/editor/src/components/canvas/plugins/style-plugins.ts b/editor/src/components/canvas/plugins/style-plugins.ts
index 42ecae83b215..79ef09308456 100644
--- a/editor/src/components/canvas/plugins/style-plugins.ts
+++ b/editor/src/components/canvas/plugins/style-plugins.ts
@@ -51,12 +51,27 @@ export function deleteCSSProp(property: string): DeleteCSSProp {
export type StyleUpdate = UpdateCSSProp | DeleteCSSProp
+export type SceneSize = { type: 'no-scene' } | { type: 'scene'; width: number }
+export function noSceneSize(): SceneSize {
+ return { type: 'no-scene' }
+}
+export function sceneSize(width: number | undefined | null): SceneSize {
+ if (width == null) {
+ return noSceneSize()
+ }
+ return { type: 'scene', width: width }
+}
+export type StylePluginContext = {
+ sceneSize: SceneSize
+}
+
export interface StylePlugin {
name: string
styleInfoFactory: StyleInfoFactory
readStyleFromElementProps: (
attributes: JSXAttributes,
prop: T,
+ context: StylePluginContext,
) => CSSStyleProperty> | null
updateStyles: (
editorState: EditorState,
@@ -248,6 +263,7 @@ export function patchRemovedProperties(editorState: EditorState): EditorState {
const styleInfoReader = activePlugin.styleInfoFactory({
projectContents: editorState.projectContents,
+ jsxMetadata: editorState.jsxMetadata,
})
const propertiesUpdatedDuringInteraction = getPropertiesUpdatedDuringInteraction(editorState)
diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts
index 2941f0bc9a12..e9fadf9ef689 100644
--- a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts
+++ b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts
@@ -5,7 +5,7 @@ import { getElementFromProjectContents } from '../../editor/store/editor-state'
import type { ParsedCSSProperties } from '../../inspector/common/css-utils'
import { cssParsers } from '../../inspector/common/css-utils'
import { mapDropNulls } from '../../../core/shared/array-utils'
-import type { StylePlugin } from './style-plugins'
+import type { StylePlugin, StylePluginContext } from './style-plugins'
import type { Config } from 'tailwindcss/types/config'
import type { StyleInfo } from '../canvas-types'
import { cssStyleProperty, type CSSStyleProperty } from '../canvas-types'
@@ -18,17 +18,20 @@ import {
import { emptyComments, type JSXAttributes } from 'utopia-shared/src/types'
import * as PP from '../../../core/shared/property-path'
import { jsExpressionValue } from '../../../core/shared/element-template'
-
-function parseTailwindProperty(
- value: string | number | undefined,
- prop: T,
-): CSSStyleProperty> | null {
- const parsed = cssParsers[prop](value, null)
- if (isLeft(parsed) || parsed.value == null) {
- return null
+import { getContainingSceneSize } from '../responsive-utils'
+
+const parseTailwindPropertyFactory =
+ (config: Config | null, context: StylePluginContext) =>
+ (
+ value: string | number | undefined,
+ prop: T,
+ ): CSSStyleProperty> | null => {
+ const parsed = cssParsers[prop](value, null)
+ if (isLeft(parsed) || parsed.value == null) {
+ return null
+ }
+ return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments))
}
- return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments))
-}
const TailwindPropertyMapping: Record = {
left: 'positionLeft',
@@ -100,6 +103,7 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
readStyleFromElementProps: (
attributes: JSXAttributes,
prop: P,
+ context: StylePluginContext,
): CSSStyleProperty> | null => {
const classNameAttribute = defaultEither(
null,
@@ -114,10 +118,11 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
}
const mapping = getTailwindClassMapping(classNameAttribute.split(' '), config)
+ const parseTailwindProperty = parseTailwindPropertyFactory(config, context)
return parseTailwindProperty(mapping[TailwindPropertyMapping[prop]], prop)
},
styleInfoFactory:
- ({ projectContents }) =>
+ ({ projectContents, jsxMetadata }) =>
(elementPath) => {
const classList = getClassNameAttribute(
getElementFromProjectContents(elementPath, projectContents),
@@ -128,6 +133,9 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({
}
const mapping = getTailwindClassMapping(classList.split(' '), config)
+ const parseTailwindProperty = parseTailwindPropertyFactory(config, {
+ sceneSize: getContainingSceneSize(elementPath, jsxMetadata),
+ })
return {
gap: parseTailwindProperty(mapping[TailwindPropertyMapping.gap], 'gap'),
diff --git a/editor/src/components/canvas/responsive-utils.ts b/editor/src/components/canvas/responsive-utils.ts
index 52349915b1b6..9d828e6399d5 100644
--- a/editor/src/components/canvas/responsive-utils.ts
+++ b/editor/src/components/canvas/responsive-utils.ts
@@ -3,6 +3,35 @@ import * as csstree from 'css-tree'
import type { StyleMediaSizeModifier, StyleModifier } from './canvas-types'
import { type CSSNumber, type CSSNumberUnit, cssNumber } from '../inspector/common/css-utils'
import { memoize } from '../../core/shared/memoize'
+import type { ElementPath } from '../../core/shared/project-file-types'
+import type { ElementInstanceMetadataMap } from '../../core/shared/element-template'
+import { MetadataUtils } from '../../core/model/element-metadata-utils'
+import { noSceneSize, sceneSize, type SceneSize } from './plugins/style-plugins'
+import type { EditorState } from '../editor/store/editor-state'
+
+export function getContainingSceneSize(
+ selectedElement: ElementPath,
+ jsxMetadata: ElementInstanceMetadataMap,
+): SceneSize {
+ if (selectedElement == null) {
+ return noSceneSize()
+ }
+ const containingScene = MetadataUtils.getParentSceneMetadata(jsxMetadata, selectedElement)
+ return sceneSize(containingScene?.specialSizeMeasurements?.clientWidth)
+}
+
+export function getContainingSceneSizeFromEditorState(editor: EditorState): SceneSize {
+ if (editor == null) {
+ return noSceneSize()
+ }
+ // we're taking the first selected element because we're assuming elements are in the same scene
+ // TODO: support multiple selected elements that are in different scenes?
+ const selectedElement = editor.selectedViews.at(0)
+ if (selectedElement == null) {
+ return noSceneSize()
+ }
+ return getContainingSceneSize(selectedElement, editor.jsxMetadata)
+}
/**
* Extracts the screen size from a CSS string, for example:
diff --git a/editor/src/components/inspector/common/property-path-hooks.ts b/editor/src/components/inspector/common/property-path-hooks.ts
index 1c8c719c6911..c0ef77d62597 100644
--- a/editor/src/components/inspector/common/property-path-hooks.ts
+++ b/editor/src/components/inspector/common/property-path-hooks.ts
@@ -1,5 +1,4 @@
import * as PP from '../../../core/shared/property-path'
-import * as EP from '../../../core/shared/element-path'
import deepEqual from 'fast-deep-equal'
import * as ObjectPath from 'object-path'
@@ -96,6 +95,7 @@ import { getActivePlugin } from '../../canvas/plugins/style-plugins'
import { isStyleInfoKey, type StyleInfo } from '../../canvas/canvas-types'
import { assertNever } from '../../../core/shared/utils'
import { maybeCssPropertyFromInlineStyle } from '../../canvas/commands/utils/property-utils'
+import { getContainingSceneSizeFromEditorState } from '../../canvas/responsive-utils'
export interface InspectorPropsContextData {
selectedViews: Array
@@ -763,7 +763,9 @@ export function useGetMultiselectedProps(
const styleInfoReaderRef = useRefEditorState(
(store) =>
(props: JSXAttributes, prop: keyof StyleInfo): GetModifiableAttributeResult => {
- const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop)
+ const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop, {
+ sceneSize: getContainingSceneSizeFromEditorState(store.editor),
+ })
if (elementStyle == null) {
return right({ type: 'ATTRIBUTE_NOT_FOUND' })
}
diff --git a/editor/src/core/model/element-metadata-utils.ts b/editor/src/core/model/element-metadata-utils.ts
index 7c3661bbcc77..e9892d1e1c96 100644
--- a/editor/src/core/model/element-metadata-utils.ts
+++ b/editor/src/core/model/element-metadata-utils.ts
@@ -1061,6 +1061,13 @@ export const MetadataUtils = {
const elementMetadata = MetadataUtils.findElementByElementPath(metadata, path)
return elementMetadata != null && isSceneFromMetadata(elementMetadata)
},
+ getParentSceneMetadata(
+ metadata: ElementInstanceMetadataMap,
+ path: ElementPath,
+ ): ElementInstanceMetadata | null {
+ const parentPath = MetadataUtils.findSceneOfTarget(path, metadata)
+ return parentPath == null ? null : MetadataUtils.findElementByElementPath(metadata, parentPath)
+ },
overflows(allElementProps: AllElementProps, path: ElementPath): boolean {
const elementProps = allElementProps[EP.toString(path)] ?? {}
const styleProps = elementProps.style ?? null