@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
@@ -11,11 +11,12 @@ import { actions } from "../actions/register";
11
11
  import { trackEvent } from "../analytics";
12
12
  import { getDefaultAppState, isEraserActive, isHandToolActive, } from "../appState";
13
13
  import { copyTextToSystemClipboard, parseClipboard } from "../clipboard";
14
- import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, FRAME_STYLE, GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, MIME_TYPES, MQ_MAX_HEIGHT_LANDSCAPE, MQ_MAX_WIDTH_LANDSCAPE, MQ_MAX_WIDTH_PORTRAIT, MQ_RIGHT_SIDEBAR_MIN_WIDTH, POINTER_BUTTON, ROUNDNESS, SCROLL_TIMEOUT, TAP_TWICE_TIMEOUT, TEXT_TO_CENTER_SNAP_THRESHOLD, THEME, THEME_FILTER, TOUCH_CTX_MENU_TIMEOUT, VERTICAL_ALIGN, YOUTUBE_STATES, ZOOM_STEP, POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, isIOS, supportsResizeObserver, DEFAULT_COLLISION_THRESHOLD, } from "../constants";
14
+ import { DEFAULT_FONT_SIZE } from "../constants";
15
+ import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, FRAME_STYLE, GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, MIME_TYPES, MQ_MAX_HEIGHT_LANDSCAPE, MQ_MAX_WIDTH_LANDSCAPE, MQ_MAX_WIDTH_PORTRAIT, MQ_RIGHT_SIDEBAR_MIN_WIDTH, POINTER_BUTTON, ROUNDNESS, SCROLL_TIMEOUT, TAP_TWICE_TIMEOUT, TEXT_TO_CENTER_SNAP_THRESHOLD, THEME, THEME_FILTER, TOUCH_CTX_MENU_TIMEOUT, VERTICAL_ALIGN, YOUTUBE_STATES, ZOOM_STEP, POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, isIOS, supportsResizeObserver, DEFAULT_COLLISION_THRESHOLD, DEFAULT_TEXT_ALIGN, } from "../constants";
15
16
  import { exportCanvas, loadFromBlob } from "../data";
16
17
  import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
17
18
  import { restore, restoreElements } from "../data/restore";
18
- import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, updateTextElement, redrawTextBoundingBox, getElementAbsoluteCoords, } from "../element";
19
+ import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, refreshTextDimensions, redrawTextBoundingBox, getElementAbsoluteCoords, } from "../element";
19
20
  import { bindOrUnbindLinearElement, bindOrUnbindLinearElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, updateBoundElements, getSuggestedBindingsForArrows, } from "../element/binding";
20
21
  import { LinearElementEditor } from "../element/linearElementEditor";
21
22
  import { mutateElement, newElementWith } from "../element/mutateElement";
@@ -44,7 +45,7 @@ import { dataURLToFile, generateIdFromFile, getDataURL, getFileFromEvent, ImageU
44
45
  import { getInitializedImageElements, loadHTMLImageElement, normalizeSVG, updateImageCache as _updateImageCache, } from "../element/image";
45
46
  import throttle from "lodash.throttle";
46
47
  import { fileOpen } from "../data/filesystem";
47
- import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
48
+ import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getMinTextElementWidth, isMeasureTextSupported, isValidTextContainer, measureText, wrapText, } from "../element/textElement";
48
49
  import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
49
50
  import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
50
51
  import { shouldShowBoundingBox } from "../element/transformHandles";
@@ -90,6 +91,9 @@ import { isOverScrollBars } from "../scene/scrollbars";
90
91
  import { syncInvalidIndices, syncMovedIndices } from "../fractionalIndex";
91
92
  import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
92
93
  import { getShortcutFromShortcutName } from "../actions/shortcuts";
94
+ import { actionTextAutoResize } from "../actions/actionTextAutoResize";
95
+ import { getVisibleSceneBounds } from "../element/bounds";
96
+ import { isMaybeMermaidDefinition } from "../mermaid";
93
97
  const AppContext = React.createContext(null);
94
98
  const AppPropsContext = React.createContext(null);
95
99
  const deviceContextInitialValue = {
@@ -286,10 +290,7 @@ class App extends React.Component {
286
290
  container: this.excalidrawContainerRef.current,
287
291
  id: this.id,
288
292
  };
289
- this.fonts = new Fonts({
290
- scene: this.scene,
291
- onSceneUpdated: this.onSceneUpdated,
292
- });
293
+ this.fonts = new Fonts({ scene: this.scene });
293
294
  this.history = new History();
294
295
  this.actionManager.registerAll(actions);
295
296
  this.actionManager.registerAction(createUndoAction(this.history, this.store));
@@ -444,7 +445,7 @@ class App extends React.Component {
444
445
  return false;
445
446
  });
446
447
  if (updated) {
447
- this.scene.informMutation();
448
+ this.scene.triggerUpdate();
448
449
  }
449
450
  // GC
450
451
  this.iFrameRefs.forEach((ref, id) => {
@@ -803,9 +804,9 @@ class App extends React.Component {
803
804
  render() {
804
805
  const selectedElements = this.scene.getSelectedElements(this.state);
805
806
  const { renderTopRightUI, renderCustomStats } = this.props;
806
- const versionNonce = this.scene.getVersionNonce();
807
+ const sceneNonce = this.scene.getSceneNonce();
807
808
  const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
808
- versionNonce,
809
+ sceneNonce,
809
810
  zoom: this.state.zoom,
810
811
  offsetLeft: this.state.offsetLeft,
811
812
  offsetTop: this.state.offsetTop,
@@ -874,14 +875,14 @@ class App extends React.Component {
874
875
  this.focusContainer();
875
876
  callback?.();
876
877
  });
877
- } })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elementsMap: elementsMap, allElementsMap: allElementsMap, visibleElements: visibleElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
878
+ } })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elementsMap: elementsMap, allElementsMap: allElementsMap, visibleElements: visibleElements, sceneNonce: sceneNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
878
879
  imageCache: this.imageCache,
879
880
  isExporting: false,
880
881
  renderGrid: true,
881
882
  canvasBackgroundColor: this.state.viewBackgroundColor,
882
883
  embedsValidationStatus: this.embedsValidationStatus,
883
884
  elementsPendingErasure: this.elementsPendingErasure,
884
- } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, selectedElements: selectedElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, device: this.device, renderInteractiveSceneCallback: this.renderInteractiveSceneCallback, handleCanvasRef: this.handleInteractiveCanvasRef, onContextMenu: this.handleCanvasContextMenu, onPointerMove: this.handleCanvasPointerMove, onPointerUp: this.handleCanvasPointerUp, onPointerCancel: this.removePointer, onTouchMove: this.handleTouchMove, onPointerDown: this.handleCanvasPointerDown, onDoubleClick: this.handleCanvasDoubleClick }), this.state.userToFollow && (_jsx(FollowMode, { width: this.state.width, height: this.state.height, userToFollow: this.state.userToFollow, onDisconnect: this.maybeUnfollowRemoteUser })), this.renderFrameNames()] }), this.renderEmbeddables()] }) }) }) }) }) }) }) }));
885
+ } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, allElementsMap: allElementsMap, selectedElements: selectedElements, sceneNonce: sceneNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, device: this.device, renderInteractiveSceneCallback: this.renderInteractiveSceneCallback, handleCanvasRef: this.handleInteractiveCanvasRef, onContextMenu: this.handleCanvasContextMenu, onPointerMove: this.handleCanvasPointerMove, onPointerUp: this.handleCanvasPointerUp, onPointerCancel: this.removePointer, onTouchMove: this.handleTouchMove, onPointerDown: this.handleCanvasPointerDown, onDoubleClick: this.handleCanvasDoubleClick }), this.state.userToFollow && (_jsx(FollowMode, { width: this.state.width, height: this.state.height, userToFollow: this.state.userToFollow, onDisconnect: this.maybeUnfollowRemoteUser })), this.renderFrameNames()] }), this.renderEmbeddables()] }) }) }) }) }) }) }) }));
885
886
  }
886
887
  focusContainer = () => {
887
888
  this.excalidrawContainerRef.current?.focus();
@@ -931,7 +932,7 @@ class App extends React.Component {
931
932
  mutateElement(frameElement, { customData: { generationData: data } }, false);
932
933
  }
933
934
  this.magicGenerations.set(frameElement.id, data);
934
- this.onSceneUpdated();
935
+ this.triggerRender();
935
936
  };
936
937
  getTextFromElements(elements) {
937
938
  const text = elements
@@ -1441,7 +1442,7 @@ class App extends React.Component {
1441
1442
  this.store.onStoreIncrementEmitter.on((increment) => {
1442
1443
  this.history.record(increment.elementsChange, increment.appStateChange);
1443
1444
  });
1444
- this.scene.addCallback(this.onSceneUpdated);
1445
+ this.scene.onUpdate(this.triggerRender);
1445
1446
  this.addEventListeners();
1446
1447
  if (this.props.autoFocus && this.excalidrawContainerRef.current) {
1447
1448
  this.focusContainer();
@@ -1479,6 +1480,7 @@ class App extends React.Component {
1479
1480
  componentWillUnmount() {
1480
1481
  this.renderer.destroy();
1481
1482
  this.scene = new Scene();
1483
+ this.fonts = new Fonts({ scene: this.scene });
1482
1484
  this.renderer = new Renderer(this.scene);
1483
1485
  this.files = {};
1484
1486
  this.imageCache.clear();
@@ -1534,7 +1536,7 @@ class App extends React.Component {
1534
1536
  this.onRemoveEventListenersEmitter.once(addEventListener(document, EVENT.KEYDOWN, this.onKeyDown, false));
1535
1537
  }
1536
1538
  this.onRemoveEventListenersEmitter.once(addEventListener(this.excalidrawContainerRef.current, EVENT.WHEEL, this.onWheel, { passive: false }), addEventListener(window, EVENT.MESSAGE, this.onWindowMessage, false), addEventListener(document, EVENT.POINTER_UP, this.removePointer), // #3553
1537
- addEventListener(document, EVENT.COPY, this.onCopy), addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), addEventListener(document, EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition),
1539
+ addEventListener(document, EVENT.COPY, this.onCopy), addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), addEventListener(document, EVENT.POINTER_MOVE, this.updateCurrentCursorPosition),
1538
1540
  // rerender text elements on font load to fix #637 && #1553
1539
1541
  addEventListener(document.fonts, "loadingdone", (event) => {
1540
1542
  const loadedFontFaces = event.fontfaces;
@@ -1543,6 +1545,9 @@ class App extends React.Component {
1543
1545
  // Safari-only desktop pinch zoom
1544
1546
  addEventListener(document, EVENT.GESTURE_START, this.onGestureStart, false), addEventListener(document, EVENT.GESTURE_CHANGE, this.onGestureChange, false), addEventListener(document, EVENT.GESTURE_END, this.onGestureEnd, false), addEventListener(window, EVENT.FOCUS, () => {
1545
1547
  this.maybeCleanupAfterMissingPointerUp(null);
1548
+ // browsers (chrome?) tend to free up memory a lot, which results
1549
+ // in canvas context being cleared. Thus re-render on focus.
1550
+ this.triggerRender(true);
1546
1551
  }));
1547
1552
  if (this.state.viewModeEnabled) {
1548
1553
  return;
@@ -1849,6 +1854,26 @@ class App extends React.Component {
1849
1854
  });
1850
1855
  }
1851
1856
  else if (data.text) {
1857
+ if (data.text && isMaybeMermaidDefinition(data.text)) {
1858
+ const api = await import("@excalidraw/mermaid-to-excalidraw");
1859
+ try {
1860
+ const { elements: skeletonElements, files } = await api.parseMermaidToExcalidraw(data.text, {
1861
+ fontSize: DEFAULT_FONT_SIZE,
1862
+ });
1863
+ const elements = convertToExcalidrawElements(skeletonElements, {
1864
+ regenerateIds: true,
1865
+ });
1866
+ this.addElementsFromPasteOrLibrary({
1867
+ elements,
1868
+ files,
1869
+ position: "cursor",
1870
+ });
1871
+ return;
1872
+ }
1873
+ catch (err) {
1874
+ console.warn(`parsing pasted text as mermaid definition failed: ${err.message}`);
1875
+ }
1876
+ }
1852
1877
  const nonEmptyLines = normalizeEOL(data.text)
1853
1878
  .split(/\n+/)
1854
1879
  .map((s) => s.trim())
@@ -2053,27 +2078,46 @@ class App extends React.Component {
2053
2078
  text,
2054
2079
  fontSize: this.state.currentItemFontSize,
2055
2080
  fontFamily: this.state.currentItemFontFamily,
2056
- textAlign: this.state.currentItemTextAlign,
2081
+ textAlign: DEFAULT_TEXT_ALIGN,
2057
2082
  verticalAlign: DEFAULT_VERTICAL_ALIGN,
2058
2083
  locked: false,
2059
2084
  };
2085
+ const fontString = getFontString({
2086
+ fontSize: textElementProps.fontSize,
2087
+ fontFamily: textElementProps.fontFamily,
2088
+ });
2089
+ const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
2090
+ const [x1, , x2] = getVisibleSceneBounds(this.state);
2091
+ // long texts should not go beyond 800 pixels in width nor should it go below 200 px
2092
+ const maxTextWidth = Math.max(Math.min((x2 - x1) * 0.5, 800), 200);
2060
2093
  const LINE_GAP = 10;
2061
2094
  let currentY = y;
2062
2095
  const lines = isPlainPaste ? [text] : text.split("\n");
2063
2096
  const textElements = lines.reduce((acc, line, idx) => {
2064
- const text = line.trim();
2065
- const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
2066
- if (text.length) {
2097
+ const originalText = line.trim();
2098
+ if (originalText.length) {
2067
2099
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
2068
2100
  x,
2069
2101
  y: currentY,
2070
2102
  });
2103
+ let metrics = measureText(originalText, fontString, lineHeight);
2104
+ const isTextWrapped = metrics.width > maxTextWidth;
2105
+ const text = isTextWrapped
2106
+ ? wrapText(originalText, fontString, maxTextWidth)
2107
+ : originalText;
2108
+ metrics = isTextWrapped
2109
+ ? measureText(text, fontString, lineHeight)
2110
+ : metrics;
2111
+ const startX = x - metrics.width / 2;
2112
+ const startY = currentY - metrics.height / 2;
2071
2113
  const element = newTextElement({
2072
2114
  ...textElementProps,
2073
- x,
2074
- y: currentY,
2115
+ x: startX,
2116
+ y: startY,
2075
2117
  text,
2118
+ originalText,
2076
2119
  lineHeight,
2120
+ autoResize: !isTextWrapped,
2077
2121
  frameId: topLayerFrame ? topLayerFrame.id : null,
2078
2122
  });
2079
2123
  acc.push(element);
@@ -2292,7 +2336,7 @@ class App extends React.Component {
2292
2336
  ShapeCache.delete(element);
2293
2337
  }
2294
2338
  });
2295
- this.scene.informMutation();
2339
+ this.scene.triggerUpdate();
2296
2340
  this.addNewImagesToImageCache();
2297
2341
  });
2298
2342
  updateScene = withBatchedUpdates((sceneData) => {
@@ -2326,8 +2370,15 @@ class App extends React.Component {
2326
2370
  this.setState({ collaborators: sceneData.collaborators });
2327
2371
  }
2328
2372
  });
2329
- onSceneUpdated = () => {
2330
- this.setState({});
2373
+ triggerRender = (
2374
+ /** force always re-renders canvas even if no change */
2375
+ force) => {
2376
+ if (force === true) {
2377
+ this.scene.triggerUpdate();
2378
+ }
2379
+ else {
2380
+ this.setState({});
2381
+ }
2331
2382
  };
2332
2383
  /**
2333
2384
  * @returns whether the menu was toggled on or off
@@ -2772,15 +2823,16 @@ class App extends React.Component {
2772
2823
  });
2773
2824
  handleTextWysiwyg(element, { isExistingElement = false, }) {
2774
2825
  const elementsMap = this.scene.getElementsMapIncludingDeleted();
2775
- const updateElement = (text, originalText, isDeleted) => {
2826
+ const updateElement = (nextOriginalText, isDeleted) => {
2776
2827
  this.scene.replaceAllElements([
2777
2828
  // Not sure why we include deleted elements as well hence using deleted elements map
2778
2829
  ...this.scene.getElementsIncludingDeleted().map((_element) => {
2779
2830
  if (_element.id === element.id && isTextElement(_element)) {
2780
- return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
2781
- text,
2782
- isDeleted,
2783
- originalText,
2831
+ return newElementWith(_element, {
2832
+ originalText: nextOriginalText,
2833
+ isDeleted: isDeleted ?? _element.isDeleted,
2834
+ // returns (wrapped) text and new dimensions
2835
+ ...refreshTextDimensions(_element, getContainerElement(_element, elementsMap), elementsMap, nextOriginalText),
2784
2836
  });
2785
2837
  }
2786
2838
  return _element;
@@ -2800,15 +2852,15 @@ class App extends React.Component {
2800
2852
  viewportY - this.state.offsetTop,
2801
2853
  ];
2802
2854
  },
2803
- onChange: withBatchedUpdates((text) => {
2804
- updateElement(text, text, false);
2855
+ onChange: withBatchedUpdates((nextOriginalText) => {
2856
+ updateElement(nextOriginalText, false);
2805
2857
  if (isNonDeletedElement(element)) {
2806
2858
  updateBoundElements(element, elementsMap);
2807
2859
  }
2808
2860
  }),
2809
- onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
2810
- const isDeleted = !text.trim();
2811
- updateElement(text, originalText, isDeleted);
2861
+ onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
2862
+ const isDeleted = !nextOriginalText.trim();
2863
+ updateElement(nextOriginalText, isDeleted);
2812
2864
  // select the created text element only if submitting via keyboard
2813
2865
  // (when submitting via click it should act as signal to deselect)
2814
2866
  if (!isDeleted && viaKeyboard) {
@@ -2842,12 +2894,17 @@ class App extends React.Component {
2842
2894
  element,
2843
2895
  excalidrawContainer: this.excalidrawContainerRef.current,
2844
2896
  app: this,
2897
+ // when text is selected, it's hard (at least on iOS) to re-position the
2898
+ // caret (i.e. deselect). There's not much use for always selecting
2899
+ // the text on edit anyway (and users can select-all from contextmenu
2900
+ // if needed)
2901
+ autoSelect: !this.device.isTouchScreen,
2845
2902
  });
2846
2903
  // deselect all other elements when inserting text
2847
2904
  this.deselectElements();
2848
2905
  // do an initial update to re-initialize element position since we were
2849
2906
  // modifying element's x/y for sake of editor (case: syncing to remote)
2850
- updateElement(element.text, element.originalText, false);
2907
+ updateElement(element.originalText, false);
2851
2908
  }
2852
2909
  deselectElements() {
2853
2910
  this.setState({
@@ -3044,7 +3101,7 @@ class App extends React.Component {
3044
3101
  }
3045
3102
  return isTextBindableContainer(hitElement, false) ? hitElement : null;
3046
3103
  }
3047
- startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, }) => {
3104
+ startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, autoEdit = true, }) => {
3048
3105
  let shouldBindToContainer = false;
3049
3106
  let parentCenterPosition = insertAtParentCenter &&
3050
3107
  this.getTextWysiwygSnappedToCenterPosition(sceneX, sceneY, this.state, container);
@@ -3145,12 +3202,17 @@ class App extends React.Component {
3145
3202
  this.scene.insertElement(element);
3146
3203
  }
3147
3204
  }
3148
- this.setState({
3149
- editingElement: element,
3150
- });
3151
- this.handleTextWysiwyg(element, {
3152
- isExistingElement: !!existingTextElement,
3153
- });
3205
+ if (autoEdit || existingTextElement || container) {
3206
+ this.handleTextWysiwyg(element, {
3207
+ isExistingElement: !!existingTextElement,
3208
+ });
3209
+ }
3210
+ else {
3211
+ this.setState({
3212
+ draggingElement: element,
3213
+ multiElement: null,
3214
+ });
3215
+ }
3154
3216
  };
3155
3217
  handleCanvasDoubleClick = (event) => {
3156
3218
  // case: double-clicking with arrow/line tool selected would both create
@@ -3317,8 +3379,11 @@ class App extends React.Component {
3317
3379
  }, state);
3318
3380
  this.translateCanvas({
3319
3381
  zoom: zoomState.zoom,
3320
- scrollX: zoomState.scrollX + deltaX / nextZoom,
3321
- scrollY: zoomState.scrollY + deltaY / nextZoom,
3382
+ // 2x multiplier is just a magic number that makes this work correctly
3383
+ // on touchscreen devices (note: if we get report that panning is slower/faster
3384
+ // than actual movement, consider swapping with devicePixelRatio)
3385
+ scrollX: zoomState.scrollX + 2 * (deltaX / nextZoom),
3386
+ scrollY: zoomState.scrollY + 2 * (deltaY / nextZoom),
3322
3387
  shouldCacheIgnoreZoom: true,
3323
3388
  });
3324
3389
  });
@@ -3639,7 +3704,7 @@ class App extends React.Component {
3639
3704
  }
3640
3705
  }
3641
3706
  this.elementsPendingErasure = new Set(this.elementsPendingErasure);
3642
- this.onSceneUpdated();
3707
+ this.triggerRender();
3643
3708
  }
3644
3709
  };
3645
3710
  // set touch moving for mobile context menu
@@ -3850,7 +3915,6 @@ class App extends React.Component {
3850
3915
  }
3851
3916
  if (this.state.activeTool.type === "text") {
3852
3917
  this.handleTextOnPointerDown(event, pointerDownState);
3853
- return;
3854
3918
  }
3855
3919
  else if (this.state.activeTool.type === "arrow" ||
3856
3920
  this.state.activeTool.type === "line") {
@@ -4398,6 +4462,7 @@ class App extends React.Component {
4398
4462
  sceneY,
4399
4463
  insertAtParentCenter: !event.altKey,
4400
4464
  container,
4465
+ autoEdit: false,
4401
4466
  });
4402
4467
  resetCursor(this.interactiveCanvas);
4403
4468
  if (!this.state.activeTool.locked) {
@@ -5288,6 +5353,21 @@ class App extends React.Component {
5288
5353
  }
5289
5354
  return;
5290
5355
  }
5356
+ if (isTextElement(draggingElement)) {
5357
+ const minWidth = getMinTextElementWidth(getFontString({
5358
+ fontSize: draggingElement.fontSize,
5359
+ fontFamily: draggingElement.fontFamily,
5360
+ }), draggingElement.lineHeight);
5361
+ if (draggingElement.width < minWidth) {
5362
+ mutateElement(draggingElement, {
5363
+ autoResize: true,
5364
+ });
5365
+ }
5366
+ this.resetCursor();
5367
+ this.handleTextWysiwyg(draggingElement, {
5368
+ isExistingElement: true,
5369
+ });
5370
+ }
5291
5371
  if (activeTool.type !== "selection" &&
5292
5372
  draggingElement &&
5293
5373
  isInvisiblySmallElement(draggingElement)) {
@@ -5323,7 +5403,7 @@ class App extends React.Component {
5323
5403
  groupIds: [],
5324
5404
  });
5325
5405
  removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
5326
- this.scene.informMutation();
5406
+ this.scene.triggerUpdate();
5327
5407
  }
5328
5408
  }
5329
5409
  }
@@ -5640,7 +5720,7 @@ class App extends React.Component {
5640
5720
  }
5641
5721
  restoreReadyToEraseElements = () => {
5642
5722
  this.elementsPendingErasure = new Set();
5643
- this.onSceneUpdated();
5723
+ this.triggerRender();
5644
5724
  };
5645
5725
  eraseElements = () => {
5646
5726
  let didChange = false;
@@ -5932,7 +6012,7 @@ class App extends React.Component {
5932
6012
  if (uncachedImageElements.length) {
5933
6013
  const { updatedFiles } = await this.updateImageCache(uncachedImageElements, files);
5934
6014
  if (updatedFiles.size) {
5935
- this.scene.informMutation();
6015
+ this.scene.triggerUpdate();
5936
6016
  }
5937
6017
  }
5938
6018
  };
@@ -6211,7 +6291,7 @@ class App extends React.Component {
6211
6291
  }
6212
6292
  if (draggingElement.type === "selection" &&
6213
6293
  this.state.activeTool.type !== "eraser") {
6214
- dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.origin.x, pointerDownState.origin.y, pointerCoords.x, pointerCoords.y, distance(pointerDownState.origin.x, pointerCoords.x), distance(pointerDownState.origin.y, pointerCoords.y), shouldMaintainAspectRatio(event), shouldResizeFromCenter(event));
6294
+ dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.origin.x, pointerDownState.origin.y, pointerCoords.x, pointerCoords.y, distance(pointerDownState.origin.x, pointerCoords.x), distance(pointerDownState.origin.y, pointerCoords.y), shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), this.state.zoom.value);
6215
6295
  }
6216
6296
  else {
6217
6297
  let [gridX, gridY] = getGridPoint(pointerCoords.x, pointerCoords.y, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
@@ -6237,7 +6317,7 @@ class App extends React.Component {
6237
6317
  });
6238
6318
  dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.originInGrid.x, pointerDownState.originInGrid.y, gridX, gridY, distance(pointerDownState.originInGrid.x, gridX), distance(pointerDownState.originInGrid.y, gridY), isImageElement(draggingElement)
6239
6319
  ? !shouldMaintainAspectRatio(event)
6240
- : shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), aspectRatio, this.state.originSnapOffset);
6320
+ : shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), this.state.zoom.value, aspectRatio, this.state.originSnapOffset);
6241
6321
  // highlight elements that are to be added to frames on frames creation
6242
6322
  if (this.state.activeTool.type === TOOL_TYPE.frame ||
6243
6323
  this.state.activeTool.type === TOOL_TYPE.magicframe) {
@@ -6346,6 +6426,7 @@ class App extends React.Component {
6346
6426
  return [actionCopy, ...options];
6347
6427
  }
6348
6428
  return [
6429
+ CONTEXT_MENU_SEPARATOR,
6349
6430
  actionCut,
6350
6431
  actionCopy,
6351
6432
  actionPaste,
@@ -6358,6 +6439,7 @@ class App extends React.Component {
6358
6439
  actionPasteStyles,
6359
6440
  CONTEXT_MENU_SEPARATOR,
6360
6441
  actionGroup,
6442
+ actionTextAutoResize,
6361
6443
  actionUnbindText,
6362
6444
  actionBindText,
6363
6445
  actionWrapTextInContainer,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
3
  // TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect />
4
- export const ButtonIconSelect = (props) => (_jsx("div", { className: "buttonList buttonListIcon", children: props.options.map((option) => props.type === "button" ? (_jsx("button", { onClick: (event) => props.onClick(option.value, event), className: clsx({
4
+ export const ButtonIconSelect = (props) => (_jsx("div", { className: "buttonList buttonListIcon", children: props.options.map((option) => props.type === "button" ? (_jsx("button", { type: "button", onClick: (event) => props.onClick(option.value, event), className: clsx({
5
5
  active: option.active ?? props.value === option.value,
6
6
  }), "data-testid": option.testId, title: option.text, children: option.icon }, option.text)) : (_jsxs("label", { className: clsx({ active: props.value === option.value }), title: option.text, children: [_jsx("input", { type: "radio", name: props.group, onChange: () => props.onChange(option.value), checked: props.value === option.value, "data-testid": option.testId }), option.icon] }, option.text))) }));
@@ -6,5 +6,5 @@ export const CheckboxItem = ({ children, checked, onChange, className }) => {
6
6
  return (_jsxs("div", { className: clsx("Checkbox", className, { "is-checked": checked }), onClick: (event) => {
7
7
  onChange(!checked, event);
8
8
  event.currentTarget.querySelector(".Checkbox-box").focus();
9
- }, children: [_jsx("button", { className: "Checkbox-box", role: "checkbox", "aria-checked": checked, children: checkIcon }), _jsx("div", { className: "Checkbox-label", children: children })] }));
9
+ }, children: [_jsx("button", { type: "button", className: "Checkbox-box", role: "checkbox", "aria-checked": checked, children: checkIcon }), _jsx("div", { className: "Checkbox-label", children: children })] }));
10
10
  };
@@ -416,7 +416,7 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
416
416
  ...command,
417
417
  icon: command.icon || boltIcon,
418
418
  order: command.order ?? getCategoryOrder(command.category),
419
- haystack: `${deburr(command.label)} ${command.keywords?.join(" ") || ""}`,
419
+ haystack: `${deburr(command.label.toLocaleLowerCase())} ${command.keywords?.join(" ") || ""}`,
420
420
  };
421
421
  });
422
422
  setAllCommands(allCommands);
@@ -584,7 +584,7 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
584
584
  setCurrentCommand(showLastUsed ? lastUsed : matchingCommands[0] || null);
585
585
  return;
586
586
  }
587
- const _query = deburr(commandSearch.replace(/[<>-_| ]/g, ""));
587
+ const _query = deburr(commandSearch.toLocaleLowerCase().replace(/[<>_| -]/g, ""));
588
588
  matchingCommands = fuzzy
589
589
  .filter(_query, matchingCommands, {
590
590
  extract: (command) => command.haystack,
@@ -46,7 +46,7 @@ export const ContextMenu = React.memo(({ actionManager, items, top, left, onClos
46
46
  onClose(() => {
47
47
  actionManager.executeAction(item, "contextMenu");
48
48
  });
49
- }, children: _jsxs("button", { className: clsx("context-menu-item", {
49
+ }, children: _jsxs("button", { type: "button", className: clsx("context-menu-item", {
50
50
  dangerous: actionName === "deleteSelectedElements",
51
51
  checkmark: item.checked?.(appState),
52
52
  }), children: [_jsx("div", { className: "context-menu-item__label", children: label }), _jsx("kbd", { className: "context-menu-item__shortcut", children: actionName
@@ -72,5 +72,5 @@ export const Dialog = (props) => {
72
72
  };
73
73
  return (_jsx(Modal, { className: clsx("Dialog", props.className, {
74
74
  "Dialog--fullscreen": isFullscreen,
75
- }), labelledBy: "dialog-title", maxWidth: getDialogSize(props.size), onCloseRequest: onClose, closeOnClickOutside: props.closeOnClickOutside, children: _jsxs(Island, { ref: setIslandNode, children: [props.title && (_jsx("h2", { id: `${id}-dialog-title`, className: "Dialog__title", children: _jsx("span", { className: "Dialog__titleContent", children: props.title }) })), isFullscreen && (_jsx("button", { className: "Dialog__close", onClick: onClose, title: t("buttons.close"), "aria-label": t("buttons.close"), children: CloseIcon })), _jsx("div", { className: "Dialog__content", children: props.children })] }) }));
75
+ }), labelledBy: "dialog-title", maxWidth: getDialogSize(props.size), onCloseRequest: onClose, closeOnClickOutside: props.closeOnClickOutside, children: _jsxs(Island, { ref: setIslandNode, children: [props.title && (_jsx("h2", { id: `${id}-dialog-title`, className: "Dialog__title", children: _jsx("span", { className: "Dialog__titleContent", children: props.title }) })), isFullscreen && (_jsx("button", { className: "Dialog__close", onClick: onClose, title: t("buttons.close"), "aria-label": t("buttons.close"), type: "button", children: CloseIcon })), _jsx("div", { className: "Dialog__content", children: props.children })] }) }));
76
76
  };
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CloseIcon } from "../icons";
3
3
  import "./FollowMode.scss";
4
4
  const FollowMode = ({ height, width, userToFollow, onDisconnect, }) => {
5
- return (_jsx("div", { className: "follow-mode", style: { width, height }, children: _jsxs("div", { className: "follow-mode__badge", children: [_jsxs("div", { className: "follow-mode__badge__label", children: ["Following", " ", _jsx("span", { className: "follow-mode__badge__username", title: userToFollow.username, children: userToFollow.username })] }), _jsx("button", { onClick: onDisconnect, className: "follow-mode__disconnect-btn", children: CloseIcon })] }) }));
5
+ return (_jsx("div", { className: "follow-mode", style: { width, height }, children: _jsxs("div", { className: "follow-mode__badge", children: [_jsxs("div", { className: "follow-mode__badge__label", children: ["Following", " ", _jsx("span", { className: "follow-mode__badge__username", title: userToFollow.username, children: userToFollow.username })] }), _jsx("button", { type: "button", onClick: onDisconnect, className: "follow-mode__disconnect-btn", children: CloseIcon })] }) }));
6
6
  };
7
7
  export default FollowMode;
@@ -72,7 +72,7 @@ function Picker({ options, value, label, onChange, onClose, }) {
72
72
  event.nativeEvent.stopImmediatePropagation();
73
73
  event.stopPropagation();
74
74
  };
75
- return (_jsx("div", { className: `picker`, role: "dialog", "aria-modal": "true", "aria-label": label, onKeyDown: handleKeyDown, children: _jsx("div", { className: "picker-content", ref: rGallery, children: options.map((option, i) => (_jsxs("button", { className: clsx("picker-option", {
75
+ return (_jsx("div", { className: `picker`, role: "dialog", "aria-modal": "true", "aria-label": label, onKeyDown: handleKeyDown, children: _jsx("div", { className: "picker-content", ref: rGallery, children: options.map((option, i) => (_jsxs("button", { type: "button", className: clsx("picker-option", {
76
76
  active: value === option.value,
77
77
  }), onClick: (event) => {
78
78
  event.currentTarget.focus();
@@ -92,7 +92,7 @@ export function IconPicker({ value, label, options, onChange, group = "", }) {
92
92
  const [isActive, setActive] = React.useState(false);
93
93
  const rPickerButton = React.useRef(null);
94
94
  const isRTL = getLanguage().rtl;
95
- return (_jsxs("div", { children: [_jsx("button", { name: group, className: isActive ? "active" : "", "aria-label": label, onClick: () => setActive(!isActive), ref: rPickerButton, children: options.find((option) => option.value === value)?.icon }), _jsx(React.Suspense, { fallback: "", children: isActive ? (_jsxs(_Fragment, { children: [_jsx(Popover, { onCloseRequest: (event) => event.target !== rPickerButton.current && setActive(false), ...(isRTL ? { right: 5.5 } : { left: -5.5 }), children: _jsx(Picker, { options: options.filter((opt) => opt.showInPicker !== false), value: value, label: label, onChange: onChange, onClose: () => {
95
+ return (_jsxs("div", { children: [_jsx("button", { name: group, type: "button", className: isActive ? "active" : "", "aria-label": label, onClick: () => setActive(!isActive), ref: rPickerButton, children: options.find((option) => option.value === value)?.icon }), _jsx(React.Suspense, { fallback: "", children: isActive ? (_jsxs(_Fragment, { children: [_jsx(Popover, { onCloseRequest: (event) => event.target !== rPickerButton.current && setActive(false), ...(isRTL ? { right: 5.5 } : { left: -5.5 }), children: _jsx(Picker, { options: options.filter((opt) => opt.showInPicker !== false), value: value, label: label, onChange: onChange, onClose: () => {
96
96
  setActive(false);
97
97
  rPickerButton.current?.focus();
98
98
  } }) }), _jsx("div", { className: "picker-triangle" })] })) : null })] }));
@@ -126,7 +126,7 @@ const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas
126
126
  }, false);
127
127
  ShapeCache.delete(element);
128
128
  }
129
- Scene.getScene(selectedElements[0])?.informMutation();
129
+ Scene.getScene(selectedElements[0])?.triggerUpdate();
130
130
  }
131
131
  else if (colorPickerType === "elementBackground") {
132
132
  setAppState({
@@ -160,7 +160,7 @@ const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas
160
160
  ? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
161
161
  : {}, children: [renderWelcomeScreen && _jsx(tunnels.WelcomeScreenCenterTunnel.Out, {}), renderFixedSideContainer(), _jsx(Footer, { appState: appState, actionManager: actionManager, showExitZenModeBtn: showExitZenModeBtn, renderWelcomeScreen: renderWelcomeScreen }), appState.showStats && (_jsx(Stats, { appState: appState, setAppState: setAppState, elements: elements, onClose: () => {
162
162
  actionManager.executeAction(actionToggleStats);
163
- }, renderCustomStats: renderCustomStats })), appState.scrolledOutside && (_jsx("button", { className: "scroll-back-to-content", onClick: () => {
163
+ }, renderCustomStats: renderCustomStats })), appState.scrolledOutside && (_jsx("button", { type: "button", className: "scroll-back-to-content", onClick: () => {
164
164
  setAppState((appState) => ({
165
165
  ...calculateScrollCenter(elements, appState),
166
166
  }));
@@ -37,7 +37,7 @@ export const MobileMenu = ({ appState, elements, actionManager, setAppState, onL
37
37
  !appState.viewModeEnabled &&
38
38
  showSelectedShapeActions(appState, elements) ? (_jsx(Section, { className: "App-mobile-menu", heading: "selectedShapeActions", children: _jsx(SelectedShapeActions, { appState: appState, elementsMap: app.scene.getNonDeletedElementsMap(), renderAction: actionManager.renderAction }) })) : null, _jsxs("footer", { className: "App-toolbar", children: [renderAppToolbar(), appState.scrolledOutside &&
39
39
  !appState.openMenu &&
40
- !appState.openSidebar && (_jsx("button", { className: "scroll-back-to-content", onClick: () => {
40
+ !appState.openSidebar && (_jsx("button", { type: "button", className: "scroll-back-to-content", onClick: () => {
41
41
  setAppState((appState) => ({
42
42
  ...calculateScrollCenter(elements, appState),
43
43
  }));
@@ -35,7 +35,7 @@ const ChartPreviewBtn = (props) => {
35
35
  previewNode.replaceChildren();
36
36
  };
37
37
  }, [props.spreadsheet, props.chartType, props.selected]);
38
- return (_jsx("button", { className: "ChartPreview", onClick: () => {
38
+ return (_jsx("button", { type: "button", className: "ChartPreview", onClick: () => {
39
39
  if (chartElements) {
40
40
  props.onClick(props.chartType, chartElements);
41
41
  }
@@ -2,14 +2,15 @@ import React from "react";
2
2
  import type { DOMAttributes } from "react";
3
3
  import type { Device, InteractiveCanvasAppState } from "../../types";
4
4
  import type { RenderableElementsMap, RenderInteractiveSceneCallback } from "../../scene/types";
5
- import type { NonDeletedExcalidrawElement } from "../../element/types";
5
+ import type { NonDeletedExcalidrawElement, NonDeletedSceneElementsMap } from "../../element/types";
6
6
  type InteractiveCanvasProps = {
7
7
  containerRef: React.RefObject<HTMLDivElement>;
8
8
  canvas: HTMLCanvasElement | null;
9
9
  elementsMap: RenderableElementsMap;
10
10
  visibleElements: readonly NonDeletedExcalidrawElement[];
11
11
  selectedElements: readonly NonDeletedExcalidrawElement[];
12
- versionNonce: number | undefined;
12
+ allElementsMap: NonDeletedSceneElementsMap;
13
+ sceneNonce: number | undefined;
13
14
  selectionNonce: number | undefined;
14
15
  scale: number;
15
16
  appState: InteractiveCanvasAppState;
@@ -49,6 +49,7 @@ const InteractiveCanvas = (props) => {
49
49
  elementsMap: props.elementsMap,
50
50
  visibleElements: props.visibleElements,
51
51
  selectedElements: props.selectedElements,
52
+ allElementsMap: props.allElementsMap,
52
53
  scale: window.devicePixelRatio,
53
54
  appState: props.appState,
54
55
  renderConfig: {
@@ -99,14 +100,15 @@ const getRelevantAppStateProps = (appState) => ({
99
100
  activeEmbeddable: appState.activeEmbeddable,
100
101
  snapLines: appState.snapLines,
101
102
  zenModeEnabled: appState.zenModeEnabled,
103
+ editingElement: appState.editingElement,
102
104
  });
103
105
  const areEqual = (prevProps, nextProps) => {
104
106
  // This could be further optimised if needed, as we don't have to render interactive canvas on each scene mutation
105
107
  if (prevProps.selectionNonce !== nextProps.selectionNonce ||
106
- prevProps.versionNonce !== nextProps.versionNonce ||
108
+ prevProps.sceneNonce !== nextProps.sceneNonce ||
107
109
  prevProps.scale !== nextProps.scale ||
108
110
  // we need to memoize on elementsMap because they may have renewed
109
- // even if versionNonce didn't change (e.g. we filter elements out based
111
+ // even if sceneNonce didn't change (e.g. we filter elements out based
110
112
  // on appState)
111
113
  prevProps.elementsMap !== nextProps.elementsMap ||
112
114
  prevProps.visibleElements !== nextProps.visibleElements ||