@excalidraw/excalidraw 0.17.1-7500-ac247a0 → 0.17.1-b7babe5

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 (255) hide show
  1. package/CHANGELOG.md +56 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-6NMK7JTV.js} +13 -6
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-6NMK7JTV.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js +20324 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-BZY7JRTM.js} +4 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js → image-CVN3YKRW.js} +2 -4
  8. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
  10. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
  12. package/dist/browser/dev/index.css +189 -129
  13. package/dist/browser/dev/index.css.map +3 -3
  14. package/dist/browser/dev/index.js +34964 -37
  15. package/dist/browser/dev/index.js.map +4 -4
  16. package/dist/browser/prod/excalidraw-assets/chunk-VJAIK3AX.js +55 -0
  17. package/dist/browser/prod/excalidraw-assets/chunk-YYO5DFUW.js +11 -0
  18. package/dist/browser/prod/excalidraw-assets/en-O2YCQM2W.js +1 -0
  19. package/dist/browser/prod/excalidraw-assets/image-6FKY54X5.js +1 -0
  20. package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
  21. package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
  22. package/dist/browser/prod/index.css +1 -1
  23. package/dist/browser/prod/index.js +203 -1
  24. package/dist/{prod/en-RLIAOBCI.json → dev/en-EY7E2L5O.json} +10 -5
  25. package/dist/dev/index.css +189 -129
  26. package/dist/dev/index.css.map +3 -3
  27. package/dist/dev/index.js +38702 -39409
  28. package/dist/dev/index.js.map +4 -4
  29. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +15 -15
  30. package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
  31. package/dist/excalidraw/actions/actionAlign.js +2 -1
  32. package/dist/excalidraw/actions/actionBoundText.d.ts +10 -10
  33. package/dist/excalidraw/actions/actionBoundText.js +8 -8
  34. package/dist/excalidraw/actions/actionCanvas.d.ts +58 -58
  35. package/dist/excalidraw/actions/actionClipboard.d.ts +34 -34
  36. package/dist/excalidraw/actions/actionClipboard.js +9 -2
  37. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +15 -15
  38. package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
  39. package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
  40. package/dist/excalidraw/actions/actionDistribute.js +1 -1
  41. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
  42. package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
  43. package/dist/excalidraw/actions/actionElementLock.d.ts +10 -10
  44. package/dist/excalidraw/actions/actionExport.d.ts +43 -43
  45. package/dist/excalidraw/actions/actionExport.js +4 -4
  46. package/dist/excalidraw/actions/actionFinalize.d.ts +9 -9
  47. package/dist/excalidraw/actions/actionFinalize.js +7 -6
  48. package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
  49. package/dist/excalidraw/actions/actionFlip.js +11 -11
  50. package/dist/excalidraw/actions/actionFrame.d.ts +16 -16
  51. package/dist/excalidraw/actions/actionFrame.js +1 -1
  52. package/dist/excalidraw/actions/actionGroup.d.ts +10 -10
  53. package/dist/excalidraw/actions/actionGroup.js +3 -2
  54. package/dist/excalidraw/actions/actionLinearEditor.d.ts +5 -5
  55. package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
  56. package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +29 -51
  57. package/dist/excalidraw/actions/actionLink.js +40 -0
  58. package/dist/excalidraw/actions/actionMenu.d.ts +13 -13
  59. package/dist/excalidraw/actions/actionNavigate.d.ts +10 -10
  60. package/dist/excalidraw/actions/actionNavigate.js +1 -1
  61. package/dist/excalidraw/actions/actionProperties.d.ts +77 -77
  62. package/dist/excalidraw/actions/actionProperties.js +32 -27
  63. package/dist/excalidraw/actions/actionSelectAll.d.ts +5 -5
  64. package/dist/excalidraw/actions/actionSelectAll.js +1 -1
  65. package/dist/excalidraw/actions/actionStyles.d.ts +7 -7
  66. package/dist/excalidraw/actions/actionStyles.js +4 -4
  67. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +5 -5
  68. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +5 -5
  69. package/dist/excalidraw/actions/actionToggleStats.d.ts +5 -5
  70. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +5 -5
  71. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +5 -5
  72. package/dist/excalidraw/actions/index.d.ts +1 -1
  73. package/dist/excalidraw/actions/index.js +1 -1
  74. package/dist/excalidraw/actions/manager.js +2 -1
  75. package/dist/excalidraw/align.d.ts +2 -2
  76. package/dist/excalidraw/align.js +2 -2
  77. package/dist/excalidraw/animated-trail.d.ts +33 -0
  78. package/dist/excalidraw/animated-trail.js +96 -0
  79. package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
  80. package/dist/excalidraw/animation-frame-handler.js +55 -0
  81. package/dist/excalidraw/appState.d.ts +1 -1
  82. package/dist/excalidraw/appState.js +1 -3
  83. package/dist/excalidraw/clipboard.js +5 -5
  84. package/dist/excalidraw/components/Actions.d.ts +3 -3
  85. package/dist/excalidraw/components/Actions.js +18 -7
  86. package/dist/excalidraw/components/App.d.ts +23 -16
  87. package/dist/excalidraw/components/App.js +387 -272
  88. package/dist/excalidraw/components/Button.d.ts +1 -1
  89. package/dist/excalidraw/components/FilledButton.d.ts +2 -2
  90. package/dist/excalidraw/components/FilledButton.js +27 -3
  91. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  92. package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
  93. package/dist/excalidraw/components/ImageExportDialog.js +17 -13
  94. package/dist/excalidraw/components/JSONExportDialog.js +1 -1
  95. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
  96. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
  97. package/dist/excalidraw/components/LayerUI.js +3 -3
  98. package/dist/excalidraw/components/MobileMenu.js +1 -1
  99. package/dist/excalidraw/components/ProjectName.d.ts +0 -1
  100. package/dist/excalidraw/components/ProjectName.js +1 -1
  101. package/dist/excalidraw/components/PublishLibrary.js +1 -1
  102. package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
  103. package/dist/excalidraw/components/SVGLayer.js +20 -0
  104. package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
  105. package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  106. package/dist/excalidraw/components/Stack.d.ts +2 -2
  107. package/dist/excalidraw/components/TTDDialog/common.js +10 -1
  108. package/dist/excalidraw/components/TextField.d.ts +5 -2
  109. package/dist/excalidraw/components/TextField.js +6 -3
  110. package/dist/excalidraw/components/Toast.d.ts +3 -2
  111. package/dist/excalidraw/components/Toast.js +2 -2
  112. package/dist/excalidraw/components/ToolButton.js +2 -1
  113. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
  114. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
  115. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
  116. package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
  117. package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
  118. package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
  119. package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
  120. package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
  121. package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
  122. package/dist/excalidraw/components/icons.d.ts +2 -1
  123. package/dist/excalidraw/components/icons.js +2 -1
  124. package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
  125. package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
  126. package/dist/excalidraw/constants.d.ts +6 -0
  127. package/dist/excalidraw/constants.js +6 -0
  128. package/dist/excalidraw/data/blob.js +13 -14
  129. package/dist/excalidraw/data/filesystem.d.ts +1 -1
  130. package/dist/excalidraw/data/index.d.ts +2 -1
  131. package/dist/excalidraw/data/index.js +20 -16
  132. package/dist/excalidraw/data/json.d.ts +1 -1
  133. package/dist/excalidraw/data/json.js +5 -3
  134. package/dist/excalidraw/data/library.d.ts +60 -8
  135. package/dist/excalidraw/data/library.js +302 -33
  136. package/dist/excalidraw/data/resave.d.ts +1 -1
  137. package/dist/excalidraw/data/resave.js +2 -2
  138. package/dist/excalidraw/data/restore.js +8 -13
  139. package/dist/excalidraw/data/transform.js +13 -9
  140. package/dist/excalidraw/distribute.d.ts +2 -2
  141. package/dist/excalidraw/distribute.js +2 -2
  142. package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
  143. package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
  144. package/dist/excalidraw/element/binding.d.ts +9 -9
  145. package/dist/excalidraw/element/binding.js +61 -59
  146. package/dist/excalidraw/element/bounds.d.ts +5 -5
  147. package/dist/excalidraw/element/bounds.js +29 -32
  148. package/dist/excalidraw/element/collision.d.ts +11 -11
  149. package/dist/excalidraw/element/collision.js +49 -46
  150. package/dist/excalidraw/element/containerCache.d.ts +11 -0
  151. package/dist/excalidraw/element/containerCache.js +14 -0
  152. package/dist/excalidraw/element/dragElements.js +10 -19
  153. package/dist/excalidraw/element/embeddable.d.ts +12 -13
  154. package/dist/excalidraw/element/embeddable.js +17 -27
  155. package/dist/excalidraw/element/image.js +1 -2
  156. package/dist/excalidraw/element/index.d.ts +8 -1
  157. package/dist/excalidraw/element/index.js +23 -1
  158. package/dist/excalidraw/element/linearElementEditor.d.ts +36 -36
  159. package/dist/excalidraw/element/linearElementEditor.js +79 -80
  160. package/dist/excalidraw/element/newElement.d.ts +4 -6
  161. package/dist/excalidraw/element/newElement.js +11 -16
  162. package/dist/excalidraw/element/resizeElements.d.ts +6 -6
  163. package/dist/excalidraw/element/resizeElements.js +40 -46
  164. package/dist/excalidraw/element/resizeTest.d.ts +3 -3
  165. package/dist/excalidraw/element/resizeTest.js +4 -4
  166. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  167. package/dist/excalidraw/element/sizeHelpers.js +2 -2
  168. package/dist/excalidraw/element/textElement.d.ts +34 -21
  169. package/dist/excalidraw/element/textElement.js +87 -111
  170. package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
  171. package/dist/excalidraw/element/textWysiwyg.js +15 -37
  172. package/dist/excalidraw/element/transformHandles.d.ts +4 -4
  173. package/dist/excalidraw/element/transformHandles.js +6 -6
  174. package/dist/excalidraw/element/typeChecks.js +4 -1
  175. package/dist/excalidraw/element/types.d.ts +24 -11
  176. package/dist/excalidraw/frame.d.ts +26 -20
  177. package/dist/excalidraw/frame.js +157 -84
  178. package/dist/excalidraw/groups.d.ts +3 -3
  179. package/dist/excalidraw/groups.js +11 -3
  180. package/dist/excalidraw/history.d.ts +1 -1
  181. package/dist/excalidraw/hooks/useLibraryItemSvg.js +1 -1
  182. package/dist/excalidraw/index.d.ts +9 -10
  183. package/dist/excalidraw/index.js +16 -12
  184. package/dist/excalidraw/laser-trails.d.ts +19 -0
  185. package/dist/excalidraw/laser-trails.js +95 -0
  186. package/dist/excalidraw/locales/en.json +10 -5
  187. package/dist/excalidraw/queue.d.ts +9 -0
  188. package/dist/excalidraw/queue.js +27 -0
  189. package/dist/excalidraw/reactUtils.d.ts +14 -0
  190. package/dist/excalidraw/reactUtils.js +45 -0
  191. package/dist/excalidraw/renderer/helpers.d.ts +13 -0
  192. package/dist/excalidraw/renderer/helpers.js +39 -0
  193. package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
  194. package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
  195. package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
  196. package/dist/excalidraw/renderer/renderElement.js +54 -366
  197. package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
  198. package/dist/excalidraw/renderer/staticScene.js +205 -0
  199. package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
  200. package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
  201. package/dist/excalidraw/scene/Fonts.js +2 -1
  202. package/dist/excalidraw/scene/Renderer.d.ts +1 -1
  203. package/dist/excalidraw/scene/Renderer.js +32 -20
  204. package/dist/excalidraw/scene/Scene.d.ts +10 -9
  205. package/dist/excalidraw/scene/Scene.js +45 -21
  206. package/dist/excalidraw/scene/Shape.d.ts +3 -1
  207. package/dist/excalidraw/scene/Shape.js +7 -5
  208. package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
  209. package/dist/excalidraw/scene/ShapeCache.js +1 -0
  210. package/dist/excalidraw/scene/comparisons.js +2 -1
  211. package/dist/excalidraw/scene/export.d.ts +3 -0
  212. package/dist/excalidraw/scene/export.js +20 -40
  213. package/dist/excalidraw/scene/index.d.ts +0 -1
  214. package/dist/excalidraw/scene/index.js +0 -1
  215. package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
  216. package/dist/excalidraw/scene/scrollbars.js +1 -1
  217. package/dist/excalidraw/scene/selection.d.ts +5 -5
  218. package/dist/excalidraw/scene/selection.js +16 -14
  219. package/dist/excalidraw/scene/types.d.ts +11 -5
  220. package/dist/excalidraw/snapping.d.ts +7 -7
  221. package/dist/excalidraw/snapping.js +21 -20
  222. package/dist/excalidraw/types.d.ts +16 -17
  223. package/dist/excalidraw/utility-types.d.ts +7 -0
  224. package/dist/excalidraw/utils.d.ts +21 -16
  225. package/dist/excalidraw/utils.js +43 -45
  226. package/dist/{dev/en-RLIAOBCI.json → prod/en-EY7E2L5O.json} +10 -5
  227. package/dist/prod/index.css +1 -1
  228. package/dist/prod/index.js +42 -42
  229. package/dist/utils/bbox.d.ts +2 -2
  230. package/dist/utils/export.d.ts +3 -3
  231. package/dist/utils/export.js +3 -13
  232. package/dist/utils/index.d.ts +2 -2
  233. package/dist/utils/index.js +2 -2
  234. package/dist/utils/withinBounds.d.ts +1 -1
  235. package/dist/utils/withinBounds.js +5 -2
  236. package/package.json +4 -4
  237. package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
  238. package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js +0 -53497
  239. package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js.map +0 -7
  240. package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css +0 -5797
  241. package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css.map +0 -7
  242. package/dist/browser/prod/excalidraw-assets/chunk-4YN2HN3S.js +0 -257
  243. package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
  244. package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
  245. package/dist/browser/prod/excalidraw-assets/image-LTLHTTSE.js +0 -1
  246. package/dist/browser/prod/excalidraw-assets/image-QBL334OA.css +0 -1
  247. package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
  248. package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
  249. package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
  250. package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
  251. package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
  252. package/dist/excalidraw/vite.config.d.mts +0 -2
  253. package/dist/excalidraw/vite.config.mjs +0 -13
  254. /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-BZY7JRTM.js.map} +0 -0
  255. /package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js.map → image-CVN3YKRW.js.map} +0 -0
@@ -3,15 +3,35 @@ import { Island } from "../Island";
3
3
  import { useDevice } from "../App";
4
4
  import clsx from "clsx";
5
5
  import Stack from "../Stack";
6
- import { useRef } from "react";
6
+ import { useEffect, useRef } from "react";
7
7
  import { DropdownMenuContentPropsContext } from "./common";
8
8
  import { useOutsideClick } from "../../hooks/useOutsideClick";
9
+ import { KEYS } from "../../keys";
10
+ import { EVENT } from "../../constants";
11
+ import { useStable } from "../../hooks/useStable";
9
12
  const MenuContent = ({ children, onClickOutside, className = "", onSelect, style, }) => {
10
13
  const device = useDevice();
11
14
  const menuRef = useRef(null);
15
+ const callbacksRef = useStable({ onClickOutside });
12
16
  useOutsideClick(menuRef, () => {
13
- onClickOutside?.();
17
+ callbacksRef.onClickOutside?.();
14
18
  });
19
+ useEffect(() => {
20
+ const onKeyDown = (event) => {
21
+ if (event.key === KEYS.ESCAPE) {
22
+ event.stopImmediatePropagation();
23
+ callbacksRef.onClickOutside?.();
24
+ }
25
+ };
26
+ document.addEventListener(EVENT.KEYDOWN, onKeyDown, {
27
+ // so that we can stop propagation of the event before it reaches
28
+ // event handlers that were bound before this one
29
+ capture: true,
30
+ });
31
+ return () => {
32
+ document.removeEventListener(EVENT.KEYDOWN, onKeyDown);
33
+ };
34
+ }, [callbacksRef]);
15
35
  const classNames = clsx(`dropdown-menu ${className}`, {
16
36
  "dropdown-menu--mobile": device.editor.isMobile,
17
37
  }).trim();
@@ -0,0 +1,19 @@
1
+ /// <reference types="react" />
2
+ import { AppState, ExcalidrawProps } from "../../types";
3
+ import { ElementsMap, ExcalidrawEmbeddableElement, NonDeletedExcalidrawElement } from "../../element/types";
4
+ import "./Hyperlink.scss";
5
+ export declare const Hyperlink: ({ element, elementsMap, setAppState, onLinkOpen, setToast, updateEmbedValidationStatus, }: {
6
+ element: NonDeletedExcalidrawElement;
7
+ elementsMap: ElementsMap;
8
+ setAppState: React.Component<any, AppState>["setState"];
9
+ onLinkOpen: ExcalidrawProps["onLinkOpen"];
10
+ setToast: (toast: {
11
+ message: string;
12
+ closable?: boolean;
13
+ duration?: number;
14
+ } | null) => void;
15
+ updateEmbedValidationStatus: (element: ExcalidrawEmbeddableElement, status: boolean) => void;
16
+ }) => JSX.Element | null;
17
+ export declare const getContextMenuLabel: (elements: readonly NonDeletedExcalidrawElement[], appState: AppState) => "labels.link.editEmbed" | "labels.link.edit" | "labels.link.createEmbed" | "labels.link.create";
18
+ export declare const showHyperlinkTooltip: (element: NonDeletedExcalidrawElement, appState: AppState, elementsMap: ElementsMap) => void;
19
+ export declare const hideHyperlinkToolip: () => void;
@@ -1,37 +1,32 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { getShortcutKey, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, wrapEvent, } from "../utils";
3
- import { getEmbedLink, embeddableURLValidator } from "./embeddable";
4
- import { mutateElement } from "./mutateElement";
5
- import { register } from "../actions/register";
6
- import { ToolButton } from "../components/ToolButton";
7
- import { FreedrawIcon, LinkIcon, TrashIcon } from "../components/icons";
8
- import { t } from "../i18n";
2
+ import { sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, wrapEvent, } from "../../utils";
3
+ import { getEmbedLink, embeddableURLValidator } from "../../element/embeddable";
4
+ import { mutateElement } from "../../element/mutateElement";
5
+ import { ToolButton } from "../ToolButton";
6
+ import { FreedrawIcon, TrashIcon } from "../icons";
7
+ import { t } from "../../i18n";
9
8
  import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
10
9
  import clsx from "clsx";
11
- import { KEYS } from "../keys";
12
- import { DEFAULT_LINK_SIZE } from "../renderer/renderElement";
13
- import { rotate } from "../math";
14
- import { EVENT, HYPERLINK_TOOLTIP_DELAY, MIME_TYPES } from "../constants";
15
- import { getTooltipDiv, updateTooltipPosition } from "../components/Tooltip";
16
- import { getSelectedElements } from "../scene";
17
- import { isPointHittingElementBoundingBox } from "./collision";
18
- import { getElementAbsoluteCoords } from ".";
19
- import { isLocalLink, normalizeLink } from "../data/url";
10
+ import { KEYS } from "../../keys";
11
+ import { EVENT, HYPERLINK_TOOLTIP_DELAY } from "../../constants";
12
+ import { getElementAbsoluteCoords } from "../../element/bounds";
13
+ import { getTooltipDiv, updateTooltipPosition } from "../Tooltip";
14
+ import { getSelectedElements } from "../../scene";
15
+ import { isPointHittingElementBoundingBox } from "../../element/collision";
16
+ import { isLocalLink, normalizeLink } from "../../data/url";
20
17
  import "./Hyperlink.scss";
21
- import { trackEvent } from "../analytics";
22
- import { useAppProps, useExcalidrawAppState } from "../components/App";
23
- import { isEmbeddableElement } from "./typeChecks";
24
- import { ShapeCache } from "../scene/ShapeCache";
18
+ import { trackEvent } from "../../analytics";
19
+ import { useAppProps, useExcalidrawAppState } from "../App";
20
+ import { isEmbeddableElement } from "../../element/typeChecks";
21
+ import { getLinkHandleFromCoords } from "./helpers";
25
22
  const CONTAINER_WIDTH = 320;
26
23
  const SPACE_BOTTOM = 85;
27
24
  const CONTAINER_PADDING = 5;
28
25
  const CONTAINER_HEIGHT = 42;
29
26
  const AUTO_HIDE_TIMEOUT = 500;
30
- export const EXTERNAL_LINK_IMG = document.createElement("img");
31
- EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`)}`;
32
27
  let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
33
28
  const embeddableLinkCache = new Map();
34
- export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
29
+ export const Hyperlink = ({ element, elementsMap, setAppState, onLinkOpen, setToast, updateEmbedValidationStatus, }) => {
35
30
  const appState = useExcalidrawAppState();
36
31
  const appProps = useAppProps();
37
32
  const linkVal = element.link || "";
@@ -52,9 +47,9 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
52
47
  }
53
48
  if (!link) {
54
49
  mutateElement(element, {
55
- validated: false,
56
50
  link: null,
57
51
  });
52
+ updateEmbedValidationStatus(element, false);
58
53
  return;
59
54
  }
60
55
  if (!embeddableURLValidator(link, appProps.validateEmbeddable)) {
@@ -63,16 +58,18 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
63
58
  }
64
59
  element.link && embeddableLinkCache.set(element.id, element.link);
65
60
  mutateElement(element, {
66
- validated: false,
67
61
  link,
68
62
  });
69
- ShapeCache.delete(element);
63
+ updateEmbedValidationStatus(element, false);
70
64
  }
71
65
  else {
72
66
  const { width, height } = element;
73
67
  const embedLink = getEmbedLink(link);
74
- if (embedLink?.warning) {
75
- setToast({ message: embedLink.warning, closable: true });
68
+ if (embedLink?.error instanceof URIError) {
69
+ setToast({
70
+ message: t("toast.unrecognizedLinkFormat"),
71
+ closable: true,
72
+ });
76
73
  }
77
74
  const ar = embedLink
78
75
  ? embedLink.intrinsicSize.w / embedLink.intrinsicSize.h
@@ -93,10 +90,9 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
93
90
  : height,
94
91
  }
95
92
  : {}),
96
- validated: true,
97
93
  link,
98
94
  });
99
- ShapeCache.delete(element);
95
+ updateEmbedValidationStatus(element, true);
100
96
  if (embeddableLinkCache.has(element.id)) {
101
97
  embeddableLinkCache.delete(element.id);
102
98
  }
@@ -111,6 +107,7 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
111
107
  appProps.validateEmbeddable,
112
108
  appState.activeEmbeddable,
113
109
  setAppState,
110
+ updateEmbedValidationStatus,
114
111
  ]);
115
112
  useLayoutEffect(() => {
116
113
  return () => {
@@ -126,7 +123,7 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
126
123
  if (timeoutId) {
127
124
  clearTimeout(timeoutId);
128
125
  }
129
- const shouldHide = shouldHideLinkPopup(element, appState, [
126
+ const shouldHide = shouldHideLinkPopup(element, elementsMap, appState, [
130
127
  event.clientX,
131
128
  event.clientY,
132
129
  ]);
@@ -143,7 +140,7 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
143
140
  clearTimeout(timeoutId);
144
141
  }
145
142
  };
146
- }, [appState, element, isEditing, setAppState]);
143
+ }, [appState, element, isEditing, setAppState, elementsMap]);
147
144
  const handleRemove = useCallback(() => {
148
145
  trackEvent("hyperlink", "delete");
149
146
  mutateElement(element, { link: null });
@@ -156,7 +153,7 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
156
153
  trackEvent("hyperlink", "edit", "popup-ui");
157
154
  setAppState({ showHyperlinkPopup: "editor" });
158
155
  };
159
- const { x, y } = getCoordsForPopover(element, appState);
156
+ const { x, y } = getCoordsForPopover(element, appState, elementsMap);
160
157
  if (appState.contextMenu ||
161
158
  appState.draggingElement ||
162
159
  appState.resizingElement ||
@@ -197,43 +194,13 @@ export const Hyperlink = ({ element, setAppState, onLinkOpen, setToast, }) => {
197
194
  }
198
195
  }, rel: "noopener noreferrer", children: element.link })) : (_jsx("div", { className: "excalidraw-hyperlinkContainer-link", children: t("labels.link.empty") })), _jsxs("div", { className: "excalidraw-hyperlinkContainer__buttons", children: [!isEditing && (_jsx(ToolButton, { type: "button", title: t("buttons.edit"), "aria-label": t("buttons.edit"), label: t("buttons.edit"), onClick: onEdit, className: "excalidraw-hyperlinkContainer--edit", icon: FreedrawIcon })), linkVal && !isEmbeddableElement(element) && (_jsx(ToolButton, { type: "button", title: t("buttons.remove"), "aria-label": t("buttons.remove"), label: t("buttons.remove"), onClick: handleRemove, className: "excalidraw-hyperlinkContainer--remove", icon: TrashIcon }))] })] }));
199
196
  };
200
- const getCoordsForPopover = (element, appState) => {
201
- const [x1, y1] = getElementAbsoluteCoords(element);
197
+ const getCoordsForPopover = (element, appState, elementsMap) => {
198
+ const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
202
199
  const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords({ sceneX: x1 + element.width / 2, sceneY: y1 }, appState);
203
200
  const x = viewportX - appState.offsetLeft - CONTAINER_WIDTH / 2;
204
201
  const y = viewportY - appState.offsetTop - SPACE_BOTTOM;
205
202
  return { x, y };
206
203
  };
207
- export const actionLink = register({
208
- name: "hyperlink",
209
- perform: (elements, appState) => {
210
- if (appState.showHyperlinkPopup === "editor") {
211
- return false;
212
- }
213
- return {
214
- elements,
215
- appState: {
216
- ...appState,
217
- showHyperlinkPopup: "editor",
218
- openMenu: null,
219
- },
220
- commitToHistory: true,
221
- };
222
- },
223
- trackEvent: { category: "hyperlink", action: "click" },
224
- keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
225
- contextItemLabel: (elements, appState) => getContextMenuLabel(elements, appState),
226
- predicate: (elements, appState) => {
227
- const selectedElements = getSelectedElements(elements, appState);
228
- return selectedElements.length === 1;
229
- },
230
- PanelComponent: ({ elements, appState, updateData }) => {
231
- const selectedElements = getSelectedElements(elements, appState);
232
- return (_jsx(ToolButton, { type: "button", icon: LinkIcon, "aria-label": t(getContextMenuLabel(elements, appState)), title: `${isEmbeddableElement(elements[0])
233
- ? t("labels.link.labelEmbed")
234
- : t("labels.link.label")} - ${getShortcutKey("CtrlOrCmd+K")}`, onClick: () => updateData(null), selected: selectedElements.length === 1 && !!selectedElements[0].link }));
235
- },
236
- });
237
204
  export const getContextMenuLabel = (elements, appState) => {
238
205
  const selectedElements = getSelectedElements(elements, appState);
239
206
  const label = selectedElements[0].link
@@ -245,56 +212,14 @@ export const getContextMenuLabel = (elements, appState) => {
245
212
  : "labels.link.create";
246
213
  return label;
247
214
  };
248
- export const getLinkHandleFromCoords = ([x1, y1, x2, y2], angle, appState) => {
249
- const size = DEFAULT_LINK_SIZE;
250
- const linkWidth = size / appState.zoom.value;
251
- const linkHeight = size / appState.zoom.value;
252
- const linkMarginY = size / appState.zoom.value;
253
- const centerX = (x1 + x2) / 2;
254
- const centerY = (y1 + y2) / 2;
255
- const centeringOffset = (size - 8) / (2 * appState.zoom.value);
256
- const dashedLineMargin = 4 / appState.zoom.value;
257
- // Same as `ne` resize handle
258
- const x = x2 + dashedLineMargin - centeringOffset;
259
- const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
260
- const [rotatedX, rotatedY] = rotate(x + linkWidth / 2, y + linkHeight / 2, centerX, centerY, angle);
261
- return [
262
- rotatedX - linkWidth / 2,
263
- rotatedY - linkHeight / 2,
264
- linkWidth,
265
- linkHeight,
266
- ];
267
- };
268
- export const isPointHittingLinkIcon = (element, appState, [x, y]) => {
269
- const threshold = 4 / appState.zoom.value;
270
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
271
- const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords([x1, y1, x2, y2], element.angle, appState);
272
- const hitLink = x > linkX - threshold &&
273
- x < linkX + threshold + linkWidth &&
274
- y > linkY - threshold &&
275
- y < linkY + linkHeight + threshold;
276
- return hitLink;
277
- };
278
- export const isPointHittingLink = (element, appState, [x, y], isMobile) => {
279
- if (!element.link || appState.selectedElementIds[element.id]) {
280
- return false;
281
- }
282
- const threshold = 4 / appState.zoom.value;
283
- if (!isMobile &&
284
- appState.viewModeEnabled &&
285
- isPointHittingElementBoundingBox(element, [x, y], threshold, null)) {
286
- return true;
287
- }
288
- return isPointHittingLinkIcon(element, appState, [x, y]);
289
- };
290
215
  let HYPERLINK_TOOLTIP_TIMEOUT_ID = null;
291
- export const showHyperlinkTooltip = (element, appState) => {
216
+ export const showHyperlinkTooltip = (element, appState, elementsMap) => {
292
217
  if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
293
218
  clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
294
219
  }
295
- HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(() => renderTooltip(element, appState), HYPERLINK_TOOLTIP_DELAY);
220
+ HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(() => renderTooltip(element, appState, elementsMap), HYPERLINK_TOOLTIP_DELAY);
296
221
  };
297
- const renderTooltip = (element, appState) => {
222
+ const renderTooltip = (element, appState, elementsMap) => {
298
223
  if (!element.link) {
299
224
  return;
300
225
  }
@@ -302,7 +227,7 @@ const renderTooltip = (element, appState) => {
302
227
  tooltipDiv.classList.add("excalidraw-tooltip--visible");
303
228
  tooltipDiv.style.maxWidth = "20rem";
304
229
  tooltipDiv.textContent = element.link;
305
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
230
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
306
231
  const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords([x1, y1, x2, y2], element.angle, appState);
307
232
  const linkViewportCoords = sceneCoordsToViewportCoords({ sceneX: linkX, sceneY: linkY }, appState);
308
233
  updateTooltipPosition(tooltipDiv, {
@@ -323,14 +248,14 @@ export const hideHyperlinkToolip = () => {
323
248
  getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
324
249
  }
325
250
  };
326
- export const shouldHideLinkPopup = (element, appState, [clientX, clientY]) => {
251
+ const shouldHideLinkPopup = (element, elementsMap, appState, [clientX, clientY]) => {
327
252
  const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords({ clientX, clientY }, appState);
328
253
  const threshold = 15 / appState.zoom.value;
329
254
  // hitbox to prevent hiding when hovered in element bounding box
330
- if (isPointHittingElementBoundingBox(element, [sceneX, sceneY], threshold, null)) {
255
+ if (isPointHittingElementBoundingBox(element, elementsMap, [sceneX, sceneY], threshold, null)) {
331
256
  return false;
332
257
  }
333
- const [x1, y1, x2] = getElementAbsoluteCoords(element);
258
+ const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap);
334
259
  // hit box to prevent hiding when hovered in the vertical area between element and popover
335
260
  if (sceneX >= x1 &&
336
261
  sceneX <= x2 &&
@@ -339,7 +264,7 @@ export const shouldHideLinkPopup = (element, appState, [clientX, clientY]) => {
339
264
  return false;
340
265
  }
341
266
  // hit box to prevent hiding when hovered around popover within threshold
342
- const { x: popoverX, y: popoverY } = getCoordsForPopover(element, appState);
267
+ const { x: popoverX, y: popoverY } = getCoordsForPopover(element, appState, elementsMap);
343
268
  if (clientX >= popoverX - threshold &&
344
269
  clientX <= popoverX + CONTAINER_WIDTH + CONTAINER_PADDING * 2 + threshold &&
345
270
  clientY >= popoverY - threshold &&
@@ -0,0 +1,7 @@
1
+ import { Bounds } from "../../element/bounds";
2
+ import { ElementsMap, NonDeletedExcalidrawElement } from "../../element/types";
3
+ import { AppState, UIAppState } from "../../types";
4
+ export declare const EXTERNAL_LINK_IMG: HTMLImageElement;
5
+ export declare const getLinkHandleFromCoords: ([x1, y1, x2, y2]: Bounds, angle: number, appState: Pick<UIAppState, "zoom">) => Bounds;
6
+ export declare const isPointHittingLinkIcon: (element: NonDeletedExcalidrawElement, elementsMap: ElementsMap, appState: AppState, [x, y]: readonly [number, number]) => boolean;
7
+ export declare const isPointHittingLink: (element: NonDeletedExcalidrawElement, elementsMap: ElementsMap, appState: AppState, [x, y]: readonly [number, number], isMobile: boolean) => boolean;
@@ -0,0 +1,49 @@
1
+ import { MIME_TYPES } from "../../constants";
2
+ import { getElementAbsoluteCoords } from "../../element/bounds";
3
+ import { isPointHittingElementBoundingBox } from "../../element/collision";
4
+ import { rotate } from "../../math";
5
+ import { DEFAULT_LINK_SIZE } from "../../renderer/renderElement";
6
+ export const EXTERNAL_LINK_IMG = document.createElement("img");
7
+ EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`)}`;
8
+ export const getLinkHandleFromCoords = ([x1, y1, x2, y2], angle, appState) => {
9
+ const size = DEFAULT_LINK_SIZE;
10
+ const linkWidth = size / appState.zoom.value;
11
+ const linkHeight = size / appState.zoom.value;
12
+ const linkMarginY = size / appState.zoom.value;
13
+ const centerX = (x1 + x2) / 2;
14
+ const centerY = (y1 + y2) / 2;
15
+ const centeringOffset = (size - 8) / (2 * appState.zoom.value);
16
+ const dashedLineMargin = 4 / appState.zoom.value;
17
+ // Same as `ne` resize handle
18
+ const x = x2 + dashedLineMargin - centeringOffset;
19
+ const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
20
+ const [rotatedX, rotatedY] = rotate(x + linkWidth / 2, y + linkHeight / 2, centerX, centerY, angle);
21
+ return [
22
+ rotatedX - linkWidth / 2,
23
+ rotatedY - linkHeight / 2,
24
+ linkWidth,
25
+ linkHeight,
26
+ ];
27
+ };
28
+ export const isPointHittingLinkIcon = (element, elementsMap, appState, [x, y]) => {
29
+ const threshold = 4 / appState.zoom.value;
30
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
31
+ const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords([x1, y1, x2, y2], element.angle, appState);
32
+ const hitLink = x > linkX - threshold &&
33
+ x < linkX + threshold + linkWidth &&
34
+ y > linkY - threshold &&
35
+ y < linkY + linkHeight + threshold;
36
+ return hitLink;
37
+ };
38
+ export const isPointHittingLink = (element, elementsMap, appState, [x, y], isMobile) => {
39
+ if (!element.link || appState.selectedElementIds[element.id]) {
40
+ return false;
41
+ }
42
+ const threshold = 4 / appState.zoom.value;
43
+ if (!isMobile &&
44
+ appState.viewModeEnabled &&
45
+ isPointHittingElementBoundingBox(element, elementsMap, [x, y], threshold, null)) {
46
+ return true;
47
+ }
48
+ return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
49
+ };
@@ -42,7 +42,7 @@ export declare const HelpIcon: JSX.Element;
42
42
  export declare const ExternalLinkIcon: JSX.Element;
43
43
  export declare const GithubIcon: JSX.Element;
44
44
  export declare const DiscordIcon: JSX.Element;
45
- export declare const TwitterIcon: JSX.Element;
45
+ export declare const XBrandIcon: JSX.Element;
46
46
  export declare const checkIcon: JSX.Element;
47
47
  export declare const LinkIcon: JSX.Element;
48
48
  export declare const save: JSX.Element;
@@ -60,6 +60,7 @@ export declare const UndoIcon: JSX.Element;
60
60
  export declare const RedoIcon: JSX.Element;
61
61
  export declare const questionCircle: JSX.Element;
62
62
  export declare const share: JSX.Element;
63
+ export declare const warning: JSX.Element;
63
64
  export declare const shareIOS: JSX.Element;
64
65
  export declare const shareWindows: JSX.Element;
65
66
  export declare const resetZoom: JSX.Element;
@@ -89,7 +89,7 @@ export const HelpIcon = createIcon(_jsxs("g", { strokeWidth: "1.5", children: [_
89
89
  export const ExternalLinkIcon = createIcon(_jsx("path", { strokeWidth: "1.25", d: "M9.167 5.833H5.833c-1.254 0-2.5 1.282-2.5 2.5v5.834c0 1.283 1.252 2.5 2.5 2.5h5.834c1.251 0 2.5-1.25 2.5-2.5v-3.334M8.333 11.667l8.334-8.334M12.5 3.333h4.167V7.5" }), modifiedTablerIconProps);
90
90
  export const GithubIcon = createIcon(_jsx("path", { d: "M7.5 15.833c-3.583 1.167-3.583-2.083-5-2.5m10 4.167v-2.917c0-.833.083-1.166-.417-1.666 2.334-.25 4.584-1.167 4.584-5a3.833 3.833 0 0 0-1.084-2.667 3.5 3.5 0 0 0-.083-2.667s-.917-.25-2.917 1.084a10.25 10.25 0 0 0-5.166 0C5.417 2.333 4.5 2.583 4.5 2.583a3.5 3.5 0 0 0-.083 2.667 3.833 3.833 0 0 0-1.084 2.667c0 3.833 2.25 4.75 4.584 5-.5.5-.5 1-.417 1.666V17.5", strokeWidth: "1.25" }), modifiedTablerIconProps);
91
91
  export const DiscordIcon = createIcon(_jsxs("g", { strokeWidth: "1.25", children: [_jsx("path", { d: "M7.5 10.833a.833.833 0 1 0 0-1.666.833.833 0 0 0 0 1.666ZM12.5 10.833a.833.833 0 1 0 0-1.666.833.833 0 0 0 0 1.666ZM6.25 6.25c2.917-.833 4.583-.833 7.5 0M5.833 13.75c2.917.833 5.417.833 8.334 0" }), _jsx("path", { d: "M12.917 14.167c0 .833 1.25 2.5 1.666 2.5 1.25 0 2.361-1.39 2.917-2.5.556-1.39.417-4.861-1.25-9.584-1.214-.846-2.5-1.116-3.75-1.25l-.833 2.084M7.083 14.167c0 .833-1.13 2.5-1.526 2.5-1.191 0-2.249-1.39-2.778-2.5-.529-1.39-.397-4.861 1.19-9.584 1.157-.846 2.318-1.116 3.531-1.25l.833 2.084" })] }), modifiedTablerIconProps);
92
- export const TwitterIcon = createIcon(_jsxs("g", { strokeWidth: "1.25", children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" })] }), tablerIconProps);
92
+ export const XBrandIcon = createIcon(_jsxs("g", { strokeWidth: "1.25", children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M4 4l11.733 16h4.267l-11.733 -16z" }), _jsx("path", { d: "M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772" })] }), tablerIconProps);
93
93
  export const checkIcon = createIcon(_jsx("polyline", { fill: "none", stroke: "currentColor", points: "20 6 9 17 4 12" }), {
94
94
  width: 24,
95
95
  height: 24,
@@ -111,6 +111,7 @@ export const UndoIcon = createIcon(_jsx("path", { d: "M7.5 10.833 4.167 7.5 7.5
111
111
  export const RedoIcon = createIcon(_jsx("path", { d: "M12.5 10.833 15.833 7.5 12.5 4.167M15.833 7.5H6.667a3.333 3.333 0 1 0 0 6.667H7.5", strokeWidth: "1.25" }), modifiedTablerIconProps);
112
112
  export const questionCircle = createIcon("M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z", { mirror: true });
113
113
  export const share = createIcon(_jsx("path", { d: "M5 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM15 7.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM15 17.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM7.25 8.917l5.5-2.834M7.25 11.083l5.5 2.834", strokeWidth: "1.5" }), modifiedTablerIconProps);
114
+ export const warning = createIcon("M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z");
114
115
  export const shareIOS = createIcon("M16 5l-1.42 1.42-1.59-1.59V16h-1.98V4.83L9.42 6.42 8 5l4-4 4 4zm4 5v11c0 1.1-.9 2-2 2H6c-1.11 0-2-.9-2-2V10c0-1.11.89-2 2-2h3v2H6v11h12V10h-3V8h3c1.1 0 2 .89 2 2z", { width: 24, height: 24 });
115
116
  export const shareWindows = createIcon(_jsxs(_Fragment, { children: [_jsx("path", { fill: "currentColor", d: "M40 5.6v6.1l-4.1.7c-8.9 1.4-16.5 6.9-20.6 15C13 32 10.9 43 12.4 43c.4 0 2.4-1.3 4.4-3 5-3.9 12.1-7 18.2-7.7l5-.6v12.8l11.2-11.3L62.5 22 51.2 10.8 40-.5v6.1zm10.2 22.6L44 34.5v-6.8l-6.9.6c-3.9.3-9.8 1.7-13.2 3.1-3.5 1.4-6.5 2.4-6.7 2.2-.9-1 3-7.5 6.4-10.8C28 18.6 34.4 16 40.1 16c3.7 0 3.9-.1 3.9-3.2V9.5l6.2 6.3 6.3 6.2-6.3 6.2z" }), _jsx("path", { stroke: "currentColor", fill: "currentColor", d: "M0 36v20h48v-6.2c0-6 0-6.1-2-4.3-1.1 1-2 2.9-2 4.2V52H4V34c0-17.3-.1-18-2-18s-2 .7-2 20z" })] }), { width: 64, height: 64 });
116
117
  // Icon imported form Storybook
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { t } from "../../i18n";
3
- import { usersIcon } from "../icons";
3
+ import { share } from "../icons";
4
4
  import { Button } from "../Button";
5
5
  import clsx from "clsx";
6
6
  import "./LiveCollaborationTrigger.scss";
7
7
  import { useUIAppState } from "../../context/ui-appState";
8
8
  const LiveCollaborationTrigger = ({ isCollaborating, onSelect, ...rest }) => {
9
9
  const appState = useUIAppState();
10
- return (_jsxs(Button, { ...rest, className: clsx("collab-button", { active: isCollaborating }), type: "button", onSelect: onSelect, style: { position: "relative" }, title: t("labels.liveCollaboration"), children: [usersIcon, appState.collaborators.size > 0 && (_jsx("div", { className: "CollabButton-collaborators", children: appState.collaborators.size }))] }));
10
+ const showIconOnly = appState.width < 830;
11
+ return (_jsxs(Button, { ...rest, className: clsx("collab-button", { active: isCollaborating }), type: "button", onSelect: onSelect, style: { position: "relative", width: showIconOnly ? undefined : "auto" }, title: t("labels.liveCollaboration"), children: [showIconOnly ? share : t("labels.share"), appState.collaborators.size > 0 && (_jsx("div", { className: "CollabButton-collaborators", children: appState.collaborators.size }))] }));
11
12
  };
12
13
  export default LiveCollaborationTrigger;
13
14
  LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
@@ -3,7 +3,7 @@ import { getShortcutFromShortcutName } from "../../actions/shortcuts";
3
3
  import { useI18n } from "../../i18n";
4
4
  import { useExcalidrawSetAppState, useExcalidrawActionManager, useExcalidrawElements, useAppProps, } from "../App";
5
5
  import { ExportIcon, ExportImageIcon, HelpIcon, LoadIcon, MoonIcon, save, SunIcon, TrashIcon, usersIcon, } from "../icons";
6
- import { GithubIcon, DiscordIcon, TwitterIcon } from "../icons";
6
+ import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
7
7
  import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
8
8
  import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
9
9
  import { actionClearCanvas, actionLoadScene, actionSaveToActiveFile, actionShortcuts, actionToggleTheme, } from "../../actions";
@@ -105,7 +105,10 @@ export const Export = () => {
105
105
  }, "data-testid": "json-export-button", "aria-label": t("buttons.export"), children: t("buttons.export") }));
106
106
  };
107
107
  Export.displayName = "Export";
108
- export const Socials = () => (_jsxs(_Fragment, { children: [_jsx(DropdownMenuItemLink, { icon: GithubIcon, href: "https://github.com/excalidraw/excalidraw", "aria-label": "GitHub", children: "GitHub" }), _jsx(DropdownMenuItemLink, { icon: DiscordIcon, href: "https://discord.gg/UexuTaE", "aria-label": "Discord", children: "Discord" }), _jsx(DropdownMenuItemLink, { icon: TwitterIcon, href: "https://twitter.com/excalidraw", "aria-label": "Twitter", children: "Twitter" })] }));
108
+ export const Socials = () => {
109
+ const { t } = useI18n();
110
+ return (_jsxs(_Fragment, { children: [_jsx(DropdownMenuItemLink, { icon: GithubIcon, href: "https://github.com/excalidraw/excalidraw", "aria-label": "GitHub", children: "GitHub" }), _jsx(DropdownMenuItemLink, { icon: XBrandIcon, href: "https://x.com/excalidraw", "aria-label": "X", children: t("labels.followUs") }), _jsx(DropdownMenuItemLink, { icon: DiscordIcon, href: "https://discord.gg/UexuTaE", "aria-label": "Discord", children: t("labels.discordChat") })] }));
111
+ };
109
112
  Socials.displayName = "Socials";
110
113
  export const LiveCollaborationTrigger = ({ onSelect, isCollaborating, }) => {
111
114
  const { t } = useI18n();
@@ -118,6 +118,7 @@ export declare const DEFAULT_FONT_FAMILY: FontFamilyValues;
118
118
  export declare const DEFAULT_TEXT_ALIGN = "left";
119
119
  export declare const DEFAULT_VERTICAL_ALIGN = "top";
120
120
  export declare const DEFAULT_VERSION = "{version}";
121
+ export declare const DEFAULT_TRANSFORM_HANDLE_SPACING = 2;
121
122
  export declare const CANVAS_ONLY_ACTIONS: string[];
122
123
  export declare const GRID_SIZE = 20;
123
124
  export declare const IMAGE_MIME_TYPES: {
@@ -268,3 +269,8 @@ export declare const EDITOR_LS_KEYS: {
268
269
  readonly MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw";
269
270
  readonly PUBLISH_LIBRARY: "publish-library-data";
270
271
  };
272
+ /**
273
+ * not translated as this is used only in public, stateless API as default value
274
+ * where filename is optional and we can't retrieve name from app state
275
+ */
276
+ export declare const DEFAULT_FILENAME = "Untitled";
@@ -125,6 +125,7 @@ export const DEFAULT_FONT_FAMILY = FONT_FAMILY.Virgil;
125
125
  export const DEFAULT_TEXT_ALIGN = "left";
126
126
  export const DEFAULT_VERTICAL_ALIGN = "top";
127
127
  export const DEFAULT_VERSION = "{version}";
128
+ export const DEFAULT_TRANSFORM_HANDLE_SPACING = 2;
128
129
  export const CANVAS_ONLY_ACTIONS = ["selectAll"];
129
130
  export const GRID_SIZE = 20; // TODO make it configurable?
130
131
  export const IMAGE_MIME_TYPES = {
@@ -314,3 +315,8 @@ export const EDITOR_LS_KEYS = {
314
315
  MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
315
316
  PUBLISH_LIBRARY: "publish-library-data",
316
317
  };
318
+ /**
319
+ * not translated as this is used only in public, stateless API as default value
320
+ * where filename is optional and we can't retrieve name from app state
321
+ */
322
+ export const DEFAULT_FILENAME = "Untitled";
@@ -3,7 +3,6 @@ import { cleanAppStateForExport } from "../appState";
3
3
  import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
4
4
  import { clearElementsForExport } from "../element";
5
5
  import { CanvasError, ImageSceneDataError } from "../errors";
6
- import { t } from "../i18n";
7
6
  import { calculateScrollCenter } from "../scene";
8
7
  import { bytesToHexString, isPromiseLike } from "../utils";
9
8
  import { nativeFileSystemSupported } from "./filesystem";
@@ -17,10 +16,10 @@ const parseFileContents = async (blob) => {
17
16
  }
18
17
  catch (error) {
19
18
  if (error.message === "INVALID") {
20
- throw new ImageSceneDataError(t("alerts.imageDoesNotContainScene"), "IMAGE_NOT_CONTAINS_SCENE_DATA");
19
+ throw new ImageSceneDataError("Image doesn't contain scene", "IMAGE_NOT_CONTAINS_SCENE_DATA");
21
20
  }
22
21
  else {
23
- throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
22
+ throw new ImageSceneDataError("Error: cannot restore image");
24
23
  }
25
24
  }
26
25
  }
@@ -47,10 +46,10 @@ const parseFileContents = async (blob) => {
47
46
  }
48
47
  catch (error) {
49
48
  if (error.message === "INVALID") {
50
- throw new ImageSceneDataError(t("alerts.imageDoesNotContainScene"), "IMAGE_NOT_CONTAINS_SCENE_DATA");
49
+ throw new ImageSceneDataError("Image doesn't contain scene", "IMAGE_NOT_CONTAINS_SCENE_DATA");
51
50
  }
52
51
  else {
53
- throw new ImageSceneDataError(t("alerts.cannotRestoreFromImage"));
52
+ throw new ImageSceneDataError("Error: cannot restore image");
54
53
  }
55
54
  }
56
55
  }
@@ -112,7 +111,7 @@ fileHandle) => {
112
111
  }
113
112
  catch (error) {
114
113
  if (isSupportedImageFile(blob)) {
115
- throw new ImageSceneDataError(t("alerts.imageDoesNotContainScene"), "IMAGE_NOT_CONTAINS_SCENE_DATA");
114
+ throw new ImageSceneDataError("Image doesn't contain scene", "IMAGE_NOT_CONTAINS_SCENE_DATA");
116
115
  }
117
116
  throw error;
118
117
  }
@@ -139,13 +138,13 @@ fileHandle) => {
139
138
  data,
140
139
  };
141
140
  }
142
- throw new Error(t("alerts.couldNotLoadInvalidFile"));
141
+ throw new Error("Error: invalid file");
143
142
  }
144
143
  catch (error) {
145
144
  if (error instanceof ImageSceneDataError) {
146
145
  throw error;
147
146
  }
148
- throw new Error(t("alerts.couldNotLoadInvalidFile"));
147
+ throw new Error("Error: invalid file");
149
148
  }
150
149
  };
151
150
  export const loadFromBlob = async (blob,
@@ -155,7 +154,7 @@ localAppState, localElements,
155
154
  fileHandle) => {
156
155
  const ret = await loadSceneOrLibraryFromBlob(blob, localAppState, localElements, fileHandle);
157
156
  if (ret.type !== MIME_TYPES.excalidraw) {
158
- throw new Error(t("alerts.couldNotLoadInvalidFile"));
157
+ throw new Error("Error: invalid file");
159
158
  }
160
159
  return ret.data;
161
160
  };
@@ -178,7 +177,7 @@ export const canvasToBlob = async (canvas) => {
178
177
  }
179
178
  canvas.toBlob((blob) => {
180
179
  if (!blob) {
181
- return reject(new CanvasError(t("canvasError.canvasTooBig"), "CANVAS_POSSIBLY_TOO_BIG"));
180
+ return reject(new CanvasError("Error: Canvas too big", "CANVAS_POSSIBLY_TOO_BIG"));
182
181
  }
183
182
  resolve(blob);
184
183
  });
@@ -249,7 +248,7 @@ export const resizeImageFile = async (file, opts) => {
249
248
  };
250
249
  }
251
250
  if (!isSupportedImageFile(file)) {
252
- throw new Error(t("errors.unsupportedFileType"));
251
+ throw new Error("Error: unsupported file type", { cause: "UNSUPPORTED" });
253
252
  }
254
253
  return new File([await reduce.toBlob(file, { max: opts.maxWidthOrHeight })], file.name, {
255
254
  type: opts.outputType || file.type,
@@ -266,17 +265,17 @@ export const ImageURLToFile = async (imageUrl, filename = "") => {
266
265
  response = await fetch(imageUrl);
267
266
  }
268
267
  catch (error) {
269
- throw new Error(t("errors.failedToFetchImage"));
268
+ throw new Error("Error: failed to fetch image", { cause: "FETCH_ERROR" });
270
269
  }
271
270
  if (!response.ok) {
272
- throw new Error(t("errors.failedToFetchImage"));
271
+ throw new Error("Error: failed to fetch image", { cause: "FETCH_ERROR" });
273
272
  }
274
273
  const blob = await response.blob();
275
274
  if (blob.type && isSupportedImageFile(blob)) {
276
275
  const name = filename || blob.name || "";
277
276
  return new File([blob], name, { type: blob.type });
278
277
  }
279
- throw new Error(t("errors.unsupportedFileType"));
278
+ throw new Error("Error: unsupported file type", { cause: "UNSUPPORTED" });
280
279
  };
281
280
  export const getFileFromEvent = async (event) => {
282
281
  const file = event.dataTransfer.files.item(0);
@@ -6,7 +6,7 @@ export declare const fileOpen: <M extends boolean | undefined = false>(opts: {
6
6
  description: string;
7
7
  multiple?: M | undefined;
8
8
  }) => Promise<M extends false | undefined ? File : File[]>;
9
- export declare const fileSave: (blob: Blob, opts: {
9
+ export declare const fileSave: (blob: Blob | Promise<Blob>, opts: {
10
10
  /** supply without the extension */
11
11
  name: string;
12
12
  /** file extension */
@@ -15,7 +15,8 @@ export declare const exportCanvas: (type: Omit<ExportType, "backend">, elements:
15
15
  exportBackground: boolean;
16
16
  exportPadding?: number | undefined;
17
17
  viewBackgroundColor: string;
18
- name: string;
18
+ /** filename, if applicable */
19
+ name?: string | undefined;
19
20
  fileHandle?: FileSystemHandle | null | undefined;
20
21
  exportingFrame: ExcalidrawFrameLikeElement | null;
21
22
  }) => Promise<FileSystemHandle | null | undefined>;