fix(editor): remove extremely large arrows on restore (#11235)

* fix: Temp fix for elbow arrow at restore

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Speculative fixes to avoid Infinity

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* validate/remove arrow size after point normalization & move binding repairs back

* validate even simple arrows

* remove x/y check

* remove duplicate constant

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Márk Tolmács
2026-04-25 12:03:50 +02:00
committed by GitHub
parent b1c6bfcf40
commit 2e1a529c67
4 changed files with 49 additions and 14 deletions
+6 -4
View File
@@ -1943,9 +1943,9 @@ export const calculateFixedPointForElbowArrowBinding = (
return {
fixedPoint: normalizeFixedPoint([
(nonRotatedSnappedGlobalPoint[0] - hoveredElement.x) /
hoveredElement.width,
Math.max(hoveredElement.width, PRECISION),
(nonRotatedSnappedGlobalPoint[1] - hoveredElement.y) /
hoveredElement.height,
Math.max(hoveredElement.height, PRECISION),
]),
};
};
@@ -1976,9 +1976,11 @@ export const calculateFixedPointForNonElbowArrowBinding = (
// Calculate the ratio relative to the element's bounds
const fixedPointX =
(nonRotatedPoint[0] - hoveredElement.x) / hoveredElement.width;
(nonRotatedPoint[0] - hoveredElement.x) /
Math.max(hoveredElement.width, PRECISION);
const fixedPointY =
(nonRotatedPoint[1] - hoveredElement.y) / hoveredElement.height;
(nonRotatedPoint[1] - hoveredElement.y) /
Math.max(hoveredElement.height, PRECISION);
return {
fixedPoint: normalizeFixedPoint([fixedPointX, fixedPointY]),
+2 -2
View File
@@ -2124,8 +2124,8 @@ const normalizeArrowElementUpdate = (
offsetY < -MAX_POS ||
offsetY > MAX_POS ||
offsetX + points[points.length - 1][0] < -MAX_POS ||
offsetY + points[points.length - 1][0] > MAX_POS ||
offsetX + points[points.length - 1][1] < -MAX_POS ||
offsetX + points[points.length - 1][0] > MAX_POS ||
offsetY + points[points.length - 1][1] < -MAX_POS ||
offsetY + points[points.length - 1][1] > MAX_POS
) {
console.error(
+41 -6
View File
@@ -96,6 +96,8 @@ type RestoredAppState = Omit<
"offsetTop" | "offsetLeft" | "width" | "height"
>;
const MAX_ARROW_PX = 75_000;
export const AllowedExcalidrawActiveTools: Record<
AppState["activeTool"]["type"],
boolean
@@ -467,8 +469,8 @@ export const restoreElement = (
element.endArrowhead === undefined
? "arrow"
: normalizeArrowhead(element.endArrowhead);
const x: number | undefined = element.x;
const y: number | undefined = element.y;
const x = element.x as number | undefined;
const y = element.y as number | undefined;
const points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
!Array.isArray(element.points) || element.points.length < 2
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
@@ -493,8 +495,8 @@ export const restoreElement = (
startArrowhead,
endArrowhead,
points,
x,
y,
x: x ?? 0,
y: y ?? 0,
elbowed: (element as ExcalidrawArrowElement).elbowed,
...getSizeFromPoints(points),
};
@@ -513,12 +515,44 @@ export const restoreElement = (
})
: restoreElementWithProperties(element as ExcalidrawArrowElement, base);
return {
const normalizedRestoredElement = {
...restoredElement,
...LinearElementEditor.getNormalizeElementPointsAndCoords(
restoredElement,
),
};
// Last resort fix for extremely large arrows
if (
normalizedRestoredElement.width > MAX_ARROW_PX ||
normalizedRestoredElement.height > MAX_ARROW_PX
) {
console.error(
`Removing extremely large arrow ${
normalizedRestoredElement.id
} (type: ${
isElbowArrow(normalizedRestoredElement) ? "elbow" : "simple"
}, width: ${normalizedRestoredElement.width}, height: ${
normalizedRestoredElement.height
}, x: ${normalizedRestoredElement.x}, y: ${
normalizedRestoredElement.y
})`,
);
return {
...normalizedRestoredElement,
x: 0,
y: 0,
width: 100,
height: 100,
points: [
pointFrom<LocalPoint>(0, 0),
pointFrom<LocalPoint>(100, 100),
],
isDeleted: true,
};
}
return normalizedRestoredElement;
}
// generic elements
@@ -666,6 +700,7 @@ export const restoreElements = <T extends ExcalidrawElement>(
const existingElementsMap = existingElements
? arrayToMap(existingElements)
: null;
const restoredElements = syncInvalidIndices(
(targetElements || []).reduce((elements, element) => {
// filtering out selection, which is legacy, no longer kept in elements,
@@ -762,7 +797,7 @@ export const restoreElements = <T extends ExcalidrawElement>(
}
}
// NOTE (mtolmacs): Temporary fix for extremely large arrows
// NOTE (mtolmacs): Temporary fix for invalid/self-bound elbow arrows
// Need to iterate again so we have attached text nodes in elementsMap
return restoredElements.map((element) => {
if (
-2
View File
@@ -1,5 +1,3 @@
export const PRECISION = 10e-5;
// Legendre-Gauss abscissae (x values) and weights for n=24
// Refeerence: https://pomax.github.io/bezierinfo/legendre-gauss.html
export const LegendreGaussN24TValues = [