feat: expose applyTo options, don't commit empty text element (#9744)

* Expose applyTo options, skip re-draw for empty text

* Don't commit empty text elements
This commit is contained in:
Marcel Mraz 2025-07-17 15:22:32 +02:00 committed by GitHub
parent 678dff25ed
commit e46f038132
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 27 additions and 8 deletions

View File

@ -27,6 +27,8 @@ import {
isImageElement,
} from "./index";
import type { ApplyToOptions } from "./delta";
import type {
ExcalidrawElement,
OrderedExcalidrawElement,
@ -570,9 +572,15 @@ export class StoreDelta {
delta: StoreDelta,
elements: SceneElementsMap,
appState: AppState,
options: ApplyToOptions = {
excludedProperties: new Set(),
},
): [SceneElementsMap, AppState, boolean] {
const [nextElements, elementsContainVisibleChange] =
delta.elements.applyTo(elements);
const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo(
elements,
StoreSnapshot.empty().elements,
options,
);
const [nextAppState, appStateContainsVisibleChange] =
delta.appState.applyTo(appState, nextElements);

View File

@ -4925,7 +4925,17 @@ class App extends React.Component<AppProps, AppState> {
}),
onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
const isDeleted = !nextOriginalText.trim();
updateElement(nextOriginalText, isDeleted);
if (isDeleted && !isExistingElement) {
// let's just remove the element from the scene, as it's an empty just created text element
this.scene.replaceAllElements(
this.scene
.getElementsIncludingDeleted()
.filter((x) => x.id !== element.id),
);
} else {
updateElement(nextOriginalText, isDeleted);
}
// select the created text element only if submitting via keyboard
// (when submitting via click it should act as signal to deselect)
if (!isDeleted && viaKeyboard) {
@ -4954,9 +4964,10 @@ class App extends React.Component<AppProps, AppState> {
element,
]);
}
if (!isDeleted || isExistingElement) {
this.store.scheduleCapture();
}
// we need to record either way, whether the text element was added or removed
// since we need to sync this delta to other clients, otherwise it would end up with inconsistencies
this.store.scheduleCapture();
flushSync(() => {
this.setState({

View File

@ -704,7 +704,7 @@ describe("textWysiwyg", () => {
rectangle.x + rectangle.width / 2,
rectangle.y + rectangle.height / 2,
);
expect(h.elements.length).toBe(3);
expect(h.elements.length).toBe(2);
text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
@ -1198,7 +1198,7 @@ describe("textWysiwyg", () => {
updateTextEditor(editor, " ");
Keyboard.exitTextEditor(editor);
expect(rectangle.boundElements).toStrictEqual([]);
expect(h.elements[1].isDeleted).toBe(true);
expect(h.elements[1]).toBeUndefined();
});
it("should restore original container height and clear cache once text is unbind", async () => {