From bca6b3fbe107acad09321ba48568a4216b520a59 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Wed, 25 Jun 2025 22:12:11 +0200 Subject: [PATCH] Timed binding mode change for simple arrows --- packages/common/src/constants.ts | 2 + packages/element/src/linearElementEditor.ts | 5 +- packages/excalidraw/appState.ts | 2 + packages/excalidraw/components/App.tsx | 41 ++++++++++-- .../__snapshots__/contextmenu.test.tsx.snap | 17 +++++ .../tests/__snapshots__/history.test.tsx.snap | 63 +++++++++++++++++++ .../regressionTests.test.tsx.snap | 52 +++++++++++++++ packages/excalidraw/types.ts | 1 + .../tests/__snapshots__/export.test.ts.snap | 1 + 9 files changed, 177 insertions(+), 7 deletions(-) diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index c3c348ceb..fe21cfafb 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -514,3 +514,5 @@ export enum UserIdleState { * the start and end points) */ export const LINE_POLYGON_POINT_MERGE_DISTANCE = 20; + +export const BIND_MODE_TIMEOUT = 1000; // ms diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 539b0a9d6..cadd1f201 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -411,6 +411,7 @@ export class LinearElementEditor { event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), elements, app.state.zoom, + app.state.bindMode, ), ); } @@ -2028,6 +2029,7 @@ const pointDraggingUpdates = ( gridSize: NullableGridSize, elements: readonly Ordered[], zoom: AppState["zoom"], + bindMode: AppState["bindMode"], ): PointsPositionUpdates => { const hasMidPoints = selectedPointsIndices.filter( @@ -2103,7 +2105,8 @@ const pointDraggingUpdates = ( !( hoveredElement?.id === otherHoveredElement?.id && hoveredElement != null - ) + ) && + bindMode === "focus" ) { newGlobalPointPosition = getOutlineAvoidingPoint( element, diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index dcc3fba11..8267564de 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -124,6 +124,7 @@ export const getDefaultAppState = (): Omit< searchMatches: null, lockedMultiSelections: {}, activeLockedId: null, + bindMode: "focus", }; }; @@ -249,6 +250,7 @@ const APP_STATE_STORAGE_CONF = (< searchMatches: { browser: false, export: false, server: false }, lockedMultiSelections: { browser: true, export: true, server: true }, activeLockedId: { browser: false, export: false, server: false }, + bindMode: { browser: true, export: false, server: false }, }); const _clearAppStateForStorage = < diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 83b9edbe8..a025c585a 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -100,6 +100,7 @@ import { randomInteger, CLASSES, Emitter, + BIND_MODE_TIMEOUT, } from "@excalidraw/common"; import { @@ -570,7 +571,6 @@ class App extends React.Component { public renderer: Renderer; public visibleElements: readonly NonDeletedExcalidrawElement[]; private resizeObserver: ResizeObserver | undefined; - private nearestScrollableContainer: HTMLElement | Document | undefined; public library: AppClassProperties["library"]; public libraryItemsFromStorage: LibraryItems | undefined; public id: string; @@ -600,6 +600,8 @@ class App extends React.Component { public flowChartCreator: FlowChartCreator = new FlowChartCreator(); private flowChartNavigator: FlowChartNavigator = new FlowChartNavigator(); + private bindModeHandler: ReturnType | null = null; + hitLinkElement?: NonDeletedExcalidrawElement; lastPointerDownEvent: React.PointerEvent | null = null; lastPointerUpEvent: React.PointerEvent | PointerEvent | null = @@ -647,11 +649,6 @@ class App extends React.Component { >(); onRemoveEventListenersEmitter = new Emitter<[]>(); - // setState(s: any, t: any) { - // s.editingLinearElement && console.trace(s.editingLinearElement); - // super.setState(s, t); - // } - constructor(props: AppProps) { super(props); const defaultAppState = getDefaultAppState(); @@ -8321,6 +8318,32 @@ class App extends React.Component { return; } + // Timed bind mode handler for arrow elements + if (this.state.bindMode === "focus") { + const pointerMovementDistance = Math.hypot( + (this.lastPointerMoveCoords?.x ?? Infinity) - pointerCoords.x, + ); + if (this.bindModeHandler && pointerMovementDistance < 1) { + clearTimeout(this.bindModeHandler); + } + this.bindModeHandler = setTimeout(() => { + const hoveredElement = getHoveredElementForBinding( + pointerCoords, + this.scene.getNonDeletedElements(), + elementsMap, + this.state.zoom, + ); + + if (hoveredElement) { + this.setState({ + bindMode: "fixed", + }); + } else { + this.bindModeHandler = null; + } + }, BIND_MODE_TIMEOUT); + } + const newState = LinearElementEditor.handlePointDragging( event, this, @@ -9127,8 +9150,14 @@ class App extends React.Component { }); } + if (this.bindModeHandler) { + clearTimeout(this.bindModeHandler); + this.bindModeHandler = null; + } + this.setState({ selectedElementsAreBeingDragged: false, + bindMode: "focus", }); const elementsMap = this.scene.getNonDeletedElementsMap(); diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 5f609f1e4..33c11d971 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -11,6 +11,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ @@ -1083,6 +1084,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1296,6 +1298,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1626,6 +1629,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1956,6 +1960,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2169,6 +2174,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2409,6 +2415,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2706,6 +2713,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3077,6 +3085,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3569,6 +3578,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3891,6 +3901,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4213,6 +4224,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4623,6 +4635,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ @@ -5839,6 +5852,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ @@ -7106,6 +7120,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ @@ -7772,6 +7787,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ @@ -8762,6 +8778,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": { "items": [ diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 0ba803f81..00a69e1d2 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -11,6 +11,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -630,6 +631,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1159,6 +1161,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1523,6 +1526,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1890,6 +1894,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2152,6 +2157,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2607,6 +2613,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2868,6 +2875,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3133,6 +3141,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3426,6 +3435,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3711,6 +3721,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3945,6 +3956,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4201,6 +4213,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4471,6 +4484,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4699,6 +4713,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4927,6 +4942,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5153,6 +5169,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5379,6 +5396,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5634,6 +5652,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5895,6 +5914,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6257,6 +6277,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6630,6 +6651,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6941,6 +6963,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7247,6 +7270,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7444,6 +7468,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7795,6 +7820,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8146,6 +8172,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8551,6 +8578,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "type": "freedraw", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8837,6 +8865,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9100,6 +9129,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9364,6 +9394,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9595,6 +9626,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9891,6 +9923,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10240,6 +10273,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10464,6 +10498,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10911,6 +10946,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11170,6 +11206,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11404,6 +11441,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11640,6 +11678,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "locked": false, "type": "freedraw", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12042,6 +12081,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12251,6 +12291,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12460,6 +12501,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12683,6 +12725,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12906,6 +12949,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13150,6 +13194,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13386,6 +13431,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13622,6 +13668,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13868,6 +13915,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14198,6 +14246,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14367,6 +14416,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14650,6 +14700,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14912,6 +14963,7 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15064,6 +15116,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15345,6 +15398,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15506,6 +15560,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -16257,6 +16312,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -16905,6 +16961,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -17553,6 +17610,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -18304,6 +18362,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -19073,6 +19132,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -19552,6 +19612,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -20062,6 +20123,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -20520,6 +20582,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index d611d8dd9..a1b5b41b1 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -11,6 +11,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -436,6 +437,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -851,6 +853,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1416,6 +1419,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1622,6 +1626,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2005,6 +2010,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2249,6 +2255,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = ` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2428,6 +2435,7 @@ exports[`regression tests > can drag element that covers another element, while "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2752,6 +2760,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3006,6 +3015,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3246,6 +3256,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3481,6 +3492,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`] "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3738,6 +3750,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4051,6 +4064,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4486,6 +4500,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4768,6 +4783,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5043,6 +5059,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5250,6 +5267,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5449,6 +5467,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5841,6 +5860,7 @@ exports[`regression tests > drags selected elements from point inside common bou "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6137,6 +6157,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1` "locked": false, "type": "freedraw", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6972,6 +6993,7 @@ exports[`regression tests > given a group of selected elements with an element t "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7305,6 +7327,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7583,6 +7606,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7817,6 +7841,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8056,6 +8081,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8235,6 +8261,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8414,6 +8441,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8593,6 +8621,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8818,6 +8847,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`] "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9041,6 +9071,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState "locked": false, "type": "freedraw", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9236,6 +9267,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9461,6 +9493,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9640,6 +9673,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`] "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9863,6 +9897,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10042,6 +10077,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState "locked": false, "type": "freedraw", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10237,6 +10273,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10416,6 +10453,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10946,6 +10984,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11225,6 +11264,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = ` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11347,6 +11387,7 @@ exports[`regression tests > shift click on selected element should deselect it o "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11546,6 +11587,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11864,6 +11906,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12292,6 +12335,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12931,6 +12975,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13056,6 +13101,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`] "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13686,6 +13732,7 @@ exports[`regression tests > switches from group of selected elements to another "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14024,6 +14071,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14287,6 +14335,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`] "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14409,6 +14458,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14801,6 +14851,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes "locked": false, "type": "text", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14923,6 +14974,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = ` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 7981e7b7f..e078f4b59 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -444,6 +444,7 @@ export interface AppState { // as elements are unlocked, we remove the groupId from the elements // and also remove groupId from this map lockedMultiSelections: { [groupId: string]: true }; + bindMode: "focus" | "fixed"; } export type SearchMatch = { diff --git a/packages/utils/tests/__snapshots__/export.test.ts.snap b/packages/utils/tests/__snapshots__/export.test.ts.snap index 209ef8757..b61577f02 100644 --- a/packages/utils/tests/__snapshots__/export.test.ts.snap +++ b/packages/utils/tests/__snapshots__/export.test.ts.snap @@ -11,6 +11,7 @@ exports[`exportToSvg > with default arguments 1`] = ` "locked": false, "type": "selection", }, + "bindMode": "focus", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null,