@excalidraw/excalidraw 0.17.1-1ed53b1 → 0.17.1-22b3927

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.
Files changed (122) hide show
  1. package/dist/browser/dev/excalidraw-assets-dev/{chunk-JKPJV7MZ.js → chunk-Q6A4M3MN.js} +4 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/chunk-Q6A4M3MN.js.map +7 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/{chunk-OKAZAA6U.js → chunk-VC7RRIDZ.js} +230 -93
  4. package/dist/browser/dev/excalidraw-assets-dev/chunk-VC7RRIDZ.js.map +7 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/{dist-ITJNUBZF.js → dist-6QVAH5JA.js} +36 -14
  6. package/dist/browser/dev/excalidraw-assets-dev/dist-6QVAH5JA.js.map +7 -0
  7. package/dist/browser/dev/excalidraw-assets-dev/{en-BF4XUPIZ.js → en-Y27YPU72.js} +2 -2
  8. package/dist/browser/dev/excalidraw-assets-dev/{image-LVS32KQQ.js → image-J7S3ALXP.js} +2 -2
  9. package/dist/browser/dev/index.js +335 -116
  10. package/dist/browser/dev/index.js.map +4 -4
  11. package/dist/browser/prod/excalidraw-assets/chunk-CWO763YJ.js +55 -0
  12. package/dist/browser/prod/excalidraw-assets/{chunk-O4AI3NNG.js → chunk-IZMZ6RPD.js} +1 -1
  13. package/dist/browser/prod/excalidraw-assets/dist-567JAXHK.js +7 -0
  14. package/dist/browser/prod/excalidraw-assets/{en-N7CLNF6C.js → en-GSUSWMSH.js} +1 -1
  15. package/dist/browser/prod/excalidraw-assets/image-SZBFRCU2.js +1 -0
  16. package/dist/browser/prod/index.js +24 -24
  17. package/dist/dev/{en-UQDDYCH7.json → en-OIPCBIOA.json} +3 -1
  18. package/dist/dev/index.js +576 -207
  19. package/dist/dev/index.js.map +4 -4
  20. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +3 -3
  21. package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
  22. package/dist/excalidraw/actions/actionBoundText.d.ts +3 -3
  23. package/dist/excalidraw/actions/actionBoundText.js +3 -1
  24. package/dist/excalidraw/actions/actionCanvas.d.ts +13 -13
  25. package/dist/excalidraw/actions/actionClipboard.d.ts +12 -12
  26. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +3 -3
  27. package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
  28. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
  29. package/dist/excalidraw/actions/actionElementLock.d.ts +2 -2
  30. package/dist/excalidraw/actions/actionExport.d.ts +11 -11
  31. package/dist/excalidraw/actions/actionFinalize.d.ts +2 -2
  32. package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
  33. package/dist/excalidraw/actions/actionFrame.d.ts +312 -4
  34. package/dist/excalidraw/actions/actionGroup.d.ts +312 -2
  35. package/dist/excalidraw/actions/actionHistory.js +4 -4
  36. package/dist/excalidraw/actions/actionLinearEditor.d.ts +1 -1
  37. package/dist/excalidraw/actions/actionLink.d.ts +1 -1
  38. package/dist/excalidraw/actions/actionMenu.d.ts +3 -3
  39. package/dist/excalidraw/actions/actionNavigate.d.ts +2 -2
  40. package/dist/excalidraw/actions/actionProperties.d.ts +13 -13
  41. package/dist/excalidraw/actions/actionProperties.js +1 -1
  42. package/dist/excalidraw/actions/actionSelectAll.d.ts +1 -1
  43. package/dist/excalidraw/actions/actionStyles.d.ts +5 -2
  44. package/dist/excalidraw/actions/actionTextAutoResize.d.ts +17 -0
  45. package/dist/excalidraw/actions/actionTextAutoResize.js +38 -0
  46. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +1 -1
  47. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +1 -1
  48. package/dist/excalidraw/actions/actionToggleStats.d.ts +1 -1
  49. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +1 -1
  50. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +1 -1
  51. package/dist/excalidraw/actions/actionZindex.d.ts +4 -4
  52. package/dist/excalidraw/actions/types.d.ts +1 -1
  53. package/dist/excalidraw/change.js +13 -6
  54. package/dist/excalidraw/components/Actions.js +1 -1
  55. package/dist/excalidraw/components/App.d.ts +2 -2
  56. package/dist/excalidraw/components/App.js +133 -51
  57. package/dist/excalidraw/components/ButtonIconSelect.js +1 -1
  58. package/dist/excalidraw/components/CheckboxItem.js +1 -1
  59. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +2 -2
  60. package/dist/excalidraw/components/ContextMenu.js +1 -1
  61. package/dist/excalidraw/components/Dialog.js +1 -1
  62. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  63. package/dist/excalidraw/components/IconPicker.js +2 -2
  64. package/dist/excalidraw/components/LayerUI.js +2 -2
  65. package/dist/excalidraw/components/MobileMenu.js +1 -1
  66. package/dist/excalidraw/components/PasteChartDialog.js +1 -1
  67. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +3 -2
  68. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +4 -2
  69. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +1 -1
  70. package/dist/excalidraw/components/canvases/StaticCanvas.js +2 -2
  71. package/dist/excalidraw/components/icons.js +6 -2
  72. package/dist/excalidraw/constants.d.ts +1 -0
  73. package/dist/excalidraw/constants.js +5 -0
  74. package/dist/excalidraw/data/restore.js +3 -0
  75. package/dist/excalidraw/element/dragElements.d.ts +2 -2
  76. package/dist/excalidraw/element/dragElements.js +27 -3
  77. package/dist/excalidraw/element/embeddable.d.ts +1 -1
  78. package/dist/excalidraw/element/index.d.ts +1 -1
  79. package/dist/excalidraw/element/index.js +1 -1
  80. package/dist/excalidraw/element/mutateElement.d.ts +1 -1
  81. package/dist/excalidraw/element/mutateElement.js +5 -3
  82. package/dist/excalidraw/element/newElement.d.ts +2 -5
  83. package/dist/excalidraw/element/newElement.js +16 -14
  84. package/dist/excalidraw/element/resizeElements.js +73 -21
  85. package/dist/excalidraw/element/resizeTest.js +2 -4
  86. package/dist/excalidraw/element/textElement.d.ts +1 -0
  87. package/dist/excalidraw/element/textElement.js +11 -3
  88. package/dist/excalidraw/element/textWysiwyg.d.ts +10 -4
  89. package/dist/excalidraw/element/textWysiwyg.js +38 -17
  90. package/dist/excalidraw/element/transformHandles.js +0 -10
  91. package/dist/excalidraw/element/types.d.ts +7 -0
  92. package/dist/excalidraw/fractionalIndex.js +2 -4
  93. package/dist/excalidraw/locales/en.json +3 -1
  94. package/dist/excalidraw/mermaid.d.ts +2 -0
  95. package/dist/excalidraw/mermaid.js +28 -0
  96. package/dist/excalidraw/renderer/interactiveScene.d.ts +1 -1
  97. package/dist/excalidraw/renderer/interactiveScene.js +31 -5
  98. package/dist/excalidraw/renderer/renderElement.d.ts +2 -2
  99. package/dist/excalidraw/renderer/renderElement.js +2 -2
  100. package/dist/excalidraw/scene/Fonts.d.ts +1 -3
  101. package/dist/excalidraw/scene/Fonts.js +6 -12
  102. package/dist/excalidraw/scene/Renderer.d.ts +1 -1
  103. package/dist/excalidraw/scene/Renderer.js +2 -3
  104. package/dist/excalidraw/scene/Scene.d.ts +10 -4
  105. package/dist/excalidraw/scene/Scene.js +14 -8
  106. package/dist/excalidraw/scene/export.js +1 -1
  107. package/dist/excalidraw/scene/types.d.ts +2 -1
  108. package/dist/excalidraw/snapping.js +2 -1
  109. package/dist/excalidraw/store.d.ts +32 -2
  110. package/dist/excalidraw/store.js +27 -0
  111. package/dist/excalidraw/types.d.ts +1 -0
  112. package/dist/prod/{en-UQDDYCH7.json → en-OIPCBIOA.json} +3 -1
  113. package/dist/prod/index.js +42 -42
  114. package/package.json +2 -2
  115. package/dist/browser/dev/excalidraw-assets-dev/chunk-JKPJV7MZ.js.map +0 -7
  116. package/dist/browser/dev/excalidraw-assets-dev/chunk-OKAZAA6U.js.map +0 -7
  117. package/dist/browser/dev/excalidraw-assets-dev/dist-ITJNUBZF.js.map +0 -7
  118. package/dist/browser/prod/excalidraw-assets/chunk-SXBDZOS3.js +0 -55
  119. package/dist/browser/prod/excalidraw-assets/dist-54276HPL.js +0 -6
  120. package/dist/browser/prod/excalidraw-assets/image-VAGBVQ3G.js +0 -1
  121. /package/dist/browser/dev/excalidraw-assets-dev/{en-BF4XUPIZ.js.map → en-Y27YPU72.js.map} +0 -0
  122. /package/dist/browser/dev/excalidraw-assets-dev/{image-LVS32KQQ.js.map → image-J7S3ALXP.js.map} +0 -0
@@ -9,7 +9,7 @@ type StaticCanvasProps = {
9
9
  elementsMap: RenderableElementsMap;
10
10
  allElementsMap: NonDeletedSceneElementsMap;
11
11
  visibleElements: readonly NonDeletedExcalidrawElement[];
12
- versionNonce: number | undefined;
12
+ sceneNonce: number | undefined;
13
13
  selectionNonce: number | undefined;
14
14
  scale: number;
15
15
  appState: StaticCanvasAppState;
@@ -70,10 +70,10 @@ const getRelevantAppStateProps = (appState) => ({
70
70
  editingGroupId: appState.editingGroupId,
71
71
  });
72
72
  const areEqual = (prevProps, nextProps) => {
73
- if (prevProps.versionNonce !== nextProps.versionNonce ||
73
+ if (prevProps.sceneNonce !== nextProps.sceneNonce ||
74
74
  prevProps.scale !== nextProps.scale ||
75
75
  // we need to memoize on elementsMap because they may have renewed
76
- // even if versionNonce didn't change (e.g. we filter elements out based
76
+ // even if sceneNonce didn't change (e.g. we filter elements out based
77
77
  // on appState)
78
78
  prevProps.elementsMap !== nextProps.elementsMap ||
79
79
  prevProps.visibleElements !== nextProps.visibleElements) {
@@ -126,12 +126,16 @@ const arrownNarrowUpJSX = (_jsxs("g", { strokeWidth: 1.5, children: [_jsx("path"
126
126
  export const BringForwardIcon = createIcon(arrownNarrowUpJSX, tablerIconProps);
127
127
  export const SendBackwardIcon = createIcon(arrownNarrowUpJSX, {
128
128
  ...tablerIconProps,
129
- transform: "rotate(180)",
129
+ style: {
130
+ transform: "rotate(180deg)",
131
+ },
130
132
  });
131
133
  export const BringToFrontIcon = createIcon(arrowBarToTopJSX, tablerIconProps);
132
134
  export const SendToBackIcon = createIcon(arrowBarToTopJSX, {
133
135
  ...tablerIconProps,
134
- transform: "rotate(180)",
136
+ style: {
137
+ transform: "rotate(180deg)",
138
+ },
135
139
  });
136
140
  //
137
141
  // Align action icons created from scratch to match those of z-index actions
@@ -10,6 +10,7 @@ export declare const isIOS: boolean;
10
10
  export declare const isBrave: () => boolean;
11
11
  export declare const supportsResizeObserver: boolean;
12
12
  export declare const APP_NAME = "Excalidraw";
13
+ export declare const TEXT_AUTOWRAP_THRESHOLD = 36;
13
14
  export declare const DRAGGING_THRESHOLD = 10;
14
15
  export declare const LINE_CONFIRM_THRESHOLD = 8;
15
16
  export declare const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
@@ -15,6 +15,11 @@ export const isIOS = /iPad|iPhone/.test(navigator.platform) ||
15
15
  export const isBrave = () => navigator.brave?.isBrave?.name === "isBrave";
16
16
  export const supportsResizeObserver = typeof window !== "undefined" && "ResizeObserver" in window;
17
17
  export const APP_NAME = "Excalidraw";
18
+ // distance when creating text before it's considered `autoResize: false`
19
+ // we're using higher threshold so that clicks that end up being drags
20
+ // don't unintentionally create text elements that are wrapped to a few chars
21
+ // (happens a lot with fast clicks with the text tool)
22
+ export const TEXT_AUTOWRAP_THRESHOLD = 36; // px
18
23
  export const DRAGGING_THRESHOLD = 10; // px
19
24
  export const LINE_CONFIRM_THRESHOLD = 8; // px
20
25
  export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
@@ -10,6 +10,7 @@ import { arrayToMap } from "../utils";
10
10
  import { detectLineHeight, getContainerElement, getDefaultLineHeight, } from "../element/textElement";
11
11
  import { normalizeLink } from "./url";
12
12
  import { syncInvalidIndices } from "../fractionalIndex";
13
+ import { getSizeFromPoints } from "../points";
13
14
  export const AllowedExcalidrawActiveTools = {
14
15
  selection: true,
15
16
  text: true,
@@ -124,6 +125,7 @@ const restoreElement = (element) => {
124
125
  verticalAlign: element.verticalAlign || DEFAULT_VERTICAL_ALIGN,
125
126
  containerId: element.containerId ?? null,
126
127
  originalText: element.originalText || text,
128
+ autoResize: element.autoResize ?? true,
127
129
  lineHeight,
128
130
  });
129
131
  // if empty text, mark as deleted. We keep in array
@@ -177,6 +179,7 @@ const restoreElement = (element) => {
177
179
  points,
178
180
  x,
179
181
  y,
182
+ ...getSizeFromPoints(points),
180
183
  });
181
184
  }
182
185
  // generic elements
@@ -1,5 +1,5 @@
1
1
  import type { NonDeletedExcalidrawElement } from "./types";
2
- import type { AppState, PointerDownState } from "../types";
2
+ import type { AppState, NormalizedZoomValue, PointerDownState } from "../types";
3
3
  import type Scene from "../scene/Scene";
4
4
  export declare const dragSelectedElements: (pointerDownState: PointerDownState, selectedElements: NonDeletedExcalidrawElement[], offset: {
5
5
  x: number;
@@ -9,7 +9,7 @@ export declare const dragSelectedElements: (pointerDownState: PointerDownState,
9
9
  y: number;
10
10
  }, gridSize: AppState["gridSize"]) => void;
11
11
  export declare const getDragOffsetXY: (selectedElements: NonDeletedExcalidrawElement[], x: number, y: number) => [number, number];
12
- export declare const dragNewElement: (draggingElement: NonDeletedExcalidrawElement, elementType: AppState["activeTool"]["type"], originX: number, originY: number, x: number, y: number, width: number, height: number, shouldMaintainAspectRatio: boolean, shouldResizeFromCenter: boolean, widthAspectRatio?: number | null, originOffset?: {
12
+ export declare const dragNewElement: (draggingElement: NonDeletedExcalidrawElement, elementType: AppState["activeTool"]["type"], originX: number, originY: number, x: number, y: number, width: number, height: number, shouldMaintainAspectRatio: boolean, shouldResizeFromCenter: boolean, zoom: NormalizedZoomValue, widthAspectRatio?: number | null, originOffset?: {
13
13
  x: number;
14
14
  y: number;
15
15
  } | null) => void;
@@ -2,9 +2,11 @@ import { updateBoundElements } from "./binding";
2
2
  import { getCommonBounds } from "./bounds";
3
3
  import { mutateElement } from "./mutateElement";
4
4
  import { getPerfectElementSize } from "./sizeHelpers";
5
- import { getBoundTextElement } from "./textElement";
5
+ import { getBoundTextElement, getMinTextElementWidth } from "./textElement";
6
6
  import { getGridPoint } from "../math";
7
- import { isArrowElement, isFrameLikeElement } from "./typeChecks";
7
+ import { isArrowElement, isFrameLikeElement, isTextElement, } from "./typeChecks";
8
+ import { getFontString } from "../utils";
9
+ import { TEXT_AUTOWRAP_THRESHOLD } from "../constants";
8
10
  export const dragSelectedElements = (pointerDownState, selectedElements, offset, appState, scene, snapOffset, gridSize) => {
9
11
  // we do not want a frame and its elements to be selected at the same time
10
12
  // but when it happens (due to some bug), we want to avoid updating element
@@ -68,7 +70,7 @@ export const getDragOffsetXY = (selectedElements, x, y) => {
68
70
  const [x1, y1] = getCommonBounds(selectedElements);
69
71
  return [x - x1, y - y1];
70
72
  };
71
- export const dragNewElement = (draggingElement, elementType, originX, originY, x, y, width, height, shouldMaintainAspectRatio, shouldResizeFromCenter,
73
+ export const dragNewElement = (draggingElement, elementType, originX, originY, x, y, width, height, shouldMaintainAspectRatio, shouldResizeFromCenter, zoom,
72
74
  /** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
73
75
  true */
74
76
  widthAspectRatio, originOffset = null) => {
@@ -99,12 +101,34 @@ widthAspectRatio, originOffset = null) => {
99
101
  newX = originX - width / 2;
100
102
  newY = originY - height / 2;
101
103
  }
104
+ let textAutoResize = null;
105
+ // NOTE this should apply only to creating text elements, not existing
106
+ // (once we rewrite appState.draggingElement to actually mean dragging
107
+ // elements)
108
+ if (isTextElement(draggingElement)) {
109
+ height = draggingElement.height;
110
+ const minWidth = getMinTextElementWidth(getFontString({
111
+ fontSize: draggingElement.fontSize,
112
+ fontFamily: draggingElement.fontFamily,
113
+ }), draggingElement.lineHeight);
114
+ width = Math.max(width, minWidth);
115
+ if (Math.abs(x - originX) > TEXT_AUTOWRAP_THRESHOLD / zoom) {
116
+ textAutoResize = {
117
+ autoResize: false,
118
+ };
119
+ }
120
+ newY = originY;
121
+ if (shouldResizeFromCenter) {
122
+ newX = originX - width / 2;
123
+ }
124
+ }
102
125
  if (width !== 0 && height !== 0) {
103
126
  mutateElement(draggingElement, {
104
127
  x: newX + (originOffset?.x ?? 0),
105
128
  y: newY + (originOffset?.y ?? 0),
106
129
  width,
107
130
  height,
131
+ ...textAutoResize,
108
132
  });
109
133
  }
110
134
  };
@@ -167,7 +167,7 @@ export declare const actionSetEmbeddableAsActiveTool: {
167
167
  userToFollow: import("../types").UserToFollow | null;
168
168
  followedBy: Set<import("../types").SocketId>;
169
169
  };
170
- storeAction: import("../store").StoreActionType;
170
+ storeAction: "none";
171
171
  };
172
172
  } & {
173
173
  keyTest?: undefined;
@@ -1,5 +1,5 @@
1
1
  import type { ExcalidrawElement, NonDeletedExcalidrawElement, NonDeleted } from "./types";
2
- export { newElement, newTextElement, updateTextElement, refreshTextDimensions, newLinearElement, newImageElement, duplicateElement, } from "./newElement";
2
+ export { newElement, newTextElement, refreshTextDimensions, newLinearElement, newImageElement, duplicateElement, } from "./newElement";
3
3
  export { getElementAbsoluteCoords, getElementBounds, getCommonBounds, getDiamondPoints, getArrowheadPoints, getClosestElementBounds, } from "./bounds";
4
4
  export { OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, getTransformHandlesFromCoords, getTransformHandles, } from "./transformHandles";
5
5
  export { resizeTest, getCursorForResizingElement, getElementWithTransformHandleType, getTransformHandleTypeFromCoords, } from "./resizeTest";
@@ -1,6 +1,6 @@
1
1
  import { isInvisiblySmallElement } from "./sizeHelpers";
2
2
  import { isLinearElementType } from "./typeChecks";
3
- export { newElement, newTextElement, updateTextElement, refreshTextDimensions, newLinearElement, newImageElement, duplicateElement, } from "./newElement";
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>) => 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)?.informMutation();
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 {
@@ -19,6 +19,7 @@ export declare const newMagicFrameElement: (opts: {
19
19
  } & ElementConstructorOpts) => NonDeleted<ExcalidrawMagicFrameElement>;
20
20
  export declare const newTextElement: (opts: {
21
21
  text: string;
22
+ originalText?: string;
22
23
  fontSize?: number;
23
24
  fontFamily?: FontFamilyValues;
24
25
  textAlign?: TextAlign;
@@ -26,6 +27,7 @@ export declare const newTextElement: (opts: {
26
27
  containerId?: ExcalidrawTextContainer["id"] | null;
27
28
  lineHeight?: ExcalidrawTextElement["lineHeight"];
28
29
  strokeWidth?: ExcalidrawTextElement["strokeWidth"];
30
+ autoResize?: ExcalidrawTextElement["autoResize"];
29
31
  } & ElementConstructorOpts) => NonDeleted<ExcalidrawTextElement>;
30
32
  export declare const refreshTextDimensions: (textElement: ExcalidrawTextElement, container: ExcalidrawTextContainer | null, elementsMap: ElementsMap, text?: string) => {
31
33
  x: number;
@@ -34,11 +36,6 @@ export declare const refreshTextDimensions: (textElement: ExcalidrawTextElement,
34
36
  height: number;
35
37
  text: string;
36
38
  } | 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
39
  export declare const newFreeDrawElement: (opts: {
43
40
  type: "freedraw";
44
41
  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 textElement = newElementWith({
88
+ const textElementProps = {
89
89
  ..._newElementBase("text", opts),
90
90
  text,
91
91
  fontSize,
@@ -97,19 +97,26 @@ export const newTextElement = (opts) => {
97
97
  width: metrics.width,
98
98
  height: metrics.height,
99
99
  containerId: opts.containerId || null,
100
- originalText: text,
100
+ originalText: opts.originalText ?? text,
101
+ autoResize: opts.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
- const { width: nextWidth, height: nextHeight } = measureText(nextText, getFontString(element), element.lineHeight);
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), getBoundTextMaxWidth(container, 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),
@@ -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, getMinTextElementWidth, } 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 === "nw" ||
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.includes("e")) {
110
- scaleX = (rotatedX - x1) / (x2 - x1);
111
- }
112
- if (transformHandleType.includes("w")) {
113
- scaleX = (x2 - rotatedX) / (x2 - x1);
114
- }
115
- if (transformHandleType.includes("n")) {
116
- scaleY = (y2 - rotatedY) / (y2 - y1);
117
- }
118
- if (transformHandleType.includes("s")) {
119
- scaleY = (rotatedY - y1) / (y2 - y1);
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,60 @@ 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 = getMinTextElementWidth(getFontString({
183
+ fontSize: element.fontSize,
184
+ fontFamily: element.fontFamily,
185
+ }), element.lineHeight);
186
+ let scaleX = atStartBoundsWidth / boundsCurrentWidth;
187
+ if (transformHandleType.includes("e")) {
188
+ scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
189
+ }
190
+ if (transformHandleType.includes("w")) {
191
+ scaleX = (startBottomRight[0] - rotatedPointer[0]) / boundsCurrentWidth;
192
+ }
193
+ const newWidth = element.width * scaleX < minWidth ? minWidth : element.width * scaleX;
194
+ const text = wrapText(element.originalText, getFontString(element), Math.abs(newWidth));
195
+ const metrics = measureText(text, getFontString(element), element.lineHeight);
196
+ const eleNewHeight = metrics.height;
197
+ const [newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2] = getResizedElementAbsoluteCoords(stateAtResizeStart, newWidth, eleNewHeight, true);
198
+ const newBoundsWidth = newBoundsX2 - newBoundsX1;
199
+ const newBoundsHeight = newBoundsY2 - newBoundsY1;
200
+ let newTopLeft = [...startTopLeft];
201
+ if (["n", "w", "nw"].includes(transformHandleType)) {
202
+ newTopLeft = [
203
+ startBottomRight[0] - Math.abs(newBoundsWidth),
204
+ startTopLeft[1],
205
+ ];
206
+ }
207
+ // adjust topLeft to new rotation point
208
+ const angle = stateAtResizeStart.angle;
209
+ const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
210
+ const newCenter = [
211
+ newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
212
+ newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
213
+ ];
214
+ const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
215
+ newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
216
+ const resizedElement = {
217
+ width: Math.abs(newWidth),
218
+ height: Math.abs(metrics.height),
219
+ x: newTopLeft[0],
220
+ y: newTopLeft[1],
221
+ text,
222
+ autoResize: false,
223
+ };
224
+ mutateElement(element, resizedElement);
225
+ }
174
226
  };
175
227
  export const resizeSingleElement = (originalElements, shouldMaintainAspectRatio, element, elementsMap, transformHandleDirection, shouldResizeFromCenter, pointerX, pointerY) => {
176
228
  const stateAtResizeStart = originalElements.get(element.id);
@@ -541,7 +593,7 @@ export const resizeMultipleElements = (originalElements, selectedElements, eleme
541
593
  handleBindTextResize(element, elementsMap, transformHandleType, true);
542
594
  }
543
595
  }
544
- Scene.getScene(elementsAndUpdates[0].element)?.informMutation();
596
+ Scene.getScene(elementsAndUpdates[0].element)?.triggerUpdate();
545
597
  };
546
598
  const rotateMultipleElements = (originalElements, elements, elementsMap, pointerX, pointerY, shouldRotateWithDiscreteAngle, centerX, centerY) => {
547
599
  let centerAngle = (5 * Math.PI) / 2 + Math.atan2(pointerY - centerY, pointerX - centerX);
@@ -574,7 +626,7 @@ const rotateMultipleElements = (originalElements, elements, elementsMap, pointer
574
626
  }, false);
575
627
  }
576
628
  });
577
- Scene.getScene(elements[0])?.informMutation();
629
+ Scene.getScene(elements[0])?.triggerUpdate();
578
630
  };
579
631
  export const getResizeOffsetXY = (transformHandleType, selectedElements, elementsMap, x, y) => {
580
632
  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
- // Note that for a text element, when "resized" from the side
32
- // we should make it wrap/unwrap
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)) {
@@ -87,4 +87,5 @@ export declare const FONT_METRICS: Record<number, {
87
87
  export declare const getDefaultLineHeight: (fontFamily: FontFamilyValues) => number & {
88
88
  _brand: "unitlessLineHeight";
89
89
  };
90
+ export declare const getMinTextElementWidth: (font: FontString, lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
90
91
  export {};
@@ -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 = getBoundTextMaxWidth(container, textElement);
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
- boundTextUpdates.width = metrics.width;
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);
@@ -661,3 +666,6 @@ export const getDefaultLineHeight = (fontFamily) => {
661
666
  }
662
667
  return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
663
668
  };
669
+ export const getMinTextElementWidth = (font, lineHeight) => {
670
+ return measureText("", font, lineHeight).width + BOUND_TEXT_PADDING * 2;
671
+ };
@@ -1,16 +1,22 @@
1
1
  import type { ExcalidrawElement, ExcalidrawTextElement } from "./types";
2
2
  import type App from "../components/App";
3
- export declare const textWysiwyg: ({ id, onChange, onSubmit, getViewportCoords, element, canvas, excalidrawContainer, app, }: {
3
+ export declare const textWysiwyg: ({ id, onChange, onSubmit, getViewportCoords, element, canvas, excalidrawContainer, app, autoSelect, }: {
4
4
  id: ExcalidrawElement["id"];
5
- onChange?: ((text: string) => void) | undefined;
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
- originalText: string;
14
+ nextOriginalText: string;
10
15
  }) => void;
11
16
  getViewportCoords: (x: number, y: number) => [number, number];
12
17
  element: ExcalidrawTextElement;
13
18
  canvas: HTMLCanvasElement;
14
19
  excalidrawContainer: HTMLDivElement | null;
15
20
  app: App;
21
+ autoSelect?: boolean | undefined;
16
22
  }) => void;