diff --git a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts index e3242fdd3f97..4c58e3d97ad9 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts @@ -48,6 +48,7 @@ import { setCursorCommand } from '../../commands/set-cursor-command' import { CSSCursor } from '../../canvas-types' import type { CanvasCommand } from '../../commands/commands' import type { Axis } from '../../gap-utils' +import { getComponentDescriptorForTarget } from '../../../../core/property-controls/property-controls-utils' export const resizeGridStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -59,6 +60,22 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + + const supportsStyleProp = MetadataUtils.targetRegisteredStyleControlsOrHonoursStyleProps( + canvasState.projectContents, + selectedElementMetadata, + canvasState.propertyControlsInfo, + 'layout-system', + ['gridTemplateColumns', 'gridTemplateRows'], + 'some', + ) const isGridCell = MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement) const isGrid = MetadataUtils.isGridLayoutedContainer( @@ -94,7 +111,9 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( }, controlsForGridPlaceholders(gridPath), ], - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1), + fitness: supportsStyleProp + ? onlyFitWhenDraggingThisControl(interactionSession, 'GRID_AXIS_HANDLE', 1) + : 0, apply: () => { if ( interactionSession == null || diff --git a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx index efcba9317cb9..742839522e89 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/set-grid-gap-strategy.tsx @@ -44,6 +44,7 @@ import type { GridGapControlProps } from '../../controls/select-mode/grid-gap-co import { GridGapControl } from '../../controls/select-mode/grid-gap-control' import type { GridControlsProps } from '../../controls/grid-controls-for-strategies' import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' +import { getComponentDescriptorForTarget } from '../../../../core/property-controls/property-controls-utils' const SetGridGapStrategyId = 'SET_GRID_GAP_STRATEGY' @@ -63,22 +64,31 @@ export const setGridGapStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) if ( - !MetadataUtils.isGridLayoutedContainer( - MetadataUtils.findElementByElementPath(canvasState.startingMetadata, selectedElement), - ) + selectedElementMetadata == null || + !MetadataUtils.isGridLayoutedContainer(selectedElementMetadata) ) { return null } - const children = recurseIntoChildrenOfMapOrFragment( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - selectedElement, + const supportsStyleProp = MetadataUtils.targetRegisteredStyleControlsOrHonoursStyleProps( + canvasState.projectContents, + selectedElementMetadata, + canvasState.propertyControlsInfo, + 'layout-system', + ['gap', 'rowGap', 'columnGap'], + 'some', ) + if (!supportsStyleProp) { + return null + } const gridGap = maybeGridGapData(canvasState.startingMetadata, selectedElement) + if (gridGap == null) { return null } diff --git a/editor/src/components/custom-code/code-file.ts b/editor/src/components/custom-code/code-file.ts index db71fa3fa70c..22fda15045d8 100644 --- a/editor/src/components/custom-code/code-file.ts +++ b/editor/src/components/custom-code/code-file.ts @@ -154,7 +154,7 @@ export interface ShownInspectorSpec { sections: Styling[] } -export type TypedInpsectorSpec = { type: 'hidden' } | ShownInspectorSpec +export type TypedInspectorSpec = { type: 'hidden' } | ShownInspectorSpec export interface ComponentDescriptor { properties: PropertyControls @@ -163,7 +163,7 @@ export interface ComponentDescriptor { variants: ComponentInfo[] source: ComponentDescriptorSource focus: Focus - inspector: TypedInpsectorSpec + inspector: TypedInspectorSpec emphasis: Emphasis icon: Icon label: string | null @@ -187,7 +187,7 @@ export function componentDescriptor( preferredChildComponents: Array, source: ComponentDescriptorSource, focus: Focus, - inspector: TypedInpsectorSpec, + inspector: TypedInspectorSpec, emphasis: Emphasis, icon: Icon, label: string | null, diff --git a/editor/src/components/editor/store/store-deep-equality-instances.ts b/editor/src/components/editor/store/store-deep-equality-instances.ts index 8db911028e77..b1b7f7816a3c 100644 --- a/editor/src/components/editor/store/store-deep-equality-instances.ts +++ b/editor/src/components/editor/store/store-deep-equality-instances.ts @@ -491,7 +491,7 @@ import type { CurriedUtopiaRequireFn, PropertyControlsInfo, ComponentDescriptorFromDescriptorFile, - TypedInpsectorSpec, + TypedInspectorSpec, ShownInspectorSpec, StyleSectionState, } from '../../custom-code/code-file' @@ -3935,7 +3935,7 @@ export function ComponentDescriptorSourceKeepDeepEquality(): KeepDeepEqualityCal } } -const InspectorSpecKeepDeepEquality: KeepDeepEqualityCall = ( +const InspectorSpecKeepDeepEquality: KeepDeepEqualityCall = ( oldValue, newValue, ) => { diff --git a/editor/src/core/model/element-metadata-utils.ts b/editor/src/core/model/element-metadata-utils.ts index 0c5ece97bed1..3c5134804b4b 100644 --- a/editor/src/core/model/element-metadata-utils.ts +++ b/editor/src/core/model/element-metadata-utils.ts @@ -1,5 +1,5 @@ import * as OPI from 'object-path-immutable' -import type { Emphasis, FlexLength, Icon, Sides } from 'utopia-api/core' +import type { Emphasis, FlexLength, Icon, Sides, Styling } from 'utopia-api/core' import { sides } from 'utopia-api/core' import { getReorderDirection } from '../../components/canvas/controls/select-mode/yoga-utils' import { getImageSize, scaleImageDimensions } from '../../components/images' @@ -113,6 +113,7 @@ import { componentUsesProperty, findJSXElementChildAtPath, elementChildSupportsChildrenAlsoText, + componentHonoursStyleProps, } from './element-template-utils' import { isImportedComponent, @@ -1212,6 +1213,70 @@ export const MetadataUtils = { return componentHonoursPropsSize(underlyingComponent) } }, + targetHonoursStyleProps( + projectContents: ProjectContentTreeRoot, + metadata: ElementInstanceMetadata | null, + props: Array, + everyOrSome: 'every' | 'some', + ): boolean { + if (metadata == null) { + return false + } + if ( + isLeft(metadata.element) || + (isRight(metadata.element) && !isJSXElement(metadata.element.value)) + ) { + return false + } + const underlyingComponent = findUnderlyingTargetComponentImplementationFromImportInfo( + projectContents, + metadata.importInfo, + ) + if (underlyingComponent == null) { + // Could be an external third party component, assuming true for now. + return true + } else { + return componentHonoursStyleProps(underlyingComponent, props, everyOrSome) + } + }, + targetRegisteredStyleControlsOrHonoursStyleProps( + projectContents: ProjectContentTreeRoot, + metadata: ElementInstanceMetadata, + propertyControlsInfo: PropertyControlsInfo, + inspectorSection: Styling, // if this inspector section is registered then the target honours the style props + props: Array, + everyOrSome: 'every' | 'some', + ): boolean { + const inspectorSectionRegistered = (() => { + const descriptor = getComponentDescriptorForTarget( + { propertyControlsInfo, projectContents }, + metadata.elementPath, + ) + + if (descriptor?.inspector == null) { + return 'no-annotation' + } + + if ( + descriptor.inspector.type === 'hidden' || + !descriptor.inspector.sections.includes(inspectorSection) + ) { + return 'registered-to-hide' + } + return 'registered-to-show' + })() + + switch (inspectorSectionRegistered) { + case 'registered-to-show': // + return true + case 'registered-to-hide': + return false + case 'no-annotation': + return this.targetHonoursStyleProps(projectContents, metadata, props, everyOrSome) + default: + assertNever(inspectorSectionRegistered) + } + }, targetHonoursPropsPosition( projectContents: ProjectContentTreeRoot, metadata: ElementInstanceMetadata | null, diff --git a/editor/src/core/model/element-template-utils.ts b/editor/src/core/model/element-template-utils.ts index f5274b447419..1f00ca7c7849 100644 --- a/editor/src/core/model/element-template-utils.ts +++ b/editor/src/core/model/element-template-utils.ts @@ -969,6 +969,46 @@ export function componentHonoursPropsSize(component: UtopiaJSXComponent): boolea } } +export function componentHonoursStyleProps( + component: UtopiaJSXComponent, + props: Array, + everyOrSome: 'every' | 'some', +): boolean { + if (component.params == null) { + return false + } else { + const nonNullParams = component.params + function checkElements(rootElement: JSXElementChild): boolean { + switch (rootElement.type) { + case 'JSX_ELEMENT': + if (propsStyleIsSpreadInto(nonNullParams, rootElement.props)) { + return true + } + const styleAttrs = props.map((prop) => ({ + propName: prop, + attr: getJSXAttributesAtPath(rootElement.props, PP.create('style', prop)), + })) + const filterFn = (styleAttr: { propName: string; attr: GetJSXAttributeResult }) => + propertyComesFromPropsStyle(nonNullParams, styleAttr.attr, styleAttr.propName) + switch (everyOrSome) { + case 'some': + return styleAttrs.some(filterFn) + case 'every': + return styleAttrs.every(filterFn) + default: + assertNever(everyOrSome) + } + break + case 'JSX_FRAGMENT': + return rootElement.children.every(checkElements) + default: + return false + } + } + return checkElements(component.rootElement) + } +} + function checkJSReferencesVariable( jsExpression: JSExpressionOtherJavaScript, variableName: string, diff --git a/editor/src/core/property-controls/property-controls-local.ts b/editor/src/core/property-controls/property-controls-local.ts index 3449b3322b0a..44aa40be87cf 100644 --- a/editor/src/core/property-controls/property-controls-local.ts +++ b/editor/src/core/property-controls/property-controls-local.ts @@ -43,7 +43,7 @@ import type { ComponentDescriptorWithName, ComponentInfo, PropertyControlsInfo, - TypedInpsectorSpec, + TypedInspectorSpec, } from '../../components/custom-code/code-file' import { dependenciesFromPackageJson } from '../../components/editor/npm-dependency/npm-dependency' import { parseControlDescription } from './property-controls-parser' @@ -998,7 +998,7 @@ async function parseComponentVariants( return parsedVariants } -function parseInspectorSpec(inspector: InspectorSpec | undefined): TypedInpsectorSpec { +function parseInspectorSpec(inspector: InspectorSpec | undefined): TypedInspectorSpec { if (inspector == null) { return ComponentDescriptorDefaults.inspector }