Compare commits

...

4 Commits

Author SHA1 Message Date
Mark Tolmacs 54a123e7c0 Merge branch 'master' into mtolmacs/fix/z-ordering
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-06-04 16:37:21 +00:00
David Luzar 3372149277 feat(packages/excalidraw): export applyDarkModeFilter and simplify (#11429) 2026-06-01 15:43:45 +02:00
Mark Tolmacs fed4af4381 fix: Move to top or bottom
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-04-25 16:04:14 +00:00
Mark Tolmacs e97c3704eb fix: Z-orddering with frames
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-04-23 19:38:05 +00:00
10 changed files with 74 additions and 89 deletions
+5 -1
View File
@@ -80,7 +80,11 @@ const cssInvert = (
return { r: invertedR, g: invertedG, b: invertedB }; return { r: invertedR, g: invertedG, b: invertedB };
}; };
export const applyDarkModeFilter = (color: string): string => { export const applyDarkModeFilter = (color: string, enable = true): string => {
if (!enable) {
return color;
}
const cached = DARK_MODE_COLORS_CACHE?.get(color); const cached = DARK_MODE_COLORS_CACHE?.get(color);
if (cached) { if (cached) {
return cached; return cached;
+12 -12
View File
@@ -422,10 +422,10 @@ const drawElementOnCanvas = (
for (const shape of shapes) { for (const shape of shapes) {
if (typeof shape === "string") { if (typeof shape === "string") {
context.fillStyle = context.fillStyle = applyDarkModeFilter(
renderConfig.theme === THEME.DARK element.strokeColor,
? applyDarkModeFilter(element.strokeColor) renderConfig.theme === THEME.DARK,
: element.strokeColor; );
context.fill(new Path2D(shape)); context.fill(new Path2D(shape));
} else { } else {
rc.draw(shape); rc.draw(shape);
@@ -555,10 +555,10 @@ const drawElementOnCanvas = (
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr"); context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
context.save(); context.save();
context.font = getFontString(element); context.font = getFontString(element);
context.fillStyle = context.fillStyle = applyDarkModeFilter(
renderConfig.theme === THEME.DARK element.strokeColor,
? applyDarkModeFilter(element.strokeColor) renderConfig.theme === THEME.DARK,
: element.strokeColor; );
context.textAlign = element.textAlign as CanvasTextAlign; context.textAlign = element.textAlign as CanvasTextAlign;
// Canvas does not support multiline text by default // Canvas does not support multiline text by default
@@ -811,10 +811,10 @@ export const renderElement = (
context.fillStyle = "rgba(0, 0, 200, 0.04)"; context.fillStyle = "rgba(0, 0, 200, 0.04)";
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value; context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
context.strokeStyle = context.strokeStyle = applyDarkModeFilter(
appState.theme === THEME.DARK FRAME_STYLE.strokeColor,
? applyDarkModeFilter(FRAME_STYLE.strokeColor) appState.theme === THEME.DARK,
: FRAME_STYLE.strokeColor; );
// TODO change later to only affect AI frames // TODO change later to only affect AI frames
if (isMagicFrameElement(element)) { if (isMagicFrameElement(element)) {
+8 -15
View File
@@ -218,9 +218,7 @@ export const generateRoughOptions = (
fillWeight: element.strokeWidth / 2, fillWeight: element.strokeWidth / 2,
hachureGap: element.strokeWidth * 4, hachureGap: element.strokeWidth * 4,
roughness: adjustRoughness(element), roughness: adjustRoughness(element),
stroke: isDarkMode stroke: applyDarkModeFilter(element.strokeColor, isDarkMode),
? applyDarkModeFilter(element.strokeColor)
: element.strokeColor,
preserveVertices: preserveVertices:
continuousPath || element.roughness < ROUGHNESS.cartoonist, continuousPath || element.roughness < ROUGHNESS.cartoonist,
}; };
@@ -234,9 +232,7 @@ export const generateRoughOptions = (
options.fillStyle = element.fillStyle; options.fillStyle = element.fillStyle;
options.fill = isTransparent(element.backgroundColor) options.fill = isTransparent(element.backgroundColor)
? undefined ? undefined
: isDarkMode : applyDarkModeFilter(element.backgroundColor, isDarkMode);
? applyDarkModeFilter(element.backgroundColor)
: element.backgroundColor;
if (element.type === "ellipse") { if (element.type === "ellipse") {
options.curveFitting = 1; options.curveFitting = 1;
} }
@@ -249,9 +245,7 @@ export const generateRoughOptions = (
options.fill = options.fill =
element.backgroundColor === "transparent" element.backgroundColor === "transparent"
? undefined ? undefined
: isDarkMode : applyDarkModeFilter(element.backgroundColor, isDarkMode);
? applyDarkModeFilter(element.backgroundColor)
: element.backgroundColor;
} }
return options; return options;
} }
@@ -386,12 +380,11 @@ const getArrowheadShapes = (
return []; return [];
} }
const strokeColor = isDarkMode const strokeColor = applyDarkModeFilter(element.strokeColor, isDarkMode);
? applyDarkModeFilter(element.strokeColor) const backgroundFillColor = applyDarkModeFilter(
: element.strokeColor; canvasBackgroundColor,
const backgroundFillColor = isDarkMode isDarkMode,
? applyDarkModeFilter(canvasBackgroundColor) );
: canvasBackgroundColor;
const cardinalityOneOrManyOffset = -0.25; const cardinalityOneOrManyOffset = -0.25;
const cardinalityZeroCircleScale = 0.8; const cardinalityZeroCircleScale = 0.8;
+21 -38
View File
@@ -409,8 +409,10 @@ const shiftElementsToEnd = (
let trailingIndex: number; let trailingIndex: number;
if (direction === "left") { if (direction === "left") {
if (containingFrame) { if (containingFrame) {
leadingIndex = findIndex(elements, (el) => leadingIndex = findIndex(
isOfTargetFrame(el, containingFrame), elements,
(el) =>
el.id !== containingFrame && isOfTargetFrame(el, containingFrame),
); );
} else if (appState.editingGroupId) { } else if (appState.editingGroupId) {
const groupElements = getElementsInGroup( const groupElements = getElementsInGroup(
@@ -428,8 +430,10 @@ const shiftElementsToEnd = (
trailingIndex = indicesToMove[indicesToMove.length - 1]; trailingIndex = indicesToMove[indicesToMove.length - 1];
} else { } else {
if (containingFrame) { if (containingFrame) {
trailingIndex = findLastIndex(elements, (el) => trailingIndex = findLastIndex(
isOfTargetFrame(el, containingFrame), elements,
(el) =>
el.id !== containingFrame && isOfTargetFrame(el, containingFrame),
); );
} else if (appState.editingGroupId) { } else if (appState.editingGroupId) {
const groupElements = getElementsInGroup( const groupElements = getElementsInGroup(
@@ -499,10 +503,7 @@ function shiftElementsAccountingForFrames(
}), }),
); );
const frameAwareContiguousElementsToMove: { const regularElements: ExcalidrawElement[] = [];
regularElements: ExcalidrawElement[];
frameChildren: Map<ExcalidrawFrameLikeElement["id"], ExcalidrawElement[]>;
} = { regularElements: [], frameChildren: new Map() };
const fullySelectedFrames = new Set<ExcalidrawFrameLikeElement["id"]>(); const fullySelectedFrames = new Set<ExcalidrawFrameLikeElement["id"]>();
@@ -512,51 +513,33 @@ function shiftElementsAccountingForFrames(
} }
} }
const selectedFrameIds = new Set<ExcalidrawFrameLikeElement["id"]>();
for (const element of allElements) { for (const element of allElements) {
if (elementsToMove.has(element.id)) { if (elementsToMove.has(element.id)) {
if ( if (
isFrameLikeElement(element) || isFrameLikeElement(element) ||
(element.frameId && fullySelectedFrames.has(element.frameId)) (element.frameId && fullySelectedFrames.has(element.frameId))
) { ) {
frameAwareContiguousElementsToMove.regularElements.push(element); regularElements.push(element);
} else if (!element.frameId) { } else if (!element.frameId) {
frameAwareContiguousElementsToMove.regularElements.push(element); regularElements.push(element);
} else { } else if (!selectedFrameIds.has(element.frameId)) {
const frameChildren = selectedFrameIds.add(element.frameId);
frameAwareContiguousElementsToMove.frameChildren.get( regularElements.push(element);
element.frameId,
) || [];
frameChildren.push(element);
frameAwareContiguousElementsToMove.frameChildren.set(
element.frameId,
frameChildren,
);
} }
} }
} }
let nextElements = allElements; const containingFrame =
selectedFrameIds.size > 0 ? [...selectedFrameIds][0] : null;
const frameChildrenSets = Array.from(
frameAwareContiguousElementsToMove.frameChildren.entries(),
);
for (const [frameId, children] of frameChildrenSets) {
nextElements = shiftFunction(
allElements,
appState,
direction,
frameId,
children,
);
}
return shiftFunction( return shiftFunction(
nextElements, allElements,
appState, appState,
direction, direction,
null, containingFrame,
frameAwareContiguousElementsToMove.regularElements, regularElements,
); );
} }
+4 -3
View File
@@ -1995,9 +1995,10 @@ class App extends React.Component<AppProps, AppState> {
} }
}} }}
style={{ style={{
background: isDarkTheme background: applyDarkModeFilter(
? applyDarkModeFilter(this.state.viewBackgroundColor) this.state.viewBackgroundColor,
: this.state.viewBackgroundColor, isDarkTheme,
),
zIndex: 2, zIndex: 2,
border: "none", border: "none",
display: "block", display: "block",
+3
View File
@@ -7,6 +7,7 @@ import React, {
} from "react"; } from "react";
import { import {
applyDarkModeFilter,
DEFAULT_IMAGE_OPTIONS, DEFAULT_IMAGE_OPTIONS,
DEFAULT_UI_OPTIONS, DEFAULT_UI_OPTIONS,
isShallowEqual, isShallowEqual,
@@ -450,3 +451,5 @@ export function useExcalidrawStateValue(
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export { _useOnAppStateChange as useOnExcalidrawStateChange }; export { _useOnAppStateChange as useOnExcalidrawStateChange };
export { applyDarkModeFilter };
+4 -4
View File
@@ -62,10 +62,10 @@ export const bootstrapCanvas = ({
context.clearRect(0, 0, normalizedWidth, normalizedHeight); context.clearRect(0, 0, normalizedWidth, normalizedHeight);
} }
context.save(); context.save();
context.fillStyle = context.fillStyle = applyDarkModeFilter(
theme === THEME.DARK viewBackgroundColor,
? applyDarkModeFilter(viewBackgroundColor) theme === THEME.DARK,
: viewBackgroundColor; );
context.fillRect(0, 0, normalizedWidth, normalizedHeight); context.fillRect(0, 0, normalizedWidth, normalizedHeight);
context.restore(); context.restore();
} else { } else {
+12 -9
View File
@@ -386,9 +386,10 @@ const renderElementToSvg = (
const path = svgRoot.ownerDocument.createElementNS(SVG_NS, "path"); const path = svgRoot.ownerDocument.createElementNS(SVG_NS, "path");
path.setAttribute( path.setAttribute(
"fill", "fill",
renderConfig.theme === THEME.DARK applyDarkModeFilter(
? applyDarkModeFilter(element.strokeColor) element.strokeColor,
: element.strokeColor, renderConfig.theme === THEME.DARK,
),
); );
path.setAttribute("d", shape); path.setAttribute("d", shape);
wrapper.appendChild(path); wrapper.appendChild(path);
@@ -621,9 +622,10 @@ const renderElementToSvg = (
rect.setAttribute("fill", "none"); rect.setAttribute("fill", "none");
rect.setAttribute( rect.setAttribute(
"stroke", "stroke",
renderConfig.theme === THEME.DARK applyDarkModeFilter(
? applyDarkModeFilter(FRAME_STYLE.strokeColor) FRAME_STYLE.strokeColor,
: FRAME_STYLE.strokeColor, renderConfig.theme === THEME.DARK,
),
); );
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString()); rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
@@ -677,9 +679,10 @@ const renderElementToSvg = (
text.setAttribute("font-size", `${element.fontSize}px`); text.setAttribute("font-size", `${element.fontSize}px`);
text.setAttribute( text.setAttribute(
"fill", "fill",
renderConfig.theme === THEME.DARK applyDarkModeFilter(
? applyDarkModeFilter(element.strokeColor) element.strokeColor,
: element.strokeColor, renderConfig.theme === THEME.DARK,
),
); );
text.setAttribute("text-anchor", textAnchor); text.setAttribute("text-anchor", textAnchor);
text.setAttribute("style", "white-space: pre;"); text.setAttribute("style", "white-space: pre;");
+1 -3
View File
@@ -459,9 +459,7 @@ export const exportToSvg = async (
rect.setAttribute("height", `${height}`); rect.setAttribute("height", `${height}`);
rect.setAttribute( rect.setAttribute(
"fill", "fill",
exportWithDarkMode applyDarkModeFilter(viewBackgroundColor, exportWithDarkMode),
? applyDarkModeFilter(viewBackgroundColor)
: viewBackgroundColor,
); );
svgRoot.appendChild(rect); svgRoot.appendChild(rect);
} }
+4 -4
View File
@@ -392,10 +392,10 @@ export const textWysiwyg = ({
), ),
textAlign, textAlign,
verticalAlign, verticalAlign,
color: color: applyDarkModeFilter(
appState.theme === THEME.DARK updatedTextElement.strokeColor,
? applyDarkModeFilter(updatedTextElement.strokeColor) appState.theme === THEME.DARK,
: updatedTextElement.strokeColor, ),
opacity: updatedTextElement.opacity / 100, opacity: updatedTextElement.opacity / 100,
maxHeight: `${editorMaxHeight}px`, maxHeight: `${editorMaxHeight}px`,
}); });