Restore master

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs 2025-05-01 09:44:02 +02:00
parent 43561b6631
commit f6203daac5
No known key found for this signature in database
7 changed files with 93 additions and 252 deletions

View file

@ -27,9 +27,7 @@ import {
PRECISION, PRECISION,
} from "@excalidraw/math"; } from "@excalidraw/math";
import { isPointInShape } from "@excalidraw/utils/collision"; import { isPointOnShape } from "@excalidraw/utils/collision";
import { getEllipseShape, getPolygonShape } from "@excalidraw/utils/shape";
import type { LocalPoint, Radians } from "@excalidraw/math"; import type { LocalPoint, Radians } from "@excalidraw/math";
@ -46,6 +44,7 @@ import { intersectElementWithLineSegment } from "./collision";
import { distanceToBindableElement } from "./distance"; import { distanceToBindableElement } from "./distance";
import { import {
headingForPointFromElement, headingForPointFromElement,
headingIsHorizontal,
vectorToHeading, vectorToHeading,
type Heading, type Heading,
} from "./heading"; } from "./heading";
@ -64,7 +63,7 @@ import {
isTextElement, isTextElement,
} from "./typeChecks"; } from "./typeChecks";
import { aabbForElement } from "./shapes"; import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
import { updateElbowArrowPoints } from "./elbowArrow"; import { updateElbowArrowPoints } from "./elbowArrow";
import type Scene from "./Scene"; import type Scene from "./Scene";
@ -108,7 +107,7 @@ export const isBindingEnabled = (appState: AppState): boolean => {
}; };
export const FIXED_BINDING_DISTANCE = 5; export const FIXED_BINDING_DISTANCE = 5;
const BINDING_HIGHLIGHT_THICKNESS = 10; export const BINDING_HIGHLIGHT_THICKNESS = 10;
export const BINDING_HIGHLIGHT_OFFSET = 4; export const BINDING_HIGHLIGHT_OFFSET = 4;
const getNonDeletedElements = ( const getNonDeletedElements = (
@ -442,15 +441,19 @@ const normalizePointBinding = (
binding: { focus: number; gap: number }, binding: { focus: number; gap: number },
hoveredElement: ExcalidrawBindableElement, hoveredElement: ExcalidrawBindableElement,
) => { ) => {
let gap = binding.gap;
const maxGap = maxBindingGap( const maxGap = maxBindingGap(
hoveredElement, hoveredElement,
hoveredElement.width, hoveredElement.width,
hoveredElement.height, hoveredElement.height,
); );
if (gap > maxGap) {
gap = BINDING_HIGHLIGHT_THICKNESS + BINDING_HIGHLIGHT_OFFSET;
}
return { return {
...binding, ...binding,
gap: Math.min(binding.gap, maxGap), gap,
}; };
}; };
@ -562,8 +565,7 @@ export const getHoveredElementForBinding = (
let cullRest = false; let cullRest = false;
const candidateElements = getAllElementsAtPositionForBinding( const candidateElements = getAllElementsAtPositionForBinding(
elements, elements,
(element) => { (element) =>
const result =
isBindableElement(element, false) && isBindableElement(element, false) &&
bindingBorderTest( bindingBorderTest(
element, element,
@ -577,10 +579,7 @@ export const getHoveredElementForBinding = (
// disable fullshape snapping for frame elements so we // disable fullshape snapping for frame elements so we
// can bind to frame children // can bind to frame children
!isFrameLikeElement(element), !isFrameLikeElement(element),
); ),
return result;
},
).filter((element) => { ).filter((element) => {
if (cullRest) { if (cullRest) {
return false; return false;
@ -888,12 +887,7 @@ export const getHeadingForElbowArrowSnap = (
return otherPointHeading; return otherPointHeading;
} }
const distance = getDistanceForBinding( const distance = getDistanceForBinding(origPoint, bindableElement, zoom);
origPoint,
bindableElement,
true,
zoom,
);
if (!distance) { if (!distance) {
return vectorToHeading( return vectorToHeading(
@ -904,10 +898,9 @@ export const getHeadingForElbowArrowSnap = (
return headingForPointFromElement(bindableElement, aabb, p); return headingForPointFromElement(bindableElement, aabb, p);
}; };
export const getDistanceForBinding = ( const getDistanceForBinding = (
point: Readonly<GlobalPoint>, point: Readonly<GlobalPoint>,
bindableElement: ExcalidrawBindableElement, bindableElement: ExcalidrawBindableElement,
fullShape: boolean,
zoom?: AppState["zoom"], zoom?: AppState["zoom"],
) => { ) => {
const distance = distanceToBindableElement(bindableElement, point); const distance = distanceToBindableElement(bindableElement, point);
@ -917,16 +910,8 @@ export const getDistanceForBinding = (
bindableElement.height, bindableElement.height,
zoom, zoom,
); );
const isInside = fullShape
? isPointInShape(
point,
bindableElement.type === "ellipse"
? getEllipseShape(bindableElement)
: getPolygonShape(bindableElement),
)
: false;
return distance > bindDistance && !isInside ? null : distance; return distance > bindDistance ? null : distance;
}; };
export const bindPointToSnapToElementOutline = ( export const bindPointToSnapToElementOutline = (
@ -962,16 +947,23 @@ export const bindPointToSnapToElementOutline = (
let intersection: GlobalPoint | null = null; let intersection: GlobalPoint | null = null;
if (elbowed) { if (elbowed) {
const isHorizontal = headingIsHorizontal(
headingForPointFromElement(bindableElement, aabb, globalP),
);
const otherPoint = pointFrom<GlobalPoint>(
isHorizontal ? center[0] : edgePoint[0],
!isHorizontal ? center[1] : edgePoint[1],
);
intersection = intersectElementWithLineSegment( intersection = intersectElementWithLineSegment(
bindableElement, bindableElement,
lineSegment( lineSegment(
center, otherPoint,
pointFromVector( pointFromVector(
vectorScale( vectorScale(
vectorNormalize(vectorFromPoint(edgePoint, center)), vectorNormalize(vectorFromPoint(edgePoint, otherPoint)),
Math.max(bindableElement.width, bindableElement.height) * 2, Math.max(bindableElement.width, bindableElement.height) * 2,
), ),
center, otherPoint,
), ),
), ),
)[0]; )[0];
@ -1179,48 +1171,6 @@ export const snapToMid = (
center, center,
angle, angle,
); );
} else if (element.type === "diamond") {
const distance = FIXED_BINDING_DISTANCE - 1;
const topLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + height / 4 - distance,
);
const topRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + height / 4 - distance,
);
const bottomLeft = pointFrom<GlobalPoint>(
x + width / 4 - distance,
y + (3 * height) / 4 + distance,
);
const bottomRight = pointFrom<GlobalPoint>(
x + (3 * width) / 4 + distance,
y + (3 * height) / 4 + distance,
);
if (
pointDistance(topLeft, nonRotated) <
Math.max(horizontalThrehsold, verticalThrehsold)
) {
return pointRotateRads(topLeft, center, angle);
}
if (
pointDistance(topRight, nonRotated) <
Math.max(horizontalThrehsold, verticalThrehsold)
) {
return pointRotateRads(topRight, center, angle);
}
if (
pointDistance(bottomLeft, nonRotated) <
Math.max(horizontalThrehsold, verticalThrehsold)
) {
return pointRotateRads(bottomLeft, center, angle);
}
if (
pointDistance(bottomRight, nonRotated) <
Math.max(horizontalThrehsold, verticalThrehsold)
) {
return pointRotateRads(bottomRight, center, angle);
}
} }
return p; return p;
@ -1561,14 +1511,14 @@ export const bindingBorderTest = (
zoom?: AppState["zoom"], zoom?: AppState["zoom"],
fullShape?: boolean, fullShape?: boolean,
): boolean => { ): boolean => {
const distance = getDistanceForBinding( const threshold = maxBindingGap(element, element.width, element.height, zoom);
pointFrom(x, y),
element,
!!fullShape,
zoom,
);
return !!distance; const shape = getElementShape(element, elementsMap);
return (
isPointOnShape(pointFrom(x, y), shape, threshold) ||
(fullShape === true &&
pointInsideBounds(pointFrom(x, y), aabbForElement(element)))
);
}; };
export const maxBindingGap = ( export const maxBindingGap = (

View file

@ -31,7 +31,6 @@ import {
getGlobalFixedPointForBindableElement, getGlobalFixedPointForBindableElement,
snapToMid, snapToMid,
getHoveredElementForBinding, getHoveredElementForBinding,
getDistanceForBinding,
} from "./binding"; } from "./binding";
import { distanceToBindableElement } from "./distance"; import { distanceToBindableElement } from "./distance";
import { import {
@ -1256,7 +1255,6 @@ const getElbowArrowData = (
origStartGlobalPoint, origStartGlobalPoint,
hoveredStartElement, hoveredStartElement,
options?.isDragging, options?.isDragging,
options?.zoom,
); );
const endGlobalPoint = getGlobalPoint( const endGlobalPoint = getGlobalPoint(
{ {
@ -1270,7 +1268,6 @@ const getElbowArrowData = (
origEndGlobalPoint, origEndGlobalPoint,
hoveredEndElement, hoveredEndElement,
options?.isDragging, options?.isDragging,
options?.zoom,
); );
const startHeading = getBindPointHeading( const startHeading = getBindPointHeading(
startGlobalPoint, startGlobalPoint,
@ -2214,14 +2211,16 @@ const getGlobalPoint = (
initialPoint: GlobalPoint, initialPoint: GlobalPoint,
element?: ExcalidrawBindableElement | null, element?: ExcalidrawBindableElement | null,
isDragging?: boolean, isDragging?: boolean,
zoom?: AppState["zoom"],
): GlobalPoint => { ): GlobalPoint => {
if (isDragging) { if (isDragging) {
if (element && getDistanceForBinding(initialPoint, element, true, zoom)) { if (element) {
return snapToMid( const snapPoint = bindPointToSnapToElementOutline(
arrow,
element, element,
bindPointToSnapToElementOutline(arrow, element, startOrEnd), startOrEnd,
); );
return snapToMid(element, snapPoint);
} }
return initialPoint; return initialPoint;

View file

@ -3,12 +3,10 @@ import {
viewportCoordsToSceneCoords, viewportCoordsToSceneCoords,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { pointsEqual } from "@excalidraw/math";
import type { AppState, Offsets, Zoom } from "@excalidraw/excalidraw/types"; import type { AppState, Offsets, Zoom } from "@excalidraw/excalidraw/types";
import { getCommonBounds, getElementBounds } from "./bounds"; import { getCommonBounds, getElementBounds } from "./bounds";
import { isElbowArrow, isFreeDrawElement, isLinearElement } from "./typeChecks"; import { isFreeDrawElement, isLinearElement } from "./typeChecks";
import type { ElementsMap, ExcalidrawElement } from "./types"; import type { ElementsMap, ExcalidrawElement } from "./types";
@ -18,12 +16,6 @@ import type { ElementsMap, ExcalidrawElement } from "./types";
export const isInvisiblySmallElement = ( export const isInvisiblySmallElement = (
element: ExcalidrawElement, element: ExcalidrawElement,
): boolean => { ): boolean => {
if (isElbowArrow(element)) {
return (
element.points.length < 2 ||
pointsEqual(element.points[0], element.points[element.points.length - 1])
);
}
if (isLinearElement(element) || isFreeDrawElement(element)) { if (isLinearElement(element) || isFreeDrawElement(element)) {
return element.points.length < 2; return element.points.length < 2;
} }

View file

@ -199,7 +199,7 @@ describe("elbow arrow routing", () => {
points: [pointFrom<LocalPoint>(0, 0), pointFrom<LocalPoint>(90, 200)], points: [pointFrom<LocalPoint>(0, 0), pointFrom<LocalPoint>(90, 200)],
}); });
expect(arrow.points).toCloselyEqualPoints([ expect(arrow.points).toEqual([
[0, 0], [0, 0],
[45, 0], [45, 0],
[45, 200], [45, 200],
@ -253,7 +253,7 @@ describe("elbow arrow ui", () => {
expect(arrow.type).toBe("arrow"); expect(arrow.type).toBe("arrow");
expect(arrow.elbowed).toBe(true); expect(arrow.elbowed).toBe(true);
expect(arrow.points).toCloselyEqualPoints([ expect(arrow.points).toEqual([
[0, 0], [0, 0],
[45, 0], [45, 0],
[45, 200], [45, 200],
@ -351,7 +351,7 @@ describe("elbow arrow ui", () => {
expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.id).not.toBe(originalArrowId);
expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.type).toBe("arrow");
expect(duplicatedArrow.elbowed).toBe(true); expect(duplicatedArrow.elbowed).toBe(true);
expect(duplicatedArrow.points).toCloselyEqualPoints([ expect(duplicatedArrow.points).toEqual([
[0, 0], [0, 0],
[45, 0], [45, 0],
[45, 200], [45, 200],
@ -405,98 +405,11 @@ describe("elbow arrow ui", () => {
expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.id).not.toBe(originalArrowId);
expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.type).toBe("arrow");
expect(duplicatedArrow.elbowed).toBe(true); expect(duplicatedArrow.elbowed).toBe(true);
expect(duplicatedArrow.points).toCloselyEqualPoints([ expect(duplicatedArrow.points).toEqual([
[0, 0], [0, 0],
[0, 100], [0, 100],
[90, 100], [90, 100],
[90, 200], [90, 200],
]); ]);
}); });
it("elbow arrow snap at diamond quarter point too", async () => {
UI.createElement("diamond", {
x: -50,
y: -50,
width: 100,
height: 100,
});
UI.clickTool("arrow");
UI.clickOnTestId("elbow-arrow");
mouse.reset();
mouse.moveTo(43, 99);
mouse.click();
mouse.moveTo(27, 25);
mouse.click();
let arrow = h.scene.getSelectedElements(
h.state,
)[0] as ExcalidrawArrowElement;
expect(arrow.endBinding).not.toBe(null);
expect(arrow.x + arrow.points[arrow.points.length - 1][0]).toBeCloseTo(
29.0355,
);
expect(arrow.y + arrow.points[arrow.points.length - 1][1]).toBeCloseTo(
29.0355,
);
UI.clickTool("arrow");
UI.clickOnTestId("elbow-arrow");
mouse.reset();
mouse.moveTo(43, 99);
mouse.click();
mouse.moveTo(-23, 25);
mouse.click();
arrow = h.scene.getSelectedElements(h.state)[0] as ExcalidrawArrowElement;
expect(arrow.endBinding).not.toBe(null);
expect(arrow.x + arrow.points[arrow.points.length - 1][0]).toBeCloseTo(
-28.5559,
);
expect(arrow.y + arrow.points[arrow.points.length - 1][1]).toBeCloseTo(
28.5559,
);
UI.clickTool("arrow");
UI.clickOnTestId("elbow-arrow");
mouse.reset();
mouse.moveTo(43, 99);
mouse.click();
mouse.moveTo(-27, -25);
mouse.click();
arrow = h.scene.getSelectedElements(h.state)[0] as ExcalidrawArrowElement;
expect(arrow.endBinding).not.toBe(null);
expect(arrow.x + arrow.points[arrow.points.length - 1][0]).toBeCloseTo(
-28.0355,
);
expect(arrow.y + arrow.points[arrow.points.length - 1][1]).toBeCloseTo(
-28.0355,
);
UI.clickTool("arrow");
UI.clickOnTestId("elbow-arrow");
mouse.reset();
mouse.moveTo(43, 99);
mouse.click();
mouse.moveTo(23, -25);
mouse.click();
arrow = h.scene.getSelectedElements(h.state)[0] as ExcalidrawArrowElement;
expect(arrow.endBinding).not.toBe(null);
expect(arrow.x + arrow.points[arrow.points.length - 1][0]).toBeCloseTo(
28.5559,
);
expect(arrow.y + arrow.points[arrow.points.length - 1][1]).toBeCloseTo(
-28.5559,
);
});
}); });

View file

@ -7,7 +7,6 @@ import {
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
import { import {
isArrowElement,
isBindingElement, isBindingElement,
isLinearElement, isLinearElement,
} from "@excalidraw/element/typeChecks"; } from "@excalidraw/element/typeChecks";
@ -17,12 +16,6 @@ import { isPathALoop } from "@excalidraw/element/shapes";
import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers"; import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
import type {
ExcalidrawElement,
ExcalidrawLinearElement,
NonDeleted,
} from "@excalidraw/element/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { resetCursor } from "../cursor"; import { resetCursor } from "../cursor";
import { done } from "../components/icons"; import { done } from "../components/icons";
@ -69,7 +62,6 @@ export const actionFinalize = register({
captureUpdate: CaptureUpdateAction.IMMEDIATELY, captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}; };
} }
} else if (isArrowElement(appState.newElement)) {
} }
let newElements = elements; let newElements = elements;
@ -90,55 +82,48 @@ export const actionFinalize = register({
focusContainer(); focusContainer();
} }
let element: NonDeleted<ExcalidrawElement> | null = null; const multiPointElement = appState.multiElement
if (appState.multiElement) { ? appState.multiElement
element = appState.multiElement; : appState.newElement?.type === "freedraw"
} else if ( ? appState.newElement
appState.newElement?.type === "freedraw" || : null;
isBindingElement(appState.newElement)
) {
element = appState.newElement;
} else if (Object.keys(appState.selectedElementIds).length === 1) {
const candidate = elementsMap.get(
Object.keys(appState.selectedElementIds)[0],
) as NonDeleted<ExcalidrawLinearElement> | undefined;
if (candidate) {
element = candidate;
}
}
if (element) { if (multiPointElement) {
// pen and mouse have hover // pen and mouse have hover
if ( if (
appState.multiElement && multiPointElement.type !== "freedraw" &&
element.type !== "freedraw" &&
appState.lastPointerDownWith !== "touch" appState.lastPointerDownWith !== "touch"
) { ) {
const { points, lastCommittedPoint } = element; const { points, lastCommittedPoint } = multiPointElement;
if ( if (
!lastCommittedPoint || !lastCommittedPoint ||
points[points.length - 1] !== lastCommittedPoint points[points.length - 1] !== lastCommittedPoint
) { ) {
scene.mutateElement(element, { scene.mutateElement(multiPointElement, {
points: element.points.slice(0, -1), points: multiPointElement.points.slice(0, -1),
}); });
} }
} }
if (isInvisiblySmallElement(element)) { if (isInvisiblySmallElement(multiPointElement)) {
// TODO: #7348 in theory this gets recorded by the store, so the invisible elements could be restored by the undo/redo, which might be not what we would want // TODO: #7348 in theory this gets recorded by the store, so the invisible elements could be restored by the undo/redo, which might be not what we would want
newElements = newElements.filter((el) => el.id !== element!.id); newElements = newElements.filter(
(el) => el.id !== multiPointElement.id,
);
} }
// If the multi point line closes the loop, // If the multi point line closes the loop,
// set the last point to first point. // set the last point to first point.
// This ensures that loop remains closed at different scales. // This ensures that loop remains closed at different scales.
const isLoop = isPathALoop(element.points, appState.zoom.value); const isLoop = isPathALoop(multiPointElement.points, appState.zoom.value);
if (element.type === "line" || element.type === "freedraw") { if (
multiPointElement.type === "line" ||
multiPointElement.type === "freedraw"
) {
if (isLoop) { if (isLoop) {
const linePoints = element.points; const linePoints = multiPointElement.points;
const firstPoint = linePoints[0]; const firstPoint = linePoints[0];
scene.mutateElement(element, { scene.mutateElement(multiPointElement, {
points: linePoints.map((p, index) => points: linePoints.map((p, index) =>
index === linePoints.length - 1 index === linePoints.length - 1
? pointFrom(firstPoint[0], firstPoint[1]) ? pointFrom(firstPoint[0], firstPoint[1])
@ -149,24 +134,23 @@ export const actionFinalize = register({
} }
if ( if (
isBindingElement(element) && isBindingElement(multiPointElement) &&
!isLoop && !isLoop &&
element.points.length > 1 && multiPointElement.points.length > 1
!appState.selectedElementIds[element.id]
) { ) {
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates( const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
element, multiPointElement,
-1, -1,
arrayToMap(elements), arrayToMap(elements),
); );
maybeBindLinearElement(element, appState, { x, y }, scene); maybeBindLinearElement(multiPointElement, appState, { x, y }, scene);
} }
} }
if ( if (
(!appState.activeTool.locked && (!appState.activeTool.locked &&
appState.activeTool.type !== "freedraw") || appState.activeTool.type !== "freedraw") ||
!element !multiPointElement
) { ) {
resetCursor(interactiveCanvas); resetCursor(interactiveCanvas);
} }
@ -193,7 +177,7 @@ export const actionFinalize = register({
activeTool: activeTool:
(appState.activeTool.locked || (appState.activeTool.locked ||
appState.activeTool.type === "freedraw") && appState.activeTool.type === "freedraw") &&
element multiPointElement
? appState.activeTool ? appState.activeTool
: activeTool, : activeTool,
activeEmbeddable: null, activeEmbeddable: null,
@ -204,18 +188,21 @@ export const actionFinalize = register({
startBoundElement: null, startBoundElement: null,
suggestedBindings: [], suggestedBindings: [],
selectedElementIds: selectedElementIds:
element && multiPointElement &&
!appState.activeTool.locked && !appState.activeTool.locked &&
appState.activeTool.type !== "freedraw" appState.activeTool.type !== "freedraw"
? { ? {
...appState.selectedElementIds, ...appState.selectedElementIds,
[element.id]: true, [multiPointElement.id]: true,
} }
: appState.selectedElementIds, : appState.selectedElementIds,
// To select the linear element when user has finished mutipoint editing // To select the linear element when user has finished mutipoint editing
selectedLinearElement: selectedLinearElement:
element && isLinearElement(element) multiPointElement && isLinearElement(multiPointElement)
? new LinearElementEditor(element, arrayToMap(newElements)) ? new LinearElementEditor(
multiPointElement,
arrayToMap(newElements),
)
: appState.selectedLinearElement, : appState.selectedLinearElement,
pendingImageElementId: null, pendingImageElementId: null,
}, },

View file

@ -9022,7 +9022,6 @@ class App extends React.Component<AppProps, AppState> {
linearElementEditor; linearElementEditor;
const element = this.scene.getElement(linearElementEditor.elementId); const element = this.scene.getElement(linearElementEditor.elementId);
if (isBindingElement(element)) { if (isBindingElement(element)) {
this.actionManager.executeAction(actionFinalize);
bindOrUnbindLinearElement( bindOrUnbindLinearElement(
element, element,
startBindingElement, startBindingElement,

View file

@ -17,6 +17,7 @@ import {
import { import {
BINDING_HIGHLIGHT_OFFSET, BINDING_HIGHLIGHT_OFFSET,
BINDING_HIGHLIGHT_THICKNESS,
maxBindingGap, maxBindingGap,
} from "@excalidraw/element/binding"; } from "@excalidraw/element/binding";
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
@ -260,9 +261,9 @@ const renderBindingHighlightForBindableElement = (
const height = y2 - y1; const height = y2 - y1;
context.strokeStyle = "rgba(0,0,0,.05)"; context.strokeStyle = "rgba(0,0,0,.05)";
context.lineWidth = // When zooming out, make line width greater for visibility
maxBindingGap(element, element.width, element.height, zoom) - const zoomValue = zoom.value < 1 ? zoom.value : 1;
BINDING_HIGHLIGHT_OFFSET; context.lineWidth = BINDING_HIGHLIGHT_THICKNESS / zoomValue;
// To ensure the binding highlight doesn't overlap the element itself // To ensure the binding highlight doesn't overlap the element itself
const padding = context.lineWidth / 2 + BINDING_HIGHLIGHT_OFFSET; const padding = context.lineWidth / 2 + BINDING_HIGHLIGHT_OFFSET;