@excalidraw/excalidraw 0.17.1-1ed53b1 → 0.17.1-2f9526d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-JKPJV7MZ.js → chunk-Q6A4M3MN.js} +4 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-Q6A4M3MN.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-OKAZAA6U.js → chunk-Q6NFAEKN.js} +156 -74
- package/dist/browser/dev/excalidraw-assets-dev/chunk-Q6NFAEKN.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{dist-ITJNUBZF.js → dist-6QVAH5JA.js} +36 -14
- package/dist/browser/dev/excalidraw-assets-dev/dist-6QVAH5JA.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-BF4XUPIZ.js → en-Y27YPU72.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-LVS32KQQ.js → image-Y5X7K6KW.js} +2 -2
- package/dist/browser/dev/index.js +159 -86
- package/dist/browser/dev/index.js.map +3 -3
- package/dist/browser/prod/excalidraw-assets/{chunk-O4AI3NNG.js → chunk-IZMZ6RPD.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/chunk-MDMKPHYD.js +55 -0
- package/dist/browser/prod/excalidraw-assets/dist-567JAXHK.js +7 -0
- package/dist/browser/prod/excalidraw-assets/{en-N7CLNF6C.js → en-GSUSWMSH.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-7MVXYJUE.js +1 -0
- package/dist/browser/prod/index.js +14 -14
- package/dist/dev/{en-UQDDYCH7.json → en-OIPCBIOA.json} +3 -1
- package/dist/dev/index.js +320 -157
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionBoundText.js +3 -1
- package/dist/excalidraw/actions/actionHistory.js +4 -4
- package/dist/excalidraw/actions/actionProperties.js +1 -1
- package/dist/excalidraw/actions/actionTextAutoResize.d.ts +17 -0
- package/dist/excalidraw/actions/actionTextAutoResize.js +38 -0
- package/dist/excalidraw/actions/types.d.ts +1 -1
- package/dist/excalidraw/components/Actions.js +1 -1
- package/dist/excalidraw/components/App.d.ts +1 -1
- package/dist/excalidraw/components/App.js +47 -32
- package/dist/excalidraw/components/ButtonIconSelect.js +1 -1
- package/dist/excalidraw/components/CheckboxItem.js +1 -1
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +2 -2
- package/dist/excalidraw/components/ContextMenu.js +1 -1
- package/dist/excalidraw/components/Dialog.js +1 -1
- package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
- package/dist/excalidraw/components/IconPicker.js +2 -2
- package/dist/excalidraw/components/LayerUI.js +2 -2
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/PasteChartDialog.js +1 -1
- package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +1 -1
- package/dist/excalidraw/components/canvases/InteractiveCanvas.js +2 -2
- package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +1 -1
- package/dist/excalidraw/components/canvases/StaticCanvas.js +2 -2
- package/dist/excalidraw/components/icons.js +6 -2
- package/dist/excalidraw/data/restore.js +1 -0
- package/dist/excalidraw/element/index.d.ts +1 -1
- package/dist/excalidraw/element/index.js +1 -1
- package/dist/excalidraw/element/mutateElement.d.ts +1 -1
- package/dist/excalidraw/element/mutateElement.js +5 -3
- package/dist/excalidraw/element/newElement.d.ts +0 -5
- package/dist/excalidraw/element/newElement.js +15 -13
- package/dist/excalidraw/element/resizeElements.js +71 -22
- package/dist/excalidraw/element/resizeTest.js +2 -4
- package/dist/excalidraw/element/textElement.js +8 -3
- package/dist/excalidraw/element/textWysiwyg.d.ts +8 -3
- package/dist/excalidraw/element/textWysiwyg.js +6 -8
- package/dist/excalidraw/element/transformHandles.js +0 -10
- package/dist/excalidraw/element/types.d.ts +7 -0
- package/dist/excalidraw/locales/en.json +3 -1
- package/dist/excalidraw/scene/Fonts.d.ts +1 -3
- package/dist/excalidraw/scene/Fonts.js +6 -12
- package/dist/excalidraw/scene/Renderer.d.ts +1 -1
- package/dist/excalidraw/scene/Renderer.js +2 -3
- package/dist/excalidraw/scene/Scene.d.ts +10 -4
- package/dist/excalidraw/scene/Scene.js +14 -8
- package/dist/excalidraw/scene/export.js +1 -1
- package/dist/prod/{en-UQDDYCH7.json → en-OIPCBIOA.json} +3 -1
- package/dist/prod/index.js +28 -28
- package/package.json +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-JKPJV7MZ.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-OKAZAA6U.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/dist-ITJNUBZF.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-SXBDZOS3.js +0 -55
- package/dist/browser/prod/excalidraw-assets/dist-54276HPL.js +0 -6
- package/dist/browser/prod/excalidraw-assets/image-VAGBVQ3G.js +0 -1
- /package/dist/browser/dev/excalidraw-assets-dev/{en-BF4XUPIZ.js.map → en-Y27YPU72.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-LVS32KQQ.js.map → image-Y5X7K6KW.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isInvisiblySmallElement } from "./sizeHelpers";
|
|
2
2
|
import { isLinearElementType } from "./typeChecks";
|
|
3
|
-
export { newElement, newTextElement,
|
|
3
|
+
export { newElement, newTextElement, refreshTextDimensions, newLinearElement, newImageElement, duplicateElement, } from "./newElement";
|
|
4
4
|
export { getElementAbsoluteCoords, getElementBounds, getCommonBounds, getDiamondPoints, getArrowheadPoints, getClosestElementBounds, } from "./bounds";
|
|
5
5
|
export { OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, getTransformHandlesFromCoords, getTransformHandles, } from "./transformHandles";
|
|
6
6
|
export { resizeTest, getCursorForResizingElement, getElementWithTransformHandleType, getTransformHandleTypeFromCoords, } from "./resizeTest";
|
|
@@ -2,7 +2,7 @@ import type { ExcalidrawElement } from "./types";
|
|
|
2
2
|
import type { Mutable } from "../utility-types";
|
|
3
3
|
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<Partial<TElement>, "id" | "version" | "versionNonce" | "updated">;
|
|
4
4
|
export declare const mutateElement: <TElement extends Mutable<ExcalidrawElement>>(element: TElement, updates: ElementUpdate<TElement>, informMutation?: boolean) => TElement;
|
|
5
|
-
export declare const newElementWith: <TElement extends ExcalidrawElement>(element: TElement, updates: ElementUpdate<TElement
|
|
5
|
+
export declare const newElementWith: <TElement extends ExcalidrawElement>(element: TElement, updates: ElementUpdate<TElement>, force?: boolean) => TElement;
|
|
6
6
|
/**
|
|
7
7
|
* Mutates element, bumping `version`, `versionNonce`, and `updated`.
|
|
8
8
|
*
|
|
@@ -71,11 +71,13 @@ export const mutateElement = (element, updates, informMutation = true) => {
|
|
|
71
71
|
element.versionNonce = randomInteger();
|
|
72
72
|
element.updated = getUpdatedTimestamp();
|
|
73
73
|
if (informMutation) {
|
|
74
|
-
Scene.getScene(element)?.
|
|
74
|
+
Scene.getScene(element)?.triggerUpdate();
|
|
75
75
|
}
|
|
76
76
|
return element;
|
|
77
77
|
};
|
|
78
|
-
export const newElementWith = (element, updates
|
|
78
|
+
export const newElementWith = (element, updates,
|
|
79
|
+
/** pass `true` to always regenerate */
|
|
80
|
+
force = false) => {
|
|
79
81
|
let didChange = false;
|
|
80
82
|
for (const key in updates) {
|
|
81
83
|
const value = updates[key];
|
|
@@ -88,7 +90,7 @@ export const newElementWith = (element, updates) => {
|
|
|
88
90
|
didChange = true;
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
if (!didChange) {
|
|
93
|
+
if (!didChange && !force) {
|
|
92
94
|
return element;
|
|
93
95
|
}
|
|
94
96
|
return {
|
|
@@ -34,11 +34,6 @@ export declare const refreshTextDimensions: (textElement: ExcalidrawTextElement,
|
|
|
34
34
|
height: number;
|
|
35
35
|
text: string;
|
|
36
36
|
} | undefined;
|
|
37
|
-
export declare const updateTextElement: (textElement: ExcalidrawTextElement, container: ExcalidrawTextContainer | null, elementsMap: ElementsMap, { text, isDeleted, originalText, }: {
|
|
38
|
-
text: string;
|
|
39
|
-
isDeleted?: boolean | undefined;
|
|
40
|
-
originalText: string;
|
|
41
|
-
}) => ExcalidrawTextElement;
|
|
42
37
|
export declare const newFreeDrawElement: (opts: {
|
|
43
38
|
type: "freedraw";
|
|
44
39
|
points?: ExcalidrawFreeDrawElement["points"];
|
|
@@ -85,7 +85,7 @@ export const newTextElement = (opts) => {
|
|
|
85
85
|
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
|
86
86
|
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
|
87
87
|
const offsets = getTextElementPositionOffsets({ textAlign, verticalAlign }, metrics);
|
|
88
|
-
const
|
|
88
|
+
const textElementProps = {
|
|
89
89
|
..._newElementBase("text", opts),
|
|
90
90
|
text,
|
|
91
91
|
fontSize,
|
|
@@ -98,18 +98,25 @@ export const newTextElement = (opts) => {
|
|
|
98
98
|
height: metrics.height,
|
|
99
99
|
containerId: opts.containerId || null,
|
|
100
100
|
originalText: text,
|
|
101
|
+
autoResize: true,
|
|
101
102
|
lineHeight,
|
|
102
|
-
}
|
|
103
|
+
};
|
|
104
|
+
const textElement = newElementWith(textElementProps, {});
|
|
103
105
|
return textElement;
|
|
104
106
|
};
|
|
105
107
|
const getAdjustedDimensions = (element, elementsMap, nextText) => {
|
|
106
|
-
|
|
108
|
+
let { width: nextWidth, height: nextHeight } = measureText(nextText, getFontString(element), element.lineHeight);
|
|
109
|
+
// wrapped text
|
|
110
|
+
if (!element.autoResize) {
|
|
111
|
+
nextWidth = element.width;
|
|
112
|
+
}
|
|
107
113
|
const { textAlign, verticalAlign } = element;
|
|
108
114
|
let x;
|
|
109
115
|
let y;
|
|
110
116
|
if (textAlign === "center" &&
|
|
111
117
|
verticalAlign === VERTICAL_ALIGN.MIDDLE &&
|
|
112
|
-
!element.containerId
|
|
118
|
+
!element.containerId &&
|
|
119
|
+
element.autoResize) {
|
|
113
120
|
const prevMetrics = measureText(element.text, getFontString(element), element.lineHeight);
|
|
114
121
|
const offsets = getTextElementPositionOffsets(element, {
|
|
115
122
|
width: nextWidth - prevMetrics.width,
|
|
@@ -142,19 +149,14 @@ export const refreshTextDimensions = (textElement, container, elementsMap, text
|
|
|
142
149
|
if (textElement.isDeleted) {
|
|
143
150
|
return;
|
|
144
151
|
}
|
|
145
|
-
if (container) {
|
|
146
|
-
text = wrapText(text, getFontString(textElement),
|
|
152
|
+
if (container || !textElement.autoResize) {
|
|
153
|
+
text = wrapText(text, getFontString(textElement), container
|
|
154
|
+
? getBoundTextMaxWidth(container, textElement)
|
|
155
|
+
: textElement.width);
|
|
147
156
|
}
|
|
148
157
|
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
|
149
158
|
return { text, ...dimensions };
|
|
150
159
|
};
|
|
151
|
-
export const updateTextElement = (textElement, container, elementsMap, { text, isDeleted, originalText, }) => {
|
|
152
|
-
return newElementWith(textElement, {
|
|
153
|
-
originalText,
|
|
154
|
-
isDeleted: isDeleted ?? textElement.isDeleted,
|
|
155
|
-
...refreshTextDimensions(textElement, container, elementsMap, originalText),
|
|
156
|
-
});
|
|
157
|
-
};
|
|
158
160
|
export const newFreeDrawElement = (opts) => {
|
|
159
161
|
return {
|
|
160
162
|
..._newElementBase(opts.type, opts),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
|
|
1
|
+
import { BOUND_TEXT_PADDING, MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE, } from "../constants";
|
|
2
2
|
import { rescalePoints } from "../points";
|
|
3
3
|
import { rotate, centerPoint, rotatePoint } from "../math";
|
|
4
4
|
import { getElementAbsoluteCoords, getCommonBounds, getResizedElementAbsoluteCoords, getCommonBoundingBox, } from "./bounds";
|
|
@@ -7,7 +7,7 @@ import { mutateElement } from "./mutateElement";
|
|
|
7
7
|
import { getFontString } from "../utils";
|
|
8
8
|
import { updateBoundElements } from "./binding";
|
|
9
9
|
import Scene from "../scene/Scene";
|
|
10
|
-
import { getApproxMinLineWidth, getBoundTextElement, getBoundTextElementId, getContainerElement, handleBindTextResize, getBoundTextMaxWidth, getApproxMinLineHeight, } from "./textElement";
|
|
10
|
+
import { getApproxMinLineWidth, getBoundTextElement, getBoundTextElementId, getContainerElement, handleBindTextResize, getBoundTextMaxWidth, getApproxMinLineHeight, wrapText, measureText, getMinCharWidth, } from "./textElement";
|
|
11
11
|
import { LinearElementEditor } from "./linearElementEditor";
|
|
12
12
|
import { isInGroup } from "../groups";
|
|
13
13
|
export const normalizeAngle = (angle) => {
|
|
@@ -27,12 +27,8 @@ export const transformElements = (originalElements, transformHandleType, selecte
|
|
|
27
27
|
rotateSingleElement(element, elementsMap, pointerX, pointerY, shouldRotateWithDiscreteAngle);
|
|
28
28
|
updateBoundElements(element, elementsMap);
|
|
29
29
|
}
|
|
30
|
-
else if (isTextElement(element) &&
|
|
31
|
-
(transformHandleType
|
|
32
|
-
transformHandleType === "ne" ||
|
|
33
|
-
transformHandleType === "sw" ||
|
|
34
|
-
transformHandleType === "se")) {
|
|
35
|
-
resizeSingleTextElement(element, elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, pointerY);
|
|
30
|
+
else if (isTextElement(element) && transformHandleType) {
|
|
31
|
+
resizeSingleTextElement(originalElements, element, elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, pointerY);
|
|
36
32
|
updateBoundElements(element, elementsMap);
|
|
37
33
|
}
|
|
38
34
|
else if (transformHandleType) {
|
|
@@ -100,23 +96,25 @@ const measureFontSizeFromWidth = (element, elementsMap, nextWidth) => {
|
|
|
100
96
|
size: nextFontSize,
|
|
101
97
|
};
|
|
102
98
|
};
|
|
103
|
-
const resizeSingleTextElement = (element, elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, pointerY) => {
|
|
99
|
+
const resizeSingleTextElement = (originalElements, element, elementsMap, transformHandleType, shouldResizeFromCenter, pointerX, pointerY) => {
|
|
104
100
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
|
105
101
|
// rotation pointer with reverse angle
|
|
106
102
|
const [rotatedX, rotatedY] = rotate(pointerX, pointerY, cx, cy, -element.angle);
|
|
107
103
|
let scaleX = 0;
|
|
108
104
|
let scaleY = 0;
|
|
109
|
-
if (transformHandleType
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
if (transformHandleType !== "e" && transformHandleType !== "w") {
|
|
106
|
+
if (transformHandleType.includes("e")) {
|
|
107
|
+
scaleX = (rotatedX - x1) / (x2 - x1);
|
|
108
|
+
}
|
|
109
|
+
if (transformHandleType.includes("w")) {
|
|
110
|
+
scaleX = (x2 - rotatedX) / (x2 - x1);
|
|
111
|
+
}
|
|
112
|
+
if (transformHandleType.includes("n")) {
|
|
113
|
+
scaleY = (y2 - rotatedY) / (y2 - y1);
|
|
114
|
+
}
|
|
115
|
+
if (transformHandleType.includes("s")) {
|
|
116
|
+
scaleY = (rotatedY - y1) / (y2 - y1);
|
|
117
|
+
}
|
|
120
118
|
}
|
|
121
119
|
const scale = Math.max(scaleX, scaleY);
|
|
122
120
|
if (scale > 0) {
|
|
@@ -171,6 +169,57 @@ const resizeSingleTextElement = (element, elementsMap, transformHandleType, shou
|
|
|
171
169
|
y: nextY,
|
|
172
170
|
});
|
|
173
171
|
}
|
|
172
|
+
if (transformHandleType === "e" || transformHandleType === "w") {
|
|
173
|
+
const stateAtResizeStart = originalElements.get(element.id);
|
|
174
|
+
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(stateAtResizeStart, stateAtResizeStart.width, stateAtResizeStart.height, true);
|
|
175
|
+
const startTopLeft = [x1, y1];
|
|
176
|
+
const startBottomRight = [x2, y2];
|
|
177
|
+
const startCenter = centerPoint(startTopLeft, startBottomRight);
|
|
178
|
+
const rotatedPointer = rotatePoint([pointerX, pointerY], startCenter, -stateAtResizeStart.angle);
|
|
179
|
+
const [esx1, , esx2] = getResizedElementAbsoluteCoords(element, element.width, element.height, true);
|
|
180
|
+
const boundsCurrentWidth = esx2 - esx1;
|
|
181
|
+
const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
|
|
182
|
+
const minWidth = getMinCharWidth(getFontString(element)) + BOUND_TEXT_PADDING * 2;
|
|
183
|
+
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
|
184
|
+
if (transformHandleType.includes("e")) {
|
|
185
|
+
scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
|
|
186
|
+
}
|
|
187
|
+
if (transformHandleType.includes("w")) {
|
|
188
|
+
scaleX = (startBottomRight[0] - rotatedPointer[0]) / boundsCurrentWidth;
|
|
189
|
+
}
|
|
190
|
+
const newWidth = element.width * scaleX < minWidth ? minWidth : element.width * scaleX;
|
|
191
|
+
const text = wrapText(element.originalText, getFontString(element), Math.abs(newWidth));
|
|
192
|
+
const metrics = measureText(text, getFontString(element), element.lineHeight);
|
|
193
|
+
const eleNewHeight = metrics.height;
|
|
194
|
+
const [newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2] = getResizedElementAbsoluteCoords(stateAtResizeStart, newWidth, eleNewHeight, true);
|
|
195
|
+
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
|
196
|
+
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
|
197
|
+
let newTopLeft = [...startTopLeft];
|
|
198
|
+
if (["n", "w", "nw"].includes(transformHandleType)) {
|
|
199
|
+
newTopLeft = [
|
|
200
|
+
startBottomRight[0] - Math.abs(newBoundsWidth),
|
|
201
|
+
startTopLeft[1],
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
// adjust topLeft to new rotation point
|
|
205
|
+
const angle = stateAtResizeStart.angle;
|
|
206
|
+
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
|
207
|
+
const newCenter = [
|
|
208
|
+
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
|
209
|
+
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
|
210
|
+
];
|
|
211
|
+
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
|
212
|
+
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
|
213
|
+
const resizedElement = {
|
|
214
|
+
width: Math.abs(newWidth),
|
|
215
|
+
height: Math.abs(metrics.height),
|
|
216
|
+
x: newTopLeft[0],
|
|
217
|
+
y: newTopLeft[1],
|
|
218
|
+
text,
|
|
219
|
+
autoResize: false,
|
|
220
|
+
};
|
|
221
|
+
mutateElement(element, resizedElement);
|
|
222
|
+
}
|
|
174
223
|
};
|
|
175
224
|
export const resizeSingleElement = (originalElements, shouldMaintainAspectRatio, element, elementsMap, transformHandleDirection, shouldResizeFromCenter, pointerX, pointerY) => {
|
|
176
225
|
const stateAtResizeStart = originalElements.get(element.id);
|
|
@@ -541,7 +590,7 @@ export const resizeMultipleElements = (originalElements, selectedElements, eleme
|
|
|
541
590
|
handleBindTextResize(element, elementsMap, transformHandleType, true);
|
|
542
591
|
}
|
|
543
592
|
}
|
|
544
|
-
Scene.getScene(elementsAndUpdates[0].element)?.
|
|
593
|
+
Scene.getScene(elementsAndUpdates[0].element)?.triggerUpdate();
|
|
545
594
|
};
|
|
546
595
|
const rotateMultipleElements = (originalElements, elements, elementsMap, pointerX, pointerY, shouldRotateWithDiscreteAngle, centerX, centerY) => {
|
|
547
596
|
let centerAngle = (5 * Math.PI) / 2 + Math.atan2(pointerY - centerY, pointerX - centerX);
|
|
@@ -574,7 +623,7 @@ const rotateMultipleElements = (originalElements, elements, elementsMap, pointer
|
|
|
574
623
|
}, false);
|
|
575
624
|
}
|
|
576
625
|
});
|
|
577
|
-
Scene.getScene(elements[0])?.
|
|
626
|
+
Scene.getScene(elements[0])?.triggerUpdate();
|
|
578
627
|
};
|
|
579
628
|
export const getResizeOffsetXY = (transformHandleType, selectedElements, elementsMap, x, y) => {
|
|
580
629
|
const [x1, y1, x2, y2] = selectedElements.length === 1
|
|
@@ -28,10 +28,8 @@ export const resizeTest = (element, elementsMap, appState, x, y, zoom, pointerTy
|
|
|
28
28
|
}
|
|
29
29
|
if (canResizeFromSides(device)) {
|
|
30
30
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
if (element.type !== "text" &&
|
|
34
|
-
!(isLinearElement(element) && element.points.length <= 2)) {
|
|
31
|
+
// do not resize from the sides for linear elements with only two points
|
|
32
|
+
if (!(isLinearElement(element) && element.points.length <= 2)) {
|
|
35
33
|
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
|
36
34
|
const sides = getSelectionBorders([x1 - SPACING, y1 - SPACING], [x2 + SPACING, y2 + SPACING], [cx, cy], angleToDegrees(element.angle));
|
|
37
35
|
for (const [dir, side] of Object.entries(sides)) {
|
|
@@ -24,12 +24,17 @@ export const redrawTextBoundingBox = (textElement, container, elementsMap, infor
|
|
|
24
24
|
angle: container?.angle ?? textElement.angle,
|
|
25
25
|
};
|
|
26
26
|
boundTextUpdates.text = textElement.text;
|
|
27
|
-
if (container) {
|
|
28
|
-
maxWidth =
|
|
27
|
+
if (container || !textElement.autoResize) {
|
|
28
|
+
maxWidth = container
|
|
29
|
+
? getBoundTextMaxWidth(container, textElement)
|
|
30
|
+
: textElement.width;
|
|
29
31
|
boundTextUpdates.text = wrapText(textElement.originalText, getFontString(textElement), maxWidth);
|
|
30
32
|
}
|
|
31
33
|
const metrics = measureText(boundTextUpdates.text, getFontString(textElement), textElement.lineHeight);
|
|
32
|
-
|
|
34
|
+
// Note: only update width for unwrapped text and bound texts (which always have autoResize set to true)
|
|
35
|
+
if (textElement.autoResize) {
|
|
36
|
+
boundTextUpdates.width = metrics.width;
|
|
37
|
+
}
|
|
33
38
|
boundTextUpdates.height = metrics.height;
|
|
34
39
|
if (container) {
|
|
35
40
|
const maxContainerHeight = getBoundTextMaxHeight(container, textElement);
|
|
@@ -2,11 +2,16 @@ import type { ExcalidrawElement, ExcalidrawTextElement } from "./types";
|
|
|
2
2
|
import type App from "../components/App";
|
|
3
3
|
export declare const textWysiwyg: ({ id, onChange, onSubmit, getViewportCoords, element, canvas, excalidrawContainer, app, }: {
|
|
4
4
|
id: ExcalidrawElement["id"];
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* textWysiwyg only deals with `originalText`
|
|
7
|
+
*
|
|
8
|
+
* Note: `text`, which can be wrapped and therefore different from `originalText`,
|
|
9
|
+
* is derived from `originalText`
|
|
10
|
+
*/
|
|
11
|
+
onChange?: ((nextOriginalText: string) => void) | undefined;
|
|
6
12
|
onSubmit: (data: {
|
|
7
|
-
text: string;
|
|
8
13
|
viaKeyboard: boolean;
|
|
9
|
-
|
|
14
|
+
nextOriginalText: string;
|
|
10
15
|
}) => void;
|
|
11
16
|
getViewportCoords: (x: number, y: number) => [number, number];
|
|
12
17
|
element: ExcalidrawTextElement;
|
|
@@ -53,8 +53,6 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
53
53
|
let maxWidth = updatedTextElement.width;
|
|
54
54
|
let maxHeight = updatedTextElement.height;
|
|
55
55
|
let textElementWidth = updatedTextElement.width;
|
|
56
|
-
// Set to element height by default since that's
|
|
57
|
-
// what is going to be used for unbounded text
|
|
58
56
|
const textElementHeight = updatedTextElement.height;
|
|
59
57
|
if (container && updatedTextElement.containerId) {
|
|
60
58
|
if (isArrowElement(container)) {
|
|
@@ -113,6 +111,9 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
113
111
|
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
|
114
112
|
textElementWidth = Math.min(textElementWidth, maxWidth);
|
|
115
113
|
}
|
|
114
|
+
else {
|
|
115
|
+
textElementWidth += 0.5;
|
|
116
|
+
}
|
|
116
117
|
// Make sure text editor height doesn't go beyond viewport
|
|
117
118
|
const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value;
|
|
118
119
|
Object.assign(editable.style, {
|
|
@@ -149,7 +150,7 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
149
150
|
editable.classList.add("excalidraw-wysiwyg");
|
|
150
151
|
let whiteSpace = "pre";
|
|
151
152
|
let wordBreak = "normal";
|
|
152
|
-
if (isBoundToContainer(element)) {
|
|
153
|
+
if (isBoundToContainer(element) || !element.autoResize) {
|
|
153
154
|
whiteSpace = "pre-wrap";
|
|
154
155
|
wordBreak = "break-word";
|
|
155
156
|
}
|
|
@@ -334,10 +335,8 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
334
335
|
if (!updateElement) {
|
|
335
336
|
return;
|
|
336
337
|
}
|
|
337
|
-
let text = editable.value;
|
|
338
338
|
const container = getContainerElement(updateElement, app.scene.getNonDeletedElementsMap());
|
|
339
339
|
if (container) {
|
|
340
|
-
text = updateElement.text;
|
|
341
340
|
if (editable.value.trim()) {
|
|
342
341
|
const boundTextElementId = getBoundTextElementId(container);
|
|
343
342
|
if (!boundTextElementId || boundTextElementId !== element.id) {
|
|
@@ -361,9 +360,8 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
361
360
|
redrawTextBoundingBox(updateElement, container, app.scene.getNonDeletedElementsMap());
|
|
362
361
|
}
|
|
363
362
|
onSubmit({
|
|
364
|
-
text,
|
|
365
363
|
viaKeyboard: submittedViaKeyboard,
|
|
366
|
-
|
|
364
|
+
nextOriginalText: editable.value,
|
|
367
365
|
});
|
|
368
366
|
};
|
|
369
367
|
const cleanup = () => {
|
|
@@ -441,7 +439,7 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
|
|
|
441
439
|
}
|
|
442
440
|
};
|
|
443
441
|
// handle updates of textElement properties of editing element
|
|
444
|
-
const unbindUpdate = Scene.getScene(element).
|
|
442
|
+
const unbindUpdate = Scene.getScene(element).onUpdate(() => {
|
|
445
443
|
updateWysiwygStyle();
|
|
446
444
|
const isColorPickerActive = !!document.activeElement?.closest(".color-picker-content");
|
|
447
445
|
if (!isColorPickerActive) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getElementAbsoluteCoords } from "./bounds";
|
|
2
2
|
import { rotate } from "../math";
|
|
3
|
-
import { isTextElement } from ".";
|
|
4
3
|
import { isFrameLikeElement, isLinearElement } from "./typeChecks";
|
|
5
4
|
import { DEFAULT_TRANSFORM_HANDLE_SPACING, isAndroid, isIOS, } from "../constants";
|
|
6
5
|
const transformHandleSizes = {
|
|
@@ -28,12 +27,6 @@ export const OMIT_SIDES_FOR_FRAME = {
|
|
|
28
27
|
w: true,
|
|
29
28
|
rotation: true,
|
|
30
29
|
};
|
|
31
|
-
const OMIT_SIDES_FOR_TEXT_ELEMENT = {
|
|
32
|
-
e: true,
|
|
33
|
-
s: true,
|
|
34
|
-
n: true,
|
|
35
|
-
w: true,
|
|
36
|
-
};
|
|
37
30
|
const OMIT_SIDES_FOR_LINE_SLASH = {
|
|
38
31
|
e: true,
|
|
39
32
|
s: true,
|
|
@@ -147,9 +140,6 @@ export const getTransformHandles = (element, zoom, elementsMap, pointerType = "m
|
|
|
147
140
|
}
|
|
148
141
|
}
|
|
149
142
|
}
|
|
150
|
-
else if (isTextElement(element)) {
|
|
151
|
-
omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
|
|
152
|
-
}
|
|
153
143
|
else if (isFrameLikeElement(element)) {
|
|
154
144
|
omitSides = {
|
|
155
145
|
...omitSides,
|
|
@@ -154,6 +154,13 @@ export type ExcalidrawTextElement = _ExcalidrawElementBase & Readonly<{
|
|
|
154
154
|
verticalAlign: VerticalAlign;
|
|
155
155
|
containerId: ExcalidrawGenericElement["id"] | null;
|
|
156
156
|
originalText: string;
|
|
157
|
+
/**
|
|
158
|
+
* If `true` the width will fit the text. If `false`, the text will
|
|
159
|
+
* wrap to fit the width.
|
|
160
|
+
*
|
|
161
|
+
* @default true
|
|
162
|
+
*/
|
|
163
|
+
autoResize: boolean;
|
|
157
164
|
/**
|
|
158
165
|
* Unitless line height (aligned to W3C). To get line height in px, multiply
|
|
159
166
|
* with font size (using `getLineHeightInPx` helper).
|
|
@@ -148,7 +148,9 @@
|
|
|
148
148
|
"discordChat": "Discord chat",
|
|
149
149
|
"zoomToFitViewport": "Zoom to fit in viewport",
|
|
150
150
|
"zoomToFitSelection": "Zoom to fit selection",
|
|
151
|
-
"zoomToFit": "Zoom to fit all elements"
|
|
151
|
+
"zoomToFit": "Zoom to fit all elements",
|
|
152
|
+
"installPWA": "Install Excalidraw locally (PWA)",
|
|
153
|
+
"autoResize": "Enable text auto-resizing"
|
|
152
154
|
},
|
|
153
155
|
"library": {
|
|
154
156
|
"noItems": "No items added yet...",
|
|
@@ -2,10 +2,8 @@ import type { ExcalidrawElement } from "../element/types";
|
|
|
2
2
|
import type Scene from "./Scene";
|
|
3
3
|
export declare class Fonts {
|
|
4
4
|
private scene;
|
|
5
|
-
|
|
6
|
-
constructor({ scene, onSceneUpdated, }: {
|
|
5
|
+
constructor({ scene }: {
|
|
7
6
|
scene: Scene;
|
|
8
|
-
onSceneUpdated: () => void;
|
|
9
7
|
});
|
|
10
8
|
private static loadedFontFaces;
|
|
11
9
|
/**
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { isTextElement
|
|
1
|
+
import { isTextElement } from "../element";
|
|
2
2
|
import { newElementWith } from "../element/mutateElement";
|
|
3
|
-
import { getContainerElement } from "../element/textElement";
|
|
4
|
-
import { isBoundToContainer } from "../element/typeChecks";
|
|
5
3
|
import { getFontString } from "../utils";
|
|
6
4
|
import { ShapeCache } from "./ShapeCache";
|
|
7
5
|
export class Fonts {
|
|
8
6
|
scene;
|
|
9
|
-
|
|
10
|
-
constructor({ scene, onSceneUpdated, }) {
|
|
7
|
+
constructor({ scene }) {
|
|
11
8
|
this.scene = scene;
|
|
12
|
-
this.onSceneUpdated = onSceneUpdated;
|
|
13
9
|
}
|
|
14
10
|
// it's ok to track fonts across multiple instances only once, so let's use
|
|
15
11
|
// a static member to reduce memory footprint
|
|
@@ -39,17 +35,15 @@ export class Fonts {
|
|
|
39
35
|
}
|
|
40
36
|
let didUpdate = false;
|
|
41
37
|
this.scene.mapElements((element) => {
|
|
42
|
-
if (isTextElement(element)
|
|
43
|
-
ShapeCache.delete(element);
|
|
38
|
+
if (isTextElement(element)) {
|
|
44
39
|
didUpdate = true;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
40
|
+
ShapeCache.delete(element);
|
|
41
|
+
return newElementWith(element, {}, true);
|
|
48
42
|
}
|
|
49
43
|
return element;
|
|
50
44
|
});
|
|
51
45
|
if (didUpdate) {
|
|
52
|
-
this.
|
|
46
|
+
this.scene.triggerUpdate();
|
|
53
47
|
}
|
|
54
48
|
};
|
|
55
49
|
loadFontsForElements = async (elements) => {
|
|
@@ -14,7 +14,7 @@ export declare class Renderer {
|
|
|
14
14
|
width: AppState["width"];
|
|
15
15
|
editingElement: AppState["editingElement"];
|
|
16
16
|
pendingImageElementId: AppState["pendingImageElementId"];
|
|
17
|
-
|
|
17
|
+
sceneNonce: ReturnType<InstanceType<typeof Scene>["getSceneNonce"]>;
|
|
18
18
|
}) => {
|
|
19
19
|
elementsMap: Map<string, NonDeletedExcalidrawElement> & import("../utility-types").MakeBrand<"NonDeletedElementsMap"> & import("../utility-types").MakeBrand<"RenderableElementsMap">;
|
|
20
20
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
|
@@ -45,9 +45,8 @@ export class Renderer {
|
|
|
45
45
|
return elementsMap;
|
|
46
46
|
};
|
|
47
47
|
return memoize(({ zoom, offsetLeft, offsetTop, scrollX, scrollY, height, width, editingElement, pendingImageElementId,
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
versionNonce: _versionNonce, }) => {
|
|
48
|
+
// cache-invalidation nonce
|
|
49
|
+
sceneNonce: _sceneNonce, }) => {
|
|
51
50
|
const elements = this.scene.getNonDeletedElements();
|
|
52
51
|
const elementsMap = getRenderableElements({
|
|
53
52
|
elements,
|
|
@@ -19,7 +19,14 @@ declare class Scene {
|
|
|
19
19
|
private frames;
|
|
20
20
|
private elementsMap;
|
|
21
21
|
private selectedElementsCache;
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Random integer regenerated each scene update.
|
|
24
|
+
*
|
|
25
|
+
* Does not relate to elements versions, it's only a renderer
|
|
26
|
+
* cache-invalidation nonce at the moment.
|
|
27
|
+
*/
|
|
28
|
+
private sceneNonce;
|
|
29
|
+
getSceneNonce(): number | undefined;
|
|
23
30
|
getNonDeletedElementsMap(): Map<string, Ordered<NonDeletedExcalidrawElement>> & import("../utility-types").MakeBrand<"NonDeletedSceneElementsMap">;
|
|
24
31
|
getElementsIncludingDeleted(): readonly OrderedExcalidrawElement[];
|
|
25
32
|
getElementsMapIncludingDeleted(): Map<string, Ordered<ExcalidrawElement>> & import("../utility-types").MakeBrand<"SceneElementsMap">;
|
|
@@ -38,7 +45,6 @@ declare class Scene {
|
|
|
38
45
|
}): NonDeleted<ExcalidrawElement>[];
|
|
39
46
|
getNonDeletedFramesLikes(): readonly NonDeleted<ExcalidrawFrameLikeElement>[];
|
|
40
47
|
getElement<T extends ExcalidrawElement>(id: T["id"]): T | null;
|
|
41
|
-
getVersionNonce(): number | undefined;
|
|
42
48
|
getNonDeletedElement(id: ExcalidrawElement["id"]): NonDeleted<ExcalidrawElement> | null;
|
|
43
49
|
/**
|
|
44
50
|
* A utility method to help with updating all scene elements, with the added
|
|
@@ -54,8 +60,8 @@ declare class Scene {
|
|
|
54
60
|
*/
|
|
55
61
|
mapElements(iteratee: (element: ExcalidrawElement) => ExcalidrawElement): boolean;
|
|
56
62
|
replaceAllElements(nextElements: ElementsMapOrArray): void;
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
triggerUpdate(): void;
|
|
64
|
+
onUpdate(cb: SceneStateCallback): SceneStateCallbackRemover;
|
|
59
65
|
destroy(): void;
|
|
60
66
|
insertElementAtIndex(element: ExcalidrawElement, index: number): void;
|
|
61
67
|
insertElementsAtIndex(elements: ExcalidrawElement[], index: number): void;
|
|
@@ -72,7 +72,16 @@ class Scene {
|
|
|
72
72
|
elements: null,
|
|
73
73
|
cache: new Map(),
|
|
74
74
|
};
|
|
75
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Random integer regenerated each scene update.
|
|
77
|
+
*
|
|
78
|
+
* Does not relate to elements versions, it's only a renderer
|
|
79
|
+
* cache-invalidation nonce at the moment.
|
|
80
|
+
*/
|
|
81
|
+
sceneNonce;
|
|
82
|
+
getSceneNonce() {
|
|
83
|
+
return this.sceneNonce;
|
|
84
|
+
}
|
|
76
85
|
getNonDeletedElementsMap() {
|
|
77
86
|
return this.nonDeletedElementsMap;
|
|
78
87
|
}
|
|
@@ -118,9 +127,6 @@ class Scene {
|
|
|
118
127
|
getElement(id) {
|
|
119
128
|
return this.elementsMap.get(id) || null;
|
|
120
129
|
}
|
|
121
|
-
getVersionNonce() {
|
|
122
|
-
return this.versionNonce;
|
|
123
|
-
}
|
|
124
130
|
getNonDeletedElement(id) {
|
|
125
131
|
const element = this.getElement(id);
|
|
126
132
|
if (element && isNonDeletedElement(element)) {
|
|
@@ -179,15 +185,15 @@ class Scene {
|
|
|
179
185
|
this.nonDeletedElementsMap = nonDeletedElements.elementsMap;
|
|
180
186
|
this.frames = nextFrameLikes;
|
|
181
187
|
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
|
|
182
|
-
this.
|
|
188
|
+
this.triggerUpdate();
|
|
183
189
|
}
|
|
184
|
-
|
|
185
|
-
this.
|
|
190
|
+
triggerUpdate() {
|
|
191
|
+
this.sceneNonce = randomInteger();
|
|
186
192
|
for (const callback of Array.from(this.callbacks)) {
|
|
187
193
|
callback();
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
|
-
|
|
196
|
+
onUpdate(cb) {
|
|
191
197
|
if (this.callbacks.has(cb)) {
|
|
192
198
|
throw new Error();
|
|
193
199
|
}
|
|
@@ -200,7 +200,7 @@ export const exportToSvg = async (elements, appState, files, opts) => {
|
|
|
200
200
|
if (import.meta.env.VITE_IS_EXCALIDRAW_NPM_PACKAGE) {
|
|
201
201
|
assetPath =
|
|
202
202
|
window.EXCALIDRAW_ASSET_PATH ||
|
|
203
|
-
`https://unpkg.com/${import.meta.env.VITE_PKG_NAME}@${import.meta.env.
|
|
203
|
+
`https://unpkg.com/${import.meta.env.VITE_PKG_NAME}@${import.meta.env.VITE_PKG_VERSION}`;
|
|
204
204
|
if (assetPath?.startsWith("/")) {
|
|
205
205
|
assetPath = assetPath.replace("/", `${window.location.origin}/`);
|
|
206
206
|
}
|
|
@@ -148,7 +148,9 @@
|
|
|
148
148
|
"discordChat": "Discord chat",
|
|
149
149
|
"zoomToFitViewport": "Zoom to fit in viewport",
|
|
150
150
|
"zoomToFitSelection": "Zoom to fit selection",
|
|
151
|
-
"zoomToFit": "Zoom to fit all elements"
|
|
151
|
+
"zoomToFit": "Zoom to fit all elements",
|
|
152
|
+
"installPWA": "Install Excalidraw locally (PWA)",
|
|
153
|
+
"autoResize": "Enable text auto-resizing"
|
|
152
154
|
},
|
|
153
155
|
"library": {
|
|
154
156
|
"noItems": "No items added yet...",
|