From 7f377c9846b2cca836b7df4c5ace3b41a2469156 Mon Sep 17 00:00:00 2001 From: Sean Parsons <217400+seanparsons@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:15:34 +0100 Subject: [PATCH] fix(canvas) Fixed handling of parent frames to get the right positioning. (#6586) - Split `maybeAddContainLayout` into that and another function `shouldAddContainLayout`. - Corrected two tests which were inserting into the second flex child and were also offset similar to how the grid items were. - `getAbsoluteReparentPropertyChanges` now takes a parameter which specifies if `contain: 'layout'` will be added to the container. - `getMoveCommandsForSelectedElement` now has an additional fallback for `elementParentBounds` when the closest non fragment parent provides bounds for absolute children. - `getLocalFrame` now has an optional parameter to specify the parent to target, to allow for handling the lookups where the element has yet to be reparented. --- .../strategies/absolute-reparent-strategy.tsx | 35 +++- .../absolute-resize-bounding-box-strategy.tsx | 2 +- .../draw-to-insert-strategy.spec.browser2.tsx | 172 +++++++++++++++++- ...lute-reparent-strategies.spec.browser2.tsx | 4 +- .../reparent-helpers/reparent-helpers.ts | 4 +- .../reparent-property-changes.ts | 8 +- .../reparent-property-strategies.ts | 1 + .../shared-move-strategies-helpers.ts | 42 ++++- editor/src/components/canvas/canvas-utils.ts | 5 +- .../flex-gap-control.test-utils.tsx | 1 + .../controls/select-mode/flex-gap-control.tsx | 2 +- .../convert-to-flex-strategy.ts | 2 +- .../src/components/editor/actions/actions.tsx | 7 +- .../insert-as-absolute-strategy.tsx | 1 + ...eparent-to-unwrap-as-absolute-strategy.tsx | 1 + .../components/inspector/inspector-common.ts | 4 +- ...-updating-layout-section.spec.browser2.tsx | 1 + .../frame-updating-layout-section.tsx | 24 ++- .../components/text-editor/text-handling.ts | 2 +- editor/src/core/layout/layout-utils.ts | 2 +- .../src/core/model/element-metadata-utils.ts | 11 +- 21 files changed, 297 insertions(+), 34 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx index 504a642b1f0d..587de432e8c3 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx @@ -37,6 +37,7 @@ import type { InteractionSession, UpdatedPathMap } from '../interaction-state' import { absoluteMoveStrategy } from './absolute-move-strategy' import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' +import type { ShouldAddContainLayout } from './reparent-helpers/reparent-helpers' import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' import type { ForcePins } from './reparent-helpers/reparent-property-changes' import { getAbsoluteReparentPropertyChanges } from './reparent-helpers/reparent-property-changes' @@ -184,6 +185,12 @@ export function applyAbsoluteReparent( projectContents, nodeModules, 'force-pins', + shouldAddContainLayout( + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + newParent.intendedParentPath, + ), ), selectedElements, ) @@ -254,6 +261,7 @@ export function createAbsoluteReparentAndOffsetCommands( projectContents: ProjectContentTreeRoot, nodeModules: NodeModules, forcePins: ForcePins, + willContainLayoutBeAdded: ShouldAddContainLayout, ) { const reparentResult = getReparentOutcome( metadata, @@ -271,12 +279,13 @@ export function createAbsoluteReparentAndOffsetCommands( if (reparentResult == null) { return null } else { - const offsetCommands = replaceFragmentLikePathsWithTheirChildrenRecursive( + const replacedPaths = replaceFragmentLikePathsWithTheirChildrenRecursive( metadata, elementProps, pathTree, [target], - ).flatMap((p) => { + ) + const offsetCommands = replacedPaths.flatMap((p) => { return getAbsoluteReparentPropertyChanges( p, newParent.intendedParentPath, @@ -284,6 +293,7 @@ export function createAbsoluteReparentAndOffsetCommands( metadata, projectContents, forcePins, + willContainLayoutBeAdded, ) }) @@ -296,12 +306,12 @@ export function createAbsoluteReparentAndOffsetCommands( } } -function maybeAddContainLayout( +export function shouldAddContainLayout( metadata: ElementInstanceMetadataMap, allElementProps: AllElementProps, pathTrees: ElementPathTrees, path: ElementPath, -): CanvasCommand[] { +): ShouldAddContainLayout { const closestNonFragmentParent = MetadataUtils.getClosestNonFragmentParent( metadata, allElementProps, @@ -310,14 +320,23 @@ function maybeAddContainLayout( ) if (EP.isStoryboardPath(closestNonFragmentParent)) { - return [] + return 'dont-add-contain-layout' } const parentProvidesBoundsForAbsoluteChildren = MetadataUtils.findElementByElementPath(metadata, closestNonFragmentParent) ?.specialSizeMeasurements.providesBoundsForAbsoluteChildren === true - return parentProvidesBoundsForAbsoluteChildren - ? [] - : [setProperty('always', path, PP.create('style', 'contain'), 'layout')] + return parentProvidesBoundsForAbsoluteChildren ? 'dont-add-contain-layout' : 'add-contain-layout' +} + +function maybeAddContainLayout( + metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + pathTrees: ElementPathTrees, + path: ElementPath, +): CanvasCommand[] { + return shouldAddContainLayout(metadata, allElementProps, pathTrees, path) === 'add-contain-layout' + ? [setProperty('always', path, PP.create('style', 'contain'), 'layout')] + : [] } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx index 8942da7ab335..0409ecb7a166 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-resize-bounding-box-strategy.tsx @@ -482,7 +482,7 @@ function getConstrainedSizes( constraints.right || constraints.width - const localFrame = MetadataUtils.getLocalFrame(element.elementPath, jsxMetadata) + const localFrame = MetadataUtils.getLocalFrame(element.elementPath, jsxMetadata, null) if ( isConstrained && localFrame != null && diff --git a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx index 9c26194be566..07f3ec449d2c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-strategy.spec.browser2.tsx @@ -16,9 +16,15 @@ import { import { RightMenuTab } from '../../../editor/store/editor-state' import { FOR_TESTS_setNextGeneratedUid } from '../../../../core/model/element-template-utils.test-utils' import type { WindowPoint } from '../../../../core/shared/math-utils' -import { windowPoint } from '../../../../core/shared/math-utils' +import { + nullIfInfinity, + rectangleContainsRectangle, + windowPoint, +} from '../../../../core/shared/math-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' import CanvasActions from '../../canvas-actions' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { forceNotNull } from '../../../../core/shared/optional-utils' function slightlyOffsetWindowPointBecauseVeryWeirdIssue(point: { x: number; y: number }) { // FIXME when running in headless chrome, the result of getBoundingClientRect will be slightly @@ -201,6 +207,109 @@ export var storyboard = ( ) +` + + const projectWithGridContent = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+
+
+
+
+
+ + +) ` it('can click to insert into a grid', async () => { @@ -377,5 +486,66 @@ export var storyboard = ( width: '20px', }) }) + + it('can draw to insert into an element in a grid', async () => { + const editor = await renderTestEditorWithCode( + projectWithGridContent, + 'await-first-dom-report', + ) + + await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/grid-item-8')]) + + await pressKey('d') + ensureInInsertMode(editor) + + const gridItem = editor.renderedDOM.getByTestId('grid-item-8') + const gridBB = gridItem.getBoundingClientRect() + + const target: WindowPoint = windowPoint({ + x: gridBB.x + 5, + y: gridBB.y + 5, + }) + + const canvasControlsLayer = editor.renderedDOM.getByTestId(CanvasControlsContainerID) + + await mouseMoveToPoint(canvasControlsLayer, target) + await mouseDragFromPointToPoint(canvasControlsLayer, target, { + x: target.x + 40, + y: target.y + 60, + }) + await editor.getDispatchFollowUpActionsFinished() + + const child = gridItem.firstChild + if (child == null) { + throw new Error('Draw to insert should be able to insert an element') + } + + const { position, top, left, width, height } = (child as HTMLElement).style + + expect({ position, top, left, width, height }).toEqual({ + position: 'absolute', + left: '6px', + top: '6px', + width: '40px', + height: '60px', + }) + + const gridItem8Metadata = + editor.getEditorState().editor.jsxMetadata['sb/scene/grid/grid-item-8'] + const gridItem8GlobalFrame = forceNotNull( + 'Item 8 should have a global frame.', + nullIfInfinity(gridItem8Metadata.globalFrame), + ) + const gridItem8Children = MetadataUtils.getChildrenUnordered( + editor.getEditorState().editor.jsxMetadata, + EP.fromString('sb/scene/grid/grid-item-8'), + ) + const gridItem8Child = forceNotNull('Could not find child.', gridItem8Children.at(0)) + const gridItem8ChildGlobalFrame = forceNotNull( + 'Child of item 8 should have a global frame.', + nullIfInfinity(gridItem8Child.globalFrame), + ) + expect(rectangleContainsRectangle(gridItem8GlobalFrame, gridItem8ChildGlobalFrame)).toBe(true) + }) }) }) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx index 7c9ec0953a84..a9712b9857f4 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/forced-absolute-reparent-strategies.spec.browser2.tsx @@ -281,7 +281,7 @@ describe('Fallback Absolute Reparent Strategies', () => {
{
= [ PP.create('style', 'left'), @@ -90,6 +91,7 @@ export function getAbsoluteReparentPropertyChanges( newParentStartingMetadata: ElementInstanceMetadataMap, projectContents: ProjectContentTreeRoot, forcePins: ForcePins, + willContainLayoutBeAdded: ShouldAddContainLayout, ): Array { const element: JSXElement | null = getJSXElementFromProjectContents(target, projectContents) @@ -108,7 +110,10 @@ export function getAbsoluteReparentPropertyChanges( const currentParentContentBox = MetadataUtils.getGlobalContentBoxForChildren(originalParentInstance) - const newParentContentBox = MetadataUtils.getGlobalContentBoxForChildren(newParentInstance) + const newParentContentBox = + willContainLayoutBeAdded === 'add-contain-layout' + ? nullIfInfinity(newParentInstance.globalFrame) + : MetadataUtils.getGlobalContentBoxForChildren(newParentInstance) if (currentParentContentBox == null || newParentContentBox == null) { return [] @@ -349,6 +354,7 @@ export function getReparentPropertyChanges( currentMetadata, projectContents, 'force-pins', + 'dont-add-contain-layout', ) const strategyCommands = runReparentPropertyStrategies([ diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts index 39c06c04f062..526cb7d73a83 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-strategies.ts @@ -313,6 +313,7 @@ export const convertFragmentLikeChildrenToVisualSize = metadata.currentMetadata, projectContents, 'force-pins', + 'dont-add-contain-layout', ) } else { const directions = singleAxisAutoLayoutContainerDirections( diff --git a/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts index 4f93b1e1f635..7ec32d192246 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/shared-move-strategies-helpers.ts @@ -31,6 +31,7 @@ import { } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' +import type { AllElementProps } from '../../../editor/store/editor-state' import { getJSXElementFromProjectContents } from '../../../editor/store/editor-state' import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks' import { determineConstrainedDragAxis } from '../../canvas-controls-frame' @@ -78,6 +79,7 @@ import { } from '../../commands/set-css-length-command' import type { ActiveFrame, ActiveFrameAction } from '../../commands/set-active-frames-command' import { activeFrameTargetRect, setActiveFrames } from '../../commands/set-active-frames-command' +import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' export interface MoveCommandsOptions { ignoreLocalFrame?: boolean @@ -227,6 +229,8 @@ function getAppropriateLocalFrame( export function getDirectMoveCommandsForSelectedElement( projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, + startingAllElementProps: AllElementProps, + startingElementPathTree: ElementPathTrees, selectedElement: ElementPath, mappedPath: ElementPath, leftOrTop: 'left' | 'top', @@ -246,6 +250,8 @@ export function getDirectMoveCommandsForSelectedElement( return getMoveCommandsForSelectedElement( projectContents, startingMetadata, + startingAllElementProps, + startingElementPathTree, selectedElement, mappedPath, drag, @@ -255,6 +261,8 @@ export function getDirectMoveCommandsForSelectedElement( export function getMoveCommandsForSelectedElement( projectContents: ProjectContentTreeRoot, startingMetadata: ElementInstanceMetadataMap, + startingAllElementProps: AllElementProps, + startingElementPathTree: ElementPathTrees, selectedElement: ElementPath, mappedPath: ElementPath, drag: CanvasVector, @@ -273,14 +281,34 @@ export function getMoveCommandsForSelectedElement( selectedElement, ) + const closestNonFragmentParent = MetadataUtils.getClosestNonFragmentParent( + startingMetadata, + startingAllElementProps, + startingElementPathTree, + EP.parentPath(mappedPath), + ) + const closestNonFragmentParentMetadata = MetadataUtils.findElementByElementPath( + startingMetadata, + closestNonFragmentParent, + ) + const providesBoundsForAbsoluteChildren = + closestNonFragmentParentMetadata?.specialSizeMeasurements.providesBoundsForAbsoluteChildren ?? + false + const elementParentBounds = - elementMetadata?.specialSizeMeasurements.coordinateSystemBounds ?? null + elementMetadata?.specialSizeMeasurements.coordinateSystemBounds ?? + (providesBoundsForAbsoluteChildren + ? nullIfInfinity(closestNonFragmentParentMetadata?.globalFrame) + : null) ?? + null const globalFrame = nullIfInfinity( MetadataUtils.getFrameInCanvasCoords(selectedElement, startingMetadata), ) - const localFrame = nullIfInfinity(MetadataUtils.getLocalFrame(selectedElement, startingMetadata)) + const localFrame = nullIfInfinity( + MetadataUtils.getLocalFrame(selectedElement, startingMetadata, EP.parentPath(mappedPath)), + ) if (element == null) { return { commands: [], intendedBounds: [] } @@ -330,6 +358,8 @@ export function getInteractionMoveCommandsForSelectedElement( return getMoveCommandsForSelectedElement( canvasState.projectContents, canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, selectedElement, mappedPath, drag, @@ -339,6 +369,8 @@ export function getInteractionMoveCommandsForSelectedElement( export function moveInspectorStrategy( metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + elementPathTree: ElementPathTrees, selectedElementPaths: ElementPath[], projectContents: ProjectContentTreeRoot, movement: CanvasVector, @@ -352,6 +384,8 @@ export function moveInspectorStrategy( const moveCommandsResult = getMoveCommandsForSelectedElement( projectContents, metadata, + allElementProps, + elementPathTree, selectedPath, selectedPath, movement, @@ -368,6 +402,8 @@ export function moveInspectorStrategy( export function directMoveInspectorStrategy( metadata: ElementInstanceMetadataMap, + allElementProps: AllElementProps, + elementPathTree: ElementPathTrees, selectedElementPaths: ElementPath[], projectContents: ProjectContentTreeRoot, leftOrTop: 'left' | 'top', @@ -382,6 +418,8 @@ export function directMoveInspectorStrategy( const moveCommandsResult = getDirectMoveCommandsForSelectedElement( projectContents, metadata, + allElementProps, + elementPathTree, selectedPath, selectedPath, leftOrTop, diff --git a/editor/src/components/canvas/canvas-utils.ts b/editor/src/components/canvas/canvas-utils.ts index a40fa5ee34b2..2bfd96433fdc 100644 --- a/editor/src/components/canvas/canvas-utils.ts +++ b/editor/src/components/canvas/canvas-utils.ts @@ -356,6 +356,7 @@ export function updateFramesOfScenesAndComponents( const localFrame = MetadataUtils.getLocalFrame( frameAndTarget.target, workingEditorState.jsxMetadata, + null, ) const valueFromDOM = getObservableValueForLayoutProp( elementMetadata, @@ -439,7 +440,7 @@ export function updateFramesOfScenesAndComponents( frameAndTarget.frame, ) const currentLocalFrame = nullIfInfinity( - MetadataUtils.getLocalFrame(target, workingEditorState.jsxMetadata), + MetadataUtils.getLocalFrame(target, workingEditorState.jsxMetadata, null), ) const currentFullFrame = optionalMap(Frame.getFullFrame, currentLocalFrame) const fullFrame = Frame.getFullFrame(newLocalFrame) @@ -935,7 +936,7 @@ export function collectGuidelines( } const instance = MetadataUtils.findElementByElementPath(metadata, selectedView) - const localFrame = MetadataUtils.getLocalFrame(selectedView, metadata) + const localFrame = MetadataUtils.getLocalFrame(selectedView, metadata, null) if ( instance != null && MetadataUtils.isImg(instance) && diff --git a/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx b/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx index a17811afdd54..a5d6243d21ae 100644 --- a/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx +++ b/editor/src/components/canvas/controls/select-mode/flex-gap-control.test-utils.tsx @@ -38,6 +38,7 @@ export async function checkFlexGapHandlesPositionedCorrectly( const localFrame = MetadataUtils.getLocalFrame( selectedElement.elementPath, editorState.jsxMetadata, + null, ) const selectedElementFrame = zeroRectIfNullOrInfinity(localFrame) // If this is a flex element and it has a gap specified. 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 950974fdf078..f210ec1e0621 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 @@ -178,7 +178,7 @@ export const FlexGapControl = controlForStrategyMemoized((p const bounds = boundingRectangleArray( mapDropNulls((c) => { - const localFrame = MetadataUtils.getLocalFrame(c.elementPath, metadata) + const localFrame = MetadataUtils.getLocalFrame(c.elementPath, metadata, null) return localFrame != null && isFiniteRectangle(localFrame) ? localFrame : null }, children), ) 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 812fd1f10dc7..f1963d1ee0c4 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 @@ -174,7 +174,7 @@ function convertThreeElementGroupRow( ).map((element) => { return { ...element, - localFrame: MetadataUtils.getLocalFrame(element.elementPath, metadata), + localFrame: MetadataUtils.getLocalFrame(element.elementPath, metadata, null), } }) if (childrenMetadata.length === 3) { diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx index 05968e0c0bbf..9f5793794639 100644 --- a/editor/src/components/editor/actions/actions.tsx +++ b/editor/src/components/editor/actions/actions.tsx @@ -1129,7 +1129,7 @@ function deleteElements( if (metadata == null || isLeft(metadata.element)) { return null } - const frame = MetadataUtils.getLocalFrame(path, editor.jsxMetadata) + const frame = MetadataUtils.getLocalFrame(path, editor.jsxMetadata, null) if (frame == null || !isFiniteRectangle(frame)) { return null } @@ -3316,7 +3316,7 @@ export const UPDATE_FNS = { }, RESET_PINS: (action: ResetPins, editor: EditorModel): EditorModel => { const target = action.target - const frame = MetadataUtils.getLocalFrame(target, editor.jsxMetadata) + const frame = MetadataUtils.getLocalFrame(target, editor.jsxMetadata, null) if (frame == null || isInfinityRectangle(frame)) { return editor @@ -3344,7 +3344,7 @@ export const UPDATE_FNS = { } }, UPDATE_FRAME_DIMENSIONS: (action: UpdateFrameDimensions, editor: EditorModel): EditorModel => { - const initialFrame = MetadataUtils.getLocalFrame(action.element, editor.jsxMetadata) + const initialFrame = MetadataUtils.getLocalFrame(action.element, editor.jsxMetadata, null) if (initialFrame == null || isInfinityRectangle(initialFrame)) { return editor @@ -5516,6 +5516,7 @@ export const UPDATE_FNS = { const localFrame = MetadataUtils.getLocalFrame( action.insertionPath.intendedParentPath, editor.jsxMetadata, + null, ) if (group != null && localFrame != null) { switch (element.type) { diff --git a/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx b/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx index db58c7c4a590..4358eeae2143 100644 --- a/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx +++ b/editor/src/components/editor/one-shot-insertion-strategies/insert-as-absolute-strategy.tsx @@ -72,6 +72,7 @@ export const insertAsAbsoluteStrategy = ( metadata, state.projectContents, 'force-pins', + 'dont-add-contain-layout', ), setProperty('always', result.newPath, PP.create('style', 'position'), 'absolute'), ], diff --git a/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx b/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx index f6fa75be2874..1734b52a4b1d 100644 --- a/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx +++ b/editor/src/components/editor/one-shot-unwrap-strategies/reparent-to-unwrap-as-absolute-strategy.tsx @@ -48,6 +48,7 @@ export const reparentToUnwrapAsAbsoluteStrategy = ( projectContents, nodeModules, 'do-not-force-pins', + 'dont-add-contain-layout', ) if (result == null) { diff --git a/editor/src/components/inspector/inspector-common.ts b/editor/src/components/inspector/inspector-common.ts index decc614de98c..e06101e7d485 100644 --- a/editor/src/components/inspector/inspector-common.ts +++ b/editor/src/components/inspector/inspector-common.ts @@ -1245,7 +1245,7 @@ export function setParentToFixedIfHugCommands( return [] } - const globalFrame = MetadataUtils.getLocalFrame(parentPath, metadata) + const globalFrame = MetadataUtils.getLocalFrame(parentPath, metadata, null) if (globalFrame == null || isInfinityRectangle(globalFrame)) { return [] } @@ -1458,7 +1458,7 @@ export function getConvertIndividualElementToAbsoluteCommandsFromMetadata( jsxMetadata: ElementInstanceMetadataMap, elementPathTree: ElementPathTrees, ): Array { - const localFrame = MetadataUtils.getLocalFrame(target, jsxMetadata) + const localFrame = MetadataUtils.getLocalFrame(target, jsxMetadata, null) if (localFrame == null || isInfinityRectangle(localFrame)) { return [] } diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx index 3321e3df30c6..4ae0d9813c11 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx @@ -142,6 +142,7 @@ describe('Frame updating layout section', () => { const actualLocalFrame = MetadataUtils.getLocalFrame( metadataForElement.elementPath, metadataMap, + null, ) expect(actualLocalFrame).toEqual(expectedFrame) } diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx index 92d1aaa6f306..d5187fa60c8e 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.tsx @@ -36,7 +36,12 @@ import { import { InspectorContextMenuWrapper } from '../../../../context-menu-wrapper' import { useDispatch } from '../../../../editor/store/dispatch-context' import { Substores, useEditorState, useRefEditorState } from '../../../../editor/store/store-hook' -import { metadataSelector, selectedViewsSelector } from '../../../../inspector/inpector-selectors' +import { + allElementPropsSelector, + metadataSelector, + pathTreesSelector, + selectedViewsSelector, +} from '../../../../inspector/inpector-selectors' import { unsetPropertyMenuItem } from '../../../common/context-menu-items' import { cssNumber, @@ -93,6 +98,8 @@ interface LTWHPixelValues { export const FrameUpdatingLayoutSection = React.memo(() => { const dispatch = useDispatch() const metadataRef = useRefEditorState(metadataSelector) + const allElementPropsRef = useRefEditorState(allElementPropsSelector) + const elementPathTreesRef = useRefEditorState(pathTreesSelector) const selectedViewsRef = useRefEditorState(selectedViewsSelector) const projectContentsRef = useRefEditorState((store) => store.editor.projectContents) const originalGlobalFrame: CanvasRectangle = useEditorState( @@ -182,6 +189,7 @@ export const FrameUpdatingLayoutSection = React.memo(() => { const maybeInfinityLocalFrame = MetadataUtils.getLocalFrame( selectedView, store.editor.jsxMetadata, + null, ) if (maybeInfinityLocalFrame == null || isInfinityRectangle(maybeInfinityLocalFrame)) { result.left.push(0) @@ -217,6 +225,8 @@ export const FrameUpdatingLayoutSection = React.memo(() => { [ moveInspectorStrategy( metadataRef.current, + allElementPropsRef.current, + elementPathTreesRef.current, selectedViewsRef.current, projectContentsRef.current, frameUpdate.edgeMovement, @@ -252,6 +262,8 @@ export const FrameUpdatingLayoutSection = React.memo(() => { [ directMoveInspectorStrategy( metadataRef.current, + allElementPropsRef.current, + elementPathTreesRef.current, selectedViewsRef.current, projectContentsRef.current, leftOrTop, @@ -282,7 +294,15 @@ export const FrameUpdatingLayoutSection = React.memo(() => { assertNever(frameUpdate) } }, - [dispatch, metadataRef, originalGlobalFrame, projectContentsRef, selectedViewsRef], + [ + allElementPropsRef, + dispatch, + elementPathTreesRef, + metadataRef, + originalGlobalFrame, + projectContentsRef, + selectedViewsRef, + ], ) const disableXYControls = React.useMemo(() => { diff --git a/editor/src/components/text-editor/text-handling.ts b/editor/src/components/text-editor/text-handling.ts index 45797293ed43..bf953188a747 100644 --- a/editor/src/components/text-editor/text-handling.ts +++ b/editor/src/components/text-editor/text-handling.ts @@ -280,7 +280,7 @@ export function getLocalRectangleWithFixedWidthHandlingText( pathTrees: ElementPathTrees, elementPath: ElementPath, ): MaybeInfinityLocalRectangle | null { - const localFrame = MetadataUtils.getLocalFrame(elementPath, metadata) + const localFrame = MetadataUtils.getLocalFrame(elementPath, metadata, null) if ( localFrame == null || diff --git a/editor/src/core/layout/layout-utils.ts b/editor/src/core/layout/layout-utils.ts index 940ce6a8e875..f3730d112d5e 100644 --- a/editor/src/core/layout/layout-utils.ts +++ b/editor/src/core/layout/layout-utils.ts @@ -148,7 +148,7 @@ export function switchPinnedChildToFlex( newParentMainAxis: 'horizontal' | 'vertical' | null, propertyTarget: ReadonlyArray, ): SwitchLayoutTypeResult { - const currentFrame = MetadataUtils.getLocalFrame(target, targetOriginalContextMetadata) + const currentFrame = MetadataUtils.getLocalFrame(target, targetOriginalContextMetadata, null) const newParent = findJSXElementAtPath(newParentPath, components) const element = findJSXElementAtPath(target, components) diff --git a/editor/src/core/model/element-metadata-utils.ts b/editor/src/core/model/element-metadata-utils.ts index f5f4fc0aca1f..0c5ece97bed1 100644 --- a/editor/src/core/model/element-metadata-utils.ts +++ b/editor/src/core/model/element-metadata-utils.ts @@ -1342,7 +1342,7 @@ export const MetadataUtils = { Utils.fastForEach(targets, (target) => { const instance = MetadataUtils.findElementByElementPath(metadata, target) if (instance != null && this.isImg(instance)) { - const componentFrame = MetadataUtils.getLocalFrame(target, metadata) + const componentFrame = MetadataUtils.getLocalFrame(target, metadata, null) if (componentFrame != null && isFiniteRectangle(componentFrame)) { const imageSize = getImageSize(allElementProps, instance) const widthMultiplier = imageSize.width / componentFrame.width @@ -1535,6 +1535,7 @@ export const MetadataUtils = { getLocalFrame( path: ElementPath, metadata: ElementInstanceMetadataMap, + parentToTarget: ElementPath | null, ): MaybeInfinityLocalRectangle | null { function getNonRootParent(parentOf: ElementPath): ElementPath { // If the target is the root element of an instance (`a/b/c:root`), then we want to instead @@ -1553,7 +1554,7 @@ export const MetadataUtils = { } const targetGlobalFrame = MetadataUtils.getFrameInCanvasCoords(path, metadata) - const parentPath = getNonRootParent(path) + const parentPath = parentToTarget ?? getNonRootParent(path) const parentGlobalFrame = MetadataUtils.getFrameInCanvasCoords(parentPath, metadata) if (targetGlobalFrame == null || parentGlobalFrame == null) { return null @@ -1619,7 +1620,7 @@ export const MetadataUtils = { return aabb }, getFrameOrZeroRect(path: ElementPath, metadata: ElementInstanceMetadataMap): LocalRectangle { - const frame = MetadataUtils.getLocalFrame(path, metadata) + const frame = MetadataUtils.getLocalFrame(path, metadata, null) return zeroRectIfNullOrInfinity(frame) }, getFrameRelativeTo( @@ -1632,7 +1633,7 @@ export const MetadataUtils = { } else { const paths = EP.allPathsForLastPart(parent) const parentFrames: Array = Utils.stripNulls( - paths.map((path) => this.getLocalFrame(path, metadata)), + paths.map((path) => this.getLocalFrame(path, metadata, null)), ) return parentFrames.reduce((working, next) => { if (isInfinityRectangle(next)) { @@ -2846,7 +2847,7 @@ export function propertyHasSimpleValue( } // This function creates a fake metadata for the given element -// Useful when metadata is needed before the real on is created. +// Useful when metadata is needed before the real one is created. export function createFakeMetadataForElement( path: ElementPath, element: JSXElementChild,