Skip to content

Commit

Permalink
fix(grids) Handle Nested Grid Dragging (#6526)
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
seanparsons authored Oct 16, 2024
1 parent e6f3148 commit aa29342
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
StrategyApplicationResult,
InteractionLifecycle,
CustomStrategyState,
WhenToShowControl,
} from './canvas-strategy-types'
import {
ControlDelay,
Expand Down Expand Up @@ -83,6 +84,11 @@ import { getReparentTargetUnified } from './strategies/reparent-helpers/reparent
import { gridRearrangeResizeKeyboardStrategy } from './strategies/grid-rearrange-keyboard-strategy'
import createCachedSelector from 're-reselect'
import { getActivePlugin } from '../plugins/style-plugins'
import {
controlsForGridPlaceholders,
GridControls,
isGridControlsProps,
} from '../controls/grid-controls-for-strategies'

export type CanvasStrategyFactory = (
canvasState: InteractionCanvasState,
Expand Down Expand Up @@ -648,6 +654,42 @@ function controlPriorityToNumber(prio: ControlWithProps<any>['priority']): numbe
}
}

export function combineApplicableControls(
strategyControls: Array<ControlWithProps<unknown>>,
): Array<ControlWithProps<unknown>> {
// Separate out the instances of `GridControls`.
let result: Array<ControlWithProps<unknown>> = []
let gridControlsInstances: Array<ControlWithProps<unknown>> = []
for (const control of strategyControls) {
if (control.control === GridControls) {
gridControlsInstances.push(control)
} else {
result.push(control)
}
}

// Sift the instances of `GridControls`, storing their targets by when they should be shown.
let gridControlsTargets: Map<WhenToShowControl, Array<ElementPath>> = new Map()
for (const control of gridControlsInstances) {
if (isGridControlsProps(control.props)) {
const possibleTargets = gridControlsTargets.get(control.show)
if (possibleTargets == null) {
gridControlsTargets.set(control.show, control.props.targets)
} else {
possibleTargets.push(...control.props.targets)
}
}
}

// Create new instances of `GridControls` with the combined targets.
for (const [show, targets] of gridControlsTargets) {
result.push(controlsForGridPlaceholders(targets, show, `-${show}`))
}

// Return the newly created controls with the combined entries.
return result
}

const controlEquals = (l: ControlWithProps<any>, r: ControlWithProps<any>) => {
return l.control === r.control && l.key === r.key
}
Expand All @@ -667,34 +709,37 @@ export function useGetApplicableStrategyControls(localSelectedViews: Array<Eleme
'useGetApplicableStrategyControls currentlyInProgress',
)
return React.useMemo(() => {
let strategyControls: Array<ControlWithProps<unknown>> = []
let isResizable: boolean = false
const bottomStrategyControls: Array<ControlWithProps<unknown>> = []
const middleStrategyControls: Array<ControlWithProps<unknown>> = []
const topStrategyControls: Array<ControlWithProps<unknown>> = []
// Add the controls for currently applicable strategies.
for (const strategy of applicableStrategies) {
if (isResizableStrategy(strategy)) {
isResizable = true
}
const strategyControls = getApplicableControls(currentStrategy, strategy)

// uniquely add the strategyControls to the bottom, middle, and top arrays
for (const control of strategyControls) {
switch (control.priority) {
case 'bottom':
pushUniquelyBy(bottomStrategyControls, control, controlEquals)
break
case undefined:
pushUniquelyBy(middleStrategyControls, control, controlEquals)
break
case 'top':
pushUniquelyBy(topStrategyControls, control, controlEquals)
break
default:
assertNever(control.priority)
}
strategyControls.push(...getApplicableControls(currentStrategy, strategy))
}
const combinedControls = combineApplicableControls(strategyControls)
const bottomStrategyControls: Array<ControlWithProps<unknown>> = []
const middleStrategyControls: Array<ControlWithProps<unknown>> = []
const topStrategyControls: Array<ControlWithProps<unknown>> = []

// uniquely add the strategyControls to the bottom, middle, and top arrays
for (const control of combinedControls) {
switch (control.priority) {
case 'bottom':
pushUniquelyBy(bottomStrategyControls, control, controlEquals)
break
case undefined:
pushUniquelyBy(middleStrategyControls, control, controlEquals)
break
case 'top':
pushUniquelyBy(topStrategyControls, control, controlEquals)
break
default:
assertNever(control.priority)
}
}

// Special case controls.
if (!isResizable && !currentlyInProgress) {
middleStrategyControls.push(notResizableControls)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,13 +646,13 @@ export function reorderSlider(): ReorderSlider {

export interface GridCellHandle {
type: 'GRID_CELL_HANDLE'
id: string
path: ElementPath
}

export function gridCellHandle(params: { id: string }): GridCellHandle {
export function gridCellHandle(params: { path: ElementPath }): GridCellHandle {
return {
type: 'GRID_CELL_HANDLE',
id: params.id,
path: params.path,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
import { selectComponentsForTest } from '../../../../utils/utils.test-utils'
import CanvasActions from '../../canvas-actions'
import { GridCellTestId } from '../../controls/grid-controls-for-strategies'
import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils'
import { CanvasControlsContainerID } from '../../controls/new-canvas-controls'
import { mouseDragFromPointToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils'
import type { EditorRenderResult } from '../../ui-jsx.test-utils'
import { renderTestEditorWithCode } from '../../ui-jsx.test-utils'
import type { GridCellCoordinates } from './grid-cell-bounds'
Expand Down Expand Up @@ -572,6 +573,7 @@ export var storyboard = (
childCenter,
offsetPoint(childCenter, windowPoint({ x: 280, y: 120 })),
{
staggerMoveEvents: false,
moveBeforeMouseDown: true,
},
)
Expand Down Expand Up @@ -767,24 +769,28 @@ async function runMoveTest(
const sourceRect = sourceGridCell.getBoundingClientRect()
const targetRect = targetGridCell.getBoundingClientRect()

const endPoint = getRectCenter(
localRectangle({
x: targetRect.x,
y: targetRect.y,
width: targetRect.width,
height: targetRect.height,
}),
)

await mouseDragFromPointToPoint(
sourceGridCell,
{
x: sourceRect.x + 10,
y: sourceRect.y + 10,
},
getRectCenter(
localRectangle({
x: targetRect.x,
y: targetRect.y,
width: targetRect.width,
height: targetRect.height,
}),
),
endPoint,
{
staggerMoveEvents: false,
moveBeforeMouseDown: true,
},
)
await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint)

return editor.renderedDOM.getByTestId(props.testId).style
}
Expand Down Expand Up @@ -941,7 +947,7 @@ export var storyboard = (
height: '100%',
}}
data-uid='pink'
data-testid='pink'
data-testid='pink'
data-label='pink'
/>
<div
Expand All @@ -951,7 +957,7 @@ export var storyboard = (
height: '100%',
}}
data-uid='orange'
data-testid='orange'
data-testid='orange'
data-label='orange'
/>
<div
Expand All @@ -961,7 +967,7 @@ export var storyboard = (
height: '100%',
}}
data-uid='cyan'
data-testid='cyan'
data-testid='cyan'
data-label='cyan'
/>
<div
Expand All @@ -971,7 +977,7 @@ export var storyboard = (
height: '100%',
}}
data-uid='blue'
data-testid='blue'
data-testid='blue'
data-label='blue'
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ export const rearrangeGridSwapStrategy: CanvasStrategyFactory = (

if (
pointerOverChild != null &&
EP.toUid(pointerOverChild.elementPath) !== interactionSession.activeControl.id
EP.toUid(pointerOverChild.elementPath) !== EP.toUid(interactionSession.activeControl.path)
) {
commands.push(
...swapChildrenCommands({
grabbedElementUid: interactionSession.activeControl.id,
grabbedElementUid: EP.toUid(interactionSession.activeControl.path),
swapToElementUid: EP.toUid(pointerOverChild.elementPath),
children: children,
parentPath: EP.parentPath(selectedElement),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,14 @@ export interface GridControlProps {
}

export interface GridControlsProps {
type: 'GRID_CONTROLS_PROPS'
targets: ElementPath[]
}

export function isGridControlsProps(props: unknown): props is GridControlsProps {
return (props as GridControlsProps).type === 'GRID_CONTROLS_PROPS'
}

export const GridControls = controlForStrategyMemoized<GridControlsProps>(GridControlsComponent)

interface GridResizeControlProps {
Expand Down Expand Up @@ -264,13 +269,17 @@ export function edgePositionToGridResizeEdge(position: EdgePosition): GridResize
}

export function controlsForGridPlaceholders(
gridPath: ElementPath,
gridPath: ElementPath | Array<ElementPath>,
whenToShow: WhenToShowControl = 'always-visible',
suffix: string | null = null,
): ControlWithProps<any> {
return {
control: GridControls,
props: { targets: [gridPath] },
key: GridControlsKey(gridPath),
props: {
type: 'GRID_CONTROLS_PROPS',
targets: Array.isArray(gridPath) ? gridPath : [gridPath],
},
key: `GridControls${suffix == null ? '' : suffix}`,
show: whenToShow,
priority: 'bottom',
}
Expand Down
Loading

0 comments on commit aa29342

Please sign in to comment.