feat: Arrow binding is a preference (#10839)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Márk Tolmács
2026-03-03 22:55:40 +01:00
committed by GitHub
parent 60b275880d
commit 437595fa65
24 changed files with 1164 additions and 146 deletions
+2
View File
@@ -438,6 +438,8 @@ export class Scene {
options: {
informMutation: boolean;
isDragging: boolean;
isBindingEnabled?: boolean;
isMidpointSnappingEnabled?: boolean;
} = {
informMutation: true,
isDragging: false,
+48 -23
View File
@@ -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<HTMLElement>,
) => {
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<GlobalPoint>,
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<GlobalPoint>(
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,
+14 -2
View File
@@ -915,6 +915,8 @@ export const updateElbowArrowPoints = (
},
options?: {
isDragging?: boolean;
isBindingEnabled?: boolean;
isMidpointSnappingEnabled?: boolean;
},
): ElementUpdate<ExcalidrawElbowArrowElement> => {
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,
);
}
+50 -18
View File
@@ -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<GlobalPoint>(
scenePointerX - linearElementEditor.pointerOffset.x,
scenePointerY - linearElementEditor.pointerOffset.y,
),
),
midPoint: app.state.isMidpointSnappingEnabled
? snapToMid(
suggestedBindingElement,
elementsMap,
pointFrom<GlobalPoint>(
scenePointerX - linearElementEditor.pointerOffset.x,
scenePointerY - linearElementEditor.pointerOffset.y,
),
)
: undefined,
}
: null,
},
+2
View File
@@ -40,6 +40,8 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
updates: ElementUpdate<TElement>,
options?: {
isDragging?: boolean;
isBindingEnabled?: boolean;
isMidpointSnappingEnabled?: boolean;
},
) => {
let didChange = false;
+11 -8
View File
@@ -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
@@ -1830,6 +1830,7 @@ export const actionChangeArrowType = register<keyof typeof ARROW_TYPE>({
startElement,
"start",
elementsMap,
appState.isBindingEnabled,
),
}
: null;
@@ -1843,6 +1844,7 @@ export const actionChangeArrowType = register<keyof typeof ARROW_TYPE>({
endElement,
"end",
elementsMap,
appState.isBindingEnabled,
),
}
: null;
@@ -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",
});
@@ -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,
});
+2
View File
@@ -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";
+2
View File
@@ -59,6 +59,8 @@ export type ActionName =
| "gridMode"
| "zenMode"
| "objectsSnapMode"
| "arrowBinding"
| "midpointSnapping"
| "stats"
| "changeStrokeColor"
| "changeBackgroundColor"
+5 -1
View File
@@ -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,
+36 -19
View File
@@ -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<AppProps, AppState> {
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<AppProps, AppState> {
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<AppProps, AppState> {
}
}
}
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<AppProps, AppState> {
private handleCanvasPointerDown = (
event: React.PointerEvent<HTMLElement>,
) => {
// 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<AppProps, AppState> {
}
this.clearSelectionIfNotUsingSelection();
this.updateBindingEnabledOnPointerMove(event);
if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
return;
@@ -7581,6 +7596,13 @@ class App extends React.Component<AppProps, AppState> {
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<AppProps, AppState> {
): 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<AppProps, AppState> {
this.addNewImagesToImageCache();
}, IMAGE_RENDER_TIMEOUT);
private updateBindingEnabledOnPointerMove = (
event: React.PointerEvent<HTMLElement>,
) => {
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<AppProps, AppState> {
CONTEXT_MENU_SEPARATOR,
actionToggleGridMode,
actionToggleObjectsSnapMode,
actionToggleArrowBinding,
actionToggleMidpointSnapping,
actionToggleZenMode,
actionToggleViewMode,
actionToggleStats,
@@ -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,
@@ -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 (
<DropdownMenuItemCheckbox
checked={appState.bindingPreference === "enabled"}
onSelect={(event) => {
actionManager.executeAction(actionToggleArrowBinding);
event.preventDefault();
}}
>
{t("labels.arrowBinding")}
</DropdownMenuItemCheckbox>
);
};
const PreferencesToggleMidpointSnappingItem = () => {
const { t } = useI18n();
const actionManager = useExcalidrawActionManager();
const appState = useUIAppState();
return (
<DropdownMenuItemCheckbox
checked={appState.isMidpointSnappingEnabled}
onSelect={(event) => {
actionManager.executeAction(actionToggleMidpointSnapping);
event.preventDefault();
}}
>
{t("labels.midpointSnapping")}
</DropdownMenuItemCheckbox>
);
};
export const PreferencesToggleGridModeItem = () => {
const { t } = useI18n();
const actionManager = useExcalidrawActionManager();
@@ -538,6 +574,8 @@ export const Preferences = ({
<PreferencesToggleZenModeItem />
<PreferencesToggleViewModeItem />
<PreferencesToggleElementPropertiesItem />
<PreferencesToggleArrowBindingItem />
<PreferencesToggleMidpointSnappingItem />
</>
)}
{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;
+3 -1
View File
@@ -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",
@@ -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<GlobalPoint>(
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<GlobalPoint>(
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 {
@@ -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": <svg
@@ -7680,6 +7731,7 @@ exports[`contextMenu element > 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",
@@ -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",
@@ -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",
@@ -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<KeyboardEventInit> = {}) =>
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(<Excalidraw handleKeyboardGlobally={true} />);
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<GlobalPoint>(1010, 570);
const originalArrowEndGlobal = pointFrom<GlobalPoint>(
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<GlobalPoint>(
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<LocalPoint>(0, 0), pointFrom<LocalPoint>(502, -48)],
}) as ExcalidrawArrowElement;
const elementsMap = new Map<string, ExcalidrawElement>([
[rect.id, rect],
[arrow.id, arrow],
]);
const point = pointFrom<GlobalPoint>(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);
});
});
});
@@ -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}"]`),
+9
View File
@@ -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<ExcalidrawBindableElement> | null;
suggestedBinding: {
element: NonDeleted<ExcalidrawBindableElement>;
@@ -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",