@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
@@ -2,7 +2,7 @@ import rough from "roughjs/bin/rough";
2
2
  import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
3
3
  import { renderSceneToSvg } from "../renderer/staticSvgScene";
4
4
  import { arrayToMap, distance, getFontString, toBrandedType } from "../utils";
5
- import { DEFAULT_EXPORT_PADDING, FRAME_STYLE, FONT_FAMILY, SVG_NS, THEME, THEME_FILTER, } from "../constants";
5
+ import { DEFAULT_EXPORT_PADDING, FRAME_STYLE, FONT_FAMILY, SVG_NS, THEME, THEME_FILTER, MIME_TYPES, EXPORT_DATA_TYPES, } from "../constants";
6
6
  import { getDefaultAppState } from "../appState";
7
7
  import { serializeAsJSON } from "../data/json";
8
8
  import { getInitializedImageElements, updateImageCache, } from "../element/image";
@@ -13,7 +13,7 @@ import { isFrameLikeElement } from "../element/typeChecks";
13
13
  import { syncInvalidIndices } from "../fractionalIndex";
14
14
  import { renderStaticScene } from "../renderer/staticScene";
15
15
  import { Fonts } from "../fonts";
16
- const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
16
+ import { base64ToString, decode, encode, stringToBase64 } from "../data/encode";
17
17
  const truncateText = (element, maxWidth) => {
18
18
  if (element.width <= maxWidth) {
19
19
  return element;
@@ -157,6 +157,12 @@ export const exportToCanvas = async (elements, appState, files, { exportBackgrou
157
157
  });
158
158
  return canvas;
159
159
  };
160
+ const createHTMLComment = (text) => {
161
+ // surrounding with spaces to maintain prettified consistency with previous
162
+ // iterations
163
+ // <!-- comment -->
164
+ return document.createComment(` ${text} `);
165
+ };
160
166
  export const exportToSvg = async (elements, appState, files, opts) => {
161
167
  const frameRendering = getFrameRenderingConfig(opts?.exportingFrame ?? null, appState.frameRendering ?? null);
162
168
  let { exportPadding = DEFAULT_EXPORT_PADDING, exportWithDarkMode = false, viewBackgroundColor, exportScale = 1, exportEmbedScene, } = appState;
@@ -170,67 +176,84 @@ export const exportToSvg = async (elements, appState, files, opts) => {
170
176
  if (exportingFrame) {
171
177
  exportPadding = 0;
172
178
  }
173
- let metadata = "";
179
+ const [minX, minY, width, height] = getCanvasSize(exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportPadding);
180
+ const offsetX = -minX + exportPadding;
181
+ const offsetY = -minY + exportPadding;
182
+ // ---------------------------------------------------------------------------
183
+ // initialize SVG root element
184
+ // ---------------------------------------------------------------------------
185
+ const svgRoot = document.createElementNS(SVG_NS, "svg");
186
+ svgRoot.setAttribute("version", "1.1");
187
+ svgRoot.setAttribute("xmlns", SVG_NS);
188
+ svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`);
189
+ svgRoot.setAttribute("width", `${width * exportScale}`);
190
+ svgRoot.setAttribute("height", `${height * exportScale}`);
191
+ if (exportWithDarkMode) {
192
+ svgRoot.setAttribute("filter", THEME_FILTER);
193
+ }
194
+ const defsElement = svgRoot.ownerDocument.createElementNS(SVG_NS, "defs");
195
+ const metadataElement = svgRoot.ownerDocument.createElementNS(SVG_NS, "metadata");
196
+ svgRoot.appendChild(createHTMLComment("svg-source:excalidraw"));
197
+ svgRoot.appendChild(metadataElement);
198
+ svgRoot.appendChild(defsElement);
199
+ // ---------------------------------------------------------------------------
200
+ // scene embed
201
+ // ---------------------------------------------------------------------------
174
202
  // we need to serialize the "original" elements before we put them through
175
203
  // the tempScene hack which duplicates and regenerates ids
176
204
  if (exportEmbedScene) {
177
205
  try {
178
- metadata = (await import("../data/image")).encodeSvgMetadata({
206
+ encodeSvgBase64Payload({
207
+ metadataElement,
179
208
  // when embedding scene, we want to embed the origionally supplied
180
209
  // elements which don't contain the temp frame labels.
181
210
  // But it also requires that the exportToSvg is being supplied with
182
211
  // only the elements that we're exporting, and no extra.
183
- text: serializeAsJSON(elements, appState, files || {}, "local"),
212
+ payload: serializeAsJSON(elements, appState, files || {}, "local"),
184
213
  });
185
214
  }
186
215
  catch (error) {
187
216
  console.error(error);
188
217
  }
189
218
  }
190
- const [minX, minY, width, height] = getCanvasSize(exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), exportPadding);
191
- // initialize SVG root
192
- const svgRoot = document.createElementNS(SVG_NS, "svg");
193
- svgRoot.setAttribute("version", "1.1");
194
- svgRoot.setAttribute("xmlns", SVG_NS);
195
- svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`);
196
- svgRoot.setAttribute("width", `${width * exportScale}`);
197
- svgRoot.setAttribute("height", `${height * exportScale}`);
198
- if (exportWithDarkMode) {
199
- svgRoot.setAttribute("filter", THEME_FILTER);
200
- }
201
- const offsetX = -minX + exportPadding;
202
- const offsetY = -minY + exportPadding;
219
+ // ---------------------------------------------------------------------------
220
+ // frame clip paths
221
+ // ---------------------------------------------------------------------------
203
222
  const frameElements = getFrameLikeElements(elements);
204
- let exportingFrameClipPath = "";
205
- const elementsMap = arrayToMap(elements);
206
- for (const frame of frameElements) {
207
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
208
- const cx = (x2 - x1) / 2 - (frame.x - x1);
209
- const cy = (y2 - y1) / 2 - (frame.y - y1);
210
- exportingFrameClipPath += `<clipPath id=${frame.id}>
211
- <rect transform="translate(${frame.x + offsetX} ${frame.y + offsetY}) rotate(${frame.angle} ${cx} ${cy})"
212
- width="${frame.width}"
213
- height="${frame.height}"
214
- ${exportingFrame
215
- ? ""
216
- : `rx=${FRAME_STYLE.radius} ry=${FRAME_STYLE.radius}`}
217
- >
218
- </rect>
219
- </clipPath>`;
223
+ if (frameElements.length) {
224
+ const elementsMap = arrayToMap(elements);
225
+ for (const frame of frameElements) {
226
+ const clipPath = svgRoot.ownerDocument.createElementNS(SVG_NS, "clipPath");
227
+ clipPath.setAttribute("id", frame.id);
228
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
229
+ const cx = (x2 - x1) / 2 - (frame.x - x1);
230
+ const cy = (y2 - y1) / 2 - (frame.y - y1);
231
+ const rect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
232
+ rect.setAttribute("transform", `translate(${frame.x + offsetX} ${frame.y + offsetY}) rotate(${frame.angle} ${cx} ${cy})`);
233
+ rect.setAttribute("width", `${frame.width}`);
234
+ rect.setAttribute("height", `${frame.height}`);
235
+ if (!exportingFrame) {
236
+ rect.setAttribute("rx", `${FRAME_STYLE.radius}`);
237
+ rect.setAttribute("ry", `${FRAME_STYLE.radius}`);
238
+ }
239
+ clipPath.appendChild(rect);
240
+ defsElement.appendChild(clipPath);
241
+ }
220
242
  }
243
+ // ---------------------------------------------------------------------------
244
+ // inline font faces
245
+ // ---------------------------------------------------------------------------
221
246
  const fontFaces = !opts?.skipInliningFonts
222
247
  ? await Fonts.generateFontFaceDeclarations(elements)
223
248
  : [];
224
249
  const delimiter = "\n "; // 6 spaces
225
- svgRoot.innerHTML = `
226
- ${SVG_EXPORT_TAG}
227
- ${metadata}
228
- <defs>
229
- <style class="style-fonts">${delimiter}${fontFaces.join(delimiter)}
230
- </style>
231
- ${exportingFrameClipPath}
232
- </defs>
233
- `;
250
+ const style = svgRoot.ownerDocument.createElementNS(SVG_NS, "style");
251
+ style.classList.add("style-fonts");
252
+ style.appendChild(document.createTextNode(`${delimiter}${fontFaces.join(delimiter)}`));
253
+ defsElement.appendChild(style);
254
+ // ---------------------------------------------------------------------------
255
+ // background
256
+ // ---------------------------------------------------------------------------
234
257
  // render background rect
235
258
  if (appState.exportBackground && viewBackgroundColor) {
236
259
  const rect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
@@ -241,6 +264,9 @@ export const exportToSvg = async (elements, appState, files, opts) => {
241
264
  rect.setAttribute("fill", viewBackgroundColor);
242
265
  svgRoot.appendChild(rect);
243
266
  }
267
+ // ---------------------------------------------------------------------------
268
+ // render elements
269
+ // ---------------------------------------------------------------------------
244
270
  const rsvg = rough.svg(svgRoot);
245
271
  const renderEmbeddables = opts?.renderEmbeddables ?? false;
246
272
  renderSceneToSvg(elementsForRender, toBrandedType(arrayToMap(elementsForRender)), rsvg, svgRoot, files || {}, {
@@ -258,8 +284,46 @@ export const exportToSvg = async (elements, appState, files, opts) => {
258
284
  : new Map(),
259
285
  reuseImages: opts?.reuseImages ?? true,
260
286
  });
287
+ // ---------------------------------------------------------------------------
261
288
  return svgRoot;
262
289
  };
290
+ export const encodeSvgBase64Payload = ({ payload, metadataElement, }) => {
291
+ const base64 = stringToBase64(JSON.stringify(encode({ text: payload })), true /* is already byte string */);
292
+ metadataElement.appendChild(createHTMLComment(`payload-type:${MIME_TYPES.excalidraw}`));
293
+ metadataElement.appendChild(createHTMLComment("payload-version:2"));
294
+ metadataElement.appendChild(createHTMLComment("payload-start"));
295
+ metadataElement.appendChild(document.createTextNode(base64));
296
+ metadataElement.appendChild(createHTMLComment("payload-end"));
297
+ };
298
+ export const decodeSvgBase64Payload = ({ svg }) => {
299
+ if (svg.includes(`payload-type:${MIME_TYPES.excalidraw}`)) {
300
+ const match = svg.match(/<!-- payload-start -->\s*(.+?)\s*<!-- payload-end -->/);
301
+ if (!match) {
302
+ throw new Error("INVALID");
303
+ }
304
+ const versionMatch = svg.match(/<!-- payload-version:(\d+) -->/);
305
+ const version = versionMatch?.[1] || "1";
306
+ const isByteString = version !== "1";
307
+ try {
308
+ const json = base64ToString(match[1], isByteString);
309
+ const encodedData = JSON.parse(json);
310
+ if (!("encoded" in encodedData)) {
311
+ // legacy, un-encoded scene JSON
312
+ if ("type" in encodedData &&
313
+ encodedData.type === EXPORT_DATA_TYPES.excalidraw) {
314
+ return json;
315
+ }
316
+ throw new Error("FAILED");
317
+ }
318
+ return decode(encodedData);
319
+ }
320
+ catch (error) {
321
+ console.error(error);
322
+ throw new Error("FAILED");
323
+ }
324
+ }
325
+ throw new Error("INVALID");
326
+ };
263
327
  // calculate smallest area to fit the contents in
264
328
  const getCanvasSize = (elements, exportPadding) => {
265
329
  const [minX, minY, maxX, maxY] = getCommonBounds(elements);
@@ -1,4 +1,4 @@
1
1
  export { isSomeElementSelected, getElementsWithinSelection, getCommonAttributeOfSelectedElements, getSelectedElements, getTargetElements, } from "./selection";
2
2
  export { calculateScrollCenter } from "./scroll";
3
- export { hasBackground, hasStrokeWidth, hasStrokeStyle, canHaveArrowheads, canChangeRoundness, getElementAtPosition, getElementsAtPosition, } from "./comparisons";
3
+ export { hasBackground, hasStrokeWidth, hasStrokeStyle, canHaveArrowheads, canChangeRoundness, } from "./comparisons";
4
4
  export { getNormalizedZoom, getNormalizedGridSize, getNormalizedGridStep, } from "./normalize";
@@ -1,4 +1,4 @@
1
1
  export { isSomeElementSelected, getElementsWithinSelection, getCommonAttributeOfSelectedElements, getSelectedElements, getTargetElements, } from "./selection";
2
2
  export { calculateScrollCenter } from "./scroll";
3
- export { hasBackground, hasStrokeWidth, hasStrokeStyle, canHaveArrowheads, canChangeRoundness, getElementAtPosition, getElementsAtPosition, } from "./comparisons";
3
+ export { hasBackground, hasStrokeWidth, hasStrokeStyle, canHaveArrowheads, canChangeRoundness, } from "./comparisons";
4
4
  export { getNormalizedZoom, getNormalizedGridSize, getNormalizedGridStep, } from "./normalize";
@@ -94,16 +94,19 @@ export const getCommonAttributeOfSelectedElements = (elements, appState, getAttr
94
94
  return attributes.length === 1 ? attributes[0] : null;
95
95
  };
96
96
  export const getSelectedElements = (elements, appState, opts) => {
97
+ const addedElements = new Set();
97
98
  const selectedElements = [];
98
99
  for (const element of elements.values()) {
99
100
  if (appState.selectedElementIds[element.id]) {
100
101
  selectedElements.push(element);
102
+ addedElements.add(element.id);
101
103
  continue;
102
104
  }
103
105
  if (opts?.includeBoundTextElement &&
104
106
  isBoundToContainer(element) &&
105
107
  appState.selectedElementIds[element?.containerId]) {
106
108
  selectedElements.push(element);
109
+ addedElements.add(element.id);
107
110
  continue;
108
111
  }
109
112
  }
@@ -111,7 +114,7 @@ export const getSelectedElements = (elements, appState, opts) => {
111
114
  const elementsToInclude = [];
112
115
  selectedElements.forEach((element) => {
113
116
  if (isFrameLikeElement(element)) {
114
- getFrameChildren(elements, element.id).forEach((e) => elementsToInclude.push(e));
117
+ getFrameChildren(elements, element.id).forEach((e) => !addedElements.has(e.id) && elementsToInclude.push(e));
115
118
  }
116
119
  elementsToInclude.push(element);
117
120
  });
@@ -415,6 +415,20 @@ export interface ExcalidrawProps {
415
415
  pointersMap: Gesture["pointers"];
416
416
  }) => void;
417
417
  onPaste?: (data: ClipboardData, event: ClipboardEvent | null) => Promise<boolean> | boolean;
418
+ /**
419
+ * Called when element(s) are duplicated so you can listen or modify as
420
+ * needed.
421
+ *
422
+ * Called when duplicating via mouse-drag, keyboard, paste, library insert
423
+ * etc.
424
+ *
425
+ * Returned elements will be used in place of the next elements
426
+ * (you should return all elements, including deleted, and not mutate
427
+ * the element if changes are made)
428
+ */
429
+ onDuplicate?: (nextElements: readonly ExcalidrawElement[],
430
+ /** excludes the duplicated elements */
431
+ prevElements: readonly ExcalidrawElement[]) => ExcalidrawElement[] | void;
418
432
  renderTopRightUI?: (isMobile: boolean, appState: UIAppState) => JSX.Element | null;
419
433
  langCode?: Language["code"];
420
434
  viewModeEnabled?: boolean;
@@ -533,6 +547,7 @@ export type AppClassProperties = {
533
547
  getEditorUIOffsets: App["getEditorUIOffsets"];
534
548
  visibleElements: App["visibleElements"];
535
549
  excalidrawContainerValue: App["excalidrawContainerValue"];
550
+ onPointerUpEmitter: App["onPointerUpEmitter"];
536
551
  };
537
552
  export type PointerDownState = Readonly<{
538
553
  origin: Readonly<{
@@ -30,3 +30,4 @@ export type MakeBrand<T extends string> = {
30
30
  };
31
31
  /** Maybe just promise or already fulfilled one! */
32
32
  export type MaybePromise<T> = T | Promise<T>;
33
+ export type AllPossibleKeys<T> = T extends any ? keyof T : never;
@@ -1,5 +1,5 @@
1
1
  import type { EVENT } from "./constants";
2
- import type { FontFamilyValues, FontString } from "./element/types";
2
+ import type { ExcalidrawBindableElement, FontFamilyValues, FontString } from "./element/types";
3
3
  import type { ActiveTool, AppState, ToolType, UnsubscribeCallback, Zoom } from "./types";
4
4
  import type { MaybePromise } from "./utility-types";
5
5
  export declare const setDateTimeForTests: (dateTime: string) => void;
@@ -137,6 +137,7 @@ export declare const muteFSAbortError: (error?: Error) => void;
137
137
  export declare const findIndex: <T>(array: readonly T[], cb: (element: T, index: number, array: readonly T[]) => boolean, fromIndex?: number) => number;
138
138
  export declare const findLastIndex: <T>(array: readonly T[], cb: (element: T, index: number, array: readonly T[]) => boolean, fromIndex?: number) => number;
139
139
  export declare const isTransparent: (color: string) => boolean;
140
+ export declare const isBindingFallthroughEnabled: (el: ExcalidrawBindableElement) => boolean;
140
141
  export type ResolvablePromise<T> = Promise<T> & {
141
142
  resolve: [T] extends [undefined] ? (value?: MaybePromise<Awaited<T>>) => void : (value: MaybePromise<Awaited<T>>) => void;
142
143
  reject: (error: Error) => void;
@@ -246,4 +247,10 @@ export declare class PromisePool<T> {
246
247
  constructor(source: IterableIterator<Promise<void | readonly [number, T]>>, concurrency: number);
247
248
  all(): PromiseLike<T[]>;
248
249
  }
250
+ /**
251
+ * use when you need to render unsafe string as HTML attribute, but MAKE SURE
252
+ * the attribute is double-quoted when constructing the HTML string
253
+ */
254
+ export declare const escapeDoubleQuotes: (str: string) => string;
255
+ export declare const castArray: <T>(value: T | T[]) => T[];
249
256
  export {};
@@ -346,6 +346,7 @@ export const isTransparent = (color) => {
346
346
  isRRGGBBTransparent ||
347
347
  color === COLOR_PALETTE.transparent);
348
348
  };
349
+ export const isBindingFallthroughEnabled = (el) => el.fillStyle !== "solid" || isTransparent(el.backgroundColor);
349
350
  export const resolvablePromise = () => {
350
351
  let resolve;
351
352
  let reject;
@@ -750,3 +751,11 @@ export class PromisePool {
750
751
  });
751
752
  }
752
753
  }
754
+ /**
755
+ * use when you need to render unsafe string as HTML attribute, but MAKE SURE
756
+ * the attribute is double-quoted when constructing the HTML string
757
+ */
758
+ export const escapeDoubleQuotes = (str) => {
759
+ return str.replace(/"/g, "&quot;");
760
+ };
761
+ export const castArray = (value) => Array.isArray(value) ? value : [value];
@@ -1,4 +1,4 @@
1
- import { type GlobalPoint } from "../math";
1
+ import { type GlobalPoint, type LocalPoint } from "../math";
2
2
  import type { LineSegment } from "../utils";
3
3
  import type { BoundingBox, Bounds } from "./element/bounds";
4
4
  declare global {
@@ -7,6 +7,8 @@ declare global {
7
7
  data: DebugElement[][];
8
8
  currentFrame?: number;
9
9
  };
10
+ debugDrawPoint: typeof debugDrawPoint;
11
+ debugDrawLine: typeof debugDrawLine;
10
12
  }
11
13
  }
12
14
  export type DebugElement = {
@@ -31,5 +33,10 @@ export declare const debugDrawBounds: (box: Bounds | Bounds[], opts?: {
31
33
  color?: string;
32
34
  permanent?: boolean;
33
35
  }) => void;
36
+ export declare const debugDrawPoints: ({ x, y, points, }: {
37
+ x: number;
38
+ y: number;
39
+ points: LocalPoint[];
40
+ }, options?: any) => void;
34
41
  export declare const debugCloseFrame: () => void;
35
42
  export declare const debugClear: () => void;
@@ -42,6 +42,9 @@ export const debugDrawBounds = (box, opts) => {
42
42
  permanent: !!opts?.permanent,
43
43
  }));
44
44
  };
45
+ export const debugDrawPoints = ({ x, y, points, }, options) => {
46
+ points.forEach((p) => debugDrawPoint(pointFrom(x + p[0], y + p[1]), options));
47
+ };
45
48
  export const debugCloseFrame = () => {
46
49
  window.visualDebug?.data.push([]);
47
50
  };
@@ -23,4 +23,23 @@ export declare function lineFromPointPair<P extends GlobalPoint | LocalPoint>([a
23
23
  * @returns
24
24
  */
25
25
  export declare function lineFromPointArray<P extends GlobalPoint | LocalPoint>(pointArray: P[]): Line<P> | undefined;
26
+ /**
27
+ * Return the coordinates resulting from rotating the given line about an
28
+ * origin by an angle in degrees note that when the origin is not given,
29
+ * the midpoint of the given line is used as the origin
30
+ *
31
+ * @param l
32
+ * @param angle
33
+ * @param origin
34
+ * @returns
35
+ */
26
36
  export declare const lineRotate: <Point extends GlobalPoint | LocalPoint>(l: Line<Point>, angle: Radians, origin?: Point | undefined) => Line<Point>;
37
+ /**
38
+ * Determines the intersection point (unless the lines are parallel) of two
39
+ * lines
40
+ *
41
+ * @param a
42
+ * @param b
43
+ * @returns
44
+ */
45
+ export declare const linesIntersectAt: <Point extends GlobalPoint | LocalPoint>(a: Line<Point>, b: Line<Point>) => Point | null;
package/dist/math/line.js CHANGED
@@ -1,4 +1,4 @@
1
- import { pointCenter, pointRotateRads } from "./point";
1
+ import { pointCenter, pointFrom, pointRotateRads } from "./point";
2
2
  /**
3
3
  * Create a line from two points.
4
4
  *
@@ -28,8 +28,37 @@ export function lineFromPointArray(pointArray) {
28
28
  ? line(pointArray[0], pointArray[1])
29
29
  : undefined;
30
30
  }
31
- // return the coordinates resulting from rotating the given line about an origin by an angle in degrees
32
- // note that when the origin is not given, the midpoint of the given line is used as the origin
31
+ /**
32
+ * Return the coordinates resulting from rotating the given line about an
33
+ * origin by an angle in degrees note that when the origin is not given,
34
+ * the midpoint of the given line is used as the origin
35
+ *
36
+ * @param l
37
+ * @param angle
38
+ * @param origin
39
+ * @returns
40
+ */
33
41
  export const lineRotate = (l, angle, origin) => {
34
42
  return line(pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle), pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle));
35
43
  };
44
+ /**
45
+ * Determines the intersection point (unless the lines are parallel) of two
46
+ * lines
47
+ *
48
+ * @param a
49
+ * @param b
50
+ * @returns
51
+ */
52
+ export const linesIntersectAt = (a, b) => {
53
+ const A1 = a[1][1] - a[0][1];
54
+ const B1 = a[0][0] - a[1][0];
55
+ const A2 = b[1][1] - b[0][1];
56
+ const B2 = b[0][0] - b[1][0];
57
+ const D = A1 * B2 - A2 * B1;
58
+ if (D !== 0) {
59
+ const C1 = A1 * a[0][0] + B1 * a[0][1];
60
+ const C2 = A2 * b[0][0] + B2 * b[0][1];
61
+ return pointFrom((C1 * B2 - C2 * B1) / D, (A1 * C2 - A2 * C1) / D);
62
+ }
63
+ return null;
64
+ };
@@ -28,6 +28,16 @@ export declare function pointFromPair<Point extends GlobalPoint | LocalPoint>(pa
28
28
  * @returns The point the vector points at with origin 0,0
29
29
  */
30
30
  export declare function pointFromVector<P extends GlobalPoint | LocalPoint>(v: Vector): P;
31
+ /**
32
+ * Convert the coordiante object to a point.
33
+ *
34
+ * @param coords The coordinate object with x and y properties
35
+ * @returns
36
+ */
37
+ export declare function pointFromCoords<Point extends GlobalPoint | LocalPoint>({ x, y, }: {
38
+ x: number;
39
+ y: number;
40
+ }): Point;
31
41
  /**
32
42
  * Checks if the provided value has the shape of a Point.
33
43
  *
@@ -40,6 +40,15 @@ export function pointFromPair(pair) {
40
40
  export function pointFromVector(v) {
41
41
  return v;
42
42
  }
43
+ /**
44
+ * Convert the coordiante object to a point.
45
+ *
46
+ * @param coords The coordinate object with x and y properties
47
+ * @returns
48
+ */
49
+ export function pointFromCoords({ x, y, }) {
50
+ return [x, y];
51
+ }
43
52
  /**
44
53
  * Checks if the provided value has the shape of a Point.
45
54
  *
@@ -156,7 +165,9 @@ export function pointDistance(a, b) {
156
165
  * @returns The euclidean distance between the two points.
157
166
  */
158
167
  export function pointDistanceSq(a, b) {
159
- return Math.hypot(b[0] - a[0], b[1] - a[1]);
168
+ const xDiff = b[0] - a[0];
169
+ const yDiff = b[1] - a[1];
170
+ return xDiff * xDiff + yDiff * yDiff;
160
171
  }
161
172
  /**
162
173
  * Scale a point from a given origin by the multiplier.