@excalidraw/excalidraw 0.17.1-c0b80a0 → 0.17.1-c329470

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 (178) hide show
  1. package/dist/browser/dev/excalidraw-assets-dev/{chunk-JGDL4H2X.js → chunk-3DLVY5XU.js} +8272 -6864
  2. package/dist/browser/dev/excalidraw-assets-dev/chunk-3DLVY5XU.js.map +7 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/{chunk-V7NFEZA6.js → chunk-NOAEU4NM.js} +9 -2
  4. package/dist/browser/dev/excalidraw-assets-dev/chunk-NOAEU4NM.js.map +7 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js → en-7IBTMWBG.js} +2 -2
  6. package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js → image-N5AC7SEK.js} +2 -6
  7. package/dist/browser/dev/index.css +85 -50
  8. package/dist/browser/dev/index.css.map +3 -3
  9. package/dist/browser/dev/index.js +4375 -3766
  10. package/dist/browser/dev/index.js.map +4 -4
  11. package/dist/browser/prod/excalidraw-assets/{chunk-LDVEIXGO.js → chunk-7CSIPVOW.js} +2 -2
  12. package/dist/browser/prod/excalidraw-assets/chunk-TX3BU7T2.js +47 -0
  13. package/dist/browser/prod/excalidraw-assets/{en-UPNEHLDS.js → en-LOGQBETY.js} +1 -1
  14. package/dist/browser/prod/excalidraw-assets/image-3V4U7GZE.js +1 -0
  15. package/dist/browser/prod/index.css +1 -1
  16. package/dist/browser/prod/index.js +40 -40
  17. package/dist/dev/index.css +85 -50
  18. package/dist/dev/index.css.map +3 -3
  19. package/dist/dev/index.js +8688 -6706
  20. package/dist/dev/index.js.map +4 -4
  21. package/dist/{prod/locales/en-ZXYG7GCR.json → dev/locales/en-V6KXFSCK.json} +8 -1
  22. package/dist/excalidraw/actions/actionAlign.d.ts +7 -6
  23. package/dist/excalidraw/actions/actionAlign.js +14 -14
  24. package/dist/excalidraw/actions/actionClipboard.d.ts +7 -3
  25. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +7 -3
  26. package/dist/excalidraw/actions/actionDeleteSelected.js +103 -34
  27. package/dist/excalidraw/actions/actionDuplicateSelection.js +105 -95
  28. package/dist/excalidraw/actions/actionFlip.js +16 -7
  29. package/dist/excalidraw/actions/actionFrame.d.ts +493 -0
  30. package/dist/excalidraw/actions/actionFrame.js +45 -2
  31. package/dist/excalidraw/actions/actionGroup.js +6 -4
  32. package/dist/excalidraw/actions/actionProperties.js +145 -116
  33. package/dist/excalidraw/actions/actionSelectAll.js +4 -3
  34. package/dist/excalidraw/actions/shortcuts.d.ts +1 -1
  35. package/dist/excalidraw/actions/shortcuts.js +1 -0
  36. package/dist/excalidraw/actions/types.d.ts +1 -1
  37. package/dist/excalidraw/align.d.ts +2 -1
  38. package/dist/excalidraw/align.js +15 -6
  39. package/dist/excalidraw/clipboard.d.ts +27 -5
  40. package/dist/excalidraw/clipboard.js +55 -28
  41. package/dist/excalidraw/components/Actions.d.ts +2 -1
  42. package/dist/excalidraw/components/Actions.js +4 -2
  43. package/dist/excalidraw/components/ActiveConfirmDialog.d.ts +1 -1
  44. package/dist/excalidraw/components/ActiveConfirmDialog.js +2 -3
  45. package/dist/excalidraw/components/App.d.ts +1 -0
  46. package/dist/excalidraw/components/App.js +216 -111
  47. package/dist/excalidraw/components/ColorPicker/ColorInput.js +2 -3
  48. package/dist/excalidraw/components/ColorPicker/ColorPicker.js +2 -3
  49. package/dist/excalidraw/components/ColorPicker/CustomColorList.js +1 -1
  50. package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
  51. package/dist/excalidraw/components/ColorPicker/PickerColorList.js +1 -1
  52. package/dist/excalidraw/components/ColorPicker/ShadeList.js +1 -1
  53. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
  54. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
  55. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +3 -3
  56. package/dist/excalidraw/components/ConfirmDialog.js +17 -5
  57. package/dist/excalidraw/components/Dialog.js +2 -3
  58. package/dist/excalidraw/components/EyeDropper.d.ts +1 -1
  59. package/dist/excalidraw/components/EyeDropper.js +1 -1
  60. package/dist/excalidraw/components/IconPicker.d.ts +2 -2
  61. package/dist/excalidraw/components/IconPicker.js +56 -53
  62. package/dist/excalidraw/components/LayerUI.js +6 -6
  63. package/dist/excalidraw/components/LibraryMenu.d.ts +2 -16
  64. package/dist/excalidraw/components/LibraryMenu.js +70 -28
  65. package/dist/excalidraw/components/LibraryMenuHeaderContent.js +4 -5
  66. package/dist/excalidraw/components/MobileMenu.js +1 -1
  67. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirm.js +2 -3
  68. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
  69. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.js +2 -3
  70. package/dist/excalidraw/components/Range.d.ts +9 -0
  71. package/dist/excalidraw/components/Range.js +24 -0
  72. package/dist/excalidraw/components/SearchMenu.d.ts +1 -1
  73. package/dist/excalidraw/components/SearchMenu.js +3 -4
  74. package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  75. package/dist/excalidraw/components/Sidebar/Sidebar.js +2 -3
  76. package/dist/excalidraw/components/Stats/Collapsible.d.ts +2 -1
  77. package/dist/excalidraw/components/Stats/Collapsible.js +2 -2
  78. package/dist/excalidraw/components/Stats/Dimension.js +94 -8
  79. package/dist/excalidraw/components/Stats/MultiDimension.js +8 -5
  80. package/dist/excalidraw/components/Stats/Position.js +63 -3
  81. package/dist/excalidraw/components/Stats/index.js +21 -4
  82. package/dist/excalidraw/components/Stats/utils.d.ts +1 -1
  83. package/dist/excalidraw/components/Stats/utils.js +2 -55
  84. package/dist/excalidraw/components/TTDDialog/TTDDialog.js +1 -1
  85. package/dist/excalidraw/components/ToolButton.js +4 -9
  86. package/dist/excalidraw/components/hoc/withInternalFallback.js +3 -3
  87. package/dist/excalidraw/components/hyperlink/Hyperlink.js +6 -12
  88. package/dist/excalidraw/components/icons.d.ts +9 -0
  89. package/dist/excalidraw/components/icons.js +4 -4
  90. package/dist/excalidraw/components/main-menu/DefaultItems.js +2 -3
  91. package/dist/excalidraw/constants.d.ts +5 -1
  92. package/dist/excalidraw/constants.js +9 -1
  93. package/dist/excalidraw/context/tunnels.d.ts +2 -1
  94. package/dist/excalidraw/context/tunnels.js +3 -1
  95. package/dist/excalidraw/data/blob.d.ts +1 -0
  96. package/dist/excalidraw/data/blob.js +7 -3
  97. package/dist/excalidraw/data/filesystem.d.ts +2 -1
  98. package/dist/excalidraw/data/filesystem.js +1 -0
  99. package/dist/excalidraw/data/image.d.ts +0 -6
  100. package/dist/excalidraw/data/image.js +1 -43
  101. package/dist/excalidraw/data/index.js +6 -6
  102. package/dist/excalidraw/data/library.d.ts +9 -3
  103. package/dist/excalidraw/data/library.js +43 -6
  104. package/dist/excalidraw/data/restore.js +26 -8
  105. package/dist/excalidraw/data/url.d.ts +0 -1
  106. package/dist/excalidraw/data/url.js +2 -4
  107. package/dist/excalidraw/editor-jotai.d.ts +56 -0
  108. package/dist/excalidraw/editor-jotai.js +8 -0
  109. package/dist/excalidraw/element/binding.d.ts +9 -6
  110. package/dist/excalidraw/element/binding.js +124 -44
  111. package/dist/excalidraw/element/bounds.js +10 -0
  112. package/dist/excalidraw/element/cropElement.d.ts +5 -0
  113. package/dist/excalidraw/element/cropElement.js +28 -1
  114. package/dist/excalidraw/element/dragElements.js +13 -7
  115. package/dist/excalidraw/element/elbowArrow.d.ts +16 -0
  116. package/dist/excalidraw/element/elbowArrow.js +1268 -0
  117. package/dist/excalidraw/element/embeddable.js +4 -5
  118. package/dist/excalidraw/element/flowchart.d.ts +1 -1
  119. package/dist/excalidraw/element/flowchart.js +25 -9
  120. package/dist/excalidraw/element/heading.d.ts +5 -1
  121. package/dist/excalidraw/element/heading.js +5 -1
  122. package/dist/excalidraw/element/image.js +19 -5
  123. package/dist/excalidraw/element/linearElementEditor.d.ts +9 -10
  124. package/dist/excalidraw/element/linearElementEditor.js +97 -38
  125. package/dist/excalidraw/element/mutateElement.d.ts +3 -1
  126. package/dist/excalidraw/element/mutateElement.js +31 -4
  127. package/dist/excalidraw/element/newElement.d.ts +8 -12
  128. package/dist/excalidraw/element/newElement.js +36 -21
  129. package/dist/excalidraw/element/resizeElements.d.ts +20 -5
  130. package/dist/excalidraw/element/resizeElements.js +593 -361
  131. package/dist/excalidraw/element/sortElements.js +1 -4
  132. package/dist/excalidraw/element/types.d.ts +23 -1
  133. package/dist/excalidraw/fonts/Fonts.d.ts +0 -16
  134. package/dist/excalidraw/fonts/Fonts.js +6 -31
  135. package/dist/excalidraw/frame.d.ts +11 -5
  136. package/dist/excalidraw/frame.js +146 -35
  137. package/dist/excalidraw/groups.js +3 -0
  138. package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
  139. package/dist/excalidraw/hooks/useLibraryItemSvg.js +2 -3
  140. package/dist/excalidraw/hooks/useScrollPosition.js +1 -1
  141. package/dist/excalidraw/i18n.js +3 -4
  142. package/dist/excalidraw/index.js +3 -4
  143. package/dist/excalidraw/locales/en.json +8 -1
  144. package/dist/excalidraw/renderer/interactiveScene.js +43 -32
  145. package/dist/excalidraw/renderer/staticScene.js +6 -4
  146. package/dist/excalidraw/renderer/staticSvgScene.js +1 -1
  147. package/dist/excalidraw/scene/Shape.js +40 -17
  148. package/dist/excalidraw/scene/comparisons.d.ts +0 -477
  149. package/dist/excalidraw/scene/comparisons.js +0 -37
  150. package/dist/excalidraw/scene/export.d.ts +7 -0
  151. package/dist/excalidraw/scene/export.js +107 -43
  152. package/dist/excalidraw/scene/index.d.ts +1 -1
  153. package/dist/excalidraw/scene/index.js +1 -1
  154. package/dist/excalidraw/scene/selection.js +4 -1
  155. package/dist/excalidraw/types.d.ts +15 -0
  156. package/dist/excalidraw/utility-types.d.ts +1 -0
  157. package/dist/excalidraw/utils.d.ts +8 -1
  158. package/dist/excalidraw/utils.js +9 -0
  159. package/dist/excalidraw/visualdebug.d.ts +8 -1
  160. package/dist/excalidraw/visualdebug.js +3 -0
  161. package/dist/math/line.d.ts +19 -0
  162. package/dist/math/line.js +32 -3
  163. package/dist/math/point.d.ts +10 -0
  164. package/dist/math/point.js +12 -1
  165. package/dist/prod/index.css +1 -1
  166. package/dist/prod/index.js +29 -44
  167. package/dist/{dev/locales/en-ZXYG7GCR.json → prod/locales/en-V6KXFSCK.json} +8 -1
  168. package/package.json +5 -2
  169. package/dist/browser/dev/excalidraw-assets-dev/chunk-JGDL4H2X.js.map +0 -7
  170. package/dist/browser/dev/excalidraw-assets-dev/chunk-V7NFEZA6.js.map +0 -7
  171. package/dist/browser/prod/excalidraw-assets/chunk-S2XKB3DE.js +0 -62
  172. package/dist/browser/prod/excalidraw-assets/image-OFI2YYMP.js +0 -1
  173. package/dist/excalidraw/element/routing.d.ts +0 -12
  174. package/dist/excalidraw/element/routing.js +0 -642
  175. package/dist/excalidraw/jotai.d.ts +0 -34
  176. package/dist/excalidraw/jotai.js +0 -18
  177. /package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js.map → en-7IBTMWBG.js.map} +0 -0
  178. /package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js.map → image-N5AC7SEK.js.map} +0 -0
@@ -97,8 +97,5 @@ const normalizeBoundElementsOrder = (elements) => {
97
97
  return [...sortedElements];
98
98
  };
99
99
  export const normalizeElementOrder = (elements) => {
100
- // console.time();
101
- const ret = normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
102
- // console.timeEnd();
103
- return ret;
100
+ return normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
104
101
  };
@@ -205,7 +205,7 @@ export type PointBinding = {
205
205
  export type FixedPointBinding = Merge<PointBinding, {
206
206
  fixedPoint: FixedPoint;
207
207
  }>;
208
- export type Arrowhead = "arrow" | "bar" | "dot" | "circle" | "circle_outline" | "triangle" | "triangle_outline" | "diamond" | "diamond_outline";
208
+ export type Arrowhead = "arrow" | "bar" | "dot" | "circle" | "circle_outline" | "triangle" | "triangle_outline" | "diamond" | "diamond_outline" | "crowfoot_one" | "crowfoot_many" | "crowfoot_one_or_many";
209
209
  export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{
210
210
  type: "line" | "arrow";
211
211
  points: readonly LocalPoint[];
@@ -215,6 +215,11 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{
215
215
  startArrowhead: Arrowhead | null;
216
216
  endArrowhead: Arrowhead | null;
217
217
  }>;
218
+ export type FixedSegment = {
219
+ start: LocalPoint;
220
+ end: LocalPoint;
221
+ index: number;
222
+ };
218
223
  export type ExcalidrawArrowElement = ExcalidrawLinearElement & Readonly<{
219
224
  type: "arrow";
220
225
  elbowed: boolean;
@@ -223,6 +228,23 @@ export type ExcalidrawElbowArrowElement = Merge<ExcalidrawArrowElement, {
223
228
  elbowed: true;
224
229
  startBinding: FixedPointBinding | null;
225
230
  endBinding: FixedPointBinding | null;
231
+ fixedSegments: FixedSegment[] | null;
232
+ /**
233
+ * Marks that the 3rd point should be used as the 2nd point of the arrow in
234
+ * order to temporarily hide the first segment of the arrow without losing
235
+ * the data from the points array. It allows creating the expected arrow
236
+ * path when the arrow with fixed segments is bound on a horizontal side and
237
+ * moved to a vertical and vica versa.
238
+ */
239
+ startIsSpecial: boolean | null;
240
+ /**
241
+ * Marks that the 3rd point backwards from the end should be used as the 2nd
242
+ * point of the arrow in order to temporarily hide the last segment of the
243
+ * arrow without losing the data from the points array. It allows creating
244
+ * the expected arrow path when the arrow with fixed segments is bound on a
245
+ * horizontal side and moved to a vertical and vica versa.
246
+ */
247
+ endIsSpecial: boolean | null;
226
248
  }>;
227
249
  export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase & Readonly<{
228
250
  type: "freedraw";
@@ -31,28 +31,12 @@ export declare class Fonts {
31
31
  onLoaded: (fontFaces: readonly FontFace[]) => void;
32
32
  /**
33
33
  * Load font faces for a given scene and trigger scene update.
34
- *
35
- * FontFaceSet loadingdone event we listen on may not always
36
- * fire (looking at you Safari), so on init we manually load all
37
- * fonts and rerender scene text elements once done.
38
- *
39
- * For Safari we make sure to check against each loaded font face
40
- * with the unique characters per family in the scene,
41
- * otherwise fonts might remain unloaded.
42
34
  */
43
35
  loadSceneFonts: () => Promise<FontFace[]>;
44
36
  /**
45
37
  * Load font faces for passed elements - use when the scene is unavailable (i.e. export).
46
- *
47
- * For Safari we make sure to check against each loaded font face,
48
- * with the unique characters per family in the elements
49
- * otherwise fonts might remain unloaded.
50
38
  */
51
39
  static loadElementsFonts: (elements: readonly ExcalidrawElement[]) => Promise<FontFace[]>;
52
- /**
53
- * Load all registered font faces.
54
- */
55
- static loadAllFonts: () => Promise<FontFace[]>;
56
40
  /**
57
41
  * Generate CSS @font-face declarations for the given elements.
58
42
  */
@@ -1,4 +1,4 @@
1
- import { FONT_FAMILY, FONT_FAMILY_FALLBACKS, CJK_HAND_DRAWN_FALLBACK_FONT, WINDOWS_EMOJI_FALLBACK_FONT, isSafari, getFontFamilyFallbacks, } from "../constants";
1
+ import { FONT_FAMILY, FONT_FAMILY_FALLBACKS, CJK_HAND_DRAWN_FALLBACK_FONT, WINDOWS_EMOJI_FALLBACK_FONT, getFontFamilyFallbacks, } from "../constants";
2
2
  import { isTextElement } from "../element";
3
3
  import { charWidth, getContainerElement } from "../element/textElement";
4
4
  import { containsCJK } from "../element/textWrapping";
@@ -94,43 +94,20 @@ export class Fonts {
94
94
  };
95
95
  /**
96
96
  * Load font faces for a given scene and trigger scene update.
97
- *
98
- * FontFaceSet loadingdone event we listen on may not always
99
- * fire (looking at you Safari), so on init we manually load all
100
- * fonts and rerender scene text elements once done.
101
- *
102
- * For Safari we make sure to check against each loaded font face
103
- * with the unique characters per family in the scene,
104
- * otherwise fonts might remain unloaded.
105
97
  */
106
98
  loadSceneFonts = async () => {
107
99
  const sceneFamilies = this.getSceneFamilies();
108
- const charsPerFamily = isSafari
109
- ? Fonts.getCharsPerFamily(this.scene.getNonDeletedElements())
110
- : undefined;
100
+ const charsPerFamily = Fonts.getCharsPerFamily(this.scene.getNonDeletedElements());
111
101
  return Fonts.loadFontFaces(sceneFamilies, charsPerFamily);
112
102
  };
113
103
  /**
114
104
  * Load font faces for passed elements - use when the scene is unavailable (i.e. export).
115
- *
116
- * For Safari we make sure to check against each loaded font face,
117
- * with the unique characters per family in the elements
118
- * otherwise fonts might remain unloaded.
119
105
  */
120
106
  static loadElementsFonts = async (elements) => {
121
107
  const fontFamilies = Fonts.getUniqueFamilies(elements);
122
- const charsPerFamily = isSafari
123
- ? Fonts.getCharsPerFamily(elements)
124
- : undefined;
108
+ const charsPerFamily = Fonts.getCharsPerFamily(elements);
125
109
  return Fonts.loadFontFaces(fontFamilies, charsPerFamily);
126
110
  };
127
- /**
128
- * Load all registered font faces.
129
- */
130
- static loadAllFonts = async () => {
131
- const allFamilies = Fonts.getAllFamilies();
132
- return Fonts.loadFontFaces(allFamilies);
133
- };
134
111
  /**
135
112
  * Generate CSS @font-face declarations for the given elements.
136
113
  */
@@ -184,11 +161,9 @@ export class Fonts {
184
161
  fontFamily,
185
162
  fontSize: 16,
186
163
  });
187
- // WARN: without "text" param it does not have to mean that all font faces are loaded, instead it could be just one!
188
- // for Safari on init, we rather check with the "text" param, even though it's less efficient, as otherwise fonts might remain unloaded
189
- const text = isSafari && charsPerFamily
190
- ? Fonts.getCharacters(charsPerFamily, fontFamily)
191
- : "";
164
+ // WARN: without "text" param it does not have to mean that all font faces are loaded as it could be just one irrelevant font face!
165
+ // instead, we are always checking chars used in the family, so that no required font faces remain unloaded
166
+ const text = Fonts.getCharacters(charsPerFamily, fontFamily);
192
167
  if (!window.document.fonts.check(font, text)) {
193
168
  yield promiseTry(async () => {
194
169
  try {
@@ -5,7 +5,7 @@ import type { ReadonlySetLike } from "./utility-types";
5
5
  export declare const bindElementsToFramesAfterDuplication: (nextElements: readonly ExcalidrawElement[], oldElements: readonly ExcalidrawElement[], oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>) => void;
6
6
  export declare function isElementIntersectingFrame(element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap): boolean;
7
7
  export declare const getElementsCompletelyInFrame: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => ExcalidrawElement[];
8
- export declare const isElementContainingFrame: (elements: readonly ExcalidrawElement[], element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => boolean;
8
+ export declare const isElementContainingFrame: (element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => boolean;
9
9
  export declare const getElementsIntersectingFrame: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement) => ExcalidrawElement[];
10
10
  export declare const elementsAreInFrameBounds: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => boolean;
11
11
  export declare const elementOverlapsWithFrame: (element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => boolean;
@@ -31,7 +31,8 @@ export declare const getFrameLikeElements: (allElements: ExcalidrawElementsInclu
31
31
  */
32
32
  export declare const getRootElements: (allElements: ExcalidrawElementsIncludingDeleted) => ExcalidrawElement[];
33
33
  export declare const getElementsInResizingFrame: (allElements: ExcalidrawElementsIncludingDeleted, frame: ExcalidrawFrameLikeElement, appState: AppState, elementsMap: ElementsMap) => ExcalidrawElement[];
34
- export declare const getElementsInNewFrame: (elements: ExcalidrawElementsIncludingDeleted, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => ExcalidrawElement[];
34
+ export declare const getElementsInNewFrame: (elements: ExcalidrawElementsIncludingDeleted, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => (import("./element/types").ExcalidrawSelectionElement | import("./element/types").ExcalidrawRectangleElement | import("./element/types").ExcalidrawDiamondElement | import("./element/types").ExcalidrawEllipseElement | import("./element/types").ExcalidrawTextElement | import("./element/types").ExcalidrawLinearElement | import("./element/types").ExcalidrawFreeDrawElement | import("./element/types").ExcalidrawImageElement | import("./element/types").ExcalidrawFrameElement | import("./element/types").ExcalidrawMagicFrameElement | import("./element/types").ExcalidrawIframeElement | import("./element/types").ExcalidrawEmbeddableElement)[];
35
+ export declare const omitPartialGroups: (elements: ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, allElementsMap: ElementsMap) => (import("./element/types").ExcalidrawSelectionElement | import("./element/types").ExcalidrawRectangleElement | import("./element/types").ExcalidrawDiamondElement | import("./element/types").ExcalidrawEllipseElement | import("./element/types").ExcalidrawTextElement | import("./element/types").ExcalidrawLinearElement | import("./element/types").ExcalidrawFreeDrawElement | import("./element/types").ExcalidrawImageElement | import("./element/types").ExcalidrawFrameElement | import("./element/types").ExcalidrawMagicFrameElement | import("./element/types").ExcalidrawIframeElement | import("./element/types").ExcalidrawEmbeddableElement)[];
35
36
  export declare const getContainingFrame: (element: ExcalidrawElement, elementsMap: ElementsMap) => ExcalidrawFrameLikeElement | null;
36
37
  /** */
37
38
  export declare const filterElementsEligibleAsFrameChildren: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement) => ExcalidrawElement[];
@@ -42,7 +43,7 @@ export declare const filterElementsEligibleAsFrameChildren: (elements: readonly
42
43
  *
43
44
  * @returns mutated allElements (same data structure)
44
45
  */
45
- export declare const addElementsToFrame: <T extends ElementsMapOrArray>(allElements: T, elementsToAdd: NonDeletedExcalidrawElement[], frame: ExcalidrawFrameLikeElement) => T;
46
+ export declare const addElementsToFrame: <T extends ElementsMapOrArray>(allElements: T, elementsToAdd: NonDeletedExcalidrawElement[], frame: ExcalidrawFrameLikeElement, appState: AppState) => T;
46
47
  export declare const removeElementsFromFrame: (elementsToRemove: ReadonlySetLike<NonDeletedExcalidrawElement>, elementsMap: ElementsMap) => void;
47
48
  export declare const removeAllElementsFromFrame: <T extends ExcalidrawElement>(allElements: readonly T[], frame: ExcalidrawFrameLikeElement) => readonly T[];
48
49
  export declare const replaceAllElementsInFrame: <T extends ExcalidrawElement>(allElements: readonly T[], nextElementsInFrame: ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, app: AppClassProperties) => T[];
@@ -57,7 +58,12 @@ export declare const omitGroupsContainingFrameLikes: (allElements: ElementsMapOr
57
58
  * depending on the appState, return target frame, which is the frame the given element
58
59
  * is going to be added to or remove from
59
60
  */
60
- export declare const getTargetFrame: (element: ExcalidrawElement, elementsMap: ElementsMap, appState: StaticCanvasAppState) => import("./element/types").ExcalidrawFrameElement | import("./element/types").ExcalidrawMagicFrameElement | null;
61
- export declare const isElementInFrame: (element: ExcalidrawElement, allElementsMap: ElementsMap, appState: StaticCanvasAppState) => boolean;
61
+ export declare const getTargetFrame: (element: ExcalidrawElement, elementsMap: ElementsMap, appState: StaticCanvasAppState) => ExcalidrawFrameLikeElement | null;
62
+ export declare const isElementInFrame: (element: ExcalidrawElement, allElementsMap: ElementsMap, appState: StaticCanvasAppState, opts?: {
63
+ targetFrame?: ExcalidrawFrameLikeElement;
64
+ checkedGroups?: Map<string, boolean>;
65
+ }) => boolean;
66
+ export declare const shouldApplyFrameClip: (element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, appState: StaticCanvasAppState, elementsMap: ElementsMap, checkedGroups?: Map<string, boolean>) => boolean;
62
67
  export declare const getFrameLikeTitle: (element: ExcalidrawFrameLikeElement) => string;
63
68
  export declare const getElementsOverlappingFrame: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement) => NonDeletedExcalidrawElement[];
69
+ export declare const frameAndChildrenSelectedTogether: (selectedElements: readonly ExcalidrawElement[]) => boolean;
@@ -35,8 +35,8 @@ export function isElementIntersectingFrame(element, frame, elementsMap) {
35
35
  }
36
36
  export const getElementsCompletelyInFrame = (elements, frame, elementsMap) => omitGroupsContainingFrameLikes(getElementsWithinSelection(elements, frame, elementsMap, false)).filter((element) => (!isFrameLikeElement(element) && !element.frameId) ||
37
37
  element.frameId === frame.id);
38
- export const isElementContainingFrame = (elements, element, frame, elementsMap) => {
39
- return getElementsWithinSelection(elements, element, elementsMap).some((e) => e.id === frame.id);
38
+ export const isElementContainingFrame = (element, frame, elementsMap) => {
39
+ return getElementsWithinSelection([frame], element, elementsMap).some((e) => e.id === frame.id);
40
40
  };
41
41
  export const getElementsIntersectingFrame = (elements, frame) => {
42
42
  const elementsMap = arrayToMap(elements);
@@ -53,7 +53,7 @@ export const elementsAreInFrameBounds = (elements, frame, elementsMap) => {
53
53
  export const elementOverlapsWithFrame = (element, frame, elementsMap) => {
54
54
  return (elementsAreInFrameBounds([element], frame, elementsMap) ||
55
55
  isElementIntersectingFrame(element, frame, elementsMap) ||
56
- isElementContainingFrame([frame], element, frame, elementsMap));
56
+ isElementContainingFrame(element, frame, elementsMap));
57
57
  };
58
58
  export const isCursorInFrame = (cursorCoords, frame, elementsMap) => {
59
59
  const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
@@ -122,7 +122,7 @@ export const getElementsInResizingFrame = (allElements, frame, appState, element
122
122
  const nextElementsInFrame = new Set(prevElementsInFrame);
123
123
  const elementsCompletelyInFrame = new Set([
124
124
  ...getElementsCompletelyInFrame(allElements, frame, elementsMap),
125
- ...prevElementsInFrame.filter((element) => isElementContainingFrame(allElements, element, frame, elementsMap)),
125
+ ...prevElementsInFrame.filter((element) => isElementContainingFrame(element, frame, elementsMap)),
126
126
  ]);
127
127
  const elementsNotCompletelyInFrame = prevElementsInFrame.filter((element) => !elementsCompletelyInFrame.has(element));
128
128
  // for elements that are completely in the frame
@@ -178,7 +178,31 @@ export const getElementsInResizingFrame = (allElements, frame, appState, element
178
178
  });
179
179
  };
180
180
  export const getElementsInNewFrame = (elements, frame, elementsMap) => {
181
- return omitGroupsContainingFrameLikes(elements, getElementsCompletelyInFrame(elements, frame, elementsMap));
181
+ return omitPartialGroups(omitGroupsContainingFrameLikes(elements, getElementsCompletelyInFrame(elements, frame, elementsMap)), frame, elementsMap);
182
+ };
183
+ export const omitPartialGroups = (elements, frame, allElementsMap) => {
184
+ const elementsToReturn = [];
185
+ const checkedGroups = new Map();
186
+ for (const element of elements) {
187
+ let shouldOmit = false;
188
+ if (element.groupIds.length > 0) {
189
+ // if some partial group should be omitted, then all elements in that group should be omitted
190
+ if (element.groupIds.some((gid) => checkedGroups.get(gid))) {
191
+ shouldOmit = true;
192
+ }
193
+ else {
194
+ const allElementsInGroup = new Set(element.groupIds.flatMap((gid) => getElementsInGroup(allElementsMap, gid)));
195
+ shouldOmit = !elementsAreInFrameBounds(Array.from(allElementsInGroup), frame, allElementsMap);
196
+ }
197
+ element.groupIds.forEach((gid) => {
198
+ checkedGroups.set(gid, shouldOmit);
199
+ });
200
+ }
201
+ if (!shouldOmit) {
202
+ elementsToReturn.push(element);
203
+ }
204
+ }
205
+ return elementsToReturn;
182
206
  };
183
207
  export const getContainingFrame = (element, elementsMap) => {
184
208
  if (!element.frameId) {
@@ -234,7 +258,7 @@ export const filterElementsEligibleAsFrameChildren = (elements, frame) => {
234
258
  *
235
259
  * @returns mutated allElements (same data structure)
236
260
  */
237
- export const addElementsToFrame = (allElements, elementsToAdd, frame) => {
261
+ export const addElementsToFrame = (allElements, elementsToAdd, frame, appState) => {
238
262
  const elementsMap = arrayToMap(allElements);
239
263
  const currTargetFrameChildrenMap = new Map();
240
264
  for (const element of allElements.values()) {
@@ -258,6 +282,14 @@ export const addElementsToFrame = (allElements, elementsToAdd, frame) => {
258
282
  (element.frameId && otherFrames.has(element.frameId))) {
259
283
  continue;
260
284
  }
285
+ // if the element is already in another frame (which is also in elementsToAdd),
286
+ // it means that frame and children are selected at the same time
287
+ // => keep original frame membership, do not add to the target frame
288
+ if (element.frameId &&
289
+ appState.selectedElementIds[element.id] &&
290
+ appState.selectedElementIds[element.frameId]) {
291
+ continue;
292
+ }
261
293
  if (!currTargetFrameChildrenMap.has(element.id)) {
262
294
  finalElementsToAdd.push(element);
263
295
  }
@@ -303,7 +335,7 @@ export const removeAllElementsFromFrame = (allElements, frame) => {
303
335
  return allElements;
304
336
  };
305
337
  export const replaceAllElementsInFrame = (allElements, nextElementsInFrame, frame, app) => {
306
- return addElementsToFrame(removeAllElementsFromFrame(allElements, frame), nextElementsInFrame, frame).slice();
338
+ return addElementsToFrame(removeAllElementsFromFrame(allElements, frame), nextElementsInFrame, frame, app.state).slice();
307
339
  };
308
340
  /** does not mutate elements, but returns new ones */
309
341
  export const updateFrameMembershipOfSelectedElements = (allElements, appState, app) => {
@@ -378,6 +410,13 @@ export const getTargetFrame = (element, elementsMap, appState) => {
378
410
  const _element = isTextElement(element)
379
411
  ? getContainerElement(element, elementsMap) || element
380
412
  : element;
413
+ // if the element and its containing frame are both selected, then
414
+ // the containing frame is the target frame
415
+ if (_element.frameId &&
416
+ appState.selectedElementIds[_element.id] &&
417
+ appState.selectedElementIds[_element.frameId]) {
418
+ return getContainingFrame(_element, elementsMap);
419
+ }
381
420
  return appState.selectedElementIds[_element.id] &&
382
421
  appState.selectedElementsAreBeingDragged
383
422
  ? appState.frameToHighlight
@@ -385,44 +424,111 @@ export const getTargetFrame = (element, elementsMap, appState) => {
385
424
  };
386
425
  // TODO: this a huge bottleneck for large scenes, optimise
387
426
  // given an element, return if the element is in some frame
388
- export const isElementInFrame = (element, allElementsMap, appState) => {
389
- const frame = getTargetFrame(element, allElementsMap, appState);
427
+ export const isElementInFrame = (element, allElementsMap, appState, opts) => {
428
+ const frame = opts?.targetFrame ?? getTargetFrame(element, allElementsMap, appState);
429
+ if (!frame) {
430
+ return false;
431
+ }
390
432
  const _element = isTextElement(element)
391
433
  ? getContainerElement(element, allElementsMap) || element
392
434
  : element;
393
- if (frame) {
394
- // Perf improvement:
395
- // For an element that's already in a frame, if it's not being dragged
396
- // then there is no need to refer to geometry (which, yes, is slow) to check if it's in a frame.
397
- // It has to be in its containing frame.
398
- if (!appState.selectedElementIds[element.id] ||
399
- !appState.selectedElementsAreBeingDragged) {
435
+ const setGroupsInFrame = (isInFrame) => {
436
+ if (opts?.checkedGroups) {
437
+ _element.groupIds.forEach((groupId) => {
438
+ opts.checkedGroups?.set(groupId, isInFrame);
439
+ });
440
+ }
441
+ };
442
+ if (
443
+ // if the element is not selected, or it is selected but not being dragged,
444
+ // frame membership won't update, so return true
445
+ !appState.selectedElementIds[_element.id] ||
446
+ !appState.selectedElementsAreBeingDragged ||
447
+ // if both frame and element are selected, won't update membership, so return true
448
+ (appState.selectedElementIds[_element.id] &&
449
+ appState.selectedElementIds[frame.id])) {
450
+ return true;
451
+ }
452
+ if (_element.groupIds.length === 0) {
453
+ return elementOverlapsWithFrame(_element, frame, allElementsMap);
454
+ }
455
+ for (const gid of _element.groupIds) {
456
+ if (opts?.checkedGroups?.has(gid)) {
457
+ return opts.checkedGroups.get(gid);
458
+ }
459
+ }
460
+ const allElementsInGroup = new Set(_element.groupIds
461
+ .filter((gid) => {
462
+ if (opts?.checkedGroups) {
463
+ return !opts.checkedGroups.has(gid);
464
+ }
465
+ return true;
466
+ })
467
+ .flatMap((gid) => getElementsInGroup(allElementsMap, gid)));
468
+ if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
469
+ const selectedElements = new Set(getSelectedElements(allElementsMap, appState));
470
+ const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
471
+ if (editingGroupOverlapsFrame) {
400
472
  return true;
401
473
  }
402
- if (_element.groupIds.length === 0) {
403
- return elementOverlapsWithFrame(_element, frame, allElementsMap);
474
+ selectedElements.forEach((selectedElement) => {
475
+ allElementsInGroup.delete(selectedElement);
476
+ });
477
+ }
478
+ for (const elementInGroup of allElementsInGroup) {
479
+ if (isFrameLikeElement(elementInGroup)) {
480
+ setGroupsInFrame(false);
481
+ return false;
404
482
  }
405
- const allElementsInGroup = new Set(_element.groupIds.flatMap((gid) => getElementsInGroup(allElementsMap, gid)));
406
- if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
407
- const selectedElements = new Set(getSelectedElements(allElementsMap, appState));
408
- const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
409
- if (editingGroupOverlapsFrame) {
410
- return true;
411
- }
412
- selectedElements.forEach((selectedElement) => {
413
- allElementsInGroup.delete(selectedElement);
414
- });
483
+ }
484
+ for (const elementInGroup of allElementsInGroup) {
485
+ if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
486
+ setGroupsInFrame(true);
487
+ return true;
415
488
  }
416
- for (const elementInGroup of allElementsInGroup) {
417
- if (isFrameLikeElement(elementInGroup)) {
418
- return false;
419
- }
489
+ }
490
+ return false;
491
+ };
492
+ export const shouldApplyFrameClip = (element, frame, appState, elementsMap, checkedGroups) => {
493
+ if (!appState.frameRendering || !appState.frameRendering.clip) {
494
+ return false;
495
+ }
496
+ // for individual elements, only clip when the element is
497
+ // a. overlapping with the frame, or
498
+ // b. containing the frame, for example when an element is used as a background
499
+ // and is therefore bigger than the frame and completely contains the frame
500
+ const shouldClipElementItself = isElementIntersectingFrame(element, frame, elementsMap) ||
501
+ isElementContainingFrame(element, frame, elementsMap);
502
+ if (shouldClipElementItself) {
503
+ for (const groupId of element.groupIds) {
504
+ checkedGroups?.set(groupId, true);
420
505
  }
421
- for (const elementInGroup of allElementsInGroup) {
422
- if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
423
- return true;
506
+ return true;
507
+ }
508
+ // if an element is outside the frame, but is part of a group that has some elements
509
+ // "in" the frame, we should clip the element
510
+ if (!shouldClipElementItself &&
511
+ element.groupIds.length > 0 &&
512
+ !elementsAreInFrameBounds([element], frame, elementsMap)) {
513
+ let shouldClip = false;
514
+ // if no elements are being dragged, we can skip the geometry check
515
+ // because we know if the element is in the given frame or not
516
+ if (!appState.selectedElementsAreBeingDragged) {
517
+ shouldClip = element.frameId === frame.id;
518
+ for (const groupId of element.groupIds) {
519
+ checkedGroups?.set(groupId, shouldClip);
424
520
  }
425
521
  }
522
+ else {
523
+ shouldClip = isElementInFrame(element, elementsMap, appState, {
524
+ targetFrame: frame,
525
+ checkedGroups,
526
+ });
527
+ }
528
+ for (const groupId of element.groupIds) {
529
+ checkedGroups?.set(groupId, shouldClip);
530
+ }
531
+ return shouldClip;
426
532
  }
427
533
  return false;
428
534
  };
@@ -444,3 +550,8 @@ export const getElementsOverlappingFrame = (elements, frame) => {
444
550
  // and thus invisible in target frame
445
551
  .filter((el) => !el.frameId || el.frameId === frame.id));
446
552
  };
553
+ export const frameAndChildrenSelectedTogether = (selectedElements) => {
554
+ const selectedElementsMap = arrayToMap(selectedElements);
555
+ return (selectedElements.length > 1 &&
556
+ selectedElements.some((element) => element.frameId && selectedElementsMap.has(element.frameId)));
557
+ };
@@ -58,6 +58,9 @@ export const selectGroupsForSelectedElements = (function () {
58
58
  // Gather all the elements within selected groups
59
59
  const groupElementsIndex = {};
60
60
  const selectedElementIdsInGroups = elements.reduce((acc, element) => {
61
+ if (element.isDeleted) {
62
+ return acc;
63
+ }
61
64
  const groupId = element.groupIds.find((id) => selectedGroupIds[id]);
62
65
  if (groupId) {
63
66
  acc[element.id] = true;
@@ -1,6 +1,6 @@
1
1
  import type { LibraryItem } from "../types";
2
2
  export type SvgCache = Map<LibraryItem["id"], SVGSVGElement>;
3
- export declare const libraryItemSvgsCache: import("jotai").PrimitiveAtom<SvgCache> & {
3
+ export declare const libraryItemSvgsCache: import("jotai/vanilla/atom").PrimitiveAtom<SvgCache> & {
4
4
  init: SvgCache;
5
5
  };
6
6
  export declare const useLibraryItemSvg: (id: LibraryItem["id"] | null, elements: LibraryItem["elements"] | undefined, svgCache: SvgCache) => SVGSVGElement | undefined;
@@ -1,7 +1,6 @@
1
- import { atom, useAtom } from "jotai";
2
1
  import { useEffect, useState } from "react";
3
2
  import { COLOR_PALETTE } from "../colors";
4
- import { jotaiScope } from "../jotai";
3
+ import { atom, useAtom } from "../editor-jotai";
5
4
  import { exportToSvg } from "../../utils/export";
6
5
  export const libraryItemSvgsCache = atom(new Map());
7
6
  const exportLibraryItemToSvg = async (elements) => {
@@ -51,7 +50,7 @@ export const useLibraryItemSvg = (id, elements, svgCache) => {
51
50
  return svg;
52
51
  };
53
52
  export const useLibraryCache = () => {
54
- const [svgCache] = useAtom(libraryItemSvgsCache, jotaiScope);
53
+ const [svgCache] = useAtom(libraryItemSvgsCache);
55
54
  const clearLibraryCache = () => svgCache.clear();
56
55
  const deleteItemsFromLibraryCache = (items) => {
57
56
  items.forEach((item) => svgCache.delete(item));
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { atom, useAtom } from "jotai";
2
+ import { atom, useAtom } from "../editor-jotai";
3
3
  import throttle from "lodash.throttle";
4
4
  const scrollPositionAtom = atom(0);
5
5
  export const useScrollPosition = (elementRef) => {
@@ -1,7 +1,6 @@
1
1
  import fallbackLangData from "./locales/en.json";
2
2
  import percentages from "./locales/percentages.json";
3
- import { jotaiScope, jotaiStore } from "./jotai";
4
- import { atom, useAtomValue } from "jotai";
3
+ import { useAtomValue, editorJotaiStore, atom } from "./editor-jotai";
5
4
  const COMPLETION_THRESHOLD = 85;
6
5
  export const defaultLang = { code: "en", label: "English" };
7
6
  export const languages = [
@@ -82,7 +81,7 @@ export const setLanguage = async (lang) => {
82
81
  currentLangData = fallbackLangData;
83
82
  }
84
83
  }
85
- jotaiStore.set(editorLangCodeAtom, lang.code);
84
+ editorJotaiStore.set(editorLangCodeAtom, lang.code);
86
85
  };
87
86
  export const getLanguage = () => currentLang;
88
87
  const findPartsForData = (data, parts) => {
@@ -132,6 +131,6 @@ const editorLangCodeAtom = atom(defaultLang.code);
132
131
  // - component is rendered internally by <Excalidraw>, but the component
133
132
  // is memoized w/o being updated on `langCode`, `AppState`, or `UIAppState`
134
133
  export const useI18n = () => {
135
- const langCode = useAtomValue(editorLangCodeAtom, jotaiScope);
134
+ const langCode = useAtomValue(editorLangCodeAtom);
136
135
  return { t, langCode };
137
136
  };
@@ -9,15 +9,14 @@ import "./css/styles.scss";
9
9
  import "./fonts/fonts.css";
10
10
  import { defaultLang } from "./i18n";
11
11
  import { DEFAULT_UI_OPTIONS } from "./constants";
12
- import { Provider } from "jotai";
13
- import { jotaiScope, jotaiStore } from "./jotai";
12
+ import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai";
14
13
  import Footer from "./components/footer/FooterCenter";
15
14
  import MainMenu from "./components/main-menu/MainMenu";
16
15
  import WelcomeScreen from "./components/welcome-screen/WelcomeScreen";
17
16
  import LiveCollaborationTrigger from "./components/live-collaboration/LiveCollaborationTrigger";
18
17
  polyfill();
19
18
  const ExcalidrawBase = (props) => {
20
- const { onChange, initialData, excalidrawAPI, isCollaborating = false, onPointerUpdate, renderTopRightUI, langCode = defaultLang.code, viewModeEnabled, zenModeEnabled, gridModeEnabled, libraryReturnUrl, theme, name, renderCustomStats, onPaste, detectScroll = true, handleKeyboardGlobally = false, onLibraryChange, autoFocus = false, generateIdForFile, onLinkOpen, generateLinkForSelection, onPointerDown, onPointerUp, onScrollChange, children, validateEmbeddable, renderEmbeddable, aiEnabled, showDeprecatedFonts, } = props;
19
+ const { onChange, initialData, excalidrawAPI, isCollaborating = false, onPointerUpdate, renderTopRightUI, langCode = defaultLang.code, viewModeEnabled, zenModeEnabled, gridModeEnabled, libraryReturnUrl, theme, name, renderCustomStats, onPaste, detectScroll = true, handleKeyboardGlobally = false, onLibraryChange, autoFocus = false, generateIdForFile, onLinkOpen, generateLinkForSelection, onPointerDown, onPointerUp, onScrollChange, onDuplicate, children, validateEmbeddable, renderEmbeddable, aiEnabled, showDeprecatedFonts, } = props;
21
20
  const canvasActions = props.UIOptions?.canvasActions;
22
21
  // FIXME normalize/set defaults in parent component so that the memo resolver
23
22
  // compares the same values
@@ -60,7 +59,7 @@ const ExcalidrawBase = (props) => {
60
59
  document.removeEventListener("touchmove", handleTouchMove);
61
60
  };
62
61
  }, []);
63
- return (_jsx(Provider, { unstable_createStore: () => jotaiStore, scope: jotaiScope, children: _jsx(InitializeApp, { langCode: langCode, theme: theme, children: _jsx(App, { onChange: onChange, initialData: initialData, excalidrawAPI: excalidrawAPI, isCollaborating: isCollaborating, onPointerUpdate: onPointerUpdate, renderTopRightUI: renderTopRightUI, langCode: langCode, viewModeEnabled: viewModeEnabled, zenModeEnabled: zenModeEnabled, gridModeEnabled: gridModeEnabled, libraryReturnUrl: libraryReturnUrl, theme: theme, name: name, renderCustomStats: renderCustomStats, UIOptions: UIOptions, onPaste: onPaste, detectScroll: detectScroll, handleKeyboardGlobally: handleKeyboardGlobally, onLibraryChange: onLibraryChange, autoFocus: autoFocus, generateIdForFile: generateIdForFile, onLinkOpen: onLinkOpen, generateLinkForSelection: generateLinkForSelection, onPointerDown: onPointerDown, onPointerUp: onPointerUp, onScrollChange: onScrollChange, validateEmbeddable: validateEmbeddable, renderEmbeddable: renderEmbeddable, aiEnabled: aiEnabled !== false, showDeprecatedFonts: showDeprecatedFonts, children: children }) }) }));
62
+ return (_jsx(EditorJotaiProvider, { store: editorJotaiStore, children: _jsx(InitializeApp, { langCode: langCode, theme: theme, children: _jsx(App, { onChange: onChange, initialData: initialData, excalidrawAPI: excalidrawAPI, isCollaborating: isCollaborating, onPointerUpdate: onPointerUpdate, renderTopRightUI: renderTopRightUI, langCode: langCode, viewModeEnabled: viewModeEnabled, zenModeEnabled: zenModeEnabled, gridModeEnabled: gridModeEnabled, libraryReturnUrl: libraryReturnUrl, theme: theme, name: name, renderCustomStats: renderCustomStats, UIOptions: UIOptions, onPaste: onPaste, detectScroll: detectScroll, handleKeyboardGlobally: handleKeyboardGlobally, onLibraryChange: onLibraryChange, autoFocus: autoFocus, generateIdForFile: generateIdForFile, onLinkOpen: onLinkOpen, generateLinkForSelection: generateLinkForSelection, onPointerDown: onPointerDown, onPointerUp: onPointerUp, onScrollChange: onScrollChange, onDuplicate: onDuplicate, validateEmbeddable: validateEmbeddable, renderEmbeddable: renderEmbeddable, aiEnabled: aiEnabled !== false, showDeprecatedFonts: showDeprecatedFonts, children: children }) }) }));
64
63
  };
65
64
  const areEqual = (prevProps, nextProps) => {
66
65
  // short-circuit early
@@ -46,6 +46,10 @@
46
46
  "arrowhead_triangle_outline": "Triangle (outline)",
47
47
  "arrowhead_diamond": "Diamond",
48
48
  "arrowhead_diamond_outline": "Diamond (outline)",
49
+ "arrowhead_crowfoot_many": "Crow's foot (many)",
50
+ "arrowhead_crowfoot_one": "Crow's foot (one)",
51
+ "arrowhead_crowfoot_one_or_many": "Crow's foot (one or many)",
52
+ "more_options": "More options",
49
53
  "arrowtypes": "Arrow type",
50
54
  "arrowtype_sharp": "Sharp arrow",
51
55
  "arrowtype_round": "Curved arrow",
@@ -157,8 +161,11 @@
157
161
  "zoomToFit": "Zoom to fit all elements",
158
162
  "installPWA": "Install Excalidraw locally (PWA)",
159
163
  "autoResize": "Enable text auto-resizing",
164
+ "imageCropping": "Image cropping",
165
+ "unCroppedDimension": "Uncropped dimension",
160
166
  "copyElementLink": "Copy link to object",
161
- "linkToElement": "Link to object"
167
+ "linkToElement": "Link to object",
168
+ "wrapSelectionInFrame": "Wrap selection in frame"
162
169
  },
163
170
  "elementLink": {
164
171
  "title": "Link to object",