Skip to content

Commit aa29342

Browse files
authored
fix(grids) Handle Nested Grid Dragging (#6526)
- Implemented `combineApplicableControls` to collate disparate instances of `GridControls`. - `controlsForGridPlaceholders` has been slightly tweaked to cater for the change in the type of `GridControlsProps` and also to handle a new option suffix. - Removed `pointerEvents` setting which was bizarrely the cause of the original issue. - Removed spurious property. - `GridControlsComponent` now includes ancestor paths. - `GridControlsComponent` now sorts the grid paths by their depth, so that the bottommost event triggers fire over those that are ancestors of that element.
1 parent e6f3148 commit aa29342

8 files changed

+198
-82
lines changed

editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx

+65-20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
StrategyApplicationResult,
2020
InteractionLifecycle,
2121
CustomStrategyState,
22+
WhenToShowControl,
2223
} from './canvas-strategy-types'
2324
import {
2425
ControlDelay,
@@ -83,6 +84,11 @@ import { getReparentTargetUnified } from './strategies/reparent-helpers/reparent
8384
import { gridRearrangeResizeKeyboardStrategy } from './strategies/grid-rearrange-keyboard-strategy'
8485
import createCachedSelector from 're-reselect'
8586
import { getActivePlugin } from '../plugins/style-plugins'
87+
import {
88+
controlsForGridPlaceholders,
89+
GridControls,
90+
isGridControlsProps,
91+
} from '../controls/grid-controls-for-strategies'
8692

8793
export type CanvasStrategyFactory = (
8894
canvasState: InteractionCanvasState,
@@ -648,6 +654,42 @@ function controlPriorityToNumber(prio: ControlWithProps<any>['priority']): numbe
648654
}
649655
}
650656

657+
export function combineApplicableControls(
658+
strategyControls: Array<ControlWithProps<unknown>>,
659+
): Array<ControlWithProps<unknown>> {
660+
// Separate out the instances of `GridControls`.
661+
let result: Array<ControlWithProps<unknown>> = []
662+
let gridControlsInstances: Array<ControlWithProps<unknown>> = []
663+
for (const control of strategyControls) {
664+
if (control.control === GridControls) {
665+
gridControlsInstances.push(control)
666+
} else {
667+
result.push(control)
668+
}
669+
}
670+
671+
// Sift the instances of `GridControls`, storing their targets by when they should be shown.
672+
let gridControlsTargets: Map<WhenToShowControl, Array<ElementPath>> = new Map()
673+
for (const control of gridControlsInstances) {
674+
if (isGridControlsProps(control.props)) {
675+
const possibleTargets = gridControlsTargets.get(control.show)
676+
if (possibleTargets == null) {
677+
gridControlsTargets.set(control.show, control.props.targets)
678+
} else {
679+
possibleTargets.push(...control.props.targets)
680+
}
681+
}
682+
}
683+
684+
// Create new instances of `GridControls` with the combined targets.
685+
for (const [show, targets] of gridControlsTargets) {
686+
result.push(controlsForGridPlaceholders(targets, show, `-${show}`))
687+
}
688+
689+
// Return the newly created controls with the combined entries.
690+
return result
691+
}
692+
651693
const controlEquals = (l: ControlWithProps<any>, r: ControlWithProps<any>) => {
652694
return l.control === r.control && l.key === r.key
653695
}
@@ -667,34 +709,37 @@ export function useGetApplicableStrategyControls(localSelectedViews: Array<Eleme
667709
'useGetApplicableStrategyControls currentlyInProgress',
668710
)
669711
return React.useMemo(() => {
712+
let strategyControls: Array<ControlWithProps<unknown>> = []
670713
let isResizable: boolean = false
671-
const bottomStrategyControls: Array<ControlWithProps<unknown>> = []
672-
const middleStrategyControls: Array<ControlWithProps<unknown>> = []
673-
const topStrategyControls: Array<ControlWithProps<unknown>> = []
674714
// Add the controls for currently applicable strategies.
675715
for (const strategy of applicableStrategies) {
676716
if (isResizableStrategy(strategy)) {
677717
isResizable = true
678718
}
679-
const strategyControls = getApplicableControls(currentStrategy, strategy)
680-
681-
// uniquely add the strategyControls to the bottom, middle, and top arrays
682-
for (const control of strategyControls) {
683-
switch (control.priority) {
684-
case 'bottom':
685-
pushUniquelyBy(bottomStrategyControls, control, controlEquals)
686-
break
687-
case undefined:
688-
pushUniquelyBy(middleStrategyControls, control, controlEquals)
689-
break
690-
case 'top':
691-
pushUniquelyBy(topStrategyControls, control, controlEquals)
692-
break
693-
default:
694-
assertNever(control.priority)
695-
}
719+
strategyControls.push(...getApplicableControls(currentStrategy, strategy))
720+
}
721+
const combinedControls = combineApplicableControls(strategyControls)
722+
const bottomStrategyControls: Array<ControlWithProps<unknown>> = []
723+
const middleStrategyControls: Array<ControlWithProps<unknown>> = []
724+
const topStrategyControls: Array<ControlWithProps<unknown>> = []
725+
726+
// uniquely add the strategyControls to the bottom, middle, and top arrays
727+
for (const control of combinedControls) {
728+
switch (control.priority) {
729+
case 'bottom':
730+
pushUniquelyBy(bottomStrategyControls, control, controlEquals)
731+
break
732+
case undefined:
733+
pushUniquelyBy(middleStrategyControls, control, controlEquals)
734+
break
735+
case 'top':
736+
pushUniquelyBy(topStrategyControls, control, controlEquals)
737+
break
738+
default:
739+
assertNever(control.priority)
696740
}
697741
}
742+
698743
// Special case controls.
699744
if (!isResizable && !currentlyInProgress) {
700745
middleStrategyControls.push(notResizableControls)

editor/src/components/canvas/canvas-strategies/interaction-state.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,13 @@ export function reorderSlider(): ReorderSlider {
646646

647647
export interface GridCellHandle {
648648
type: 'GRID_CELL_HANDLE'
649-
id: string
649+
path: ElementPath
650650
}
651651

652-
export function gridCellHandle(params: { id: string }): GridCellHandle {
652+
export function gridCellHandle(params: { path: ElementPath }): GridCellHandle {
653653
return {
654654
type: 'GRID_CELL_HANDLE',
655-
id: params.id,
655+
path: params.path,
656656
}
657657
}
658658

editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx

+19-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
import { selectComponentsForTest } from '../../../../utils/utils.test-utils'
1212
import CanvasActions from '../../canvas-actions'
1313
import { GridCellTestId } from '../../controls/grid-controls-for-strategies'
14-
import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils'
14+
import { CanvasControlsContainerID } from '../../controls/new-canvas-controls'
15+
import { mouseDragFromPointToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils'
1516
import type { EditorRenderResult } from '../../ui-jsx.test-utils'
1617
import { renderTestEditorWithCode } from '../../ui-jsx.test-utils'
1718
import type { GridCellCoordinates } from './grid-cell-bounds'
@@ -572,6 +573,7 @@ export var storyboard = (
572573
childCenter,
573574
offsetPoint(childCenter, windowPoint({ x: 280, y: 120 })),
574575
{
576+
staggerMoveEvents: false,
575577
moveBeforeMouseDown: true,
576578
},
577579
)
@@ -767,24 +769,28 @@ async function runMoveTest(
767769
const sourceRect = sourceGridCell.getBoundingClientRect()
768770
const targetRect = targetGridCell.getBoundingClientRect()
769771

772+
const endPoint = getRectCenter(
773+
localRectangle({
774+
x: targetRect.x,
775+
y: targetRect.y,
776+
width: targetRect.width,
777+
height: targetRect.height,
778+
}),
779+
)
780+
770781
await mouseDragFromPointToPoint(
771782
sourceGridCell,
772783
{
773784
x: sourceRect.x + 10,
774785
y: sourceRect.y + 10,
775786
},
776-
getRectCenter(
777-
localRectangle({
778-
x: targetRect.x,
779-
y: targetRect.y,
780-
width: targetRect.width,
781-
height: targetRect.height,
782-
}),
783-
),
787+
endPoint,
784788
{
789+
staggerMoveEvents: false,
785790
moveBeforeMouseDown: true,
786791
},
787792
)
793+
await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint)
788794

789795
return editor.renderedDOM.getByTestId(props.testId).style
790796
}
@@ -941,7 +947,7 @@ export var storyboard = (
941947
height: '100%',
942948
}}
943949
data-uid='pink'
944-
data-testid='pink'
950+
data-testid='pink'
945951
data-label='pink'
946952
/>
947953
<div
@@ -951,7 +957,7 @@ export var storyboard = (
951957
height: '100%',
952958
}}
953959
data-uid='orange'
954-
data-testid='orange'
960+
data-testid='orange'
955961
data-label='orange'
956962
/>
957963
<div
@@ -961,7 +967,7 @@ export var storyboard = (
961967
height: '100%',
962968
}}
963969
data-uid='cyan'
964-
data-testid='cyan'
970+
data-testid='cyan'
965971
data-label='cyan'
966972
/>
967973
<div
@@ -971,7 +977,7 @@ export var storyboard = (
971977
height: '100%',
972978
}}
973979
data-uid='blue'
974-
data-testid='blue'
980+
data-testid='blue'
975981
data-label='blue'
976982
/>
977983
</div>

editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ export const rearrangeGridSwapStrategy: CanvasStrategyFactory = (
104104

105105
if (
106106
pointerOverChild != null &&
107-
EP.toUid(pointerOverChild.elementPath) !== interactionSession.activeControl.id
107+
EP.toUid(pointerOverChild.elementPath) !== EP.toUid(interactionSession.activeControl.path)
108108
) {
109109
commands.push(
110110
...swapChildrenCommands({
111-
grabbedElementUid: interactionSession.activeControl.id,
111+
grabbedElementUid: EP.toUid(interactionSession.activeControl.path),
112112
swapToElementUid: EP.toUid(pointerOverChild.elementPath),
113113
children: children,
114114
parentPath: EP.parentPath(selectedElement),

editor/src/components/canvas/controls/grid-controls-for-strategies.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,14 @@ export interface GridControlProps {
219219
}
220220

221221
export interface GridControlsProps {
222+
type: 'GRID_CONTROLS_PROPS'
222223
targets: ElementPath[]
223224
}
224225

226+
export function isGridControlsProps(props: unknown): props is GridControlsProps {
227+
return (props as GridControlsProps).type === 'GRID_CONTROLS_PROPS'
228+
}
229+
225230
export const GridControls = controlForStrategyMemoized<GridControlsProps>(GridControlsComponent)
226231

227232
interface GridResizeControlProps {
@@ -264,13 +269,17 @@ export function edgePositionToGridResizeEdge(position: EdgePosition): GridResize
264269
}
265270

266271
export function controlsForGridPlaceholders(
267-
gridPath: ElementPath,
272+
gridPath: ElementPath | Array<ElementPath>,
268273
whenToShow: WhenToShowControl = 'always-visible',
274+
suffix: string | null = null,
269275
): ControlWithProps<any> {
270276
return {
271277
control: GridControls,
272-
props: { targets: [gridPath] },
273-
key: GridControlsKey(gridPath),
278+
props: {
279+
type: 'GRID_CONTROLS_PROPS',
280+
targets: Array.isArray(gridPath) ? gridPath : [gridPath],
281+
},
282+
key: `GridControls${suffix == null ? '' : suffix}`,
274283
show: whenToShow,
275284
priority: 'bottom',
276285
}

0 commit comments

Comments
 (0)