diff --git a/packages/element/src/Scene.ts b/packages/element/src/Scene.ts index eaef257960..4ba663ceba 100644 --- a/packages/element/src/Scene.ts +++ b/packages/element/src/Scene.ts @@ -438,6 +438,8 @@ export class Scene { options: { informMutation: boolean; isDragging: boolean; + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; } = { informMutation: true, isDragging: false, diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index bf1eeb63c7..566ef3c4e4 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -1,5 +1,4 @@ import { - KEYS, arrayToMap, getFeatureFlag, invariant, @@ -137,12 +136,6 @@ export const maxBindingDistance_simple = (zoom?: AppState["zoom"]): number => { ); }; -export const shouldEnableBindingForPointerEvent = ( - event: React.PointerEvent, -) => { - return !event[KEYS.CTRL_OR_CMD]; -}; - export const isBindingEnabled = (appState: { isBindingEnabled: AppState["isBindingEnabled"]; }): boolean => { @@ -177,8 +170,20 @@ export const bindOrUnbindBindingElement = ( }, ); - bindOrUnbindBindingElementEdge(arrow, start, "start", scene); - bindOrUnbindBindingElementEdge(arrow, end, "end", scene); + bindOrUnbindBindingElementEdge( + arrow, + start, + "start", + scene, + appState.isBindingEnabled, + ); + bindOrUnbindBindingElementEdge( + arrow, + end, + "end", + scene, + appState.isBindingEnabled, + ); if (start.focusPoint || end.focusPoint) { // If the strategy dictates a focus point override, then // update the arrow points to point to the focus point. @@ -221,12 +226,21 @@ const bindOrUnbindBindingElementEdge = ( { mode, element, focusPoint }: BindingStrategy, startOrEnd: "start" | "end", scene: Scene, + shouldSnapToOutline = true, ): void => { if (mode === null) { // null means break the binding unbindBindingElement(arrow, startOrEnd, scene); } else if (mode !== undefined) { - bindBindingElement(arrow, element, mode, startOrEnd, scene, focusPoint); + bindBindingElement( + arrow, + element, + mode, + startOrEnd, + scene, + focusPoint, + shouldSnapToOutline, + ); } }; @@ -798,6 +812,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( startDragged ? "start" : "end", elementsMap, appState.zoom, + appState.isMidpointSnappingEnabled, ) || globalPoint, } : { mode: null }; @@ -842,6 +857,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( startDragged ? "end" : "start", elementsMap, appState.zoom, + appState.isMidpointSnappingEnabled, ) || otherEndpoint, } : { mode: undefined } @@ -1005,6 +1021,7 @@ export const bindBindingElement = ( startOrEnd: "start" | "end", scene: Scene, focusPoint?: GlobalPoint, + shouldSnapToOutline = true, ): void => { const elementsMap = scene.getNonDeletedElementsMap(); @@ -1019,6 +1036,7 @@ export const bindBindingElement = ( hoveredElement, startOrEnd, elementsMap, + shouldSnapToOutline, ), }; } else { @@ -1352,6 +1370,7 @@ export const bindPointToSnapToElementOutline = ( startOrEnd: "start" | "end", elementsMap: ElementsMap, customIntersector?: LineSegment, + isMidpointSnappingEnabled = true, ): GlobalPoint => { const elbowed = isElbowArrow(arrowElement); const point = LinearElementEditor.getPointAtIndexGlobalCoordinates( @@ -1391,13 +1410,9 @@ export const bindPointToSnapToElementOutline = ( const isHorizontal = headingIsHorizontal( headingForPointFromElement(bindableElement, aabb, point), ); - const snapPoint = snapToMid( - bindableElement, - elementsMap, - edgePoint, - 0.05, - arrowElement, - ); + const snapPoint = isMidpointSnappingEnabled + ? snapToMid(bindableElement, elementsMap, edgePoint, 0.05, arrowElement) + : undefined; const resolved = snapPoint || point; const otherPoint = pointFrom( isHorizontal ? bindableCenter[0] : resolved[0], @@ -1892,6 +1907,8 @@ export const calculateFixedPointForElbowArrowBinding = ( hoveredElement: ExcalidrawBindableElement, startOrEnd: "start" | "end", elementsMap: ElementsMap, + shouldSnapToOutline = true, + isMidpointSnappingEnabled = true, ): { fixedPoint: FixedPoint } => { const bounds = [ hoveredElement.x, @@ -1899,12 +1916,20 @@ export const calculateFixedPointForElbowArrowBinding = ( hoveredElement.x + hoveredElement.width, hoveredElement.y + hoveredElement.height, ] as Bounds; - const snappedPoint = bindPointToSnapToElementOutline( - linearElement, - hoveredElement, - startOrEnd, - elementsMap, - ); + const snappedPoint = shouldSnapToOutline + ? bindPointToSnapToElementOutline( + linearElement, + hoveredElement, + startOrEnd, + elementsMap, + undefined, + isMidpointSnappingEnabled, + ) + : LinearElementEditor.getPointAtIndexGlobalCoordinates( + linearElement, + startOrEnd === "start" ? 0 : -1, + elementsMap, + ); const globalMidPoint = pointFrom( bounds[0] + (bounds[2] - bounds[0]) / 2, bounds[1] + (bounds[3] - bounds[1]) / 2, diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 63b3b7926d..9543b4182f 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -915,6 +915,8 @@ export const updateElbowArrowPoints = ( }, options?: { isDragging?: boolean; + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; }, ): ElementUpdate => { if (arrow.points.length < 2) { @@ -1202,6 +1204,8 @@ const getElbowArrowData = ( options?: { isDragging?: boolean; zoom?: AppState["zoom"]; + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; }, ) => { const origStartGlobalPoint: GlobalPoint = pointTranslate< @@ -1215,7 +1219,7 @@ const getElbowArrowData = ( let hoveredStartElement = null; let hoveredEndElement = null; - if (options?.isDragging) { + if (options?.isDragging && options?.isBindingEnabled !== false) { const elements = Array.from(elementsMap.values()); hoveredStartElement = getHoveredElement( @@ -1255,6 +1259,8 @@ const getElbowArrowData = ( hoveredStartElement, elementsMap, options?.isDragging, + options?.isBindingEnabled, + options?.isMidpointSnappingEnabled, ); const endGlobalPoint = getGlobalPoint( { @@ -1270,6 +1276,8 @@ const getElbowArrowData = ( hoveredEndElement, elementsMap, options?.isDragging, + options?.isBindingEnabled, + options?.isMidpointSnappingEnabled, ); const startHeading = getBindPointHeading( startGlobalPoint, @@ -2213,14 +2221,18 @@ const getGlobalPoint = ( element?: ExcalidrawBindableElement | null, elementsMap?: ElementsMap, isDragging?: boolean, + isBindingEnabled = true, + isMidpointSnappingEnabled = true, ): GlobalPoint => { if (isDragging) { - if (element && elementsMap) { + if (isBindingEnabled && element && elementsMap) { return bindPointToSnapToElementOutline( arrow, element, startOrEnd, elementsMap, + undefined, + isMidpointSnappingEnabled, ); } diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index e3d3f2e510..e57211abbc 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -359,11 +359,20 @@ export class LinearElementEditor { linearElementEditor, ); - LinearElementEditor.movePoints(element, app.scene, positions, { - startBinding: updates?.startBinding, - endBinding: updates?.endBinding, - moveMidPointsWithElement: updates?.moveMidPointsWithElement, - }); + LinearElementEditor.movePoints( + element, + app.scene, + positions, + { + startBinding: updates?.startBinding, + endBinding: updates?.endBinding, + moveMidPointsWithElement: updates?.moveMidPointsWithElement, + }, + { + isBindingEnabled: app.state.isBindingEnabled, + isMidpointSnappingEnabled: app.state.isMidpointSnappingEnabled, + }, + ); // Set the suggested binding from the updates if available if (isBindingElement(element, false)) { if (isBindingEnabled(app.state)) { @@ -418,6 +427,7 @@ export class LinearElementEditor { "start", elementsMap, app.state.zoom, + app.state.isMidpointSnappingEnabled, ) : linearElementEditor.initialState.altFocusPoint, }, @@ -538,11 +548,20 @@ export class LinearElementEditor { linearElementEditor, ); - LinearElementEditor.movePoints(element, app.scene, positions, { - startBinding: updates?.startBinding, - endBinding: updates?.endBinding, - moveMidPointsWithElement: updates?.moveMidPointsWithElement, - }); + LinearElementEditor.movePoints( + element, + app.scene, + positions, + { + startBinding: updates?.startBinding, + endBinding: updates?.endBinding, + moveMidPointsWithElement: updates?.moveMidPointsWithElement, + }, + { + isBindingEnabled: app.state.isBindingEnabled, + isMidpointSnappingEnabled: app.state.isMidpointSnappingEnabled, + }, + ); // Set the suggested binding from the updates if available if (isBindingElement(element, false)) { @@ -636,6 +655,7 @@ export class LinearElementEditor { "start", elementsMap, app.state.zoom, + app.state.isMidpointSnappingEnabled, ) : linearElementEditor.initialState.altFocusPoint, }, @@ -1524,6 +1544,10 @@ export class LinearElementEditor { endBinding?: FixedPointBinding | null; moveMidPointsWithElement?: boolean | null; }, + options?: { + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; + }, ) { const { points } = element; @@ -1592,6 +1616,8 @@ export class LinearElementEditor { otherUpdates, { isDragging: Array.from(pointUpdates.values()).some((t) => t.isDragging), + isBindingEnabled: options?.isBindingEnabled, + isMidpointSnappingEnabled: options?.isMidpointSnappingEnabled, }, ); } @@ -1706,6 +1732,8 @@ export class LinearElementEditor { isDragging?: boolean; zoom?: AppState["zoom"]; sceneElementsMap?: NonDeletedSceneElementsMap; + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; }, ) { if (isElbowArrow(element)) { @@ -1726,6 +1754,8 @@ export class LinearElementEditor { scene.mutateElement(element, updates, { informMutation: true, isDragging: options?.isDragging ?? false, + isBindingEnabled: options?.isBindingEnabled, + isMidpointSnappingEnabled: options?.isMidpointSnappingEnabled, }); } else { // TODO do we need to get precise coords here just to calc centers? @@ -2145,14 +2175,16 @@ const pointDraggingUpdates = ( suggestedBinding: suggestedBindingElement ? { element: suggestedBindingElement, - midPoint: snapToMid( - suggestedBindingElement, - elementsMap, - pointFrom( - scenePointerX - linearElementEditor.pointerOffset.x, - scenePointerY - linearElementEditor.pointerOffset.y, - ), - ), + midPoint: app.state.isMidpointSnappingEnabled + ? snapToMid( + suggestedBindingElement, + elementsMap, + pointFrom( + scenePointerX - linearElementEditor.pointerOffset.x, + scenePointerY - linearElementEditor.pointerOffset.y, + ), + ) + : undefined, } : null, }, diff --git a/packages/element/src/mutateElement.ts b/packages/element/src/mutateElement.ts index c45c6df08c..eb6350c2bf 100644 --- a/packages/element/src/mutateElement.ts +++ b/packages/element/src/mutateElement.ts @@ -40,6 +40,8 @@ export const mutateElement = >( updates: ElementUpdate, options?: { isDragging?: boolean; + isBindingEnabled?: boolean; + isMidpointSnappingEnabled?: boolean; }, ) => { let didChange = false; diff --git a/packages/element/src/utils.ts b/packages/element/src/utils.ts index 7013acf2f1..96e09bcbf1 100644 --- a/packages/element/src/utils.ts +++ b/packages/element/src/utils.ts @@ -659,20 +659,23 @@ export const projectFixedPointOntoDiagonal = ( startOrEnd: "start" | "end", elementsMap: ElementsMap, zoom: AppState["zoom"], + isMidpointSnappingEnabled: boolean = true, ): GlobalPoint | null => { invariant(arrow.points.length >= 2, "Arrow must have at least two points"); if (arrow.width < 3 && arrow.height < 3) { return null; } - const sideMidPoint = getSnapOutlineMidPoint( - point, - element, - elementsMap, - zoom, - ); - if (sideMidPoint) { - return sideMidPoint; + if (isMidpointSnappingEnabled) { + const sideMidPoint = getSnapOutlineMidPoint( + point, + element, + elementsMap, + zoom, + ); + if (sideMidPoint) { + return sideMidPoint; + } } // Do the projection onto the diagonals (or center lines diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 7fb9ed0e86..93fe0ada1a 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -1830,6 +1830,7 @@ export const actionChangeArrowType = register({ startElement, "start", elementsMap, + appState.isBindingEnabled, ), } : null; @@ -1843,6 +1844,7 @@ export const actionChangeArrowType = register({ endElement, "end", elementsMap, + appState.isBindingEnabled, ), } : null; diff --git a/packages/excalidraw/actions/actionToggleArrowBinding.tsx b/packages/excalidraw/actions/actionToggleArrowBinding.tsx new file mode 100644 index 0000000000..a4e6e50ed2 --- /dev/null +++ b/packages/excalidraw/actions/actionToggleArrowBinding.tsx @@ -0,0 +1,26 @@ +import { CaptureUpdateAction } from "@excalidraw/element"; + +import { register } from "./register"; + +export const actionToggleArrowBinding = register({ + name: "arrowBinding", + label: "labels.arrowBinding", + viewMode: false, + trackEvent: { + category: "canvas", + predicate: (appState) => appState.bindingPreference === "disabled", + }, + perform(elements, appState) { + const newPreference = + appState.bindingPreference === "enabled" ? "disabled" : "enabled"; + return { + appState: { + ...appState, + bindingPreference: newPreference, + isBindingEnabled: newPreference === "enabled", + }, + captureUpdate: CaptureUpdateAction.NEVER, + }; + }, + checked: (appState) => appState.bindingPreference === "enabled", +}); diff --git a/packages/excalidraw/actions/actionToggleMidpointSnapping.tsx b/packages/excalidraw/actions/actionToggleMidpointSnapping.tsx new file mode 100644 index 0000000000..eca9df7e27 --- /dev/null +++ b/packages/excalidraw/actions/actionToggleMidpointSnapping.tsx @@ -0,0 +1,23 @@ +import { CaptureUpdateAction } from "@excalidraw/element"; + +import { register } from "./register"; + +export const actionToggleMidpointSnapping = register({ + name: "midpointSnapping", + label: "labels.midpointSnapping", + viewMode: false, + trackEvent: { + category: "canvas", + predicate: (appState) => !appState.isMidpointSnappingEnabled, + }, + perform(elements, appState) { + return { + appState: { + ...appState, + isMidpointSnappingEnabled: !this.checked!(appState), + }, + captureUpdate: CaptureUpdateAction.NEVER, + }; + }, + checked: (appState) => appState.isMidpointSnappingEnabled, +}); diff --git a/packages/excalidraw/actions/index.ts b/packages/excalidraw/actions/index.ts index 6b888e92d3..cc9ca1789c 100644 --- a/packages/excalidraw/actions/index.ts +++ b/packages/excalidraw/actions/index.ts @@ -79,6 +79,8 @@ export { export { actionToggleGridMode } from "./actionToggleGridMode"; export { actionToggleZenMode } from "./actionToggleZenMode"; export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode"; +export { actionToggleArrowBinding } from "./actionToggleArrowBinding"; +export { actionToggleMidpointSnapping } from "./actionToggleMidpointSnapping"; export { actionToggleStats } from "./actionToggleStats"; export { actionUnbindText, actionBindText } from "./actionBoundText"; diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts index c85b0639ef..ae80e4107c 100644 --- a/packages/excalidraw/actions/types.ts +++ b/packages/excalidraw/actions/types.ts @@ -59,6 +59,8 @@ export type ActionName = | "gridMode" | "zenMode" | "objectsSnapMode" + | "arrowBinding" + | "midpointSnapping" | "stats" | "changeStrokeColor" | "changeBackgroundColor" diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index 18e303db29..e51865b2ea 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -70,6 +70,8 @@ export const getDefaultAppState = (): Omit< gridStep: DEFAULT_GRID_STEP, gridModeEnabled: false, isBindingEnabled: true, + bindingPreference: "enabled", + isMidpointSnappingEnabled: true, defaultSidebarDockedPreference: false, isLoading: false, isResizing: false, @@ -190,7 +192,9 @@ const APP_STATE_STORAGE_CONF = (< gridStep: { browser: true, export: true, server: true }, gridModeEnabled: { browser: true, export: true, server: true }, height: { browser: false, export: false, server: false }, - isBindingEnabled: { browser: false, export: false, server: false }, + isBindingEnabled: { browser: true, export: false, server: false }, + bindingPreference: { browser: true, export: false, server: false }, + isMidpointSnappingEnabled: { browser: true, export: false, server: false }, defaultSidebarDockedPreference: { browser: true, export: false, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index adbcce44e5..0b361e0e70 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -119,7 +119,6 @@ import { fixBindingsAfterDeletion, getHoveredElementForBinding, isBindingEnabled, - shouldEnableBindingForPointerEvent, updateBoundElements, LinearElementEditor, newElementWith, @@ -319,6 +318,8 @@ import { actionToggleElementLock, actionToggleLinearEditor, actionToggleObjectsSnapMode, + actionToggleArrowBinding, + actionToggleMidpointSnapping, actionToggleCropEditor, } from "../actions"; import { actionWrapTextInContainer } from "../actions/actionBoundText"; @@ -2734,7 +2735,9 @@ class App extends React.Component { private onBlur = withBatchedUpdates(() => { isHoldingSpace = false; - this.setState({ isBindingEnabled: true }); + this.setState({ + isBindingEnabled: this.state.bindingPreference === "enabled", + }); }); private onUnload = () => { @@ -4937,13 +4940,15 @@ class App extends React.Component { return; } - if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) { + if (event[KEYS.CTRL_OR_CMD] && !event.repeat) { if (getFeatureFlag("COMPLEX_BINDINGS")) { this.resetDelayedBindMode(); } flushSync(() => { - this.setState({ isBindingEnabled: false }); + this.setState({ + isBindingEnabled: this.state.bindingPreference !== "enabled", + }); }); maybeHandleArrowPointlikeDrag({ app: this, event }); @@ -5217,10 +5222,13 @@ class App extends React.Component { } } } - if (!event[KEYS.CTRL_OR_CMD] && !this.state.isBindingEnabled) { - flushSync(() => { - this.setState({ isBindingEnabled: true }); - }); + if (!event[KEYS.CTRL_OR_CMD]) { + const preferenceEnabled = this.state.bindingPreference === "enabled"; + if (this.state.isBindingEnabled !== preferenceEnabled) { + flushSync(() => { + this.setState({ isBindingEnabled: preferenceEnabled }); + }); + } maybeHandleArrowPointlikeDrag({ app: this, event }); } @@ -7138,6 +7146,14 @@ class App extends React.Component { private handleCanvasPointerDown = ( event: React.PointerEvent, ) => { + // If Ctrl is not held, ensure isBindingEnabled reflects the user preference. + if (!event.ctrlKey) { + const preferenceEnabled = this.state.bindingPreference === "enabled"; + if (this.state.isBindingEnabled !== preferenceEnabled) { + this.setState({ isBindingEnabled: preferenceEnabled }); + } + } + const scenePointer = viewportCoordsToSceneCoords(event, this.state); const { x: scenePointerX, y: scenePointerY } = scenePointer; this.lastPointerMoveCoords = { @@ -7358,7 +7374,6 @@ class App extends React.Component { } this.clearSelectionIfNotUsingSelection(); - this.updateBindingEnabledOnPointerMove(event); if (this.handleSelectionOnPointerDown(event, pointerDownState)) { return; @@ -7581,6 +7596,13 @@ class App extends React.Component { this.removePointer(event); this.lastPointerUpEvent = event; + if (!event.ctrlKey) { + const preferenceEnabled = this.state.bindingPreference === "enabled"; + if (this.state.isBindingEnabled !== preferenceEnabled) { + this.setState({ isBindingEnabled: preferenceEnabled }); + } + } + const scenePointer = viewportCoordsToSceneCoords( { clientX: event.clientX, clientY: event.clientY }, this.state, @@ -8636,7 +8658,9 @@ class App extends React.Component { ): void => { if (event.ctrlKey) { flushSync(() => { - this.setState({ isBindingEnabled: false }); + this.setState({ + isBindingEnabled: this.state.bindingPreference !== "enabled", + }); }); } @@ -11330,15 +11354,6 @@ class App extends React.Component { this.addNewImagesToImageCache(); }, IMAGE_RENDER_TIMEOUT); - private updateBindingEnabledOnPointerMove = ( - event: React.PointerEvent, - ) => { - const shouldEnableBinding = shouldEnableBindingForPointerEvent(event); - if (this.state.isBindingEnabled !== shouldEnableBinding) { - this.setState({ isBindingEnabled: shouldEnableBinding }); - } - }; - private clearSelection(hitElement: ExcalidrawElement | null): void { this.setState((prevState) => ({ selectedElementIds: makeNextSelectedElementIds({}, prevState), @@ -12100,6 +12115,8 @@ class App extends React.Component { CONTEXT_MENU_SEPARATOR, actionToggleGridMode, actionToggleObjectsSnapMode, + actionToggleArrowBinding, + actionToggleMidpointSnapping, actionToggleZenMode, actionToggleViewMode, actionToggleStats, diff --git a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx index 15ecb61eaf..c6245953b9 100644 --- a/packages/excalidraw/components/canvases/InteractiveCanvas.tsx +++ b/packages/excalidraw/components/canvases/InteractiveCanvas.tsx @@ -249,6 +249,7 @@ const getRelevantAppStateProps = ( multiElement: appState.multiElement, newElement: appState.newElement, isBindingEnabled: appState.isBindingEnabled, + isMidpointSnappingEnabled: appState.isMidpointSnappingEnabled, suggestedBinding: appState.suggestedBinding, isRotating: appState.isRotating, elementsToHighlight: appState.elementsToHighlight, diff --git a/packages/excalidraw/components/main-menu/DefaultItems.tsx b/packages/excalidraw/components/main-menu/DefaultItems.tsx index 04cea3da48..8865dfa7ae 100644 --- a/packages/excalidraw/components/main-menu/DefaultItems.tsx +++ b/packages/excalidraw/components/main-menu/DefaultItems.tsx @@ -9,7 +9,9 @@ import { actionLoadScene, actionSaveToActiveFile, actionShortcuts, + actionToggleArrowBinding, actionToggleGridMode, + actionToggleMidpointSnapping, actionToggleObjectsSnapMode, actionToggleSearchMenu, actionToggleStats, @@ -443,6 +445,40 @@ const PreferencesToggleSnapModeItem = () => { ); }; +const PreferencesToggleArrowBindingItem = () => { + const { t } = useI18n(); + const actionManager = useExcalidrawActionManager(); + const appState = useUIAppState(); + return ( + { + actionManager.executeAction(actionToggleArrowBinding); + event.preventDefault(); + }} + > + {t("labels.arrowBinding")} + + ); +}; + +const PreferencesToggleMidpointSnappingItem = () => { + const { t } = useI18n(); + const actionManager = useExcalidrawActionManager(); + const appState = useUIAppState(); + return ( + { + actionManager.executeAction(actionToggleMidpointSnapping); + event.preventDefault(); + }} + > + {t("labels.midpointSnapping")} + + ); +}; + export const PreferencesToggleGridModeItem = () => { const { t } = useI18n(); const actionManager = useExcalidrawActionManager(); @@ -538,6 +574,8 @@ export const Preferences = ({ + + )} {additionalItems} @@ -548,6 +586,8 @@ export const Preferences = ({ Preferences.ToggleToolLock = PreferencesToggleToolLockItem; Preferences.ToggleSnapMode = PreferencesToggleSnapModeItem; +Preferences.ToggleArrowBinding = PreferencesToggleArrowBindingItem; +Preferences.ToggleMidpointSnapping = PreferencesToggleMidpointSnappingItem; Preferences.ToggleGridMode = PreferencesToggleGridModeItem; Preferences.ToggleZenMode = PreferencesToggleZenModeItem; Preferences.ToggleViewMode = PreferencesToggleViewModeItem; diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index 18e8c88908..7a2a7294f8 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -177,7 +177,9 @@ "tab": "Tab", "shapeSwitch": "Switch shape", "preferences": "Preferences", - "preferences_toolLock": "Tool lock" + "preferences_toolLock": "Tool lock", + "arrowBinding": "Arrow binding", + "midpointSnapping": "Snap to midpoints" }, "elementLink": { "title": "Link to object", diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index fc9331e6ea..56c308713e 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -407,8 +407,9 @@ const renderBindingHighlightForBindableElement_simple = ( } if ( - isFrameLikeElement(suggestedBinding.element) || - isBindableElement(suggestedBinding.element) + appState.isMidpointSnappingEnabled && + (isFrameLikeElement(suggestedBinding.element) || + isBindableElement(suggestedBinding.element)) ) { // Draw midpoint indicators const linearElement = appState.selectedLinearElement; @@ -799,77 +800,79 @@ const renderBindingHighlightForBindableElement_complex = ( context.restore(); - // Draw midpoint indicators - context.save(); - context.translate( - element.x + appState.scrollX, - element.y + appState.scrollY, - ); - - const midpointRadius = 5 / appState.zoom.value; - const cutoutPadding = 5 / appState.zoom.value; - const cutoutRadius = midpointRadius + cutoutPadding; - - let midpoints; - if (element.type === "diamond") { - const [, curves] = deconstructDiamondElement(element); - const center = elementCenterPoint(element, allElementsMap); - - midpoints = curves.map((curve) => { - const point = bezierEquation(curve, 0.5); - const rotatedPoint = pointRotateRads(point, center, element.angle); - return { - x: rotatedPoint[0] - element.x, - y: rotatedPoint[1] - element.y, - }; - }); - } else { - const center = elementCenterPoint(element, allElementsMap); - const basePoints = [ - { x: element.width / 2, y: 0 }, // TOP - { x: element.width, y: element.height / 2 }, // RIGHT - { x: element.width / 2, y: element.height }, // BOTTOM - { x: 0, y: element.height / 2 }, // LEFT - ]; - midpoints = basePoints.map((point) => { - const globalPoint = pointFrom( - point.x + element.x, - point.y + element.y, - ); - const rotatedPoint = pointRotateRads( - globalPoint, - center, - element.angle, - ); - return { - x: rotatedPoint[0] - element.x, - y: rotatedPoint[1] - element.y, - }; - }); - } - - // Clear cutouts around midpoints - midpoints.forEach((midpoint) => { - context.clearRect( - midpoint.x - cutoutRadius, - midpoint.y - cutoutRadius, - cutoutRadius * 2, - cutoutRadius * 2, + if (appState.isMidpointSnappingEnabled) { + // Draw midpoint indicators + context.save(); + context.translate( + element.x + appState.scrollX, + element.y + appState.scrollY, ); - }); - context.fillStyle = - appState.theme === THEME.DARK - ? `rgba(3, 93, 161, ${opacity})` - : `rgba(106, 189, 252, ${opacity})`; + const midpointRadius = 5 / appState.zoom.value; + const cutoutPadding = 5 / appState.zoom.value; + const cutoutRadius = midpointRadius + cutoutPadding; - midpoints.forEach((midpoint) => { - context.beginPath(); - context.arc(midpoint.x, midpoint.y, midpointRadius, 0, 2 * Math.PI); - context.fill(); - }); + let midpoints; + if (element.type === "diamond") { + const [, curves] = deconstructDiamondElement(element); + const center = elementCenterPoint(element, allElementsMap); - context.restore(); + midpoints = curves.map((curve) => { + const point = bezierEquation(curve, 0.5); + const rotatedPoint = pointRotateRads(point, center, element.angle); + return { + x: rotatedPoint[0] - element.x, + y: rotatedPoint[1] - element.y, + }; + }); + } else { + const center = elementCenterPoint(element, allElementsMap); + const basePoints = [ + { x: element.width / 2, y: 0 }, // TOP + { x: element.width, y: element.height / 2 }, // RIGHT + { x: element.width / 2, y: element.height }, // BOTTOM + { x: 0, y: element.height / 2 }, // LEFT + ]; + midpoints = basePoints.map((point) => { + const globalPoint = pointFrom( + point.x + element.x, + point.y + element.y, + ); + const rotatedPoint = pointRotateRads( + globalPoint, + center, + element.angle, + ); + return { + x: rotatedPoint[0] - element.x, + y: rotatedPoint[1] - element.y, + }; + }); + } + + // Clear cutouts around midpoints + midpoints.forEach((midpoint) => { + context.clearRect( + midpoint.x - cutoutRadius, + midpoint.y - cutoutRadius, + cutoutRadius * 2, + cutoutRadius * 2, + ); + }); + + context.fillStyle = + appState.theme === THEME.DARK + ? `rgba(3, 93, 161, ${opacity})` + : `rgba(106, 189, 252, ${opacity})`; + + midpoints.forEach((midpoint) => { + context.beginPath(); + context.arc(midpoint.x, midpoint.y, midpointRadius, 0, 2 * Math.PI); + context.fill(); + }); + + context.restore(); + } } return { diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 38f748b817..15458fa366 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -12,6 +12,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -932,6 +933,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1083,6 +1085,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1129,6 +1132,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1295,6 +1299,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1341,6 +1346,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1624,6 +1630,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1670,6 +1677,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1953,6 +1961,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1999,6 +2008,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2165,6 +2175,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2211,6 +2222,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2404,6 +2416,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2450,6 +2463,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2700,6 +2714,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2746,6 +2761,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3070,6 +3086,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3116,6 +3133,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3561,6 +3579,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3607,6 +3626,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3882,6 +3902,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3928,6 +3949,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4203,6 +4225,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4249,6 +4272,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4612,6 +4636,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -5532,6 +5557,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5827,6 +5853,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -6747,6 +6774,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7093,6 +7121,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -7468,6 +7497,28 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app }, "viewMode": false, }, + { + "checked": [Function], + "label": "labels.arrowBinding", + "name": "arrowBinding", + "perform": [Function], + "trackEvent": { + "category": "canvas", + "predicate": [Function], + }, + "viewMode": false, + }, + { + "checked": [Function], + "label": "labels.midpointSnapping", + "name": "midpointSnapping", + "perform": [Function], + "trackEvent": { + "category": "canvas", + "predicate": [Function], + }, + "viewMode": false, + }, { "checked": [Function], "icon": shows context menu for canvas > [end of test] app "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7758,6 +7810,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -8678,6 +8731,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8747,6 +8801,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": { "items": [ @@ -9667,6 +9722,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 97204839fc..c81459c1c4 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -12,6 +12,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -58,6 +59,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -641,6 +643,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -687,6 +690,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1200,6 +1204,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1246,6 +1251,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1558,6 +1564,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1604,6 +1611,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1918,6 +1926,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1964,6 +1973,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2179,6 +2189,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2225,6 +2236,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2630,6 +2642,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2676,6 +2689,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2931,6 +2945,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2977,6 +2992,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3248,6 +3264,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3294,6 +3311,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3540,6 +3558,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3586,6 +3605,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3824,6 +3844,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3870,6 +3891,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4057,6 +4079,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4103,6 +4126,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4312,6 +4336,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4358,6 +4383,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4581,6 +4607,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4627,6 +4654,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4808,6 +4836,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4854,6 +4883,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5035,6 +5065,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5081,6 +5112,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5280,6 +5312,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5326,6 +5359,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5534,6 +5568,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5580,6 +5615,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5790,6 +5826,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5836,6 +5873,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -6117,6 +6155,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6163,6 +6202,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -6542,6 +6582,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6588,6 +6629,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -6914,6 +6956,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6960,6 +7003,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7224,6 +7268,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7270,6 +7315,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7514,6 +7560,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7560,6 +7607,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7742,6 +7790,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7788,6 +7837,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8092,6 +8142,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8138,6 +8189,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8442,6 +8494,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8488,6 +8541,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8846,6 +8900,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "type": "freedraw", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8892,6 +8947,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9123,6 +9179,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9169,6 +9226,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9385,6 +9443,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9431,6 +9490,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9648,6 +9708,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9694,6 +9755,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9878,6 +9940,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9924,6 +9987,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10173,6 +10237,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10219,6 +10284,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10488,6 +10554,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10534,6 +10601,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10722,6 +10790,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10768,6 +10837,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11162,6 +11232,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11208,6 +11279,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11420,6 +11492,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11466,6 +11539,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11653,6 +11727,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11699,6 +11774,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11888,6 +11964,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "type": "freedraw", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11934,6 +12011,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12277,6 +12355,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12323,6 +12402,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12485,6 +12565,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12531,6 +12612,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12690,6 +12772,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12736,6 +12819,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12989,6 +13073,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13035,6 +13120,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13285,6 +13371,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13331,6 +13418,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13528,6 +13616,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13574,6 +13663,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13763,6 +13853,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13809,6 +13900,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13998,6 +14090,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14044,6 +14137,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14243,6 +14337,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14289,6 +14384,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14572,6 +14668,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14618,6 +14715,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14740,6 +14838,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14786,6 +14885,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -15022,6 +15122,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15068,6 +15169,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -15283,6 +15385,7 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15329,6 +15432,7 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -15434,6 +15538,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15480,6 +15585,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -15714,6 +15820,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15760,6 +15867,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -15874,6 +15982,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -15920,6 +16029,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -16620,6 +16730,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -16666,6 +16777,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -17264,6 +17376,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -17310,6 +17423,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -17908,6 +18022,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -17954,6 +18069,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -18655,6 +18771,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -18701,6 +18818,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -19421,6 +19539,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -19467,6 +19586,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -19899,6 +20019,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -19945,6 +20066,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -20408,6 +20530,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -20454,6 +20577,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -20865,6 +20989,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -20911,6 +21036,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index c7dd74b91f..e97991d96f 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -12,6 +12,7 @@ exports[`given element A and group of elements B and given both are selected whe "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -58,6 +59,7 @@ exports[`given element A and group of elements B and given both are selected whe "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -436,6 +438,7 @@ exports[`given element A and group of elements B and given both are selected whe "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -482,6 +485,7 @@ exports[`given element A and group of elements B and given both are selected whe "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -850,6 +854,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -893,9 +898,10 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "gridStep": 5, "height": 768, "hoveredElementIds": {}, - "isBindingEnabled": false, + "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1414,6 +1420,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1460,6 +1467,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -1619,6 +1627,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -1665,6 +1674,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2001,6 +2011,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2047,6 +2058,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2244,6 +2256,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = ` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2290,6 +2303,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = ` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2422,6 +2436,7 @@ exports[`regression tests > can drag element that covers another element, while "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2468,6 +2483,7 @@ exports[`regression tests > can drag element that covers another element, while "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2745,6 +2761,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -2791,6 +2808,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -2998,6 +3016,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3044,6 +3063,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3237,6 +3257,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3283,6 +3304,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3471,6 +3493,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`] "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3517,6 +3540,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`] "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -3727,6 +3751,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -3773,6 +3798,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4039,6 +4065,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4085,6 +4112,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4473,6 +4501,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4519,6 +4548,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -4754,6 +4784,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -4800,6 +4831,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5028,6 +5060,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5074,6 +5107,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5234,6 +5268,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5280,6 +5315,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5432,6 +5468,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5478,6 +5515,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -5823,6 +5861,7 @@ exports[`regression tests > drags selected elements from point inside common bou "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -5869,6 +5908,7 @@ exports[`regression tests > drags selected elements from point inside common bou "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -6118,6 +6158,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1` "type": "freedraw", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6164,6 +6205,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -6904,6 +6946,7 @@ exports[`regression tests > given a group of selected elements with an element t "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -6950,6 +6993,7 @@ exports[`regression tests > given a group of selected elements with an element t "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7236,6 +7280,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7282,6 +7327,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7513,6 +7559,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7559,6 +7606,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7746,6 +7794,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -7792,6 +7841,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -7984,6 +8034,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8030,6 +8081,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8162,6 +8214,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8208,6 +8261,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8340,6 +8394,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8386,6 +8441,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8518,6 +8574,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8564,6 +8621,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8748,6 +8806,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`] "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -8794,6 +8853,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`] "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -8976,6 +9036,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState "type": "freedraw", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9022,6 +9083,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9166,6 +9228,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9212,6 +9275,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9396,6 +9460,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9442,6 +9507,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9574,6 +9640,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`] "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9620,6 +9687,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`] "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9802,6 +9870,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -9848,6 +9917,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -9980,6 +10050,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState "type": "freedraw", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10026,6 +10097,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10170,6 +10242,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10216,6 +10289,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10348,6 +10422,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10394,6 +10469,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -10877,6 +10953,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -10923,6 +11000,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11155,6 +11233,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = ` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11201,6 +11280,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = ` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "touch", @@ -11276,6 +11356,7 @@ exports[`regression tests > shift click on selected element should deselect it o "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11322,6 +11403,7 @@ exports[`regression tests > shift click on selected element should deselect it o "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11474,6 +11556,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11520,6 +11603,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -11791,6 +11875,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -11837,6 +11922,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12218,6 +12304,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12264,6 +12351,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12856,6 +12944,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -12902,6 +12991,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -12980,6 +13070,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`] "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13026,6 +13117,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`] "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13609,6 +13701,7 @@ exports[`regression tests > switches from group of selected elements to another "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13655,6 +13748,7 @@ exports[`regression tests > switches from group of selected elements to another "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -13946,6 +14040,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -13992,6 +14087,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14208,6 +14304,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`] "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14254,6 +14351,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`] "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "touch", @@ -14329,6 +14427,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14375,6 +14474,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14691,6 +14791,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes "type": "text", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14737,6 +14838,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", @@ -14812,6 +14914,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = ` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -14858,6 +14961,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = ` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse", diff --git a/packages/excalidraw/tests/arrowBinding.test.tsx b/packages/excalidraw/tests/arrowBinding.test.tsx new file mode 100644 index 0000000000..879b4b67eb --- /dev/null +++ b/packages/excalidraw/tests/arrowBinding.test.tsx @@ -0,0 +1,526 @@ +import { reseed } from "@excalidraw/common"; +import { + isElbowArrow, + projectFixedPointOntoDiagonal, +} from "@excalidraw/element"; + +import { pointFrom } from "@excalidraw/math"; + +import type { GlobalPoint, LocalPoint } from "@excalidraw/math"; + +import type { + ExcalidrawArrowElement, + ExcalidrawBindableElement, + ExcalidrawElement, +} from "@excalidraw/element/types"; + +import { actionToggleArrowBinding } from "../actions/actionToggleArrowBinding"; +import { Excalidraw, sceneCoordsToViewportCoords } from "../index"; + +import { API } from "./helpers/api"; +import { Pointer, UI } from "./helpers/ui"; +import { + render, + fireEvent, + mockBoundingClientRect, + restoreOriginalGetBoundingClientRect, + waitFor, + unmountComponent, +} from "./test-utils"; + +unmountComponent(); + +const { h } = window; +const mouse = new Pointer("mouse"); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Fire Ctrl (or Meta on Mac) keydown on document. */ +const ctrlKeyDown = (extra: Partial = {}) => + fireEvent.keyDown(document, { + key: "Control", + code: "ControlLeft", + ctrlKey: true, + repeat: false, + ...extra, + }); + +/** Fire Ctrl (or Meta on Mac) keyup on document (ctrlKey is false on keyup). */ +const ctrlKeyUp = () => + fireEvent.keyUp(document, { + key: "Control", + code: "ControlLeft", + ctrlKey: false, + }); + +// --------------------------------------------------------------------------- + +describe("Arrow binding – non-default case (bindingPreference: disabled)", () => { + beforeAll(() => { + mockBoundingClientRect(); + }); + + afterAll(() => { + restoreOriginalGetBoundingClientRect(); + }); + + beforeEach(async () => { + localStorage.clear(); + reseed(7); + await render(); + h.state.width = 1920; + h.state.height = 1080; + }); + + afterEach(() => { + mouse.reset(); + }); + + // ------------------------------------------------------------------------- + // actionToggleArrowBinding + // ------------------------------------------------------------------------- + + describe("actionToggleArrowBinding", () => { + it("isBindingEnabled defaults to true", () => { + expect(h.state.isBindingEnabled).toBe(true); + }); + + it("checked() reflects the current bindingPreference value", () => { + expect(actionToggleArrowBinding.checked!(h.state)).toBe(true); + + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + expect(actionToggleArrowBinding.checked!(h.state)).toBe(false); + }); + + it("executing the action toggles binding from enabled → disabled", () => { + expect(h.state.isBindingEnabled).toBe(true); + API.executeAction(actionToggleArrowBinding); + expect(h.state.isBindingEnabled).toBe(false); + expect(h.state.bindingPreference).toBe("disabled"); + }); + + it("executing the action toggles binding from disabled → enabled", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + API.executeAction(actionToggleArrowBinding); + expect(h.state.isBindingEnabled).toBe(true); + expect(h.state.bindingPreference).toBe("enabled"); + }); + + it("checked() returns false after action disables binding", () => { + API.executeAction(actionToggleArrowBinding); // true → false + expect(actionToggleArrowBinding.checked!(h.state)).toBe(false); + }); + }); + + // ------------------------------------------------------------------------- + // Arrow does not bind when isBindingEnabled is false + // ------------------------------------------------------------------------- + + describe("Arrow drawing with binding disabled", () => { + /** + * Baseline: verify binding IS created with binding enabled so we know the + * spatial setup is correct. + */ + it("arrow startBinding is set when binding is enabled", async () => { + API.setElements([ + API.createElement({ + type: "rectangle", + id: "baselineRect", + x: 100, + y: 100, + width: 400, + height: 200, + }), + ]); + + expect(h.state.isBindingEnabled).toBe(true); + + UI.clickTool("arrow"); + // Start inside the rectangle so startBinding can be created + mouse.down(200, 200); + mouse.up(700, 200); + + await waitFor(() => { + const arrow = h.elements.find( + (el): el is ExcalidrawArrowElement => el.type === "arrow", + ); + expect(arrow).toBeDefined(); + expect(arrow!.startBinding).not.toBeNull(); + expect(arrow!.startBinding!.elementId).toBe("baselineRect"); + }); + }); + + it("arrow has no startBinding when binding is disabled", async () => { + API.setElements([ + API.createElement({ + type: "rectangle", + id: "rect1", + x: 100, + y: 100, + width: 400, + height: 200, + }), + ]); + + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + UI.clickTool("arrow"); + // Start inside the rectangle – binding is off, so no startBinding + mouse.down(200, 200); + mouse.up(700, 200); + + await waitFor(() => { + const arrow = h.elements.find( + (el): el is ExcalidrawArrowElement => el.type === "arrow", + ); + expect(arrow).toBeDefined(); + expect(arrow!.startBinding).toBeNull(); + }); + }); + + it("arrow has no endBinding when binding is disabled", async () => { + API.setElements([ + API.createElement({ + type: "rectangle", + id: "rect2", + x: 500, + y: 100, + width: 300, + height: 200, + }), + ]); + + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + UI.clickTool("arrow"); + // End inside the target rectangle – binding off -> no endBinding + mouse.down(100, 200); + mouse.up(600, 200); + + await waitFor(() => { + const arrow = h.elements.find( + (el): el is ExcalidrawArrowElement => el.type === "arrow", + ); + expect(arrow).toBeDefined(); + expect(arrow!.endBinding).toBeNull(); + }); + }); + + it("re-enabling binding via action causes new arrows to bind again", async () => { + API.setElements([ + API.createElement({ + type: "rectangle", + id: "rect3", + x: 100, + y: 100, + width: 400, + height: 200, + }), + ]); + + // Disable then re-enable binding + API.executeAction(actionToggleArrowBinding); // true -> false + API.executeAction(actionToggleArrowBinding); // false -> true + expect(h.state.isBindingEnabled).toBe(true); + + UI.clickTool("arrow"); + mouse.down(200, 200); + mouse.up(700, 200); + + await waitFor(() => { + const arrow = h.elements.find( + (el): el is ExcalidrawArrowElement => el.type === "arrow", + ); + expect(arrow).toBeDefined(); + expect(arrow!.startBinding).not.toBeNull(); + }); + }); + + it("elbow arrow does not snap to rectangle when binding is disabled", async () => { + const rect = API.createElement({ + type: "rectangle", + id: "rect-elbow-no-snap", + x: 900, + y: 500, + width: 200, + height: 120, + }) as ExcalidrawBindableElement; + API.setElements([rect]); + + // Turn off arrow binding + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + isMidpointSnappingEnabled: false, + }); + expect(h.state.isBindingEnabled).toBe(false); + + // Create the elbow arrow + UI.clickTool("arrow"); + API.setAppState({ currentItemArrowType: "elbow" }); + mouse.downAt(700, 400); + mouse.upAt(760, 460); + + let arrow: ExcalidrawArrowElement; + await waitFor(() => { + const maybeArrow = h.elements.find(isElbowArrow); + expect(maybeArrow).toBeDefined(); + arrow = maybeArrow!; + }); + + // Move the elbow arrow + UI.clickTool("selection"); + + const insideRectanglePoint = pointFrom(1010, 570); + const originalArrowEndGlobal = pointFrom( + arrow!.x + arrow!.points[arrow!.points.length - 1][0], + arrow!.y + arrow!.points[arrow!.points.length - 1][1], + ); + const originalArrowEndViewport = sceneCoordsToViewportCoords( + { + sceneX: originalArrowEndGlobal[0], + sceneY: originalArrowEndGlobal[1], + }, + h.state, + ); + const insideRectangleViewport = sceneCoordsToViewportCoords( + { + sceneX: insideRectanglePoint[0], + sceneY: insideRectanglePoint[1], + }, + h.state, + ); + + // End point dragged inside the rectangle; with snapping enabled this + // would snap to the rectangle outline. + mouse.moveTo(originalArrowEndViewport.x, originalArrowEndViewport.y); + mouse.down(); + mouse.moveTo(insideRectangleViewport.x, insideRectangleViewport.y); + mouse.up(); + + const updatedEnd = arrow!.points[arrow!.points.length - 1]; + const updatedEndGlobal = pointFrom( + arrow!.x + updatedEnd[0], + arrow!.y + updatedEnd[1], + ); + + expect(arrow!.startBinding).toBeNull(); + expect(arrow!.endBinding).toBeNull(); + expect(updatedEndGlobal).not.toEqual(originalArrowEndGlobal); + expect(updatedEndGlobal).toEqual(insideRectanglePoint); + }); + }); + + // ------------------------------------------------------------------------- + // Arrow does snap to midpoint when isMidpointSnappingEnabled is true + // ------------------------------------------------------------------------- + describe("Arrow doesn't snap to midpoint when midpoint snapping is disabled", () => { + it("does not snap to midpoint when midpoint snapping is turned off", () => { + const rect = API.createElement({ + type: "rectangle", + id: "rectNoMidSnap", + x: 100, + y: 100, + width: 400, + height: 200, + }) as ExcalidrawBindableElement; + const arrow = API.createElement({ + type: "arrow", + x: 0, + y: 250, + width: 502, + height: -48, + points: [pointFrom(0, 0), pointFrom(502, -48)], + }) as ExcalidrawArrowElement; + const elementsMap = new Map([ + [rect.id, rect], + [arrow.id, arrow], + ]); + const point = pointFrom(502, 202); + + const snappedWithMidpoint = projectFixedPointOntoDiagonal( + arrow, + point, + rect, + "end", + elementsMap, + h.state.zoom, + true, + ); + const snappedWithoutMidpoint = projectFixedPointOntoDiagonal( + arrow, + point, + rect, + "end", + elementsMap, + h.state.zoom, + false, + ); + + expect(snappedWithMidpoint).toEqual([500, 200]); + expect(snappedWithoutMidpoint).not.toEqual([500, 200]); + }); + }); + + // ------------------------------------------------------------------------- + // Ctrl / Cmd key toggle + // ------------------------------------------------------------------------- + + describe("Ctrl key toggle when binding preference is disabled", () => { + it("Ctrl keydown temporarily enables binding when preference is disabled", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + ctrlKeyDown(); + + expect(h.state.isBindingEnabled).toBe(true); + }); + + it("Ctrl keyup restores isBindingEnabled to false after the temporary toggle", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(false); + }); + + it("full round-trip: off → Ctrl down → on → Ctrl up → off", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(false); + }); + + it("full round-trip can be repeated after Ctrl release resets state", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + // First cycle + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(true); + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(false); + + // Second cycle + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(true); + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(false); + }); + }); + + describe("Ctrl key toggle when binding starts ON", () => { + it("Ctrl keydown temporarily disables binding when it was on", () => { + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(false); + }); + + it("Ctrl keyup restores isBindingEnabled to true after the temporary toggle", () => { + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(false); + + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // event.repeat guard + // ------------------------------------------------------------------------- + + describe("event.repeat guard", () => { + it("Ctrl keydown with repeat=true does not toggle binding (preference: disabled)", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + }); + + ctrlKeyDown({ repeat: true }); + + // Must remain off – repeat events are ignored + expect(h.state.isBindingEnabled).toBe(false); + }); + + it("Ctrl keydown with repeat=true does not toggle binding (default: on)", () => { + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyDown({ repeat: true }); + + expect(h.state.isBindingEnabled).toBe(true); + }); + + it("pressing another key while Ctrl held does not change binding state", () => { + // Start ON, first Ctrl keydown flips to false + expect(h.state.isBindingEnabled).toBe(true); + + ctrlKeyDown(); + expect(h.state.isBindingEnabled).toBe(false); + + // A second keydown with Ctrl (e.g. pressing another key while Ctrl held) + // should leave state unchanged + fireEvent.keyDown(document, { + key: "z", + ctrlKey: true, + repeat: false, + }); + expect(h.state.isBindingEnabled).toBe(false); + + // Ctrl keyup restores to preference value + ctrlKeyUp(); + expect(h.state.isBindingEnabled).toBe(true); + }); + }); + + // ------------------------------------------------------------------------- + // View-mode guard + // ------------------------------------------------------------------------- + + describe("View-mode guard", () => { + it("Ctrl keydown in viewMode does not toggle isBindingEnabled", () => { + API.setAppState({ + bindingPreference: "disabled", + isBindingEnabled: false, + viewModeEnabled: true, + }); + + ctrlKeyDown(); + + // Handler returns early in viewMode — state must stay false + expect(h.state.isBindingEnabled).toBe(false); + }); + }); +}); diff --git a/packages/excalidraw/tests/contextmenu.test.tsx b/packages/excalidraw/tests/contextmenu.test.tsx index 5bb7fee8e1..3aa50090a8 100644 --- a/packages/excalidraw/tests/contextmenu.test.tsx +++ b/packages/excalidraw/tests/contextmenu.test.tsx @@ -87,20 +87,17 @@ describe("contextMenu element", () => { clientY: 1, }); const contextMenu = UI.queryContextMenu(); - const contextMenuOptions = - contextMenu?.querySelectorAll(".context-menu li"); const expectedShortcutNames: ShortcutName[] = [ "paste", "selectAll", "gridMode", + "objectsSnapMode", "zenMode", "viewMode", - "objectsSnapMode", "stats", ]; expect(contextMenu).not.toBeNull(); - expect(contextMenuOptions?.length).toBe(expectedShortcutNames.length); expectedShortcutNames.forEach((shortcutName) => { expect( contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`), diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 07701e17bb..ac185ab563 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -223,6 +223,7 @@ export type InteractiveCanvasAppState = Readonly< multiElement: AppState["multiElement"]; newElement: AppState["newElement"]; isBindingEnabled: AppState["isBindingEnabled"]; + isMidpointSnappingEnabled: AppState["isMidpointSnappingEnabled"]; suggestedBinding: AppState["suggestedBinding"]; isRotating: AppState["isRotating"]; elementsToHighlight: AppState["elementsToHighlight"]; @@ -301,7 +302,15 @@ export interface AppState { * - set on pointer down, updated during pointer move */ selectionElement: NonDeletedExcalidrawElement | null; + /** + * tracking current arrow binding editor state (takes into account + * `bindingPreference` and keyboard modifiers (ctrl/alt) + */ isBindingEnabled: boolean; + /** user arrow binding preference */ + bindingPreference: "enabled" | "disabled"; + /** user preference whether arrow snap to midpoints while binding */ + isMidpointSnappingEnabled: boolean; startBoundElement: NonDeleted | null; suggestedBinding: { element: NonDeleted; diff --git a/packages/utils/tests/__snapshots__/export.test.ts.snap b/packages/utils/tests/__snapshots__/export.test.ts.snap index 8b239c7d04..affae46199 100644 --- a/packages/utils/tests/__snapshots__/export.test.ts.snap +++ b/packages/utils/tests/__snapshots__/export.test.ts.snap @@ -12,6 +12,7 @@ exports[`exportToSvg > with default arguments 1`] = ` "type": "selection", }, "bindMode": "orbit", + "bindingPreference": "enabled", "collaborators": Map {}, "contextMenu": null, "croppingElementId": null, @@ -58,6 +59,7 @@ exports[`exportToSvg > with default arguments 1`] = ` "isBindingEnabled": true, "isCropping": false, "isLoading": false, + "isMidpointSnappingEnabled": true, "isResizing": false, "isRotating": false, "lastPointerDownWith": "mouse",