mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
feat: create new text with width (#8038)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
4eb9463f26
commit
860308eb27
13 changed files with 178 additions and 33 deletions
|
@ -330,6 +330,7 @@ import {
|
||||||
getContainerElement,
|
getContainerElement,
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
getLineHeightInPx,
|
getLineHeightInPx,
|
||||||
|
getMinTextElementWidth,
|
||||||
isMeasureTextSupported,
|
isMeasureTextSupported,
|
||||||
isValidTextContainer,
|
isValidTextContainer,
|
||||||
measureText,
|
measureText,
|
||||||
|
@ -1696,6 +1697,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
canvas={this.interactiveCanvas}
|
canvas={this.interactiveCanvas}
|
||||||
elementsMap={elementsMap}
|
elementsMap={elementsMap}
|
||||||
visibleElements={visibleElements}
|
visibleElements={visibleElements}
|
||||||
|
allElementsMap={allElementsMap}
|
||||||
selectedElements={selectedElements}
|
selectedElements={selectedElements}
|
||||||
sceneNonce={sceneNonce}
|
sceneNonce={sceneNonce}
|
||||||
selectionNonce={
|
selectionNonce={
|
||||||
|
@ -4718,6 +4720,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
sceneY,
|
sceneY,
|
||||||
insertAtParentCenter = true,
|
insertAtParentCenter = true,
|
||||||
container,
|
container,
|
||||||
|
autoEdit = true,
|
||||||
}: {
|
}: {
|
||||||
/** X position to insert text at */
|
/** X position to insert text at */
|
||||||
sceneX: number;
|
sceneX: number;
|
||||||
|
@ -4726,6 +4729,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
/** whether to attempt to insert at element center if applicable */
|
/** whether to attempt to insert at element center if applicable */
|
||||||
insertAtParentCenter?: boolean;
|
insertAtParentCenter?: boolean;
|
||||||
container?: ExcalidrawTextContainer | null;
|
container?: ExcalidrawTextContainer | null;
|
||||||
|
autoEdit?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
let shouldBindToContainer = false;
|
let shouldBindToContainer = false;
|
||||||
|
|
||||||
|
@ -4858,13 +4862,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
if (autoEdit || existingTextElement || container) {
|
||||||
editingElement: element,
|
this.handleTextWysiwyg(element, {
|
||||||
});
|
isExistingElement: !!existingTextElement,
|
||||||
|
});
|
||||||
this.handleTextWysiwyg(element, {
|
} else {
|
||||||
isExistingElement: !!existingTextElement,
|
this.setState({
|
||||||
});
|
draggingElement: element,
|
||||||
|
multiElement: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleCanvasDoubleClick = (
|
private handleCanvasDoubleClick = (
|
||||||
|
@ -5899,7 +5906,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
if (this.state.activeTool.type === "text") {
|
if (this.state.activeTool.type === "text") {
|
||||||
this.handleTextOnPointerDown(event, pointerDownState);
|
this.handleTextOnPointerDown(event, pointerDownState);
|
||||||
return;
|
|
||||||
} else if (
|
} else if (
|
||||||
this.state.activeTool.type === "arrow" ||
|
this.state.activeTool.type === "arrow" ||
|
||||||
this.state.activeTool.type === "line"
|
this.state.activeTool.type === "line"
|
||||||
|
@ -6020,6 +6026,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
);
|
);
|
||||||
const clicklength =
|
const clicklength =
|
||||||
event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
|
event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
|
||||||
|
|
||||||
if (this.device.editor.isMobile && clicklength < 300) {
|
if (this.device.editor.isMobile && clicklength < 300) {
|
||||||
const hitElement = this.getElementAtPosition(
|
const hitElement = this.getElementAtPosition(
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
|
@ -6693,6 +6700,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
sceneY,
|
sceneY,
|
||||||
insertAtParentCenter: !event.altKey,
|
insertAtParentCenter: !event.altKey,
|
||||||
container,
|
container,
|
||||||
|
autoEdit: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
resetCursor(this.interactiveCanvas);
|
resetCursor(this.interactiveCanvas);
|
||||||
|
@ -8043,6 +8051,28 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTextElement(draggingElement)) {
|
||||||
|
const minWidth = getMinTextElementWidth(
|
||||||
|
getFontString({
|
||||||
|
fontSize: draggingElement.fontSize,
|
||||||
|
fontFamily: draggingElement.fontFamily,
|
||||||
|
}),
|
||||||
|
draggingElement.lineHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (draggingElement.width < minWidth) {
|
||||||
|
mutateElement(draggingElement, {
|
||||||
|
autoResize: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetCursor();
|
||||||
|
|
||||||
|
this.handleTextWysiwyg(draggingElement, {
|
||||||
|
isExistingElement: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeTool.type !== "selection" &&
|
activeTool.type !== "selection" &&
|
||||||
draggingElement &&
|
draggingElement &&
|
||||||
|
@ -9410,6 +9440,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
distance(pointerDownState.origin.y, pointerCoords.y),
|
distance(pointerDownState.origin.y, pointerCoords.y),
|
||||||
shouldMaintainAspectRatio(event),
|
shouldMaintainAspectRatio(event),
|
||||||
shouldResizeFromCenter(event),
|
shouldResizeFromCenter(event),
|
||||||
|
this.state.zoom.value,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let [gridX, gridY] = getGridPoint(
|
let [gridX, gridY] = getGridPoint(
|
||||||
|
@ -9467,6 +9498,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
? !shouldMaintainAspectRatio(event)
|
? !shouldMaintainAspectRatio(event)
|
||||||
: shouldMaintainAspectRatio(event),
|
: shouldMaintainAspectRatio(event),
|
||||||
shouldResizeFromCenter(event),
|
shouldResizeFromCenter(event),
|
||||||
|
this.state.zoom.value,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
this.state.originSnapOffset,
|
this.state.originSnapOffset,
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,10 @@ import type {
|
||||||
RenderableElementsMap,
|
RenderableElementsMap,
|
||||||
RenderInteractiveSceneCallback,
|
RenderInteractiveSceneCallback,
|
||||||
} from "../../scene/types";
|
} from "../../scene/types";
|
||||||
import type { NonDeletedExcalidrawElement } from "../../element/types";
|
import type {
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
NonDeletedSceneElementsMap,
|
||||||
|
} from "../../element/types";
|
||||||
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
||||||
import { renderInteractiveScene } from "../../renderer/interactiveScene";
|
import { renderInteractiveScene } from "../../renderer/interactiveScene";
|
||||||
|
|
||||||
|
@ -19,6 +22,7 @@ type InteractiveCanvasProps = {
|
||||||
elementsMap: RenderableElementsMap;
|
elementsMap: RenderableElementsMap;
|
||||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[];
|
selectedElements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
allElementsMap: NonDeletedSceneElementsMap;
|
||||||
sceneNonce: number | undefined;
|
sceneNonce: number | undefined;
|
||||||
selectionNonce: number | undefined;
|
selectionNonce: number | undefined;
|
||||||
scale: number;
|
scale: number;
|
||||||
|
@ -122,6 +126,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
|
||||||
elementsMap: props.elementsMap,
|
elementsMap: props.elementsMap,
|
||||||
visibleElements: props.visibleElements,
|
visibleElements: props.visibleElements,
|
||||||
selectedElements: props.selectedElements,
|
selectedElements: props.selectedElements,
|
||||||
|
allElementsMap: props.allElementsMap,
|
||||||
scale: window.devicePixelRatio,
|
scale: window.devicePixelRatio,
|
||||||
appState: props.appState,
|
appState: props.appState,
|
||||||
renderConfig: {
|
renderConfig: {
|
||||||
|
@ -197,6 +202,7 @@ const getRelevantAppStateProps = (
|
||||||
activeEmbeddable: appState.activeEmbeddable,
|
activeEmbeddable: appState.activeEmbeddable,
|
||||||
snapLines: appState.snapLines,
|
snapLines: appState.snapLines,
|
||||||
zenModeEnabled: appState.zenModeEnabled,
|
zenModeEnabled: appState.zenModeEnabled,
|
||||||
|
editingElement: appState.editingElement,
|
||||||
});
|
});
|
||||||
|
|
||||||
const areEqual = (
|
const areEqual = (
|
||||||
|
|
|
@ -25,6 +25,11 @@ export const supportsResizeObserver =
|
||||||
|
|
||||||
export const APP_NAME = "Excalidraw";
|
export const APP_NAME = "Excalidraw";
|
||||||
|
|
||||||
|
// distance when creating text before it's considered `autoResize: false`
|
||||||
|
// we're using higher threshold so that clicks that end up being drags
|
||||||
|
// don't unintentionally create text elements that are wrapped to a few chars
|
||||||
|
// (happens a lot with fast clicks with the text tool)
|
||||||
|
export const TEXT_AUTOWRAP_THRESHOLD = 36; // px
|
||||||
export const DRAGGING_THRESHOLD = 10; // px
|
export const DRAGGING_THRESHOLD = 10; // px
|
||||||
export const LINE_CONFIRM_THRESHOLD = 8; // px
|
export const LINE_CONFIRM_THRESHOLD = 8; // px
|
||||||
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
||||||
|
|
|
@ -4,11 +4,17 @@ import { getCommonBounds } from "./bounds";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { getPerfectElementSize } from "./sizeHelpers";
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
import type { NonDeletedExcalidrawElement } from "./types";
|
import type { NonDeletedExcalidrawElement } from "./types";
|
||||||
import type { AppState, PointerDownState } from "../types";
|
import type { AppState, NormalizedZoomValue, PointerDownState } from "../types";
|
||||||
import { getBoundTextElement } from "./textElement";
|
import { getBoundTextElement, getMinTextElementWidth } from "./textElement";
|
||||||
import { getGridPoint } from "../math";
|
import { getGridPoint } from "../math";
|
||||||
import type Scene from "../scene/Scene";
|
import type Scene from "../scene/Scene";
|
||||||
import { isArrowElement, isFrameLikeElement } from "./typeChecks";
|
import {
|
||||||
|
isArrowElement,
|
||||||
|
isFrameLikeElement,
|
||||||
|
isTextElement,
|
||||||
|
} from "./typeChecks";
|
||||||
|
import { getFontString } from "../utils";
|
||||||
|
import { TEXT_AUTOWRAP_THRESHOLD } from "../constants";
|
||||||
|
|
||||||
export const dragSelectedElements = (
|
export const dragSelectedElements = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
|
@ -140,6 +146,7 @@ export const dragNewElement = (
|
||||||
height: number,
|
height: number,
|
||||||
shouldMaintainAspectRatio: boolean,
|
shouldMaintainAspectRatio: boolean,
|
||||||
shouldResizeFromCenter: boolean,
|
shouldResizeFromCenter: boolean,
|
||||||
|
zoom: NormalizedZoomValue,
|
||||||
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
||||||
true */
|
true */
|
||||||
widthAspectRatio?: number | null,
|
widthAspectRatio?: number | null,
|
||||||
|
@ -185,12 +192,41 @@ export const dragNewElement = (
|
||||||
newY = originY - height / 2;
|
newY = originY - height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let textAutoResize = null;
|
||||||
|
|
||||||
|
// NOTE this should apply only to creating text elements, not existing
|
||||||
|
// (once we rewrite appState.draggingElement to actually mean dragging
|
||||||
|
// elements)
|
||||||
|
if (isTextElement(draggingElement)) {
|
||||||
|
height = draggingElement.height;
|
||||||
|
const minWidth = getMinTextElementWidth(
|
||||||
|
getFontString({
|
||||||
|
fontSize: draggingElement.fontSize,
|
||||||
|
fontFamily: draggingElement.fontFamily,
|
||||||
|
}),
|
||||||
|
draggingElement.lineHeight,
|
||||||
|
);
|
||||||
|
width = Math.max(width, minWidth);
|
||||||
|
|
||||||
|
if (Math.abs(x - originX) > TEXT_AUTOWRAP_THRESHOLD / zoom) {
|
||||||
|
textAutoResize = {
|
||||||
|
autoResize: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
newY = originY;
|
||||||
|
if (shouldResizeFromCenter) {
|
||||||
|
newX = originX - width / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (width !== 0 && height !== 0) {
|
if (width !== 0 && height !== 0) {
|
||||||
mutateElement(draggingElement, {
|
mutateElement(draggingElement, {
|
||||||
x: newX + (originOffset?.x ?? 0),
|
x: newX + (originOffset?.x ?? 0),
|
||||||
y: newY + (originOffset?.y ?? 0),
|
y: newY + (originOffset?.y ?? 0),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
...textAutoResize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
|
||||||
BOUND_TEXT_PADDING,
|
|
||||||
MIN_FONT_SIZE,
|
|
||||||
SHIFT_LOCKING_ANGLE,
|
|
||||||
} from "../constants";
|
|
||||||
import { rescalePoints } from "../points";
|
import { rescalePoints } from "../points";
|
||||||
|
|
||||||
import { rotate, centerPoint, rotatePoint } from "../math";
|
import { rotate, centerPoint, rotatePoint } from "../math";
|
||||||
|
@ -51,7 +47,7 @@ import {
|
||||||
getApproxMinLineHeight,
|
getApproxMinLineHeight,
|
||||||
wrapText,
|
wrapText,
|
||||||
measureText,
|
measureText,
|
||||||
getMinCharWidth,
|
getMinTextElementWidth,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { isInGroup } from "../groups";
|
import { isInGroup } from "../groups";
|
||||||
|
@ -352,8 +348,13 @@ const resizeSingleTextElement = (
|
||||||
const boundsCurrentWidth = esx2 - esx1;
|
const boundsCurrentWidth = esx2 - esx1;
|
||||||
|
|
||||||
const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
|
const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
|
||||||
const minWidth =
|
const minWidth = getMinTextElementWidth(
|
||||||
getMinCharWidth(getFontString(element)) + BOUND_TEXT_PADDING * 2;
|
getFontString({
|
||||||
|
fontSize: element.fontSize,
|
||||||
|
fontFamily: element.fontFamily,
|
||||||
|
}),
|
||||||
|
element.lineHeight,
|
||||||
|
);
|
||||||
|
|
||||||
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
||||||
|
|
||||||
|
|
|
@ -938,3 +938,10 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||||
}
|
}
|
||||||
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMinTextElementWidth = (
|
||||||
|
font: FontString,
|
||||||
|
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||||
|
) => {
|
||||||
|
return measureText("", font, lineHeight).width + BOUND_TEXT_PADDING * 2;
|
||||||
|
};
|
||||||
|
|
|
@ -576,7 +576,7 @@ describe("textWysiwyg", () => {
|
||||||
|
|
||||||
it("text should never go beyond max width", async () => {
|
it("text should never go beyond max width", async () => {
|
||||||
UI.clickTool("text");
|
UI.clickTool("text");
|
||||||
mouse.clickAt(750, 300);
|
mouse.click(0, 0);
|
||||||
|
|
||||||
textarea = await getTextEditor(textEditorSelector, true);
|
textarea = await getTextEditor(textEditorSelector, true);
|
||||||
updateTextEditor(
|
updateTextEditor(
|
||||||
|
|
|
@ -47,13 +47,18 @@ import {
|
||||||
getNormalizedCanvasDimensions,
|
getNormalizedCanvasDimensions,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { isFrameLikeElement, isLinearElement } from "../element/typeChecks";
|
import {
|
||||||
|
isFrameLikeElement,
|
||||||
|
isLinearElement,
|
||||||
|
isTextElement,
|
||||||
|
} from "../element/typeChecks";
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawTextElement,
|
||||||
GroupId,
|
GroupId,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
|
@ -303,7 +308,6 @@ const renderSelectionBorder = (
|
||||||
cy: number;
|
cy: number;
|
||||||
activeEmbeddable: boolean;
|
activeEmbeddable: boolean;
|
||||||
},
|
},
|
||||||
padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2,
|
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
angle,
|
angle,
|
||||||
|
@ -320,6 +324,8 @@ const renderSelectionBorder = (
|
||||||
const elementWidth = elementX2 - elementX1;
|
const elementWidth = elementX2 - elementX1;
|
||||||
const elementHeight = elementY2 - elementY1;
|
const elementHeight = elementY2 - elementY1;
|
||||||
|
|
||||||
|
const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
||||||
|
|
||||||
const linePadding = padding / appState.zoom.value;
|
const linePadding = padding / appState.zoom.value;
|
||||||
const lineWidth = 8 / appState.zoom.value;
|
const lineWidth = 8 / appState.zoom.value;
|
||||||
const spaceWidth = 4 / appState.zoom.value;
|
const spaceWidth = 4 / appState.zoom.value;
|
||||||
|
@ -570,11 +576,34 @@ const renderTransformHandles = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderTextBox = (
|
||||||
|
text: NonDeleted<ExcalidrawTextElement>,
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
appState: InteractiveCanvasAppState,
|
||||||
|
selectionColor: InteractiveCanvasRenderConfig["selectionColor"],
|
||||||
|
) => {
|
||||||
|
context.save();
|
||||||
|
const padding = (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
||||||
|
const width = text.width + padding * 2;
|
||||||
|
const height = text.height + padding * 2;
|
||||||
|
const cx = text.x + width / 2;
|
||||||
|
const cy = text.y + height / 2;
|
||||||
|
const shiftX = -(width / 2 + padding);
|
||||||
|
const shiftY = -(height / 2 + padding);
|
||||||
|
context.translate(cx + appState.scrollX, cy + appState.scrollY);
|
||||||
|
context.rotate(text.angle);
|
||||||
|
context.lineWidth = 1 / appState.zoom.value;
|
||||||
|
context.strokeStyle = selectionColor;
|
||||||
|
context.strokeRect(shiftX, shiftY, width, height);
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
const _renderInteractiveScene = ({
|
const _renderInteractiveScene = ({
|
||||||
canvas,
|
canvas,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
visibleElements,
|
visibleElements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
|
allElementsMap,
|
||||||
scale,
|
scale,
|
||||||
appState,
|
appState,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
|
@ -626,12 +655,31 @@ const _renderInteractiveScene = ({
|
||||||
// Paint selection element
|
// Paint selection element
|
||||||
if (appState.selectionElement) {
|
if (appState.selectionElement) {
|
||||||
try {
|
try {
|
||||||
renderSelectionElement(appState.selectionElement, context, appState);
|
renderSelectionElement(
|
||||||
|
appState.selectionElement,
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
renderConfig.selectionColor,
|
||||||
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.editingElement && isTextElement(appState.editingElement)) {
|
||||||
|
const textElement = allElementsMap.get(appState.editingElement.id) as
|
||||||
|
| ExcalidrawTextElement
|
||||||
|
| undefined;
|
||||||
|
if (textElement && !textElement.autoResize) {
|
||||||
|
renderTextBox(
|
||||||
|
textElement,
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
renderConfig.selectionColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (appState.isBindingEnabled) {
|
if (appState.isBindingEnabled) {
|
||||||
appState.suggestedBindings
|
appState.suggestedBindings
|
||||||
.filter((binding) => binding != null)
|
.filter((binding) => binding != null)
|
||||||
|
@ -810,7 +858,12 @@ const _renderInteractiveScene = ({
|
||||||
"mouse", // when we render we don't know which pointer type so use mouse,
|
"mouse", // when we render we don't know which pointer type so use mouse,
|
||||||
getOmitSidesForDevice(device),
|
getOmitSidesForDevice(device),
|
||||||
);
|
);
|
||||||
if (!appState.viewModeEnabled && showBoundingBox) {
|
if (
|
||||||
|
!appState.viewModeEnabled &&
|
||||||
|
showBoundingBox &&
|
||||||
|
// do not show transform handles when text is being edited
|
||||||
|
!isTextElement(appState.editingElement)
|
||||||
|
) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
context,
|
context,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import type {
|
import type {
|
||||||
StaticCanvasRenderConfig,
|
StaticCanvasRenderConfig,
|
||||||
RenderableElementsMap,
|
RenderableElementsMap,
|
||||||
|
InteractiveCanvasRenderConfig,
|
||||||
} from "../scene/types";
|
} from "../scene/types";
|
||||||
import { distance, getFontString, isRTL } from "../utils";
|
import { distance, getFontString, isRTL } from "../utils";
|
||||||
import { getCornerRadius, isRightAngle } from "../math";
|
import { getCornerRadius, isRightAngle } from "../math";
|
||||||
|
@ -618,6 +619,7 @@ export const renderSelectionElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
|
selectionColor: InteractiveCanvasRenderConfig["selectionColor"],
|
||||||
) => {
|
) => {
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(element.x + appState.scrollX, element.y + appState.scrollY);
|
context.translate(element.x + appState.scrollX, element.y + appState.scrollY);
|
||||||
|
@ -631,7 +633,7 @@ export const renderSelectionElement = (
|
||||||
|
|
||||||
context.fillRect(offset, offset, element.width, element.height);
|
context.fillRect(offset, offset, element.width, element.height);
|
||||||
context.lineWidth = 1 / appState.zoom.value;
|
context.lineWidth = 1 / appState.zoom.value;
|
||||||
context.strokeStyle = " rgb(105, 101, 219)";
|
context.strokeStyle = selectionColor;
|
||||||
context.strokeRect(offset, offset, element.width, element.height);
|
context.strokeRect(offset, offset, element.width, element.height);
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
|
@ -55,7 +55,7 @@ export type InteractiveCanvasRenderConfig = {
|
||||||
remotePointerUserStates: Map<SocketId, UserIdleState>;
|
remotePointerUserStates: Map<SocketId, UserIdleState>;
|
||||||
remotePointerUsernames: Map<SocketId, string>;
|
remotePointerUsernames: Map<SocketId, string>;
|
||||||
remotePointerButton: Map<SocketId, string | undefined>;
|
remotePointerButton: Map<SocketId, string | undefined>;
|
||||||
selectionColor?: string;
|
selectionColor: string;
|
||||||
// extra options passed to the renderer
|
// extra options passed to the renderer
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
renderScrollbars?: boolean;
|
renderScrollbars?: boolean;
|
||||||
|
@ -83,6 +83,7 @@ export type InteractiveSceneRenderConfig = {
|
||||||
elementsMap: RenderableElementsMap;
|
elementsMap: RenderableElementsMap;
|
||||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[];
|
selectedElements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
allElementsMap: NonDeletedSceneElementsMap;
|
||||||
scale: number;
|
scale: number;
|
||||||
appState: InteractiveCanvasAppState;
|
appState: InteractiveCanvasAppState;
|
||||||
renderConfig: InteractiveCanvasRenderConfig;
|
renderConfig: InteractiveCanvasRenderConfig;
|
||||||
|
|
|
@ -1375,6 +1375,7 @@ export const isActiveToolNonLinearSnappable = (
|
||||||
activeToolType === TOOL_TYPE.diamond ||
|
activeToolType === TOOL_TYPE.diamond ||
|
||||||
activeToolType === TOOL_TYPE.frame ||
|
activeToolType === TOOL_TYPE.frame ||
|
||||||
activeToolType === TOOL_TYPE.magicframe ||
|
activeToolType === TOOL_TYPE.magicframe ||
|
||||||
activeToolType === TOOL_TYPE.image
|
activeToolType === TOOL_TYPE.image ||
|
||||||
|
activeToolType === TOOL_TYPE.text
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1051,11 +1051,11 @@ describe("Test Linear Elements", () => {
|
||||||
arrayToMap(h.elements),
|
arrayToMap(h.elements),
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": 75,
|
||||||
"y": 60,
|
"y": 60,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
expect(textElement.text).toMatchInlineSnapshot(`
|
expect(textElement.text).toMatchInlineSnapshot(`
|
||||||
"Online whiteboard
|
"Online whiteboard
|
||||||
collaboration made
|
collaboration made
|
||||||
|
|
|
@ -197,6 +197,7 @@ export type InteractiveCanvasAppState = Readonly<
|
||||||
// SnapLines
|
// SnapLines
|
||||||
snapLines: AppState["snapLines"];
|
snapLines: AppState["snapLines"];
|
||||||
zenModeEnabled: AppState["zenModeEnabled"];
|
zenModeEnabled: AppState["zenModeEnabled"];
|
||||||
|
editingElement: AppState["editingElement"];
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue