From a7281de15783d15132d6f380aa47a3e7ed3493d1 Mon Sep 17 00:00:00 2001 From: Ryan Di Date: Wed, 18 Mar 2026 19:18:05 +1100 Subject: [PATCH] fix: skip unsupported style updates --- .../actions/actionProperties.test.tsx | 77 ++++++++++++++++++- .../excalidraw/actions/actionProperties.tsx | 73 +++++++++++------- 2 files changed, 121 insertions(+), 29 deletions(-) diff --git a/packages/excalidraw/actions/actionProperties.test.tsx b/packages/excalidraw/actions/actionProperties.test.tsx index 30a465943c..c4c56e0607 100644 --- a/packages/excalidraw/actions/actionProperties.test.tsx +++ b/packages/excalidraw/actions/actionProperties.test.tsx @@ -10,7 +10,15 @@ import { import { Excalidraw } from "../index"; import { API } from "../tests/helpers/api"; import { UI } from "../tests/helpers/ui"; -import { render } from "../tests/test-utils"; +import { act, render } from "../tests/test-utils"; + +import { + actionChangeBackgroundColor, + actionChangeRoundness, + actionChangeStrokeWidth, +} from "./actionProperties"; + +const { h } = window; describe("element locking", () => { beforeEach(async () => { @@ -184,5 +192,72 @@ describe("element locking", () => { "active", ); }); + + it("should not update text background when changing background in mixed frame selection", () => { + const frame = API.createElement({ + type: "frame", + }); + const text = API.createElement({ + type: "text", + backgroundColor: COLOR_PALETTE.transparent, + }); + API.setElements([text, frame]); + API.setSelectedElements([text, frame]); + + act(() => { + h.app.actionManager.executeAction(actionChangeBackgroundColor, "ui", { + currentItemBackgroundColor: "#ffc9c9", + }); + }); + + expect(API.getElement(frame).backgroundColor).toBe("#ffc9c9"); + expect(API.getElement(text).backgroundColor).toBe( + COLOR_PALETTE.transparent, + ); + }); + + it("should not update frame stroke width when changing stroke width in mixed selection", () => { + const frame = API.createElement({ + type: "frame", + }); + const rect = API.createElement({ + type: "rectangle", + strokeWidth: STROKE_WIDTH.thin, + }); + API.setElements([rect, frame]); + API.setSelectedElements([rect, frame]); + + const originalFrameStrokeWidth = API.getElement(frame).strokeWidth; + + act(() => { + h.app.actionManager.executeAction( + actionChangeStrokeWidth, + "ui", + STROKE_WIDTH.extraBold, + ); + }); + + expect(API.getElement(rect).strokeWidth).toBe(STROKE_WIDTH.extraBold); + expect(API.getElement(frame).strokeWidth).toBe(originalFrameStrokeWidth); + }); + + it("should not update frame roundness when changing roundness in mixed selection", () => { + const frame = API.createElement({ + type: "frame", + }); + const rect = API.createElement({ + type: "rectangle", + roundness: null, + }); + API.setElements([rect, frame]); + API.setSelectedElements([rect, frame]); + + act(() => { + h.app.actionManager.executeAction(actionChangeRoundness, "ui", "round"); + }); + + expect(API.getElement(rect).roundness).not.toBe(null); + expect(API.getElement(frame).roundness).toBe(null); + }); }); }); diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 994cd3afa4..db3c5195a5 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -45,6 +45,7 @@ import { import { isArrowElement, isBoundToContainer, + isFrameElement, isElbowArrow, isLinearElement, isLineElement, @@ -52,7 +53,13 @@ import { isUsingAdaptiveRadius, } from "@excalidraw/element"; -import { hasStrokeColor } from "@excalidraw/element"; +import { + canChangeRoundness, + hasBackground, + hasStrokeColor, + hasStrokeStyle, + hasStrokeWidth, +} from "@excalidraw/element"; import { updateElbowArrowPoints, @@ -409,11 +416,13 @@ export const actionChangeBackgroundColor = register< return el; }); } else { - nextElements = changeProperty(elements, appState, (el) => - newElementWith(el, { - backgroundColor: value.currentItemBackgroundColor, - }), - ); + nextElements = changeProperty(elements, appState, (el) => { + return hasBackground(el.type) || isFrameElement(el) + ? newElementWith(el, { + backgroundColor: value.currentItemBackgroundColor, + }) + : el; + }); } return { @@ -476,11 +485,13 @@ export const actionChangeFillStyle = register({ })`, ); return { - elements: changeProperty(elements, appState, (el) => - newElementWith(el, { - fillStyle: value, - }), - ), + elements: changeProperty(elements, appState, (el) => { + return hasBackground(el.type) + ? newElementWith(el, { + fillStyle: value, + }) + : el; + }), appState: { ...appState, currentItemFillStyle: value }, captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; @@ -553,11 +564,13 @@ export const actionChangeStrokeWidth = register< trackEvent: false, perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => - newElementWith(el, { - strokeWidth: value, - }), - ), + elements: changeProperty(elements, appState, (el) => { + return hasStrokeWidth(el.type) + ? newElementWith(el, { + strokeWidth: value, + }) + : el; + }), appState: { ...appState, currentItemStrokeWidth: value }, captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; @@ -609,12 +622,14 @@ export const actionChangeSloppiness = register({ trackEvent: false, perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => - newElementWith(el, { - seed: randomInteger(), - roughness: value, - }), - ), + elements: changeProperty(elements, appState, (el) => { + return hasStrokeStyle(el.type) + ? newElementWith(el, { + seed: randomInteger(), + roughness: value, + }) + : el; + }), appState: { ...appState, currentItemRoughness: value }, captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; @@ -665,11 +680,13 @@ export const actionChangeStrokeStyle = register< trackEvent: false, perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => - newElementWith(el, { - strokeStyle: value, - }), - ), + elements: changeProperty(elements, appState, (el) => { + return hasStrokeStyle(el.type) + ? newElementWith(el, { + strokeStyle: value, + }) + : el; + }), appState: { ...appState, currentItemStrokeStyle: value }, captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; @@ -1481,7 +1498,7 @@ export const actionChangeRoundness = register<"sharp" | "round">({ perform: (elements, appState, value) => { return { elements: changeProperty(elements, appState, (el) => { - if (isElbowArrow(el)) { + if (isElbowArrow(el) || !canChangeRoundness(el.type)) { return el; }