diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx index 909950889d42..fe76a4578c6d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx @@ -299,7 +299,7 @@ describe('grid reparent strategies', () => { await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) + await dragOut(editor, EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( formatTestProjectCode( @@ -311,8 +311,8 @@ describe('grid reparent strategies', () => { width: 79, height: 86, position: 'absolute', - top: 391, - left: 492, + top: 934, + left: 1627, }} data-uid='dragme' data-testid='dragme' @@ -367,6 +367,7 @@ describe('grid reparent strategies', () => { height: 86, }} data-uid='bar' + data-testid='bar' />
{ await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2600, y: 1600 }) + const barElement = editor.renderedDOM.getByTestId('bar') + const barRect = barElement.getBoundingClientRect() + const endPoint = { + x: barRect.x - 10, + y: barRect.y + barRect.height / 2, + } + + await dragOut(editor, EP.fromString('sb/grid/dragme'), endPoint) expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( formatTestProjectCode( @@ -427,6 +435,7 @@ describe('grid reparent strategies', () => { height: 86, }} data-uid='bar' + data-testid='bar' />
{ height: 86, }} data-uid='foo' + data-testid='foo' />
{ await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2200, y: 1800 }) + const fooElement = editor.renderedDOM.getByTestId('foo') + const fooRect = fooElement.getBoundingClientRect() + const endPoint = { + x: fooRect.x + fooRect.width / 2, + y: fooRect.y + fooRect.height / 2, + } + + await dragOut(editor, EP.fromString('sb/grid/dragme'), endPoint) expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( formatTestProjectCode( @@ -526,6 +543,7 @@ describe('grid reparent strategies', () => { height: 86, }} data-uid='foo' + data-testid='foo' >
{ gridColumn: 1, }} data-uid='foo' + data-testid='foo' />
{ gridColumn: 3, }} data-uid='bar' + data-testid='bar' />
{ gridColumn: 1, }} data-uid='foo' + data-testid='foo' />
{ gridColumn: 3, }} data-uid='bar' + data-testid='bar' />
{ await selectComponentsForTest(editor, [EP.fromString('sb/grid/dragme')]) - await dragOut(editor, 'grid', EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) + await dragOut(editor, EP.fromString('sb/grid/dragme'), { x: 2000, y: 1000 }) expect(getPrintedUiJsCode(editor.getEditorState())).toEqual( formatTestProjectCode( @@ -735,8 +757,8 @@ describe('grid reparent strategies', () => { style={{ backgroundColor: '#f0f', position: 'absolute', - top: 391, - left: 492, + top: 934, + left: 1627, width: 86.5, height: 135, }} @@ -804,13 +826,9 @@ async function dragElement( async function dragOut( renderResult: EditorRenderResult, - gridTestId: string, cell: ElementPath, endPoint: Point, -) { - const grid = renderResult.renderedDOM.getByTestId(gridTestId) - const gridRect = grid.getBoundingClientRect() - +): Promise { const sourceGridCell = renderResult.renderedDOM.getByTestId(GridCellTestId(cell)) const sourceRect = sourceGridCell.getBoundingClientRect() @@ -819,10 +837,30 @@ async function dragOut( { x: sourceRect.x + 5, y: sourceRect.y + 5 }, { modifiers: cmdModifier }, ) - await mouseDragFromPointToPoint(sourceGridCell, sourceRect, endPoint, { + + await mouseDownAtPoint( + sourceGridCell, + { x: sourceRect.x + 5, y: sourceRect.y + 5 }, + { + modifiers: cmdModifier, + }, + ) + + const delta: Point = { + x: endPoint.x - sourceRect.x + 5, + y: endPoint.y - sourceRect.y + 5, + } + await mouseMoveToPoint(sourceGridCell, endPoint, { + eventOptions: { + movementX: delta.x, + movementY: delta.y, + buttons: 1, + }, + modifiers: cmdModifier, + }) + await mouseUpAtPoint(renderResult.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint, { modifiers: cmdModifier, }) - await mouseUpAtPoint(grid, gridRect, { modifiers: cmdModifier }) } async function dragOutToAnotherGrid( diff --git a/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx b/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx index 48b74f4fb8ae..8437f1734d66 100644 --- a/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx +++ b/editor/src/components/canvas/controls/select-mode/select-mode-hooks.tsx @@ -60,6 +60,7 @@ import { getAllLockedElementPaths } from '../../../../core/shared/element-lockin import { treatElementAsGroupLike } from '../../canvas-strategies/strategies/group-helpers' import { useCommentModeSelectAndHover } from '../comment-mode/comment-mode-hooks' import { useFollowModeSelectAndHover } from '../follow-mode/follow-mode-hooks' +import { handleGlobalMouseUp } from '../../../../templates/global-handlers' import { wait } from '../../../../core/model/performance-scripts' import { IS_TEST_ENVIRONMENT } from '../../../../common/env-vars' import { isFeatureEnabled } from '../../../../utils/feature-switches' @@ -667,10 +668,14 @@ function useSelectOrLiveModeSelectAndHover( (!hadInteractionSessionThatWasCancelled || !draggedOverThreshold.current) && (activeControl == null || activeControl.type === 'BOUNDING_AREA') + let editorActions: Array = [] + if (event.type === 'mousedown') { didWeHandleMouseDown.current = true } if (event.type === 'mouseup') { + const handleMouseUpActions = handleGlobalMouseUp(event.nativeEvent) + editorActions.push(...handleMouseUpActions) // Clear the interaction session tracking flag interactionSessionHappened.current = false // didWeHandleMouseDown is used to avoid selecting when closing text editing @@ -678,6 +683,7 @@ function useSelectOrLiveModeSelectAndHover( draggedOverThreshold.current = false if (!mouseUpSelectionAllowed) { + dispatch(editorActions) // We should skip this mouseup return } @@ -689,6 +695,7 @@ function useSelectOrLiveModeSelectAndHover( !active || !(isLeftClick || isRightClick) ) { + dispatch(editorActions) // Skip all of this handling if 'space' is pressed or a mousemove happened in an interaction, or the hook is not active return } @@ -709,7 +716,6 @@ function useSelectOrLiveModeSelectAndHover( const isMultiselect = event.shiftKey const isDeselect = foundTarget == null && !isMultiselect - let editorActions: Array = [] if (foundTarget != null || isDeselect) { if ( diff --git a/editor/src/templates/editor-canvas.tsx b/editor/src/templates/editor-canvas.tsx index ccfe75e5a815..6ad444843b3c 100644 --- a/editor/src/templates/editor-canvas.tsx +++ b/editor/src/templates/editor-canvas.tsx @@ -106,6 +106,7 @@ import { getCanvasViewportCenter } from './paste-helpers' import { DataPasteHandler, isPasteHandler } from '../utils/paste-handler' import { ResizeObserver } from '../components/canvas/dom-walker' import { isInsideColorPicker } from '../components/inspector/controls/color-picker-utils' +import { addMouseUpHandler, removeMouseUpHandler } from './global-handlers' const webFrame = PROBABLY_ELECTRON ? requireElectron().webFrame : null @@ -1372,14 +1373,10 @@ export class EditorCanvas extends React.Component { } } - handleWindowMouseUp = (event: any) => { - this.props.dispatch(this.handleMouseUp(event)) - } - setupWindowListeners() { window.addEventListener('mousemove', this.handleMouseMove, { capture: true }) // we use this event in the capture phase because size-box.ts calls stopPropagation() on mouseMove window.addEventListener('mouseleave', this.handleMouseLeave) - window.addEventListener('mouseup', this.handleWindowMouseUp, { capture: true }) + addMouseUpHandler(this.handleMouseUp) window.addEventListener('click', this.handleClick) window.addEventListener('dblclick', this.handleDoubleClick) ;(window as any).addEventListener('paste', this.handlePaste) @@ -1388,7 +1385,7 @@ export class EditorCanvas extends React.Component { removeEventListeners() { window.removeEventListener('mousemove', this.handleMouseMove, { capture: true }) window.removeEventListener('mouseleave', this.handleMouseLeave) - window.removeEventListener('mouseup', this.handleWindowMouseUp, { capture: true }) + removeMouseUpHandler(this.handleMouseUp) window.removeEventListener('click', this.handleClick) window.removeEventListener('dblclick', this.handleDoubleClick) ;(window as any).removeEventListener('paste', this.handlePaste) diff --git a/editor/src/templates/global-handlers.tsx b/editor/src/templates/global-handlers.tsx new file mode 100644 index 000000000000..cd0fc4443074 --- /dev/null +++ b/editor/src/templates/global-handlers.tsx @@ -0,0 +1,21 @@ +import type { EditorAction } from '../components/editor/action-types' + +export type MouseHandler = (event: MouseEvent) => Array + +let mouseUpHandlers: Array = [] + +export function addMouseUpHandler(handler: MouseHandler): void { + mouseUpHandlers.push(handler) +} + +export function removeMouseUpHandler(handler: MouseHandler): void { + mouseUpHandlers = mouseUpHandlers.filter((mouseUpHandler) => mouseUpHandler !== handler) +} + +export function handleGlobalMouseUp(event: MouseEvent): Array { + let result: Array = [] + for (const handler of mouseUpHandlers) { + result.push(...handler(event)) + } + return result +}