semi-fix bindings on generic type conversion

This commit is contained in:
dwelle 2025-04-30 18:00:21 +02:00
parent 8cf726b8f9
commit f2f62a3df9
4 changed files with 42 additions and 33 deletions

View file

@ -81,7 +81,6 @@ import type {
NonDeletedSceneElementsMap, NonDeletedSceneElementsMap,
ExcalidrawTextElement, ExcalidrawTextElement,
ExcalidrawArrowElement, ExcalidrawArrowElement,
OrderedExcalidrawElement,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
FixedPoint, FixedPoint,
FixedPointBinding, FixedPointBinding,
@ -710,29 +709,32 @@ const calculateFocusAndGap = (
// Supports translating, rotating and scaling `changedElement` with bound // Supports translating, rotating and scaling `changedElement` with bound
// linear elements. // linear elements.
// Because scaling involves moving the focus points as well, it is
// done before the `changedElement` is updated, and the `newSize` is passed
// in explicitly.
export const updateBoundElements = ( export const updateBoundElements = (
changedElement: NonDeletedExcalidrawElement, changedElement: NonDeletedExcalidrawElement,
scene: Scene, scene: Scene,
options?: { options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[]; simultaneouslyUpdated?: readonly ExcalidrawElement[];
newSize?: { width: number; height: number }; newSize?: { width: number; height: number };
changedElements?: Map<string, OrderedExcalidrawElement>; changedElements?: Map<string, ExcalidrawElement>;
}, },
) => { ) => {
if (!isBindableElement(changedElement)) {
return;
}
const { newSize, simultaneouslyUpdated } = options ?? {}; const { newSize, simultaneouslyUpdated } = options ?? {};
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds( const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
simultaneouslyUpdated, simultaneouslyUpdated,
); );
if (!isBindableElement(changedElement)) { let elementsMap: ElementsMap = scene.getNonDeletedElementsMap();
return; if (options?.changedElements) {
elementsMap = new Map(elementsMap) as typeof elementsMap;
options.changedElements.forEach((element) => {
elementsMap.set(element.id, element);
});
} }
const elementsMap = scene.getNonDeletedElementsMap();
boundElementsVisitor(elementsMap, changedElement, (element) => { boundElementsVisitor(elementsMap, changedElement, (element) => {
if (!isLinearElement(element) || element.isDeleted) { if (!isLinearElement(element) || element.isDeleted) {
return; return;
@ -836,6 +838,25 @@ export const updateBoundElements = (
}); });
}; };
export const updateBindings = (
latestElement: ExcalidrawElement,
scene: Scene,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
newSize?: { width: number; height: number };
zoom?: AppState["zoom"];
},
) => {
if (isLinearElement(latestElement)) {
bindOrUnbindLinearElements([latestElement], true, [], scene, options?.zoom);
} else {
updateBoundElements(latestElement, scene, {
...options,
changedElements: new Map([[latestElement.id, latestElement]]),
});
}
};
const doesNeedUpdate = ( const doesNeedUpdate = (
boundElement: NonDeleted<ExcalidrawLinearElement>, boundElement: NonDeleted<ExcalidrawLinearElement>,
changedElement: ExcalidrawBindableElement, changedElement: ExcalidrawBindableElement,

View file

@ -76,6 +76,7 @@ import {
} from ".."; } from "..";
import { trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { atom, editorJotaiStore, useSetAtom } from "../editor-jotai"; import { atom, editorJotaiStore, useSetAtom } from "../editor-jotai";
import { updateBindings } from "../../element/src/binding";
import "./ConvertElementTypePopup.scss"; import "./ConvertElementTypePopup.scss";
import { ToolButton } from "./ToolButton"; import { ToolButton } from "./ToolButton";
@ -945,7 +946,7 @@ const convertElementType = <
ShapeCache.delete(element); ShapeCache.delete(element);
if (isConvertibleGenericType(targetType)) { if (isConvertibleGenericType(targetType)) {
return bumpVersion( const nextElement = bumpVersion(
newElement({ newElement({
...element, ...element,
type: targetType, type: targetType,
@ -958,7 +959,11 @@ const convertElementType = <
} }
: element.roundness, : element.roundness,
}), }),
); ) as typeof element;
updateBindings(nextElement, app.scene);
return nextElement;
} }
if (isConvertibleLinearType(targetType)) { if (isConvertibleLinearType(targetType)) {

View file

@ -11,8 +11,10 @@ import type Scene from "@excalidraw/element/Scene";
import { angleIcon } from "../icons"; import { angleIcon } from "../icons";
import { updateBindings } from "../../../element/src/binding";
import DragInput from "./DragInput"; import DragInput from "./DragInput";
import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils"; import { getStepSizedValue, isPropertyEditable } from "./utils";
import type { DragInputCallbackType } from "./DragInput"; import type { DragInputCallbackType } from "./DragInput";
import type { AppState } from "../../types"; import type { AppState } from "../../types";

View file

@ -1,13 +1,8 @@
import { pointFrom, pointRotateRads } from "@excalidraw/math"; import { pointFrom, pointRotateRads } from "@excalidraw/math";
import {
bindOrUnbindLinearElements,
updateBoundElements,
} from "@excalidraw/element/binding";
import { getBoundTextElement } from "@excalidraw/element/textElement"; import { getBoundTextElement } from "@excalidraw/element/textElement";
import { import {
isFrameLikeElement, isFrameLikeElement,
isLinearElement,
isTextElement, isTextElement,
} from "@excalidraw/element/typeChecks"; } from "@excalidraw/element/typeChecks";
@ -27,6 +22,8 @@ import type {
import type Scene from "@excalidraw/element/Scene"; import type Scene from "@excalidraw/element/Scene";
import { updateBindings } from "../../../element/src/binding";
import type { AppState } from "../../types"; import type { AppState } from "../../types";
export type StatsInputProperty = export type StatsInputProperty =
@ -194,19 +191,3 @@ export const getAtomicUnits = (
}); });
return _atomicUnits; return _atomicUnits;
}; };
export const updateBindings = (
latestElement: ExcalidrawElement,
scene: Scene,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
newSize?: { width: number; height: number };
zoom?: AppState["zoom"];
},
) => {
if (isLinearElement(latestElement)) {
bindOrUnbindLinearElements([latestElement], true, [], scene, options?.zoom);
} else {
updateBoundElements(latestElement, scene, options);
}
};