diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index ce32f1cacebc..f50b2a29d55e 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -91,6 +91,7 @@ import { } from '../controls/grid-controls-for-strategies' import { gridReorderStrategy } from './strategies/grid-reorder-strategy' import { gridMoveAbsoluteStrategy } from './strategies/grid-move-absolute' +import { gridChildCornerResizeStrategy } from './strategies/grid-child-corner-resize-strategy' export type CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -143,6 +144,7 @@ const resizeStrategies: MetaCanvasStrategy = ( resizeGridStrategy, gridResizeElementStrategy, gridResizeElementRulerStrategy, + gridChildCornerResizeStrategy, ], ) } diff --git a/editor/src/components/canvas/canvas-strategies/interaction-state.ts b/editor/src/components/canvas/canvas-strategies/interaction-state.ts index 90d223c210b6..d090a7dd005d 100644 --- a/editor/src/components/canvas/canvas-strategies/interaction-state.ts +++ b/editor/src/components/canvas/canvas-strategies/interaction-state.ts @@ -15,7 +15,15 @@ import type { KeyCharacter } from '../../../utils/keyboard' import type { Modifiers } from '../../../utils/modifiers' import type { AllElementProps } from '../../editor/store/editor-state' import type { BorderRadiusCorner } from '../border-radius-control-utils' -import type { EdgePiece, EdgePosition } from '../canvas-types' +import { + EdgePositionBottom, + EdgePositionLeft, + EdgePositionRight, + EdgePositionTop, + type EdgePiece, + type EdgePosition, + type EdgePositionCorner, +} from '../canvas-types' import { MoveIntoDragThreshold } from '../canvas-utils' import type { CanvasCommand } from '../commands/commands' import type { ApplicableStrategy } from './canvas-strategies' @@ -666,6 +674,21 @@ export type GridResizeEdgeProperties = { isEnd: boolean } +export function gridResizeEdgeToEdgePosition(edge: GridResizeEdge): EdgePosition { + switch (edge) { + case 'column-start': + return EdgePositionLeft + case 'column-end': + return EdgePositionRight + case 'row-start': + return EdgePositionTop + case 'row-end': + return EdgePositionBottom + default: + assertNever(edge) + } +} + export function gridResizeEdgeProperties(edge: GridResizeEdge): GridResizeEdgeProperties { return { isRow: edge === 'row-start' || edge === 'row-end', @@ -703,6 +726,23 @@ export function gridResizeRulerHandle(id: string, edge: GridResizeEdge): GridRes } } +export interface GridChildCornerHandle { + type: 'GRID_CHILD_CORNER_HANDLE' + id: string + corner: EdgePositionCorner +} + +export function gridChildCornerHandle( + id: string, + corner: EdgePositionCorner, +): GridChildCornerHandle { + return { + type: 'GRID_CHILD_CORNER_HANDLE', + id: id, + corner: corner, + } +} + export type CanvasControlType = | BoundingArea | ResizeHandle @@ -716,6 +756,7 @@ export type CanvasControlType = | GridAxisHandle | GridResizeHandle | GridResizeRulerHandle + | GridChildCornerHandle export function isDragToPan( interaction: InteractionSession | null, 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 05d40787f5f3..c85db1afab51 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 @@ -54,10 +54,7 @@ import { import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' import type { CSSProperties } from 'react' import { MaxContent } from '../../../inspector/inspector-common' -import { - ResizePointTestId, - AbsoluteResizeControlTestId, -} from '../../controls/select-mode/absolute-resize-control' +import { AbsoluteResizeControlTestId } from '../../controls/select-mode/absolute-resize-control' import type { FragmentLikeType } from './fragment-like-helpers' import { AllFragmentLikeTypes } from './fragment-like-helpers' import { @@ -78,6 +75,7 @@ import { import { act } from 'react-dom/test-utils' import { ComponentsHonouringPropsStylesProject } from './common-projects.test-utils' import { SizeLabelTestId } from '../../controls/select-mode/size-label' +import { ResizePointTestId } from '../../controls/resize-control' // no mouseup here! it starts the interaction and resizes with drag delta async function startDragUsingActions( 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 1aabffbe33ee..e3fca9f715db 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 @@ -3,7 +3,7 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' -import type { CanvasRectangle } from '../../../../core/shared/math-utils' +import type { CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils' import { canvasRectangle, isFiniteRectangle, @@ -42,7 +42,11 @@ import { getDescriptiveStrategyLabelWithRetargetedPaths, onlyFitWhenDraggingThisControl, } from '../canvas-strategies' -import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-types' +import type { + CanvasStrategy, + InteractionCanvasState, + StrategyApplicationResult, +} from '../canvas-strategy-types' import { controlWithProps, emptyStrategyApplicationResult, @@ -55,6 +59,7 @@ import { retargetStrategyToChildrenOfFragmentLikeElements, treatElementAsFragmentLike, } from './fragment-like-helpers' +import { gridItemAndFillStatus } from './grid-helpers' import { treatElementAsGroupLike } from './group-helpers' import { getLockedAspectRatio, @@ -104,6 +109,11 @@ export function absoluteResizeBoundingBoxStrategy( return null } + const gridItemFillStatus = gridItemAndFillStatus(canvasState.startingMetadata, retargetedTargets) + if (gridItemFillStatus === 'mixed') { + return null + } + return { id: 'ABSOLUTE_RESIZE_BOUNDING_BOX', name: 'Resize', @@ -163,151 +173,14 @@ export function absoluteResizeBoundingBoxStrategy( const edgePosition = interactionSession.activeControl.edgePosition if (interactionSession.interactionData.drag != null) { - const drag = interactionSession.interactionData.drag - const originalBoundingBox = getMultiselectBounds( - canvasState.startingMetadata, + return absoluteBoundingResize( + canvasState, + interactionSession, + originalTargets, retargetedTargets, + childGroups, + edgePosition, ) - const anySelectedElementAspectRatioLocked = isAnySelectedElementAspectRatioLocked( - canvasState.startingMetadata, - retargetedTargets, - ) - if (originalBoundingBox != null) { - const lockedAspectRatio = getLockedAspectRatio( - interactionSession, - interactionSession.interactionData.modifiers, - originalBoundingBox, - anySelectedElementAspectRatioLocked, - ) - const centerBased = interactionSession.interactionData.modifiers.alt - ? 'center-based' - : 'non-center-based' - const newBoundingBox = resizeBoundingBox( - originalBoundingBox, - drag, - edgePosition, - lockedAspectRatio, - centerBased, - ) - const parentAndSiblings: ElementPath[] = gatherParentAndSiblingTargets( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - originalTargets, - ) - const childrenToSnapTo = childrenBoundsToSnapTo( - edgePosition, - originalTargets, - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - ) - const snapTargets = [...childrenToSnapTo, ...parentAndSiblings] - const { snappedBoundingBox, guidelinesWithSnappingVector } = snapBoundingBox( - snapTargets, - originalTargets, - canvasState.startingMetadata, - edgePosition, - newBoundingBox, - canvasState.scale, - lockedAspectRatio, - centerBased, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - ) - - const commandsForSelectedElements = retargetedTargets.flatMap((selectedElement) => { - const element = getJSXElementFromProjectContents( - selectedElement, - canvasState.projectContents, - ) - const originalFrame = MetadataUtils.getFrameInCanvasCoords( - selectedElement, - canvasState.startingMetadata, - ) - - if (element == null || originalFrame == null || isInfinityRectangle(originalFrame)) { - return [] - } - - const elementIsGroup = treatElementAsGroupLike( - canvasState.startingMetadata, - selectedElement, - ) - - // If there are constrained descendants of the selected elements, adjust the - // resized frame to respect the min/max dimensions that come from them. - const newFrame = applyConstraintsAdjustmentsToFrame( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - selectedElement, - originalFrame, - edgePosition, - roundRectangleToNearestWhole( - transformFrameUsingBoundingBox( - snappedBoundingBox, - originalBoundingBox, - originalFrame, - ), - ), - ) - - const metadata = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - selectedElement, - ) - const elementParentBounds = - metadata?.specialSizeMeasurements.immediateParentBounds ?? null - - const elementParentFlexDirection = - metadata?.specialSizeMeasurements.parentFlexDirection ?? null - - const ensureFramePointsExist: EnsureFramePointsExist = - !elementIsGroup || - (isEdgePositionEqualTo(edgePosition, EdgePositionLeft) && originalFrame.x === 0) || - (isEdgePositionEqualTo(edgePosition, EdgePositionTop) && originalFrame.y === 0) || - (isEdgePositionEqualTo(edgePosition, EdgePositionTopLeft) && - (originalFrame.x === 0 || originalFrame.y === 0)) - ? 'ensure-two-frame-points-per-dimension-exists' - : 'only-offset-frame-points-are-needed' - - return [ - ...createResizeCommandsFromFrame( - element, - selectedElement, - newFrame, - originalFrame, - elementParentBounds, - elementParentFlexDirection, - edgePosition, - ensureFramePointsExist, - ), - pushIntendedBoundsAndUpdateGroups( - [{ target: selectedElement, frame: newFrame }], - 'starting-metadata', - ), - queueTrueUpElement(childGroups.map(trueUpGroupElementChanged)), - setActiveFrames([ - { - action: 'resize', - target: activeFrameTargetRect(newFrame), - source: originalBoundingBox, - }, - ]), - ] - }) - - return strategyApplicationResult( - [ - ...commandsForSelectedElements, - setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), - updateHighlightedViews('mid-interaction', []), - setCursorCommand(pickCursorFromEdgePosition(edgePosition)), - ], - retargetedTargets, - ) - } } else { return strategyApplicationResult( [ @@ -324,6 +197,153 @@ export function absoluteResizeBoundingBoxStrategy( } } +export function absoluteBoundingResize( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession, + originalTargets: Array, + retargetedTargets: Array, + childGroups: Array, + edgePosition: EdgePosition, +): StrategyApplicationResult { + if ( + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null + ) { + return emptyStrategyApplicationResult + } + const drag = interactionSession.interactionData.drag + const originalBoundingBox = getMultiselectBounds(canvasState.startingMetadata, retargetedTargets) + const anySelectedElementAspectRatioLocked = isAnySelectedElementAspectRatioLocked( + canvasState.startingMetadata, + retargetedTargets, + ) + if (originalBoundingBox == null) { + return emptyStrategyApplicationResult + } + const lockedAspectRatio = getLockedAspectRatio( + interactionSession, + interactionSession.interactionData.modifiers, + originalBoundingBox, + anySelectedElementAspectRatioLocked, + ) + const centerBased = interactionSession.interactionData.modifiers.alt + ? 'center-based' + : 'non-center-based' + const newBoundingBox = resizeBoundingBox( + originalBoundingBox, + drag, + edgePosition, + lockedAspectRatio, + centerBased, + ) + const parentAndSiblings: ElementPath[] = gatherParentAndSiblingTargets( + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + originalTargets, + ) + const childrenToSnapTo = childrenBoundsToSnapTo( + edgePosition, + originalTargets, + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + ) + const snapTargets = [...childrenToSnapTo, ...parentAndSiblings] + const { snappedBoundingBox, guidelinesWithSnappingVector } = snapBoundingBox( + snapTargets, + originalTargets, + canvasState.startingMetadata, + edgePosition, + newBoundingBox, + canvasState.scale, + lockedAspectRatio, + centerBased, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + ) + + const commandsForSelectedElements = retargetedTargets.flatMap((selectedElement) => { + const element = getJSXElementFromProjectContents(selectedElement, canvasState.projectContents) + const originalFrame = MetadataUtils.getFrameInCanvasCoords( + selectedElement, + canvasState.startingMetadata, + ) + + if (element == null || originalFrame == null || isInfinityRectangle(originalFrame)) { + return [] + } + + const elementIsGroup = treatElementAsGroupLike(canvasState.startingMetadata, selectedElement) + + // If there are constrained descendants of the selected elements, adjust the + // resized frame to respect the min/max dimensions that come from them. + const newFrame = applyConstraintsAdjustmentsToFrame( + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + selectedElement, + originalFrame, + edgePosition, + roundRectangleToNearestWhole( + transformFrameUsingBoundingBox(snappedBoundingBox, originalBoundingBox, originalFrame), + ), + ) + + const metadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + const elementParentBounds = metadata?.specialSizeMeasurements.immediateParentBounds ?? null + + const elementParentFlexDirection = metadata?.specialSizeMeasurements.parentFlexDirection ?? null + + const ensureFramePointsExist: EnsureFramePointsExist = + !elementIsGroup || + (isEdgePositionEqualTo(edgePosition, EdgePositionLeft) && originalFrame.x === 0) || + (isEdgePositionEqualTo(edgePosition, EdgePositionTop) && originalFrame.y === 0) || + (isEdgePositionEqualTo(edgePosition, EdgePositionTopLeft) && + (originalFrame.x === 0 || originalFrame.y === 0)) + ? 'ensure-two-frame-points-per-dimension-exists' + : 'only-offset-frame-points-are-needed' + + return [ + ...createResizeCommandsFromFrame( + element, + selectedElement, + newFrame, + originalFrame, + elementParentBounds, + elementParentFlexDirection, + edgePosition, + ensureFramePointsExist, + ), + pushIntendedBoundsAndUpdateGroups( + [{ target: selectedElement, frame: newFrame }], + 'starting-metadata', + ), + queueTrueUpElement(childGroups.map(trueUpGroupElementChanged)), + setActiveFrames([ + { + action: 'resize', + target: activeFrameTargetRect(newFrame), + source: originalBoundingBox, + }, + ]), + ] + }) + + return strategyApplicationResult( + [ + ...commandsForSelectedElements, + setSnappingGuidelines('mid-interaction', guidelinesWithSnappingVector), + updateHighlightedViews('mid-interaction', []), + setCursorCommand(pickCursorFromEdgePosition(edgePosition)), + ], + retargetedTargets, + ) +} + /** * Returns adjusted version of the newFrame that respects any constraints that * may be set on its descendants. diff --git a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx index 8c3a65d3baf0..4508ee001190 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/basic-resize-strategy.tsx @@ -85,9 +85,6 @@ export function basicResizeStrategy( const elementParentBounds = metadata?.specialSizeMeasurements.immediateParentBounds ?? null const isGridCell = MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement) - if (isGridCell && isFillOrStretchModeApplied(canvasState.startingMetadata, selectedElement)) { - return null - } return { id: BASIC_RESIZE_STRATEGY_ID, @@ -98,13 +95,17 @@ export function basicResizeStrategy( type: 'resize', }, controlsToRender: [ - controlWithProps({ - control: AbsoluteResizeControl, - props: { targets: selectedElements, pathsWereReplaced: false }, - key: 'absolute-resize-control', - show: 'always-visible', - priority: 'top', - }), + ...(isGridCell + ? [] + : [ + controlWithProps({ + control: AbsoluteResizeControl, + props: { targets: selectedElements, pathsWereReplaced: false }, + key: 'absolute-resize-control', + show: 'always-visible', + priority: 'top', + }), + ]), controlWithProps({ control: StrategySizeLabel, props: { targets: selectedElements, pathsWereReplaced: false }, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..f63e63e731fa --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.spec.browser2.tsx @@ -0,0 +1,261 @@ +import * as EP from '../../../../core/shared/element-path' +import type { CanvasVector } from '../../../../core/shared/math-utils' +import { windowPoint, zeroCanvasPoint } from '../../../../core/shared/math-utils' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import type { Modifiers } from '../../../../utils/modifiers' +import { emptyModifiers } from '../../../../utils/modifiers' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import { selectComponents } from '../../../editor/actions/action-creators' +import CanvasActions from '../../canvas-actions' +import type { EdgePosition } from '../../canvas-types' +import { EdgePositionBottomRight } from '../../canvas-types' +import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' +import { + mouseClickAtPoint, + mouseDoubleClickAtPoint, + mouseDragFromPointWithDelta, +} from '../../event-helpers.test-utils' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { + formatTestProjectCode, + getPrintedUiJsCode, + makeTestProjectCodeWithSnippet, + renderTestEditorWithCode, + TestScenePath, +} from '../../ui-jsx.test-utils' +import { createInteractionViaMouse, updateInteractionViaMouse } from '../interaction-state' + +// no mouseup here! it starts the interaction and resizes with drag delta +async function startDragUsingActions( + renderResult: EditorRenderResult, + target: ElementPath, + edgePosition: EdgePosition, + dragDelta: CanvasVector, +) { + await renderResult.dispatch([selectComponents([target], false)], true) + const startInteractionSession = createInteractionViaMouse( + zeroCanvasPoint, + emptyModifiers, + { + type: 'RESIZE_HANDLE', + edgePosition: edgePosition, + }, + 'zero-drag-not-permitted', + ) + await renderResult.dispatch( + [CanvasActions.createInteractionSession(startInteractionSession)], + false, + ) + await renderResult.getDispatchFollowUpActionsFinished() + await renderResult.dispatch( + [ + CanvasActions.updateInteractionSession( + updateInteractionViaMouse(startInteractionSession, 'DRAG', dragDelta, emptyModifiers, { + type: 'RESIZE_HANDLE', + edgePosition: edgePosition, + }), + ), + ], + false, + ) + await renderResult.getDispatchFollowUpActionsFinished() +} + +async function doDblClickTest( + editor: EditorRenderResult, + testId: string, + verticalOffset: number = 30, +): Promise { + const canvasControlsLayer = editor.renderedDOM.getByTestId(CanvasControlsContainerID) + const div = editor.renderedDOM.getByTestId('mydiv') + const divBounds = div.getBoundingClientRect() + const divCorner = { + x: divBounds.x + 50, + y: divBounds.y + 40, + } + + await mouseClickAtPoint(canvasControlsLayer, divCorner) + + const nineBlockControlSegment = editor.renderedDOM.getByTestId(testId) + + await mouseDoubleClickAtPoint(nineBlockControlSegment, { x: 2, y: verticalOffset }) + + return div +} + +async function doSnapDrag( + editor: EditorRenderResult, + delta: { x: number; y: number }, + edgePosition: EdgePosition, + modifiers: Modifiers = emptyModifiers, +) { + const canvasControl = editor.renderedDOM.getByTestId( + `resize-control-${edgePosition.x}-${edgePosition.y}`, + ) + + const resizeCornerBounds = canvasControl.getBoundingClientRect() + const startPoint = windowPoint({ + x: resizeCornerBounds.x + 2, + y: resizeCornerBounds.y + 2, + }) + + await mouseDragFromPointWithDelta(canvasControl, startPoint, delta, { + modifiers: modifiers, + }) +} + +const RegularMultiChildProject = ` +
+
+
+
+
+
+
+` + +/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectElementWithTestIdNotToBeRendered", "expectElementWithTestIdToBeRendered"] }] */ + +describe('Grid Child Corner Resize Strategy', () => { + it('dragging corner of fixed and stretched child updates properties as expected', async () => { + const renderResult = await renderTestEditorWithCode( + makeTestProjectCodeWithSnippet(RegularMultiChildProject), + 'await-first-dom-report', + ) + await selectComponentsForTest(renderResult, [ + EP.appendNewElementPath(TestScenePath, ['root', 'grid', 'child1']), + ]) + + await doSnapDrag(renderResult, { x: 50, y: 50 }, EdgePositionBottomRight, emptyModifiers) + + expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( + formatTestProjectCode( + makeTestProjectCodeWithSnippet(` +
+
+
+
+
+
+
+`), + ), + ) + }) +}) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.ts new file mode 100644 index 000000000000..43829346f06b --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-child-corner-resize-strategy.ts @@ -0,0 +1,216 @@ +import { detectFillHugFixedState } from '../../../inspector/inspector-common' +import * as EP from '../../../../core/shared/element-path' +import { gridContainerIdentifier, gridItemIdentifier } from '../../../editor/store/editor-state' +import { + EdgePositionBottomLeft, + EdgePositionBottomRight, + EdgePositionTopLeft, + EdgePositionTopRight, + type EdgePosition, + type EdgePositionCorner, +} from '../../canvas-types' + +import { + controlsForGridPlaceholders, + GridResizeControls, +} from '../../controls/grid-controls-for-strategies' +import { + getDescriptiveStrategyLabelWithRetargetedPaths, + onlyFitWhenDraggingThisControl, +} from '../canvas-strategies' +import type { + CanvasStrategy, + InteractionCanvasState, + StrategyApplicationResult, +} from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, +} from '../canvas-strategy-types' +import type { StrategyApplicationStatus } from '../interaction-state' +import { GridResizeEdges, type GridResizeEdge, type InteractionSession } from '../interaction-state' +import { absoluteBoundingResize } from './absolute-resize-bounding-box-strategy' +import { getChildGroupsForNonGroupParents } from './fragment-like-helpers' +import type { ElementPath } from 'utopia-shared/src/types' +import { assertNever } from '../../../../core/shared/utils' +import { isEdgePositionEqualTo } from '../../canvas-utils' +import { gridResizeElement } from './grid-resize-element-strategy' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' +import type { CanvasCommand } from '../../commands/commands' +import { setCursorCommand } from '../../commands/set-cursor-command' +import { pickCursorFromEdgePosition } from './resize-helpers' +import { gridItemAndFillStatus } from './grid-helpers' + +export function gridChildCornerResizeStrategy( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +): CanvasStrategy | null { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if (selectedElements.length !== 1) { + return null + } + const selectedElement = selectedElements[0] + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const nonNullSelectedElementMetadata: ElementInstanceMetadata = selectedElementMetadata + + const selectedElementBounds = MetadataUtils.getFrameInCanvasCoords( + selectedElement, + canvasState.startingMetadata, + ) + if (selectedElementBounds == null || isInfinityRectangle(selectedElementBounds)) { + return null + } + + const gridItemFillStatus = gridItemAndFillStatus(canvasState.startingMetadata, [selectedElement]) + if (gridItemFillStatus !== 'mixed' && gridItemFillStatus !== 'all-stretch') { + return null + } + + return { + id: 'GRID_CHILD_CORNER_RESIZE', + name: 'Grid Child Corner Resize', + descriptiveLabel: getDescriptiveStrategyLabelWithRetargetedPaths('Resizing Elements', false), + icon: { + category: 'modalities', + type: 'resize', + }, + controlsToRender: [ + { + control: GridResizeControls, + props: { target: gridContainerIdentifier(selectedElement) }, + key: `grid-resize-controls-${EP.toString(selectedElement)}`, + show: 'always-visible', + }, + controlsForGridPlaceholders(gridItemIdentifier(selectedElement)), + ], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CHILD_CORNER_HANDLE', 1), + apply: (lifecycle) => { + if ( + interactionSession != null && + interactionSession.interactionData.type === 'DRAG' && + interactionSession.activeControl.type === 'GRID_CHILD_CORNER_HANDLE' + ) { + const childGroups = getChildGroupsForNonGroupParents( + canvasState.startingMetadata, + selectedElements, + ) + + const { gridResizeEdgePosition, elementResizeEdgePosition } = + fromEdgePositionCornerToStrategyValues( + canvasState, + selectedElement, + interactionSession.activeControl.corner, + ) + + let strategyApplicationResults: Array = [] + if (gridResizeEdgePosition != null) { + strategyApplicationResults.push( + gridResizeElement( + interactionSession, + selectedElement, + nonNullSelectedElementMetadata, + selectedElementBounds, + gridResizeEdgePosition, + ), + ) + } + + if (elementResizeEdgePosition != null) { + strategyApplicationResults.push( + absoluteBoundingResize( + canvasState, + interactionSession, + selectedElements, + selectedElements, + childGroups, + elementResizeEdgePosition, + ), + ) + } + + if (strategyApplicationResults.length === 0) { + return emptyStrategyApplicationResult + } + + let commands: Array = [] + let elementsToRerender: Array = [] + let status: StrategyApplicationStatus = 'success' + for (const strategyApplicationResult of strategyApplicationResults) { + commands.push(...strategyApplicationResult.commands) + elementsToRerender.push(...strategyApplicationResult.elementsToRerender) + if (strategyApplicationResult.status === 'failure') { + status = 'failure' + } + } + + commands.push( + setCursorCommand(pickCursorFromEdgePosition(interactionSession.activeControl.corner)), + ) + + return { + commands: commands, + elementsToRerender: elementsToRerender, + customStatePatch: {}, // Non-trivial to merge, but it appears not necessary for this. + status: status, + } + } + // Fallback for when the checks above are not satisfied. + return emptyStrategyApplicationResult + }, + } +} + +export interface StrategyValuesFromCorner { + gridResizeEdgePosition: EdgePosition | null + elementResizeEdgePosition: EdgePosition | null +} + +export function strategyValuesFromCorner( + gridResizeEdgePosition: EdgePosition | null, + elementResizeEdgePosition: EdgePosition | null, +): StrategyValuesFromCorner { + return { + gridResizeEdgePosition: gridResizeEdgePosition, + elementResizeEdgePosition: elementResizeEdgePosition, + } +} + +export function fromEdgePositionCornerToStrategyValues( + canvasState: InteractionCanvasState, + element: ElementPath, + corner: EdgePositionCorner, +): StrategyValuesFromCorner { + // Check stretch values. + const horizontalIsStretch = + detectFillHugFixedState('horizontal', canvasState.startingMetadata, element).fixedHugFill + ?.type === 'stretch' + const verticalIsStretch = + detectFillHugFixedState('vertical', canvasState.startingMetadata, element).fixedHugFill + ?.type === 'stretch' + + const gridResizeEdgePosition: EdgePosition = { + x: horizontalIsStretch ? 0.5 : corner.x, + y: verticalIsStretch ? 0.5 : corner.y, + } + + const elementResizeEdgePosition: EdgePosition = { + x: horizontalIsStretch ? 0.5 : corner.x, + y: verticalIsStretch ? 0.5 : corner.y, + } + + return strategyValuesFromCorner( + gridResizeEdgePosition, + isEdgePositionEqualTo(elementResizeEdgePosition, { x: 0.5, y: 0.5 }) + ? null + : elementResizeEdgePosition, + ) +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index 4250ba0d1680..f61d9a8bd49f 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -51,6 +51,7 @@ import { gridCellCoordinates, } from './grid-cell-bounds' import type { GridIdentifier } from '../../../editor/store/editor-state' +import { detectFillHugFixedState } from '../../../inspector/inspector-common' export function gridPositionToValue( p: GridPositionOrSpan | null | undefined, @@ -891,3 +892,31 @@ export function getGridRelativeContainingBlock( } } } + +export type GridItemAndFillStatus = 'not-grid-item' | 'all-fixed' | 'all-stretch' | 'mixed' + +export function gridItemAndFillStatus( + metadata: ElementInstanceMetadataMap, + targets: Array, +): GridItemAndFillStatus { + let allFixed = true + let allStretch = true + for (const target of targets) { + const isGridItem = MetadataUtils.isGridItem(metadata, target) + if (!isGridItem) { + return 'not-grid-item' + } + const horizontalType = detectFillHugFixedState('horizontal', metadata, target).fixedHugFill + ?.type + const verticalType = detectFillHugFixedState('vertical', metadata, target).fixedHugFill?.type + allFixed = allFixed && horizontalType === 'fixed' && verticalType === 'fixed' + allStretch = allStretch && horizontalType === 'stretch' && verticalType === 'stretch' + } + if (allFixed) { + return 'all-fixed' + } else if (allStretch) { + return 'all-stretch' + } else { + return 'mixed' + } +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts index 30c1fb75487a..6cd83c2fef89 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts @@ -17,6 +17,7 @@ import { gridContainerIdentifier, gridItemIdentifier } from '../../../editor/sto import { isCSSKeyword } from '../../../inspector/common/css-utils' import { controlsForGridPlaceholders, + gridEdgeToEdgePosition, GridResizeControls, } from '../../controls/grid-controls-for-strategies' import type { CanvasStrategyFactory } from '../canvas-strategies' @@ -177,6 +178,7 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( bounds.height, ) + const edgePosition = gridEdgeToEdgePosition(interactionSession.activeControl.edge) const normalizedGridProps: GridElementProperties = { gridColumnStart: normalizeGridElementPositionAfterResize( elementGridPropertiesFromProps.gridColumnStart, @@ -185,7 +187,7 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( 'start', elementGridPropertiesFromProps.gridColumnEnd, resizedProps.gridColumnEnd, - interactionSession.activeControl.edge, + edgePosition, ), gridColumnEnd: normalizeGridElementPositionAfterResize( elementGridPropertiesFromProps.gridColumnEnd, @@ -194,7 +196,7 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( 'end', elementGridPropertiesFromProps.gridColumnStart, resizedProps.gridColumnStart, - interactionSession.activeControl.edge, + edgePosition, ), gridRowStart: normalizeGridElementPositionAfterResize( elementGridPropertiesFromProps.gridRowStart, @@ -203,7 +205,7 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( 'start', elementGridPropertiesFromProps.gridRowEnd, resizedProps.gridRowEnd, - interactionSession.activeControl.edge, + edgePosition, ), gridRowEnd: normalizeGridElementPositionAfterResize( elementGridPropertiesFromProps.gridRowEnd, @@ -212,7 +214,7 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = ( 'end', elementGridPropertiesFromProps.gridRowStart, resizedProps.gridRowStart, - interactionSession.activeControl.edge, + edgePosition, ), } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx index 3231cf6bb4b3..4ea6e9b93aa4 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx @@ -20,8 +20,8 @@ import type { EditorRenderResult } from '../../ui-jsx.test-utils' import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' import type { GridResizeEdge } from '../interaction-state' import { gridCellTargetId } from './grid-cell-bounds' -import { ResizePointTestId } from '../../controls/select-mode/absolute-resize-control' import { gridEdgeToEdgePosition } from '../../controls/grid-controls-for-strategies' +import { ResizePointTestId } from '../../controls/resize-control' async function runCellResizeTest( editor: EditorRenderResult, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 09c8a5ea3003..97d10d0b21b0 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -1,6 +1,8 @@ +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 type { + ElementInstanceMetadata, GridElementProperties, GridPositionOrSpan, } from '../../../../core/shared/element-template' @@ -16,7 +18,14 @@ import { } from '../../../../core/shared/math-utils' import { gridContainerIdentifier, gridItemIdentifier } from '../../../editor/store/editor-state' import { cssKeyword } from '../../../inspector/common/css-utils' -import { isFillOrStretchModeAppliedOnAnySide } from '../../../inspector/inspector-common' +import type { EdgePosition } from '../../canvas-types' +import { + EdgePositionBottom, + EdgePositionBottomLeft, + EdgePositionBottomRight, + EdgePositionRight, + EdgePositionTopRight, +} from '../../canvas-types' import { controlsForGridPlaceholders, gridEdgeToEdgePosition, @@ -24,15 +33,20 @@ import { } from '../../controls/grid-controls-for-strategies' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { InteractionCanvasState } from '../canvas-strategy-types' +import type { InteractionCanvasState, StrategyApplicationResult } from '../canvas-strategy-types' import { getTargetPathsFromInteractionTarget, emptyStrategyApplicationResult, strategyApplicationResult, } from '../canvas-strategy-types' -import type { GridResizeEdge, InteractionSession } from '../interaction-state' -import { getCommandsForGridItemPlacement, isAutoGridPin } from './grid-helpers' -import { resizeBoundingBoxFromSide } from './resize-helpers' +import { type InteractionSession } from '../interaction-state' +import { + getCommandsForGridItemPlacement, + gridItemAndFillStatus, + isAutoGridPin, +} from './grid-helpers' +import { resizeBoundingBoxFromCorner } from './resize-helpers' +import { isEdgePositionEqualTo } from '../../canvas-utils' export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -67,7 +81,8 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return null } - if (!isFillOrStretchModeAppliedOnAnySide(canvasState.startingMetadata, selectedElement)) { + const gridItemFillStatus = gridItemAndFillStatus(canvasState.startingMetadata, [selectedElement]) + if (gridItemFillStatus !== 'all-stretch') { return null } @@ -99,85 +114,104 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } - const allCellBounds = - selectedElementMetadata.specialSizeMeasurements.parentGridCellGlobalFrames - - if (allCellBounds == null) { - return emptyStrategyApplicationResult - } - - const resizeBoundingBox = resizeBoundingBoxFromSide( + return gridResizeElement( + interactionSession, + selectedElement, + selectedElementMetadata, selectedElementBounds, - interactionSession.interactionData.drag, gridEdgeToEdgePosition(interactionSession.activeControl.edge), - 'non-center-based', - null, ) + }, + } +} + +export function gridResizeElement( + interactionSession: InteractionSession, + selectedElement: ElementPath, + selectedElementMetadata: ElementInstanceMetadata, + selectedElementBounds: CanvasRectangle, + edgePosition: EdgePosition, +): StrategyApplicationResult { + if ( + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null + ) { + return emptyStrategyApplicationResult + } - const gridPropsNumeric = getNewGridPropsFromResizeBox(resizeBoundingBox, allCellBounds) + const allCellBounds = selectedElementMetadata.specialSizeMeasurements.parentGridCellGlobalFrames + if (allCellBounds == null) { + return emptyStrategyApplicationResult + } - if (gridPropsNumeric == null) { - return emptyStrategyApplicationResult - } + const resizeBoundingBox = resizeBoundingBoxFromCorner( + selectedElementBounds, + interactionSession.interactionData.drag, + edgePosition, + 'non-center-based', + null, + ) + + const gridPropsNumeric = getNewGridPropsFromResizeBox(resizeBoundingBox, allCellBounds) - const gridTemplate = - selectedElementMetadata.specialSizeMeasurements.parentContainerGridProperties + if (gridPropsNumeric == null) { + return emptyStrategyApplicationResult + } - const elementGridPropertiesFromProps = - selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + const gridTemplate = selectedElementMetadata.specialSizeMeasurements.parentContainerGridProperties - const columnCount = - gridPropsNumeric.gridColumnEnd.numericalPosition - - gridPropsNumeric.gridColumnStart.numericalPosition - const rowCount = - gridPropsNumeric.gridRowEnd.numericalPosition - - gridPropsNumeric.gridRowStart.numericalPosition + const elementGridPropertiesFromProps = + selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps - const gridProps: GridElementProperties = { - gridColumnStart: normalizeGridElementPositionAfterResize( - elementGridPropertiesFromProps.gridColumnStart, - gridPropsNumeric.gridColumnStart, - columnCount, - 'start', - elementGridPropertiesFromProps.gridColumnEnd, - gridPropsNumeric.gridColumnEnd, - interactionSession.activeControl.edge, - ), - gridColumnEnd: normalizeGridElementPositionAfterResize( - elementGridPropertiesFromProps.gridColumnEnd, - gridPropsNumeric.gridColumnEnd, - columnCount, - 'end', - elementGridPropertiesFromProps.gridColumnStart, - gridPropsNumeric.gridColumnStart, - interactionSession.activeControl.edge, - ), - gridRowStart: normalizeGridElementPositionAfterResize( - elementGridPropertiesFromProps.gridRowStart, - gridPropsNumeric.gridRowStart, - rowCount, - 'start', - elementGridPropertiesFromProps.gridRowEnd, - gridPropsNumeric.gridRowEnd, - interactionSession.activeControl.edge, - ), - gridRowEnd: normalizeGridElementPositionAfterResize( - elementGridPropertiesFromProps.gridRowEnd, - gridPropsNumeric.gridRowEnd, - rowCount, - 'end', - elementGridPropertiesFromProps.gridRowStart, - gridPropsNumeric.gridRowStart, - interactionSession.activeControl.edge, - ), - } + const columnCount = + gridPropsNumeric.gridColumnEnd.numericalPosition - + gridPropsNumeric.gridColumnStart.numericalPosition + const rowCount = + gridPropsNumeric.gridRowEnd.numericalPosition - gridPropsNumeric.gridRowStart.numericalPosition - return strategyApplicationResult( - getCommandsForGridItemPlacement(selectedElement, gridTemplate, gridProps), - [EP.parentPath(selectedElement), selectedElement], - ) - }, + const gridProps: GridElementProperties = { + gridColumnStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnStart, + gridPropsNumeric.gridColumnStart, + columnCount, + 'start', + elementGridPropertiesFromProps.gridColumnEnd, + gridPropsNumeric.gridColumnEnd, + edgePosition, + ), + gridColumnEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridColumnEnd, + gridPropsNumeric.gridColumnEnd, + columnCount, + 'end', + elementGridPropertiesFromProps.gridColumnStart, + gridPropsNumeric.gridColumnStart, + edgePosition, + ), + gridRowStart: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowStart, + gridPropsNumeric.gridRowStart, + rowCount, + 'start', + elementGridPropertiesFromProps.gridRowEnd, + gridPropsNumeric.gridRowEnd, + edgePosition, + ), + gridRowEnd: normalizeGridElementPositionAfterResize( + elementGridPropertiesFromProps.gridRowEnd, + gridPropsNumeric.gridRowEnd, + rowCount, + 'end', + elementGridPropertiesFromProps.gridRowStart, + gridPropsNumeric.gridRowStart, + edgePosition, + ), } + + return strategyApplicationResult( + getCommandsForGridItemPlacement(selectedElement, gridTemplate, gridProps), + [EP.parentPath(selectedElement), selectedElement], + ) } function getNewGridPropsFromResizeBox( @@ -231,7 +265,7 @@ export function normalizeGridElementPositionAfterResize( bound: 'start' | 'end', counterpart: GridPositionOrSpan | null, counterpartResizedPosition: GridPositionOrSpan | null, - edge: GridResizeEdge, + edgePosition: EdgePosition, ): GridPositionOrSpan | null { function isFlowResizeOnBound( wantedBound: 'start' | 'end', @@ -239,7 +273,11 @@ export function normalizeGridElementPositionAfterResize( flowEnd: GridPositionOrSpan | null, ): boolean { return ( - (edge === 'column-end' || edge === 'row-end') && + (isEdgePositionEqualTo(edgePosition, EdgePositionBottomLeft) || + isEdgePositionEqualTo(edgePosition, EdgePositionBottom) || + isEdgePositionEqualTo(edgePosition, EdgePositionBottomRight) || + isEdgePositionEqualTo(edgePosition, EdgePositionTopRight) || + isEdgePositionEqualTo(edgePosition, EdgePositionRight)) && bound === wantedBound && (isGridSpan(flowStart) || isAutoGridPin(flowStart) || flowStart == null) && (isAutoGridPin(flowEnd) || flowEnd == null) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx index 08b263222543..eca49ca736f2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/keyboard-absolute-resize-strategy.tsx @@ -146,7 +146,11 @@ export function keyboardAbsoluteResizeStrategy( return null } - if (MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElements[0])) { + if ( + selectedElements.some((selectedElement) => { + return MetadataUtils.isGridItem(canvasState.startingMetadata, selectedElement) + }) + ) { return null } diff --git a/editor/src/components/canvas/canvas-types.ts b/editor/src/components/canvas/canvas-types.ts index 06f6f0f5220e..f25438e1f26e 100644 --- a/editor/src/components/canvas/canvas-types.ts +++ b/editor/src/components/canvas/canvas-types.ts @@ -537,6 +537,12 @@ export const EdgePositionBottomLeft: EdgePosition = { x: 0, y: 1 } export const EdgePositionBottomRight: EdgePosition = { x: 1, y: 1 } export const EdgePositionTopRight: EdgePosition = { x: 1, y: 0 } +export type EdgePositionCorner = + | typeof EdgePositionTopLeft + | typeof EdgePositionTopRight + | typeof EdgePositionBottomLeft + | typeof EdgePositionBottomRight + export type SelectionLocked = 'locked' | 'locked-hierarchy' | 'selectable' export type PropertyTag = { type: 'hover' } | { type: 'breakpoint'; name: string } diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index dc0866da7d46..1e026619f181 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -71,11 +71,12 @@ import { printGridCSSNumber, } from '../../inspector/common/css-utils' import CanvasActions from '../canvas-actions' -import type { GridResizeEdge } from '../canvas-strategies/interaction-state' +import type { CanvasControlType, GridResizeEdge } from '../canvas-strategies/interaction-state' import { createInteractionViaMouse, gridAxisHandle, gridCellHandle, + gridChildCornerHandle, gridResizeHandle, gridResizeRulerHandle, } from '../canvas-strategies/interaction-state' @@ -95,7 +96,7 @@ import { } from '../canvas-strategies/strategies/grid-helpers' import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' -import type { EdgePosition } from '../canvas-types' +import type { EdgePosition, EdgePositionCorner } from '../canvas-types' import { CSSCursor } from '../canvas-types' import { windowToCanvasCoordinates } from '../dom-lookup' import type { Axis } from '../gap-utils' @@ -135,6 +136,7 @@ import { import type { Property } from 'csstype' import { isFeatureEnabled } from '../../../utils/feature-switches' import type { ThemeObject } from '../../../uuiui/styles/theme/theme-helpers' +import { ResizeControl } from './resize-control' const CELL_ANIMATION_DURATION = 0.15 // seconds @@ -1682,6 +1684,9 @@ interface GridResizeControlProps { export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) => { const gridTarget = getGridIdentifierContainerOrComponentPath(target) + const targets = React.useMemo(() => { + return [gridTarget] + }, [gridTarget]) const colorTheme = useColorTheme() const element = useEditorState( @@ -1748,7 +1753,7 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) }, [onMouseMove, onMouseUp]) const startResizeInteraction = React.useCallback( - (uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => { + (event: React.MouseEvent, canvasControl: CanvasControlType) => { event.stopPropagation() const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) setBounds(frame) @@ -1763,7 +1768,7 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) createInteractionViaMouse( start.canvasPositionRounded, Modifier.modifiersForEvent(event), - gridResizeHandle(uid, edge), + canvasControl, 'zero-drag-not-permitted', ), ), @@ -1784,7 +1789,7 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) }, [element, scale, isResizing]) const onEdgeMouseDown = React.useCallback( - (position: EdgePosition) => (e: React.MouseEvent) => { + (e: React.MouseEvent, position: EdgePosition) => { if (element == null) { return } @@ -1794,43 +1799,21 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) return } - startResizeInteraction(EP.toUid(element.elementPath), edge)(e) + startResizeInteraction(e, gridResizeHandle(EP.toUid(element.elementPath), edge)) }, [element, startResizeInteraction], ) - const resizeEdges = useResizeEdges([gridTarget], { - onEdgeDoubleClick: () => NO_OP, - onEdgeMouseMove: NO_OP, - onEdgeMouseDown: onEdgeMouseDown, - cursors: { - top: CSSCursor.RowResize, - bottom: CSSCursor.RowResize, - left: CSSCursor.ColResize, - right: CSSCursor.ColResize, - }, - }) - - const resizeDirection = useEditorState( - Substores.metadata, - (store) => { + const onCornerMouseDown = React.useCallback( + (event: React.MouseEvent, position: EdgePositionCorner) => { + event.stopPropagation() if (element == null) { - return { horizontal: false, vertical: false } - } - return { - horizontal: isFillOrStretchModeAppliedOnSpecificSide( - store.editor.jsxMetadata, - element.elementPath, - 'horizontal', - ), - vertical: isFillOrStretchModeAppliedOnSpecificSide( - store.editor.jsxMetadata, - element.elementPath, - 'vertical', - ), + return } + + startResizeInteraction(event, gridChildCornerHandle(EP.toUid(element.elementPath), position)) }, - 'GridResizeControlsComponent resizeDirection', + [element, startResizeInteraction], ) if ( @@ -1857,29 +1840,19 @@ export const GridResizeControlsComponent = ({ target }: GridResizeControlProps) backgroundColor: isResizing ? colorTheme.primary25.value : 'transparent', }} > -
- {when( - resizeDirection.vertical, - - {resizeEdges.top} - {resizeEdges.bottom} - , - )} - {when( - resizeDirection.horizontal, - - {resizeEdges.left} - {resizeEdges.right} - , - )} -
+ />
) @@ -2157,7 +2130,9 @@ function useIsGridItemInteractionActive() { // resize (cell) store.editor.canvas.interactionSession.activeControl.type === 'GRID_RESIZE_HANDLE' || // resize (abs) - store.editor.canvas.interactionSession.activeControl.type === 'RESIZE_HANDLE' + store.editor.canvas.interactionSession.activeControl.type === 'RESIZE_HANDLE' || + // resize (grid corner) + store.editor.canvas.interactionSession.activeControl.type === 'GRID_CHILD_CORNER_HANDLE' ) }, 'useIsGridItemInteractionActive isItemInteractionActive', diff --git a/editor/src/components/canvas/controls/resize-control.tsx b/editor/src/components/canvas/controls/resize-control.tsx new file mode 100644 index 000000000000..76182fe857bb --- /dev/null +++ b/editor/src/components/canvas/controls/resize-control.tsx @@ -0,0 +1,207 @@ +import * as React from 'react' +import type { EdgePosition, EdgePositionCorner } from '../canvas-types' +import { CSSCursor } from '../canvas-types' +import type { ElementPath } from 'utopia-shared/src/types' +import { useResizeEdges } from './select-mode/use-resize-edges' +import { NO_OP } from '../../../core/shared/utils' +import { useColorTheme } from '../../../uuiui' +import { useMaybeHighlightElement } from './select-mode/select-mode-hooks' +import { Substores, useEditorState } from '../../editor/store/store-hook' +import { isEdgePositionEqualTo } from '../canvas-utils' +import { useBoundingBox } from './bounding-box-hooks' + +interface ResizePointProps { + cursor: CSSCursor + position: EdgePosition + onPointMouseDown?: (event: React.MouseEvent, position: EdgePosition) => void + onPointDoubleClick?: (event: React.MouseEvent) => void +} + +export const ResizePointTestId = (position: EdgePosition): string => + `resize-control-${position.x}-${position.y}` +const ResizePointMouseAreaSize = 12 +const ResizePointMouseAreaOffset = ResizePointMouseAreaSize / 2 +const ResizePointSize = 6 +const ResizePointOffset = ResizePointSize / 2 +const ResizePoint = React.memo( + React.forwardRef((props, ref) => { + const colorTheme = useColorTheme() + const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'ResizeEdge scale', + ) + + const onPointMouseDown = React.useCallback( + (event: React.MouseEvent) => { + if (props.onPointMouseDown != null) { + props.onPointMouseDown(event, props.position) + } + }, + [props], + ) + + const onMouseMove = React.useCallback( + (event: React.MouseEvent) => { + maybeClearHighlightsOnHoverEnd() + event.stopPropagation() + }, + [maybeClearHighlightsOnHoverEnd], + ) + + const hiddenDuringInteraction = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession != null && + store.editor.canvas.interactionSession.activeControl.type === 'RESIZE_HANDLE' && + !isEdgePositionEqualTo( + props.position, + store.editor.canvas.interactionSession.activeControl.edgePosition, + ), + 'ResizePoint hiddenDuringInteraction', + ) + + return ( +
+
+
+
+ ) + }), +) +ResizePoint.displayName = 'ResizePoint' + +interface ResizeControlProps { + ['data-testid']?: string + position: React.CSSProperties['position'] + expandToFill: 'expand' | 'do-not-expand' + targets: Array + onEdgeMouseDown?: (event: React.MouseEvent, position: EdgePosition) => void + onEdgeMouseMove?: (event: React.MouseEvent) => void + onEdgeDoubleClick?: ( + event: React.MouseEvent, + direction: 'horizontal' | 'vertical', + ) => void + onCornerMouseDown?: ( + event: React.MouseEvent, + position: EdgePositionCorner, + ) => void + onCornerDoubleClick?: (event: React.MouseEvent) => void + edgeCursors?: { + top?: CSSCursor + left?: CSSCursor + bottom?: CSSCursor + right?: CSSCursor + } +} + +export const ResizeControl = React.memo( + React.forwardRef((props, ref) => { + const resizeEdges = useResizeEdges(props.targets, { + onEdgeDoubleClick: props.onEdgeDoubleClick ?? NO_OP, + onEdgeMouseMove: props.onEdgeMouseMove ?? NO_OP, + onEdgeMouseDown: props.onEdgeMouseDown ?? NO_OP, + cursors: props.edgeCursors, + }) + + const topLeftRef = useBoundingBox(props.targets, NO_OP) + const topRightRef = useBoundingBox(props.targets, (boundingRef, boundingBox) => { + boundingRef.current.style.left = boundingBox.width + 'px' + }) + const bottomLeftRef = useBoundingBox(props.targets, (boundingRef, boundingBox) => { + boundingRef.current.style.top = boundingBox.height + 'px' + }) + const bottomRightRef = useBoundingBox(props.targets, (boundingRef, boundingBox) => { + boundingRef.current.style.left = boundingBox.width + 'px' + boundingRef.current.style.top = boundingBox.height + 'px' + }) + + return ( +
+ {resizeEdges.top} + {resizeEdges.bottom} + {resizeEdges.left} + {resizeEdges.right} + + + + +
+ ) + }), +) +ResizeControl.displayName = 'ResizeControl' diff --git a/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx b/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx index b3805a7d0f2a..e525c0fdcf74 100644 --- a/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx +++ b/editor/src/components/canvas/controls/select-mode/absolute-resize-control.tsx @@ -1,37 +1,26 @@ import React from 'react' -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' import type { CanvasVector } from '../../../../core/shared/math-utils' import { windowPoint } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' -import { NO_OP } from '../../../../core/shared/utils' import { Modifier } from '../../../../utils/modifiers' -import { when } from '../../../../utils/react-conditionals' -import { useColorTheme } from '../../../../uuiui' import type { EditorDispatch } from '../../../editor/action-types' import { applyCommandsAction } from '../../../editor/actions/action-creators' import { useDispatch } from '../../../editor/store/dispatch-context' import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook' -import { - invert, - isFillOrStretchModeAppliedOnAnySide, - isFillOrStretchModeAppliedOnSpecificSide, - resizeToFitCommands, -} from '../../../inspector/inspector-common' +import { invert, resizeToFitCommands } from '../../../inspector/inspector-common' import { setPropHugStrategies } from '../../../inspector/inspector-strategies/inspector-strategies' import { executeFirstApplicableStrategy } from '../../../inspector/inspector-strategies/inspector-strategy' import CanvasActions from '../../canvas-actions' import { controlForStrategyMemoized } from '../../canvas-strategies/canvas-strategy-types' import { createInteractionViaMouse } from '../../canvas-strategies/interaction-state' import type { EdgePosition } from '../../canvas-types' -import { CSSCursor } from '../../canvas-types' import { windowToCanvasCoordinates } from '../../dom-lookup' import { useBoundingBox } from '../bounding-box-hooks' import { CanvasOffsetWrapper } from '../canvas-offset-wrapper' import { isZeroSizedElement } from '../outline-utils' +import { ResizeControl } from '../resize-control' import { useMaybeHighlightElement } from './select-mode-hooks' -import { isEdgePositionEqualTo } from '../../canvas-utils' -import { useResizeEdges } from './use-resize-edges' export const AbsoluteResizeControlTestId = (targets: Array): string => `${targets.map(EP.toString).sort()}-absolute-resize-control` @@ -49,6 +38,7 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) + const allElementPropsRef = useRefEditorState((store) => store.editor.allElementProps) const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() @@ -64,25 +54,34 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( } }) - const topLeftRef = useBoundingBox(targets, NO_OP) - const topRightRef = useBoundingBox(targets, (ref, boundingBox) => { - ref.current.style.left = boundingBox.width + 'px' - }) - const bottomLeftRef = useBoundingBox(targets, (ref, boundingBox) => { - ref.current.style.top = boundingBox.height + 'px' - }) - const bottomRightRef = useBoundingBox(targets, (ref, boundingBox) => { - ref.current.style.left = boundingBox.width + 'px' - ref.current.style.top = boundingBox.height + 'px' - }) - const scale = useEditorState( Substores.canvasOffset, (store) => store.editor.canvas.scale, 'AbsoluteResizeControl scale', ) + + const onCornerMouseDown = React.useCallback( + (event: React.MouseEvent, position: EdgePosition) => { + startResizeInteraction(event, dispatch, position, canvasOffsetRef.current, scale) + }, + [dispatch, canvasOffsetRef, scale], + ) + + const onCornerDoubleClick = React.useCallback(() => { + dispatch([ + applyCommandsAction( + resizeToFitCommands( + metadataRef.current, + selectedElementsRef.current, + elementPathTreeRef.current, + allElementPropsRef.current, + ), + ), + ]) + }, [allElementPropsRef, dispatch, metadataRef, elementPathTreeRef, selectedElementsRef]) + const onEdgeMouseDown = React.useCallback( - (position: EdgePosition) => (event: React.MouseEvent) => { + (event: React.MouseEvent, position: EdgePosition) => { startResizeInteraction(event, dispatch, position, canvasOffsetRef.current, scale) }, [dispatch, canvasOffsetRef, scale], @@ -97,7 +96,7 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( ) const onEdgeDoubleClick = React.useCallback( - (direction: 'horizontal' | 'vertical') => () => { + (event: React.MouseEvent, direction: 'horizontal' | 'vertical') => { executeFirstApplicableStrategy( dispatch, setPropHugStrategies( @@ -111,217 +110,25 @@ export const AbsoluteResizeControl = controlForStrategyMemoized( [dispatch, metadataRef, elementPathTreeRef, selectedElementsRef], ) - const resizeEdges = useResizeEdges(targets, { - onEdgeMouseDown, - onEdgeMouseMove, - onEdgeDoubleClick, - }) - - const canResize = useEditorState( - Substores.metadata, - (store) => { - const metadata = store.editor.jsxMetadata - - let horizontally = true - let vertically = true - let diagonally = true - - for (const element of selectedElementsRef.current) { - if (MetadataUtils.isGridItem(metadata, element)) { - if (isFillOrStretchModeAppliedOnAnySide(metadata, element)) { - diagonally = false - } - if (isFillOrStretchModeAppliedOnSpecificSide(metadata, element, 'horizontal')) { - horizontally = false - } - if (isFillOrStretchModeAppliedOnSpecificSide(metadata, element, 'vertical')) { - vertically = false - } - } - } - - return { - horizontally: horizontally, - vertically: vertically, - diagonally: diagonally, - } - }, - 'AbsoluteResizeControl canResize', - ) - return ( -
- {when( - canResize.vertically, - - {resizeEdges.top} - {resizeEdges.bottom} - , - )} - {when( - canResize.horizontally, - - {resizeEdges.left} - {resizeEdges.right} - , - )} - {when( - canResize.diagonally, - - - - - - , - )} -
+ position='absolute' + expandToFill={'do-not-expand'} + targets={targets} + onCornerMouseDown={onCornerMouseDown} + onCornerDoubleClick={onCornerDoubleClick} + onEdgeDoubleClick={onEdgeDoubleClick} + onEdgeMouseMove={onEdgeMouseMove} + onEdgeMouseDown={onEdgeMouseDown} + />
) }, ) -interface ResizePointProps { - cursor: CSSCursor - position: EdgePosition -} - -export const ResizePointTestId = (position: EdgePosition): string => - `resize-control-${position.x}-${position.y}` -const ResizePointMouseAreaSize = 12 -const ResizePointMouseAreaOffset = ResizePointMouseAreaSize / 2 -const ResizePointSize = 6 -const ResizePointOffset = ResizePointSize / 2 -const ResizePoint = React.memo( - React.forwardRef((props, ref) => { - const colorTheme = useColorTheme() - const { maybeClearHighlightsOnHoverEnd } = useMaybeHighlightElement() - const scale = useEditorState( - Substores.canvas, - (store) => store.editor.canvas.scale, - 'ResizeEdge scale', - ) - const dispatch = useDispatch() - const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) - - const onPointMouseDown = React.useCallback( - (event: React.MouseEvent) => { - startResizeInteraction(event, dispatch, props.position, canvasOffsetRef.current, scale) - }, - [dispatch, props.position, canvasOffsetRef, scale], - ) - - const onMouseMove = React.useCallback( - (event: React.MouseEvent) => { - maybeClearHighlightsOnHoverEnd() - event.stopPropagation() - }, - [maybeClearHighlightsOnHoverEnd], - ) - - const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) - const selectedElementsRef = useRefEditorState((store) => store.editor.selectedViews) - const elementPathTreeRef = useRefEditorState((store) => store.editor.elementPathTree) - const allElementPropsRef = useRefEditorState((store) => store.editor.allElementProps) - - const onEdgeDblClick = React.useCallback(() => { - dispatch([ - applyCommandsAction( - resizeToFitCommands( - metadataRef.current, - selectedElementsRef.current, - elementPathTreeRef.current, - allElementPropsRef.current, - ), - ), - ]) - }, [allElementPropsRef, dispatch, metadataRef, elementPathTreeRef, selectedElementsRef]) - - const hiddenDuringInteraction = useEditorState( - Substores.canvas, - (store) => - store.editor.canvas.interactionSession != null && - store.editor.canvas.interactionSession.activeControl.type === 'RESIZE_HANDLE' && - !isEdgePositionEqualTo( - props.position, - store.editor.canvas.interactionSession.activeControl.edgePosition, - ), - 'ResizePoint hiddenDuringInteraction', - ) - - return ( -
-
-
-
- ) - }), -) -ResizePoint.displayName = 'ResizePoint' - function startResizeInteraction( event: React.MouseEvent, dispatch: EditorDispatch, diff --git a/editor/src/components/canvas/controls/select-mode/resize-edge.tsx b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx index 6e913ffe61f2..fcc626ff7703 100644 --- a/editor/src/components/canvas/controls/select-mode/resize-edge.tsx +++ b/editor/src/components/canvas/controls/select-mode/resize-edge.tsx @@ -1,6 +1,6 @@ import React from 'react' import type { CSSCursor, EdgePosition } from '../../canvas-types' -import { ResizePointTestId } from './absolute-resize-control' +import { ResizePointTestId } from '../resize-control' interface ResizeEdgeProps { cursor: CSSCursor diff --git a/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx index 34831303a362..74191c93c374 100644 --- a/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx +++ b/editor/src/components/canvas/controls/select-mode/use-resize-edges.tsx @@ -9,13 +9,14 @@ import { ResizeEdge } from './resize-edge' const RESIZE_MOUSE_AREA_SIZE = 10 export function useResizeEdges( - targets: ElementPath[], + targets: Array, params: { - onEdgeMouseDown: (position: EdgePosition) => (e: React.MouseEvent) => void + onEdgeMouseDown: (e: React.MouseEvent, position: EdgePosition) => void onEdgeMouseMove: (e: React.MouseEvent) => void onEdgeDoubleClick: ( + e: React.MouseEvent, direction: 'horizontal' | 'vertical', - ) => (e: React.MouseEvent) => void + ) => void cursors?: { top?: CSSCursor left?: CSSCursor @@ -80,6 +81,62 @@ export function useResizeEdges( ref.current.style.height = boundingBox.height + 'px' }) + const topOnMouseDown = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeMouseDown(e, { x: 0.5, y: 0 }) + }, + [params], + ) + + const leftOnMouseDown = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeMouseDown(e, { x: 0, y: 0.5 }) + }, + [params], + ) + + const bottomOnMouseDown = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeMouseDown(e, { x: 0.5, y: 1 }) + }, + [params], + ) + + const rightOnMouseDown = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeMouseDown(e, { x: 1, y: 0.5 }) + }, + [params], + ) + + const topOnDoubleClick = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeDoubleClick(e, 'horizontal') + }, + [params], + ) + + const leftOnDoubleClick = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeDoubleClick(e, 'vertical') + }, + [params], + ) + + const bottomOnDoubleClick = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeDoubleClick(e, 'horizontal') + }, + [params], + ) + + const rightOnDoubleClick = React.useCallback( + (e: React.MouseEvent) => { + params.onEdgeDoubleClick(e, 'vertical') + }, + [params], + ) + return { top: ( ), left: ( @@ -98,9 +155,9 @@ export function useResizeEdges( position={{ x: 0, y: 0.5 }} cursor={params.cursors?.left ?? CSSCursor.ResizeEW} direction='vertical' - onMouseDown={params.onEdgeMouseDown({ x: 0, y: 0.5 })} + onMouseDown={leftOnMouseDown} onMouseMove={params.onEdgeMouseMove} - onDoubleClick={params.onEdgeDoubleClick('vertical')} + onDoubleClick={leftOnDoubleClick} /> ), bottom: ( @@ -109,9 +166,9 @@ export function useResizeEdges( position={{ x: 0.5, y: 1 }} cursor={params.cursors?.bottom ?? CSSCursor.ResizeNS} direction='horizontal' - onMouseDown={params.onEdgeMouseDown({ x: 0.5, y: 1 })} + onMouseDown={bottomOnMouseDown} onMouseMove={params.onEdgeMouseMove} - onDoubleClick={params.onEdgeDoubleClick('horizontal')} + onDoubleClick={bottomOnDoubleClick} /> ), right: ( @@ -120,9 +177,9 @@ export function useResizeEdges( position={{ x: 1, y: 0.5 }} cursor={params.cursors?.right ?? CSSCursor.ResizeEW} direction='vertical' - onMouseDown={params.onEdgeMouseDown({ x: 1, y: 0.5 })} + onMouseDown={rightOnMouseDown} onMouseMove={params.onEdgeMouseMove} - onDoubleClick={params.onEdgeDoubleClick('vertical')} + onDoubleClick={rightOnDoubleClick} /> ), } 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 d1f16d90eed1..39cae6757ce4 100644 --- a/editor/src/components/editor/store/store-deep-equality-instances.ts +++ b/editor/src/components/editor/store/store-deep-equality-instances.ts @@ -462,6 +462,7 @@ import type { GridResizeEdge, GridGapHandle, GridResizeRulerHandle, + GridChildCornerHandle, } from '../../canvas/canvas-strategies/interaction-state' import { boundingArea, @@ -475,12 +476,14 @@ import { gridResizeHandle, gridGapHandle, gridResizeRulerHandle, + gridChildCornerHandle, } from '../../canvas/canvas-strategies/interaction-state' import type { Modifiers } from '../../../utils/modifiers' import type { CanvasFrameAndTarget, CSSCursor, EdgePosition, + EdgePositionCorner, FrameAndTarget, } from '../../canvas/canvas-types' import { edgePosition } from '../../canvas/canvas-types' @@ -3194,6 +3197,15 @@ export const GridResizeMarkerHandleKeepDeepEquality: KeepDeepEqualityCall = combine1EqualityCall((handle) => handle.axis, createCallWithTripleEquals(), gridGapHandle) +export const GridChildCornerHandleKeepDeepEquality: KeepDeepEqualityCall = + combine2EqualityCalls( + (handle) => handle.id, + createCallWithTripleEquals(), + (handle) => handle.corner, + createCallWithTripleEquals(), + gridChildCornerHandle, + ) + export const CanvasControlTypeKeepDeepEquality: KeepDeepEqualityCall = ( oldValue, newValue, @@ -3259,6 +3271,11 @@ export const CanvasControlTypeKeepDeepEquality: KeepDeepEqualityCall { const renderCountAfter = renderResult.getNumberOfRenders() // if this breaks, GREAT NEWS but update the test please :) - expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`665`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`646`) 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(`790`) + expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`771`) expect(renderResult.getRenderInfo()).toMatchSnapshot() }) })