@myoc/excalidraw 0.19.519 → 0.19.520

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 (88) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/dev/{chunk-RIK6B6HD.js → chunk-FQHULQ5P.js} +62 -26
  3. package/dist/dev/chunk-FQHULQ5P.js.map +7 -0
  4. package/dist/dev/{chunk-ZGRXNVW4.js → chunk-IU2VFRFU.js} +4 -1
  5. package/dist/dev/chunk-IU2VFRFU.js.map +7 -0
  6. package/dist/dev/{chunk-MCPNWEHU.js → chunk-SJJUXTTF.js} +2 -2
  7. package/dist/dev/data/{image-IRC25PM5.js → image-X3JCQEUX.js} +3 -3
  8. package/dist/dev/index.css +12 -0
  9. package/dist/dev/index.css.map +2 -2
  10. package/dist/dev/index.js +1315 -395
  11. package/dist/dev/index.js.map +4 -4
  12. package/dist/dev/locales/{en-RQFAMS2U.js → en-25BPHB4T.js} +2 -2
  13. package/dist/dev/subset-shared.chunk.js +1 -1
  14. package/dist/dev/subset-worker.chunk.js +1 -1
  15. package/dist/prod/{chunk-QW7MIEK6.js → chunk-3QXZ5NQO.js} +1 -1
  16. package/dist/prod/{chunk-VHQT4IVX.js → chunk-3UXKIWVY.js} +2 -2
  17. package/dist/prod/chunk-YJSNILE6.js +4 -0
  18. package/dist/prod/data/image-OBEPGJLJ.js +1 -0
  19. package/dist/prod/index.css +1 -1
  20. package/dist/prod/index.js +20 -20
  21. package/dist/prod/locales/{en-6UCVDQQ7.js → en-AH5BYWXT.js} +1 -1
  22. package/dist/prod/subset-shared.chunk.js +1 -1
  23. package/dist/prod/subset-worker.chunk.js +1 -1
  24. package/dist/types/common/src/constants.d.ts +8 -5
  25. package/dist/types/common/src/utils.d.ts +0 -42
  26. package/dist/types/element/src/comparisons.d.ts +1 -0
  27. package/dist/types/element/src/image.d.ts +5 -0
  28. package/dist/types/element/src/newElement.d.ts +2 -0
  29. package/dist/types/element/src/types.d.ts +8 -0
  30. package/dist/types/excalidraw/actions/actionBoundText.d.ts +4 -2
  31. package/dist/types/excalidraw/actions/actionCanvas.d.ts +22 -11
  32. package/dist/types/excalidraw/actions/actionClipboard.d.ts +4 -2
  33. package/dist/types/excalidraw/actions/actionCropEditor.d.ts +2 -1
  34. package/dist/types/excalidraw/actions/actionDeleteSelected.d.ts +6 -3
  35. package/dist/types/excalidraw/actions/actionDeselect.d.ts +2 -1
  36. package/dist/types/excalidraw/actions/actionElementLink.d.ts +2 -1
  37. package/dist/types/excalidraw/actions/actionElementLock.d.ts +4 -2
  38. package/dist/types/excalidraw/actions/actionEmbeddable.d.ts +2 -1
  39. package/dist/types/excalidraw/actions/actionExport.d.ts +4 -2
  40. package/dist/types/excalidraw/actions/actionFrame.d.ts +10 -4
  41. package/dist/types/excalidraw/actions/actionGroup.d.ts +4 -2
  42. package/dist/types/excalidraw/actions/actionLinearEditor.d.ts +4 -1
  43. package/dist/types/excalidraw/actions/actionLink.d.ts +2 -1
  44. package/dist/types/excalidraw/actions/actionMenu.d.ts +2 -1
  45. package/dist/types/excalidraw/actions/actionProperties.d.ts +16 -5
  46. package/dist/types/excalidraw/actions/actionSelectAll.d.ts +2 -1
  47. package/dist/types/excalidraw/actions/actionStyles.d.ts +2 -1
  48. package/dist/types/excalidraw/actions/actionToggleArrowBinding.d.ts +2 -1
  49. package/dist/types/excalidraw/actions/actionToggleGridMode.d.ts +2 -1
  50. package/dist/types/excalidraw/actions/actionToggleMidpointSnapping.d.ts +2 -1
  51. package/dist/types/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +2 -1
  52. package/dist/types/excalidraw/actions/actionToggleSearchMenu.d.ts +2 -1
  53. package/dist/types/excalidraw/actions/actionToggleStats.d.ts +2 -1
  54. package/dist/types/excalidraw/actions/actionToggleViewMode.d.ts +2 -1
  55. package/dist/types/excalidraw/actions/actionToggleZenMode.d.ts +2 -1
  56. package/dist/types/excalidraw/actions/index.d.ts +1 -1
  57. package/dist/types/excalidraw/actions/types.d.ts +1 -1
  58. package/dist/types/excalidraw/appState.d.ts +2 -1
  59. package/dist/types/excalidraw/components/App.d.ts +12 -28
  60. package/dist/types/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
  61. package/dist/types/excalidraw/components/ConvertElementTypePopup.d.ts +1 -1
  62. package/dist/types/excalidraw/components/EyeDropper.d.ts +1 -1
  63. package/dist/types/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
  64. package/dist/types/excalidraw/components/SearchMenu.d.ts +1 -1
  65. package/dist/types/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  66. package/dist/types/excalidraw/components/canvases/StaticCanvas.d.ts +2 -1
  67. package/dist/types/excalidraw/components/icons.d.ts +2 -0
  68. package/dist/types/excalidraw/data/blob.d.ts +2 -1
  69. package/dist/types/excalidraw/data/json.d.ts +2 -1
  70. package/dist/types/excalidraw/index.d.ts +2 -2
  71. package/dist/types/excalidraw/renderer/animation.d.ts +1 -0
  72. package/dist/types/excalidraw/scene/Renderer.d.ts +2 -0
  73. package/dist/types/excalidraw/scene/index.d.ts +1 -1
  74. package/dist/types/excalidraw/scene/types.d.ts +1 -0
  75. package/dist/types/excalidraw/scroll.d.ts +46 -0
  76. package/dist/types/excalidraw/types.d.ts +16 -3
  77. package/dist/types/laser-pointer/src/index.d.ts +2 -0
  78. package/dist/types/laser-pointer/src/math.d.ts +16 -0
  79. package/dist/types/laser-pointer/src/simplify.d.ts +2 -0
  80. package/dist/types/laser-pointer/src/state.d.ts +35 -0
  81. package/package.json +4 -4
  82. package/dist/dev/chunk-RIK6B6HD.js.map +0 -7
  83. package/dist/dev/chunk-ZGRXNVW4.js.map +0 -7
  84. package/dist/prod/chunk-FL5QOM6C.js +0 -4
  85. package/dist/prod/data/image-7LG744DI.js +0 -1
  86. /package/dist/dev/{chunk-MCPNWEHU.js.map → chunk-SJJUXTTF.js.map} +0 -0
  87. /package/dist/dev/data/{image-IRC25PM5.js.map → image-X3JCQEUX.js.map} +0 -0
  88. /package/dist/dev/locales/{en-RQFAMS2U.js.map → en-25BPHB4T.js.map} +0 -0
package/dist/dev/index.js CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  getSelectedElements,
36
36
  getTargetElements,
37
37
  hasBackground,
38
+ hasFreedrawMode,
38
39
  hasStrokeStyle,
39
40
  hasStrokeWidth,
40
41
  isEraserActive,
@@ -57,13 +58,13 @@ import {
57
58
  saveAsJSON,
58
59
  serializeAsJSON,
59
60
  strokeRectWithRotation_simple
60
- } from "./chunk-RIK6B6HD.js";
61
+ } from "./chunk-FQHULQ5P.js";
61
62
  import {
62
63
  define_import_meta_env_default
63
- } from "./chunk-MCPNWEHU.js";
64
+ } from "./chunk-SJJUXTTF.js";
64
65
  import {
65
66
  en_default
66
- } from "./chunk-ZGRXNVW4.js";
67
+ } from "./chunk-IU2VFRFU.js";
67
68
  import {
68
69
  percentages_default
69
70
  } from "./chunk-URPEKBQ3.js";
@@ -85,6 +86,7 @@ import {
85
86
  applyDarkModeFilter as applyDarkModeFilter5,
86
87
  DEFAULT_IMAGE_OPTIONS,
87
88
  DEFAULT_UI_OPTIONS,
89
+ getStrokeWidthByKey as getStrokeWidthByKey4,
88
90
  isShallowEqual as isShallowEqual9
89
91
  } from "@excalidraw/common";
90
92
 
@@ -96,7 +98,7 @@ import { flushSync as flushSync2 } from "react-dom";
96
98
  import rough3 from "roughjs/bin/rough";
97
99
  import { nanoid } from "nanoid";
98
100
  import {
99
- clamp as clamp8,
101
+ clamp as clamp10,
100
102
  pointFrom as pointFrom32,
101
103
  pointDistance as pointDistance9,
102
104
  vector as vector3,
@@ -116,6 +118,8 @@ import {
116
118
  KEYS as KEYS50,
117
119
  APP_NAME,
118
120
  CURSOR_TYPE as CURSOR_TYPE4,
121
+ DEFAULT_STROKE_STREAMLINE as DEFAULT_STROKE_STREAMLINE2,
122
+ DEFAULT_STROKE_STREAMLINE_PRECISE,
119
123
  DEFAULT_TRANSFORM_HANDLE_SPACING as DEFAULT_TRANSFORM_HANDLE_SPACING3,
120
124
  DEFAULT_VERTICAL_ALIGN,
121
125
  DRAGGING_THRESHOLD as DRAGGING_THRESHOLD3,
@@ -126,7 +130,7 @@ import {
126
130
  IMAGE_MIME_TYPES as IMAGE_MIME_TYPES2,
127
131
  IMAGE_RENDER_TIMEOUT,
128
132
  LINE_CONFIRM_THRESHOLD as LINE_CONFIRM_THRESHOLD2,
129
- MIME_TYPES as MIME_TYPES7,
133
+ MIME_TYPES as MIME_TYPES8,
130
134
  MQ_RIGHT_SIDEBAR_MIN_WIDTH,
131
135
  POINTER_BUTTON as POINTER_BUTTON2,
132
136
  ROUNDNESS as ROUNDNESS7,
@@ -164,11 +168,9 @@ import {
164
168
  updateObject as updateObject2,
165
169
  updateActiveTool as updateActiveTool8,
166
170
  isTransparent as isTransparent6,
167
- easeToValuesRAF,
168
171
  muteFSAbortError,
169
172
  isTestEnv as isTestEnv5,
170
173
  isDevEnv as isDevEnv9,
171
- easeOut as easeOut4,
172
174
  updateStable,
173
175
  addEventListener as addEventListener2,
174
176
  normalizeEOL as normalizeEOL2,
@@ -195,7 +197,9 @@ import {
195
197
  setDesktopUIMode,
196
198
  isSelectionLikeTool,
197
199
  oneOf,
198
- matchKey as matchKey3
200
+ easeOut as easeOut5,
201
+ matchKey as matchKey3,
202
+ getStrokeWidthByKey as getStrokeWidthByKey3
199
203
  } from "@excalidraw/common";
200
204
  import {
201
205
  getObservedAppState,
@@ -251,6 +255,8 @@ import {
251
255
  maybeParseEmbedSrc,
252
256
  getEmbedLink as getEmbedLink2,
253
257
  getInitializedImageElements,
258
+ generateThumbHash,
259
+ loadHTMLImageElement,
254
260
  normalizeSVG,
255
261
  updateImageCache as _updateImageCache,
256
262
  getBoundTextElement as getBoundTextElement15,
@@ -285,7 +291,6 @@ import {
285
291
  cropElement,
286
292
  wrapText as wrapText5,
287
293
  isElementLink as isElementLink2,
288
- parseElementLinkFromURL,
289
294
  isMeasureTextSupported,
290
295
  normalizeText as normalizeText2,
291
296
  measureText as measureText8,
@@ -341,7 +346,9 @@ import {
341
346
  maybeHandleArrowPointlikeDrag,
342
347
  getUncroppedWidthAndHeight as getUncroppedWidthAndHeight4,
343
348
  getActiveTextElement as getActiveTextElement2,
344
- isEligibleFrameChildType
349
+ isEligibleFrameChildType,
350
+ getBindingStrategyForDraggingBindingElementEndpoints as getBindingStrategyForDraggingBindingElementEndpoints2,
351
+ parseElementLinkFromURL
345
352
  } from "@excalidraw/element";
346
353
  import { LinearElementEditor as LinearElementEditor11 } from "@excalidraw/element/linearElementEditor";
347
354
  import { findShapeByKey } from "@excalidraw/element/shapes";
@@ -396,7 +403,7 @@ var globImport_locales_json = __glob({
396
403
  "./locales/de-CH.json": () => import("./locales/de-CH-QSJHIHEN.js"),
397
404
  "./locales/de-DE.json": () => import("./locales/de-DE-2WQ5IRPT.js"),
398
405
  "./locales/el-GR.json": () => import("./locales/el-GR-N7JWYMV5.js"),
399
- "./locales/en.json": () => import("./locales/en-RQFAMS2U.js"),
406
+ "./locales/en.json": () => import("./locales/en-25BPHB4T.js"),
400
407
  "./locales/es-ES.json": () => import("./locales/es-ES-37LJTLHM.js"),
401
408
  "./locales/eu-ES.json": () => import("./locales/eu-ES-2IFCUP7K.js"),
402
409
  "./locales/fa-IR.json": () => import("./locales/fa-IR-VDT7BNM4.js"),
@@ -1754,6 +1761,96 @@ var SloppinessCartoonistIcon = createIcon(
1754
1761
  ),
1755
1762
  modifiedTablerIconProps
1756
1763
  );
1764
+ var strokeVariabilityConstantIcon = createIcon(
1765
+ /* @__PURE__ */ jsxs("g", { children: [
1766
+ /* @__PURE__ */ jsx(
1767
+ "path",
1768
+ {
1769
+ d: "M4 12 C 5 8, 6 8, 8 12",
1770
+ fill: "none",
1771
+ strokeWidth: "1",
1772
+ strokeLinecap: "round",
1773
+ strokeLinejoin: "round"
1774
+ }
1775
+ ),
1776
+ /* @__PURE__ */ jsx(
1777
+ "path",
1778
+ {
1779
+ d: "M8 12 C 9 16, 10 16, 12 12",
1780
+ fill: "none",
1781
+ strokeWidth: "1",
1782
+ strokeLinecap: "round",
1783
+ strokeLinejoin: "round"
1784
+ }
1785
+ ),
1786
+ /* @__PURE__ */ jsx(
1787
+ "path",
1788
+ {
1789
+ d: "M12 12 C 14 8, 15 8, 16 12",
1790
+ fill: "none",
1791
+ strokeWidth: "1",
1792
+ strokeLinecap: "round",
1793
+ strokeLinejoin: "round"
1794
+ }
1795
+ ),
1796
+ /* @__PURE__ */ jsx(
1797
+ "path",
1798
+ {
1799
+ d: "M16 12 C 17 16, 18 16, 19 12",
1800
+ fill: "none",
1801
+ strokeWidth: "1",
1802
+ strokeLinecap: "round",
1803
+ strokeLinejoin: "round"
1804
+ }
1805
+ )
1806
+ ] }),
1807
+ tablerIconProps
1808
+ );
1809
+ var strokeVariabilityVariableIcon = createIcon(
1810
+ /* @__PURE__ */ jsxs("g", { children: [
1811
+ /* @__PURE__ */ jsx(
1812
+ "path",
1813
+ {
1814
+ d: "M4 12 C 5 8, 6 8, 8 12",
1815
+ fill: "none",
1816
+ strokeWidth: "1.5",
1817
+ strokeLinecap: "round",
1818
+ strokeLinejoin: "round"
1819
+ }
1820
+ ),
1821
+ /* @__PURE__ */ jsx(
1822
+ "path",
1823
+ {
1824
+ d: "M8 12 C 9 16, 10 16, 12 12",
1825
+ fill: "none",
1826
+ strokeWidth: "2",
1827
+ strokeLinecap: "round",
1828
+ strokeLinejoin: "round"
1829
+ }
1830
+ ),
1831
+ /* @__PURE__ */ jsx(
1832
+ "path",
1833
+ {
1834
+ d: "M12 12 C 14 8, 15 8, 16 12",
1835
+ fill: "none",
1836
+ strokeWidth: "2.75",
1837
+ strokeLinecap: "round",
1838
+ strokeLinejoin: "round"
1839
+ }
1840
+ ),
1841
+ /* @__PURE__ */ jsx(
1842
+ "path",
1843
+ {
1844
+ d: "M16 12 C 17 16, 18 16, 19 12",
1845
+ fill: "none",
1846
+ strokeWidth: "3.25",
1847
+ strokeLinecap: "round",
1848
+ strokeLinejoin: "round"
1849
+ }
1850
+ )
1851
+ ] }),
1852
+ tablerIconProps
1853
+ );
1757
1854
  var EdgeSharpIcon = createIcon(
1758
1855
  /* @__PURE__ */ jsxs("svg", { strokeWidth: "1.5", children: [
1759
1856
  /* @__PURE__ */ jsx("path", { d: "M3.33334 9.99998V6.66665C3.33334 6.04326 3.33403 4.9332 3.33539 3.33646C4.95233 3.33436 6.06276 3.33331 6.66668 3.33331H10" }),
@@ -3691,7 +3788,7 @@ import {
3691
3788
  DEFAULT_FONT_SIZE,
3692
3789
  FONT_FAMILY as FONT_FAMILY3,
3693
3790
  ROUNDNESS,
3694
- STROKE_WIDTH,
3791
+ STROKE_WIDTH_KEYS,
3695
3792
  VERTICAL_ALIGN,
3696
3793
  KEYS as KEYS11,
3697
3794
  randomInteger,
@@ -3699,6 +3796,7 @@ import {
3699
3796
  getFontFamilyString as getFontFamilyString2,
3700
3797
  getLineHeight,
3701
3798
  isTransparent,
3799
+ getStrokeWidthByKey,
3702
3800
  reduceToCommonValue,
3703
3801
  invariant,
3704
3802
  FONT_SIZES
@@ -6348,19 +6446,19 @@ var changeProperty = (elements, appState, callback, includeBoundText = false) =>
6348
6446
  return element;
6349
6447
  });
6350
6448
  };
6351
- var getFormValue = function(elements, app, getAttribute, elementPredicate, defaultValue) {
6449
+ var getFormValue = function(elements, app, getValue, elementPredicate, defaultValue) {
6352
6450
  const editingTextElement = app.state.editingTextElement;
6353
6451
  const nonDeletedElements = getNonDeletedElements4(elements);
6354
6452
  let ret = null;
6355
6453
  if (editingTextElement) {
6356
- ret = getAttribute(editingTextElement);
6454
+ ret = getValue(editingTextElement);
6357
6455
  }
6358
6456
  if (!ret) {
6359
6457
  const hasSelection = isSomeElementSelected(nonDeletedElements, app.state);
6360
6458
  if (hasSelection) {
6361
6459
  const selectedElements = app.scene.getSelectedElements(app.state);
6362
6460
  const targetElements = elementPredicate === true ? selectedElements : selectedElements.filter((el) => elementPredicate(el));
6363
- ret = reduceToCommonValue(targetElements, getAttribute) ?? (typeof defaultValue === "function" ? defaultValue(true) : defaultValue);
6461
+ ret = reduceToCommonValue(targetElements, getValue) ?? (typeof defaultValue === "function" ? defaultValue(true) : defaultValue);
6364
6462
  } else {
6365
6463
  ret = typeof defaultValue === "function" ? defaultValue(false) : defaultValue;
6366
6464
  }
@@ -6619,20 +6717,29 @@ var actionChangeFillStyle = register({
6619
6717
  ] });
6620
6718
  }
6621
6719
  });
6720
+ var getStrokeWidthKeyForElement = (element) => {
6721
+ return STROKE_WIDTH_KEYS.find(
6722
+ (key) => getStrokeWidthByKey(element.type, key) === element.strokeWidth
6723
+ ) ?? null;
6724
+ };
6725
+ var getStrokeWidthForElement = (element, strokeWidthKey) => {
6726
+ return getStrokeWidthByKey(element.type, strokeWidthKey);
6727
+ };
6622
6728
  var actionChangeStrokeWidth = register({
6623
6729
  name: "changeStrokeWidth",
6624
6730
  label: "labels.strokeWidth",
6625
6731
  trackEvent: false,
6626
6732
  perform: (elements, appState, value) => {
6733
+ invariant(value, "actionChangeStrokeWidth: value must be defined");
6627
6734
  return {
6628
6735
  elements: changeProperty(
6629
6736
  elements,
6630
6737
  appState,
6631
6738
  (el) => newElementWith2(el, {
6632
- strokeWidth: value
6739
+ strokeWidth: getStrokeWidthForElement(el, value)
6633
6740
  })
6634
6741
  ),
6635
- appState: { ...appState, currentItemStrokeWidth: value },
6742
+ appState: { ...appState, currentItemStrokeWidthKey: value },
6636
6743
  captureUpdate: CaptureUpdateAction5.IMMEDIATELY
6637
6744
  };
6638
6745
  },
@@ -6644,30 +6751,30 @@ var actionChangeStrokeWidth = register({
6644
6751
  group: "stroke-width",
6645
6752
  options: [
6646
6753
  {
6647
- value: STROKE_WIDTH.thin,
6754
+ value: "thin",
6648
6755
  text: t("labels.thin"),
6649
6756
  icon: StrokeWidthBaseIcon,
6650
6757
  testId: "strokeWidth-thin"
6651
6758
  },
6652
6759
  {
6653
- value: STROKE_WIDTH.bold,
6654
- text: t("labels.bold"),
6760
+ value: "medium",
6761
+ text: t("labels.medium"),
6655
6762
  icon: StrokeWidthBoldIcon,
6656
- testId: "strokeWidth-bold"
6763
+ testId: "strokeWidth-medium"
6657
6764
  },
6658
6765
  {
6659
- value: STROKE_WIDTH.extraBold,
6660
- text: t("labels.extraBold"),
6766
+ value: "bold",
6767
+ text: t("labels.bold"),
6661
6768
  icon: StrokeWidthExtraBoldIcon,
6662
- testId: "strokeWidth-extraBold"
6769
+ testId: "strokeWidth-bold"
6663
6770
  }
6664
6771
  ],
6665
6772
  value: getFormValue(
6666
6773
  elements,
6667
6774
  app,
6668
- (element) => element.strokeWidth,
6775
+ getStrokeWidthKeyForElement,
6669
6776
  (element) => element.hasOwnProperty("strokeWidth"),
6670
- (hasSelection) => hasSelection ? null : appState.currentItemStrokeWidth
6777
+ (hasSelection) => hasSelection ? null : appState.currentItemStrokeWidthKey
6671
6778
  ),
6672
6779
  onChange: (value) => updateData(value)
6673
6780
  }
@@ -6727,6 +6834,74 @@ var actionChangeSloppiness = register({
6727
6834
  ) })
6728
6835
  ] })
6729
6836
  });
6837
+ var actionChangeFreedrawMode = register({
6838
+ name: "changeFreedrawMode",
6839
+ label: "labels.pressure",
6840
+ trackEvent: false,
6841
+ perform: (elements, appState, value) => {
6842
+ const variability = value || "constant";
6843
+ return {
6844
+ elements: changeProperty(elements, appState, (el) => {
6845
+ if (el.type !== "freedraw") {
6846
+ return el;
6847
+ }
6848
+ return newElementWith2(el, {
6849
+ strokeOptions: {
6850
+ ...el.strokeOptions,
6851
+ variability
6852
+ }
6853
+ });
6854
+ }),
6855
+ appState: { ...appState, currentItemStrokeVariability: variability },
6856
+ captureUpdate: CaptureUpdateAction5.IMMEDIATELY
6857
+ };
6858
+ },
6859
+ PanelComponent: ({ elements, appState, updateData, app, data }) => {
6860
+ const strokeVariability = getFormValue(
6861
+ elements,
6862
+ app,
6863
+ (element) => element.strokeOptions?.variability,
6864
+ (element) => element.type === "freedraw",
6865
+ (hasSelection) => hasSelection ? null : appState.currentItemStrokeVariability
6866
+ ) ?? appState.currentItemStrokeVariability;
6867
+ if (data?.cycle) {
6868
+ const isVariable = strokeVariability === "variable";
6869
+ return /* @__PURE__ */ jsx34(
6870
+ ToolButton,
6871
+ {
6872
+ type: "button",
6873
+ icon: isVariable ? strokeVariabilityVariableIcon : strokeVariabilityConstantIcon,
6874
+ title: t("labels.pressure"),
6875
+ "aria-label": t("labels.pressure"),
6876
+ onClick: () => updateData(isVariable ? "constant" : "variable")
6877
+ }
6878
+ );
6879
+ }
6880
+ return /* @__PURE__ */ jsxs20("fieldset", { children: [
6881
+ /* @__PURE__ */ jsx34("legend", { children: t("labels.pressure") }),
6882
+ /* @__PURE__ */ jsx34("div", { className: "buttonList", children: /* @__PURE__ */ jsx34(
6883
+ RadioSelection,
6884
+ {
6885
+ group: "strokeOptions.variability",
6886
+ options: [
6887
+ {
6888
+ value: "constant",
6889
+ text: t("labels.pressure_constant"),
6890
+ icon: strokeVariabilityConstantIcon
6891
+ },
6892
+ {
6893
+ value: "variable",
6894
+ text: t("labels.pressure_variable"),
6895
+ icon: strokeVariabilityVariableIcon
6896
+ }
6897
+ ],
6898
+ value: strokeVariability,
6899
+ onChange: (value) => updateData(value)
6900
+ }
6901
+ ) })
6902
+ ] });
6903
+ }
6904
+ });
6730
6905
  var actionChangeStrokeStyle = register({
6731
6906
  name: "changeStrokeStyle",
6732
6907
  label: "labels.strokeStyle",
@@ -8567,6 +8742,7 @@ var actionFinalize = register({
8567
8742
  label: "",
8568
8743
  trackEvent: false,
8569
8744
  perform: (elements, appState, data, app) => {
8745
+ let shouldCommit = true;
8570
8746
  let newElements = elements;
8571
8747
  const { interactiveCanvas, focusContainer, scene } = app;
8572
8748
  const elementsMap = scene.getNonDeletedElementsMap();
@@ -8688,9 +8864,39 @@ var actionFinalize = register({
8688
8864
  const { points } = element;
8689
8865
  const { lastCommittedPoint } = appState.selectedLinearElement;
8690
8866
  if (!lastCommittedPoint || points[points.length - 1] !== lastCommittedPoint) {
8867
+ shouldCommit = false;
8691
8868
  scene.mutateElement(element, {
8692
8869
  points: element.points.slice(0, -1)
8693
8870
  });
8871
+ if (isBindingElement(element) && element.endBinding && // after slicing the trailing point a <2-point arrow may be left
8872
+ element.points.length > 1) {
8873
+ const newArrow = !!appState.newElement;
8874
+ const draggedPoints = /* @__PURE__ */ new Map([
8875
+ [
8876
+ element.points.length - 1,
8877
+ {
8878
+ point: element.points[element.points.length - 1],
8879
+ isDragging: false
8880
+ }
8881
+ ]
8882
+ ]);
8883
+ const globalPoint = LinearElementEditor5.getPointAtIndexGlobalCoordinates(
8884
+ element,
8885
+ -1,
8886
+ elementsMap
8887
+ );
8888
+ bindOrUnbindBindingElement(
8889
+ element,
8890
+ draggedPoints,
8891
+ globalPoint[0],
8892
+ globalPoint[1],
8893
+ scene,
8894
+ appState,
8895
+ {
8896
+ newArrow
8897
+ }
8898
+ );
8899
+ }
8694
8900
  }
8695
8901
  }
8696
8902
  if (element && isInvisiblySmallElement(element)) {
@@ -8773,7 +8979,7 @@ var actionFinalize = register({
8773
8979
  selectedLinearElement
8774
8980
  },
8775
8981
  // TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
8776
- captureUpdate: CaptureUpdateAction8.IMMEDIATELY
8982
+ captureUpdate: shouldCommit ? CaptureUpdateAction8.IMMEDIATELY : CaptureUpdateAction8.NEVER
8777
8983
  };
8778
8984
  },
8779
8985
  keyTest: (event, appState) => event.key === KEYS13.ESCAPE && appState.selectedLinearElement?.isEditing || (event.key === KEYS13.ESCAPE || event.key === KEYS13.ENTER) && appState.multiElement !== null,
@@ -9617,7 +9823,7 @@ var exportCanvas = async (type, elements, appState, files, {
9617
9823
  let blob = canvasToBlob(tempCanvas);
9618
9824
  if (appState.exportEmbedScene) {
9619
9825
  blob = blob.then(
9620
- (blob2) => import("./data/image-IRC25PM5.js").then(
9826
+ (blob2) => import("./data/image-X3JCQEUX.js").then(
9621
9827
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9622
9828
  blob: blob2,
9623
9829
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -11812,7 +12018,8 @@ import {
11812
12018
  TEXT_ALIGN,
11813
12019
  VERTICAL_ALIGN as VERTICAL_ALIGN2,
11814
12020
  arrayToMap as arrayToMap13,
11815
- getFontString
12021
+ getFontString,
12022
+ getStrokeWidthByKey as getStrokeWidthByKey2
11816
12023
  } from "@excalidraw/common";
11817
12024
  import {
11818
12025
  getOriginalContainerHeightFromCache,
@@ -11999,7 +12206,10 @@ var actionWrapTextInContainer = register({
11999
12206
  fillStyle: appState.currentItemFillStyle,
12000
12207
  strokeColor: appState.currentItemStrokeColor,
12001
12208
  roughness: appState.currentItemRoughness,
12002
- strokeWidth: appState.currentItemStrokeWidth,
12209
+ strokeWidth: getStrokeWidthByKey2(
12210
+ "rectangle",
12211
+ appState.currentItemStrokeWidthKey
12212
+ ),
12003
12213
  strokeStyle: appState.currentItemStrokeStyle,
12004
12214
  roundness: appState.currentItemRoundness === "round" ? {
12005
12215
  type: isUsingAdaptiveRadius2("rectangle") ? ROUNDNESS2.ADAPTIVE_RADIUS : ROUNDNESS2.PROPORTIONAL_RADIUS
@@ -12647,6 +12857,355 @@ import {
12647
12857
  import { simplify } from "points-on-curve";
12648
12858
  import { getStroke } from "perfect-freehand";
12649
12859
 
12860
+ // ../laser-pointer/src/math.ts
12861
+ function add([ax, ay, ar], [bx, by, br]) {
12862
+ return [ax + bx, ay + by, ar + br];
12863
+ }
12864
+ function sub([ax, ay, ar], [bx, by, br]) {
12865
+ return [ax - bx, ay - by, ar - br];
12866
+ }
12867
+ function smul([x, y, r], s) {
12868
+ return [x * s, y * s, r * s];
12869
+ }
12870
+ function norm([x, y, r]) {
12871
+ return [x / Math.sqrt(x ** 2 + y ** 2), y / Math.sqrt(x ** 2 + y ** 2), r];
12872
+ }
12873
+ function rot([x, y, r], rad) {
12874
+ return [
12875
+ Math.cos(rad) * x - Math.sin(rad) * y,
12876
+ Math.sin(rad) * x + Math.cos(rad) * y,
12877
+ r
12878
+ ];
12879
+ }
12880
+ function plerp(a, b, t2) {
12881
+ return add(a, smul(sub(b, a), t2));
12882
+ }
12883
+ function angle(p, p1, p2) {
12884
+ return Math.atan2(p2[1] - p[1], p2[0] - p[0]) - Math.atan2(p1[1] - p[1], p1[0] - p[0]);
12885
+ }
12886
+ function normAngle(a) {
12887
+ return Math.atan2(Math.sin(a), Math.cos(a));
12888
+ }
12889
+ function mag([x, y]) {
12890
+ return Math.sqrt(x ** 2 + y ** 2);
12891
+ }
12892
+ function dist([ax, ay], [bx, by]) {
12893
+ return Math.sqrt((bx - ax) ** 2 + (by - ay) ** 2);
12894
+ }
12895
+ function runLength(ps) {
12896
+ if (ps.length < 2) {
12897
+ return 0;
12898
+ }
12899
+ let len = 0;
12900
+ for (let i = 1; i <= ps.length - 1; i++) {
12901
+ len += dist(ps[i - 1], ps[i]);
12902
+ }
12903
+ len += dist(ps[ps.length - 2], ps[ps.length - 1]);
12904
+ return len;
12905
+ }
12906
+ var clamp2 = (v, min, max) => Math.max(min, Math.min(max, v));
12907
+ function distancePointToSegment(p3, p1, p2) {
12908
+ const sMag = dist(p1, p2);
12909
+ if (sMag === 0) {
12910
+ return dist(p3, p1);
12911
+ }
12912
+ const u = clamp2(
12913
+ ((p3[0] - p1[0]) * (p2[0] - p1[0]) + (p3[1] - p1[1]) * (p2[1] - p1[1])) / sMag ** 2,
12914
+ 0,
12915
+ 1
12916
+ );
12917
+ const pi = [
12918
+ p1[0] + u * (p2[0] - p1[0]),
12919
+ p1[1] + u * (p2[1] - p1[1]),
12920
+ p3[2]
12921
+ ];
12922
+ return dist(pi, p3);
12923
+ }
12924
+
12925
+ // ../laser-pointer/src/simplify.ts
12926
+ function douglasPeucker(points, epsilon) {
12927
+ if (epsilon === 0) {
12928
+ return points;
12929
+ }
12930
+ if (points.length <= 2) {
12931
+ return points;
12932
+ }
12933
+ const first = points[0];
12934
+ const last = points[points.length - 1];
12935
+ const [maxDistance, maxIndex] = points.reduce(
12936
+ ([maxDistance2, maxIndex2], point, index) => {
12937
+ const distance3 = distancePointToSegment(point, first, last);
12938
+ return distance3 > maxDistance2 ? [distance3, index] : [maxDistance2, maxIndex2];
12939
+ },
12940
+ [0, -1]
12941
+ );
12942
+ if (maxDistance >= epsilon) {
12943
+ const maxIndexPoint = points[maxIndex];
12944
+ return [
12945
+ ...douglasPeucker(
12946
+ [first, ...points.slice(1, maxIndex), maxIndexPoint],
12947
+ epsilon
12948
+ ).slice(0, -1),
12949
+ maxIndexPoint,
12950
+ ...douglasPeucker(
12951
+ [maxIndexPoint, ...points.slice(maxIndex, -1), last],
12952
+ epsilon
12953
+ ).slice(1)
12954
+ ];
12955
+ }
12956
+ return [first, last];
12957
+ }
12958
+
12959
+ // ../laser-pointer/src/state.ts
12960
+ var _LaserPointer = class _LaserPointer {
12961
+ constructor(options) {
12962
+ __publicField(this, "options");
12963
+ __publicField(this, "originalPoints", []);
12964
+ __publicField(this, "stablePoints", []);
12965
+ __publicField(this, "tailPoints", []);
12966
+ __publicField(this, "isFresh", true);
12967
+ this.options = Object.assign({}, _LaserPointer.defaults, options);
12968
+ }
12969
+ get lastPoint() {
12970
+ return this.tailPoints[this.tailPoints.length - 1] ?? this.stablePoints[this.stablePoints.length - 1];
12971
+ }
12972
+ addPoint(point) {
12973
+ const lastPoint = this.originalPoints[this.originalPoints.length - 1];
12974
+ if (lastPoint && lastPoint[0] === point[0] && lastPoint[1] === point[1]) {
12975
+ return;
12976
+ }
12977
+ this.originalPoints.push(point);
12978
+ if (this.isFresh) {
12979
+ this.isFresh = false;
12980
+ this.stablePoints.push(point);
12981
+ return;
12982
+ }
12983
+ if (this.options.streamline > 0) {
12984
+ point = plerp(this.lastPoint, point, 1 - this.options.streamline);
12985
+ }
12986
+ this.tailPoints.push(point);
12987
+ if (runLength(this.tailPoints) > _LaserPointer.constants.maxTailLength) {
12988
+ this.stabilizeTail();
12989
+ }
12990
+ }
12991
+ close() {
12992
+ this.stabilizeTail();
12993
+ }
12994
+ stabilizeTail() {
12995
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "tail") {
12996
+ throw new Error("Not implemented yet");
12997
+ } else {
12998
+ this.stablePoints.push(...this.tailPoints);
12999
+ this.tailPoints = [];
13000
+ }
13001
+ }
13002
+ getSize(sizeOverride, pressure, index, totalLength, runningLength) {
13003
+ return (sizeOverride ?? this.options.size) * this.options.sizeMapping({
13004
+ pressure,
13005
+ runningLength,
13006
+ currentIndex: index,
13007
+ totalLength
13008
+ });
13009
+ }
13010
+ getStrokeOutline(sizeOverride) {
13011
+ if (this.isFresh) {
13012
+ return [];
13013
+ }
13014
+ let points = [...this.stablePoints, ...this.tailPoints];
13015
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "input") {
13016
+ points = douglasPeucker(points, this.options.simplify);
13017
+ }
13018
+ const len = points.length;
13019
+ if (len === 0) {
13020
+ return [];
13021
+ }
13022
+ if (len === 1) {
13023
+ const c = points[0];
13024
+ const size = this.getSize(sizeOverride, c[2], 0, len, 0);
13025
+ if (size < 0.5) {
13026
+ return [];
13027
+ }
13028
+ const ps = [];
13029
+ for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
13030
+ ps.push(add(c, smul(rot([1, 0, 0], theta), size)));
13031
+ }
13032
+ ps.push(
13033
+ add(
13034
+ c,
13035
+ smul(
13036
+ [1, 0, 0],
13037
+ this.getSize(sizeOverride, c[2], 0, len, 0)
13038
+ )
13039
+ )
13040
+ );
13041
+ return ps;
13042
+ }
13043
+ if (len === 2) {
13044
+ const c = points[0];
13045
+ const n = points[1];
13046
+ const cSize = this.getSize(sizeOverride, c[2], 0, len, 0);
13047
+ const nSize = this.getSize(sizeOverride, n[2], 0, len, 0);
13048
+ if (cSize < 0.5 || nSize < 0.5) {
13049
+ return [];
13050
+ }
13051
+ const ps = [];
13052
+ const pAngle = angle(c, [c[0], c[1] - 100, c[2]], n);
13053
+ for (let theta = pAngle; theta <= Math.PI + pAngle; theta += Math.PI / 16) {
13054
+ ps.push(add(c, smul(rot([1, 0, 0], theta), cSize)));
13055
+ }
13056
+ for (let theta = Math.PI + pAngle; theta <= Math.PI * 2 + pAngle; theta += Math.PI / 16) {
13057
+ ps.push(add(n, smul(rot([1, 0, 0], theta), nSize)));
13058
+ }
13059
+ ps.push(ps[0]);
13060
+ return ps;
13061
+ }
13062
+ const forwardPoints = [];
13063
+ const backwardPoints = [];
13064
+ let speed = 0;
13065
+ let prevSpeed = 0;
13066
+ let visibleStartIndex = 0;
13067
+ let runningLength = 0;
13068
+ for (let i = 1; i < len - 1; i++) {
13069
+ const p = points[i - 1];
13070
+ const c = points[i];
13071
+ const n = points[i + 1];
13072
+ const pressure = c[2];
13073
+ const d = dist(p, c);
13074
+ runningLength += d;
13075
+ speed = prevSpeed + (d - prevSpeed) * 0.2;
13076
+ const cSize = this.getSize(sizeOverride, pressure, i, len, runningLength);
13077
+ if (cSize === 0) {
13078
+ visibleStartIndex = i + 1;
13079
+ continue;
13080
+ }
13081
+ const dirPC = norm(sub(p, c));
13082
+ const dirNC = norm(sub(n, c));
13083
+ const p1dirPC = rot(dirPC, Math.PI / 2);
13084
+ const p2dirPC = rot(dirPC, -Math.PI / 2);
13085
+ const p1dirNC = rot(dirNC, Math.PI / 2);
13086
+ const p2dirNC = rot(dirNC, -Math.PI / 2);
13087
+ const p1PC = add(c, smul(p1dirPC, cSize));
13088
+ const p2PC = add(c, smul(p2dirPC, cSize));
13089
+ const p1NC = add(c, smul(p1dirNC, cSize));
13090
+ const p2NC = add(c, smul(p2dirNC, cSize));
13091
+ const ftdir = add(p1dirPC, p2dirNC);
13092
+ const btdir = add(p2dirPC, p1dirNC);
13093
+ const paPC = add(
13094
+ c,
13095
+ smul(mag(ftdir) === 0 ? dirPC : norm(ftdir), cSize)
13096
+ );
13097
+ const paNC = add(
13098
+ c,
13099
+ smul(mag(btdir) === 0 ? dirNC : norm(btdir), cSize)
13100
+ );
13101
+ const cAngle = normAngle(angle(c, p, n));
13102
+ const D_ANGLE = _LaserPointer.constants.cornerDetectionMaxAngle / 180 * Math.PI * _LaserPointer.constants.cornerDetectionVariance(speed);
13103
+ if (Math.abs(cAngle) < D_ANGLE) {
13104
+ const tAngle = Math.abs(normAngle(Math.PI - cAngle));
13105
+ if (tAngle === 0) {
13106
+ continue;
13107
+ }
13108
+ if (cAngle < 0) {
13109
+ backwardPoints.push(p2PC, paNC);
13110
+ for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
13111
+ forwardPoints.push(add(c, rot(smul(p1dirPC, cSize), theta)));
13112
+ }
13113
+ for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
13114
+ backwardPoints.push(add(c, rot(smul(p1dirPC, cSize), theta)));
13115
+ }
13116
+ backwardPoints.push(paNC, p1NC);
13117
+ } else {
13118
+ forwardPoints.push(p1PC, paPC);
13119
+ for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
13120
+ backwardPoints.push(
13121
+ add(c, rot(smul(p1dirPC, -cSize), -theta))
13122
+ );
13123
+ }
13124
+ for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
13125
+ forwardPoints.push(
13126
+ add(c, rot(smul(p1dirPC, -cSize), -theta))
13127
+ );
13128
+ }
13129
+ forwardPoints.push(paPC, p2NC);
13130
+ }
13131
+ } else {
13132
+ forwardPoints.push(paPC);
13133
+ backwardPoints.push(paNC);
13134
+ }
13135
+ prevSpeed = speed;
13136
+ }
13137
+ if (visibleStartIndex >= len - 2) {
13138
+ if (this.options.keepHead) {
13139
+ const c = points[len - 1];
13140
+ const ps = [];
13141
+ for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
13142
+ ps.push(
13143
+ add(
13144
+ c,
13145
+ smul(rot([1, 0, 0], theta), this.options.size)
13146
+ )
13147
+ );
13148
+ }
13149
+ ps.push(add(c, smul([1, 0, 0], this.options.size)));
13150
+ return ps;
13151
+ }
13152
+ return [];
13153
+ }
13154
+ const first = points[visibleStartIndex];
13155
+ const second = points[visibleStartIndex + 1];
13156
+ const penultimate = points[len - 2];
13157
+ const ultimate = points[len - 1];
13158
+ const dirFS = norm(sub(second, first));
13159
+ const dirPU = norm(sub(penultimate, ultimate));
13160
+ const ppdirFS = rot(dirFS, -Math.PI / 2);
13161
+ const ppdirPU = rot(dirPU, Math.PI / 2);
13162
+ const startCapSize = this.getSize(sizeOverride, first[2], 0, len, 0);
13163
+ const startCap = [];
13164
+ const endCapSize = this.options.keepHead ? this.options.size : this.getSize(sizeOverride, penultimate[2], len - 2, len, runningLength);
13165
+ const endCap = [];
13166
+ if (startCapSize > 0.1) {
13167
+ for (let theta = 0; theta <= Math.PI; theta += Math.PI / 16) {
13168
+ startCap.unshift(
13169
+ add(first, rot(smul(ppdirFS, startCapSize), -theta))
13170
+ );
13171
+ }
13172
+ startCap.unshift(add(first, smul(ppdirFS, -startCapSize)));
13173
+ } else {
13174
+ startCap.push(first);
13175
+ }
13176
+ for (let theta = 0; theta <= Math.PI * 3; theta += Math.PI / 16) {
13177
+ endCap.push(add(ultimate, rot(smul(ppdirPU, -endCapSize), -theta)));
13178
+ }
13179
+ const strokeOutline = [
13180
+ ...startCap,
13181
+ ...forwardPoints,
13182
+ ...endCap.reverse(),
13183
+ ...backwardPoints.reverse()
13184
+ ];
13185
+ if (startCap.length > 0) {
13186
+ strokeOutline.push(startCap[0]);
13187
+ }
13188
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "output") {
13189
+ return douglasPeucker(strokeOutline, this.options.simplify);
13190
+ }
13191
+ return strokeOutline;
13192
+ }
13193
+ };
13194
+ __publicField(_LaserPointer, "defaults", {
13195
+ size: 2,
13196
+ streamline: 0.45,
13197
+ simplify: 0.1,
13198
+ simplifyPhase: "output",
13199
+ keepHead: false,
13200
+ sizeMapping: () => 1
13201
+ });
13202
+ __publicField(_LaserPointer, "constants", {
13203
+ cornerDetectionMaxAngle: 75,
13204
+ cornerDetectionVariance: (s) => s > 35 ? 0.5 : 1,
13205
+ maxTailLength: 50
13206
+ });
13207
+ var LaserPointer = _LaserPointer;
13208
+
12650
13209
  // ../utils/src/shape.ts
12651
13210
  import { pointsOnBezierCurves } from "points-on-curve";
12652
13211
  import { invariant as invariant4 } from "@excalidraw/common";
@@ -12693,7 +13252,8 @@ import {
12693
13252
  assertNever as assertNever2,
12694
13253
  COLOR_PALETTE as COLOR_PALETTE3,
12695
13254
  LINE_POLYGON_POINT_MERGE_DISTANCE,
12696
- applyDarkModeFilter as applyDarkModeFilter2
13255
+ applyDarkModeFilter as applyDarkModeFilter2,
13256
+ DEFAULT_STROKE_STREAMLINE
12697
13257
  } from "@excalidraw/common";
12698
13258
  import { RoughGenerator } from "roughjs/bin/generator";
12699
13259
 
@@ -12711,7 +13271,7 @@ import {
12711
13271
  ELEMENT_READY_TO_ERASE_OPACITY,
12712
13272
  FRAME_STYLE,
12713
13273
  DARK_THEME_FILTER,
12714
- MIME_TYPES as MIME_TYPES4,
13274
+ MIME_TYPES as MIME_TYPES5,
12715
13275
  THEME as THEME9,
12716
13276
  distance,
12717
13277
  getFontString as getFontString4,
@@ -12770,7 +13330,7 @@ import {
12770
13330
  } from "@excalidraw/common";
12771
13331
  import {
12772
13332
  PRECISION as PRECISION2,
12773
- clamp as clamp3,
13333
+ clamp as clamp4,
12774
13334
  lineSegment as lineSegment4,
12775
13335
  pointDistance as pointDistance4,
12776
13336
  pointDistanceSq,
@@ -12968,7 +13528,7 @@ var headingIsHorizontal = (a) => compareHeading(a, HEADING_RIGHT) || compareHead
12968
13528
 
12969
13529
  // ../element/src/elbowArrow.ts
12970
13530
  import {
12971
- clamp as clamp2,
13531
+ clamp as clamp3,
12972
13532
  pointDistance as pointDistance3,
12973
13533
  pointFrom as pointFrom9,
12974
13534
  pointScaleFromOrigin as pointScaleFromOrigin2,
@@ -13005,6 +13565,7 @@ import { pointsEqual as pointsEqual6 } from "@excalidraw/math";
13005
13565
 
13006
13566
  // ../element/src/zindex.ts
13007
13567
  import { arrayToMap as arrayToMap20, findIndex, findLastIndex } from "@excalidraw/common";
13568
+ import { isFiniteNumber } from "@excalidraw/math";
13008
13569
 
13009
13570
  // ../element/src/selection.ts
13010
13571
  import { arrayToMap as arrayToMap19, isShallowEqual } from "@excalidraw/common";
@@ -13276,29 +13837,29 @@ var getArrowheadPoints = (element, shape, position, arrowhead, offsetMultiplier
13276
13837
  const diameter = Math.hypot(ys - ty, xs - tx) + element.strokeWidth - 2;
13277
13838
  return [tx, ty, diameter];
13278
13839
  }
13279
- const angle = getArrowheadAngle(arrowhead);
13840
+ const angle2 = getArrowheadAngle(arrowhead);
13280
13841
  if (arrowhead === "cardinality_many" || arrowhead === "cardinality_one_or_many") {
13281
13842
  const [x32, y32] = pointRotateRads9(
13282
13843
  pointFrom13(tx, ty),
13283
13844
  pointFrom13(xs, ys),
13284
- degreesToRadians(-angle)
13845
+ degreesToRadians(-angle2)
13285
13846
  );
13286
13847
  const [x42, y42] = pointRotateRads9(
13287
13848
  pointFrom13(tx, ty),
13288
13849
  pointFrom13(xs, ys),
13289
- degreesToRadians(angle)
13850
+ degreesToRadians(angle2)
13290
13851
  );
13291
13852
  return [xs, ys, x32, y32, x42, y42];
13292
13853
  }
13293
13854
  const [x3, y3] = pointRotateRads9(
13294
13855
  pointFrom13(xs, ys),
13295
13856
  pointFrom13(tx, ty),
13296
- -angle * Math.PI / 180
13857
+ -angle2 * Math.PI / 180
13297
13858
  );
13298
13859
  const [x4, y4] = pointRotateRads9(
13299
13860
  pointFrom13(xs, ys),
13300
13861
  pointFrom13(tx, ty),
13301
- degreesToRadians(angle)
13862
+ degreesToRadians(angle2)
13302
13863
  );
13303
13864
  if (arrowhead === "diamond" || arrowhead === "diamond_outline") {
13304
13865
  let ox;
@@ -13334,17 +13895,21 @@ import {
13334
13895
  vectorAdd as vectorAdd2,
13335
13896
  vectorScale as vectorScale7,
13336
13897
  pointFromVector as pointFromVector6,
13337
- clamp as clamp4,
13898
+ clamp as clamp5,
13338
13899
  isCloseTo
13339
13900
  } from "@excalidraw/math";
13340
13901
 
13902
+ // ../element/src/image.ts
13903
+ import { MIME_TYPES as MIME_TYPES4, SVG_NS } from "@excalidraw/common";
13904
+ import { rgbaToThumbHash, thumbHashToRGBA } from "thumbhash";
13905
+
13341
13906
  // ../element/src/renderElement.ts
13342
13907
  var IMAGE_PLACEHOLDER_IMG = typeof document !== "undefined" ? document.createElement("img") : { src: "" };
13343
- IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES4.svg},${encodeURIComponent(
13908
+ IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES5.svg},${encodeURIComponent(
13344
13909
  `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="image" class="svg-inline--fa fa-image fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#888" d="M464 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM112 120c-30.928 0-56 25.072-56 56s25.072 56 56 56 56-25.072 56-56-25.072-56-56-56zM64 384h384V272l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L208 320l-55.515-55.515c-4.686-4.686-12.284-4.686-16.971 0L64 336v48z"></path></svg>`
13345
13910
  )}`;
13346
13911
  var IMAGE_ERROR_PLACEHOLDER_IMG = typeof document !== "undefined" ? document.createElement("img") : { src: "" };
13347
- IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES4.svg},${encodeURIComponent(
13912
+ IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES5.svg},${encodeURIComponent(
13348
13913
  `<svg viewBox="0 0 668 668" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M464 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48ZM112 120c-30.928 0-56 25.072-56 56s25.072 56 56 56 56-25.072 56-56-25.072-56-56-56ZM64 384h384V272l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L208 320l-55.515-55.515c-4.686-4.686-12.284-4.686-16.971 0L64 336v48Z" style="fill:#888;fill-rule:nonzero" transform="matrix(.81709 0 0 .81709 124.825 145.825)"/><path d="M256 8C119.034 8 8 119.033 8 256c0 136.967 111.034 248 248 248s248-111.034 248-248S392.967 8 256 8Zm130.108 117.892c65.448 65.448 70 165.481 20.677 235.637L150.47 105.216c70.204-49.356 170.226-44.735 235.638 20.676ZM125.892 386.108c-65.448-65.448-70-165.481-20.677-235.637L361.53 406.784c-70.203 49.356-170.226 44.736-235.638-20.676Z" style="fill:#888;fill-rule:nonzero" transform="matrix(.30366 0 0 .30366 506.822 60.065)"/></svg>`
13349
13914
  )}`;
13350
13915
  var elementWithCanvasCache = /* @__PURE__ */ new WeakMap();
@@ -13948,19 +14513,46 @@ var getFreeDrawSvgPath = (element) => {
13948
14513
  getFreedrawOutlinePoints(element)
13949
14514
  );
13950
14515
  };
13951
- var getFreedrawOutlinePoints = (element) => {
13952
- const inputPoints = element.simulatePressure ? element.points : element.points.length ? element.points.map(([x, y], i) => [x, y, element.pressures[i]]) : [[0, 0, 0.5]];
14516
+ var VARIABLE_WIDTH_FREEDRAW = {
14517
+ /** Stroke size relative to `strokeWidth` for pressure-sensitive strokes. */
14518
+ SIZE_FACTOR: 4.25,
14519
+ THINNING: 0.6,
14520
+ SMOOTHING: 0.5
14521
+ };
14522
+ var CONSTANT_WIDTH_FREEDRAW = {
14523
+ /** Stroke size relative to `strokeWidth` for uniform (laser) strokes. */
14524
+ SIZE_FACTOR: 1.4
14525
+ };
14526
+ var getFreedrawStreamline = (element) => element.strokeOptions?.streamline ?? DEFAULT_STROKE_STREAMLINE;
14527
+ var getVariableWidthFreedrawOutline = (element) => {
14528
+ const inputPoints = element.simulatePressure ? element.points : element.points.length ? element.points.map(
14529
+ ([x, y], i) => [x, y, element.pressures[i]]
14530
+ ) : [[0, 0, 0.5]];
13953
14531
  return getStroke(inputPoints, {
13954
14532
  simulatePressure: element.simulatePressure,
13955
- size: element.strokeWidth * 4.25,
13956
- thinning: 0.6,
13957
- smoothing: 0.5,
13958
- streamline: 0.5,
14533
+ size: element.strokeWidth * VARIABLE_WIDTH_FREEDRAW.SIZE_FACTOR,
14534
+ thinning: VARIABLE_WIDTH_FREEDRAW.THINNING,
14535
+ smoothing: VARIABLE_WIDTH_FREEDRAW.SMOOTHING,
14536
+ streamline: getFreedrawStreamline(element),
13959
14537
  easing: (t2) => Math.sin(t2 * Math.PI / 2),
13960
14538
  // https://easings.net/#easeOutSine
13961
14539
  last: true
13962
14540
  });
13963
14541
  };
14542
+ var createLaserPointer = (element) => new LaserPointer({
14543
+ size: element.strokeWidth * CONSTANT_WIDTH_FREEDRAW.SIZE_FACTOR,
14544
+ streamline: getFreedrawStreamline(element),
14545
+ simplify: 0,
14546
+ sizeMapping: (details) => Math.max(0.1, details.pressure)
14547
+ });
14548
+ var getConstantWidthFreedrawOutline = (element) => {
14549
+ const laserPointer = createLaserPointer(element);
14550
+ element.points.map(([x, y]) => laserPointer.addPoint([x, y, 1]));
14551
+ return laserPointer.getStrokeOutline().map(([x, y]) => [x, y]);
14552
+ };
14553
+ var getFreedrawOutlinePoints = (element) => {
14554
+ return element.strokeOptions?.variability === "constant" ? getConstantWidthFreedrawOutline(element) : getVariableWidthFreedrawOutline(element);
14555
+ };
13964
14556
  var med = (A, B) => {
13965
14557
  return [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
13966
14558
  };
@@ -16170,56 +16762,9 @@ var Renderer = class {
16170
16762
  }
16171
16763
  };
16172
16764
 
16173
- // components/ElementCanvasButtons.tsx
16174
- import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2 } from "@excalidraw/common";
16175
- import { getElementAbsoluteCoords as getElementAbsoluteCoords5 } from "@excalidraw/element";
16176
- import { jsx as jsx57 } from "react/jsx-runtime";
16177
- var CONTAINER_PADDING = 5;
16178
- var getContainerCoords2 = (element, appState, elementsMap) => {
16179
- const [x1, y1] = getElementAbsoluteCoords5(element, elementsMap);
16180
- const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords2(
16181
- { sceneX: x1 + element.width, sceneY: y1 },
16182
- appState
16183
- );
16184
- const x = viewportX - appState.offsetLeft + 10;
16185
- const y = viewportY - appState.offsetTop;
16186
- return { x, y };
16187
- };
16188
- var ElementCanvasButtons = ({
16189
- children,
16190
- element,
16191
- elementsMap
16192
- }) => {
16193
- const appState = useExcalidrawAppState();
16194
- if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
16195
- return null;
16196
- }
16197
- const { x, y } = getContainerCoords2(element, appState, elementsMap);
16198
- return /* @__PURE__ */ jsx57(
16199
- "div",
16200
- {
16201
- className: "excalidraw-canvas-buttons",
16202
- style: {
16203
- top: `${y}px`,
16204
- left: `${x}px`,
16205
- // width: CONTAINER_WIDTH,
16206
- padding: CONTAINER_PADDING
16207
- },
16208
- children
16209
- }
16210
- );
16211
- };
16212
-
16213
- // laserTrails.ts
16214
- import { DEFAULT_LASER_COLOR, easeOut } from "@excalidraw/common";
16215
-
16216
- // animatedTrail.ts
16217
- import { LaserPointer } from "@excalidraw/laser-pointer";
16218
- import {
16219
- SVG_NS,
16220
- getSvgPathFromStroke as getSvgPathFromStroke2,
16221
- sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3
16222
- } from "@excalidraw/common";
16765
+ // scroll.ts
16766
+ import { easeOut } from "@excalidraw/common";
16767
+ import { clamp as clamp6 } from "@excalidraw/math";
16223
16768
 
16224
16769
  // reactUtils.ts
16225
16770
  import { version as ReactVersion } from "react";
@@ -16345,12 +16890,139 @@ var _AnimationController = class _AnimationController {
16345
16890
  _AnimationController.animations.delete(key);
16346
16891
  _AnimationController.cancelScheduledFrameIfIdle();
16347
16892
  }
16893
+ static reset() {
16894
+ _AnimationController.animations.clear();
16895
+ _AnimationController.cancelScheduledFrame();
16896
+ }
16348
16897
  };
16349
16898
  __publicField(_AnimationController, "scheduledFrame", null);
16350
16899
  __publicField(_AnimationController, "animations", /* @__PURE__ */ new Map());
16351
16900
  var AnimationController = _AnimationController;
16352
16901
 
16902
+ // scroll.ts
16903
+ var SCROLL_TO_CONTENT_ANIMATION_KEY = "animateScrollToContent";
16904
+ var DEFAULT_ANIMATION_DURATION = 500;
16905
+ var scrollToElements = (state, target, onFrame, opts) => {
16906
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
16907
+ const viewport = getTargetViewport(state, target, opts);
16908
+ if (opts?.animate) {
16909
+ animateToViewport(
16910
+ state,
16911
+ viewport,
16912
+ opts.duration ?? DEFAULT_ANIMATION_DURATION,
16913
+ onFrame
16914
+ );
16915
+ } else {
16916
+ onFrame({ ...viewport, shouldCacheIgnoreZoom: false });
16917
+ }
16918
+ };
16919
+ var getTargetViewport = (state, targetElements, opts) => {
16920
+ if (opts?.fitToContent || opts?.fitToViewport) {
16921
+ const { appState } = zoomToFit({
16922
+ canvasOffsets: opts.canvasOffsets,
16923
+ targetElements,
16924
+ appState: state,
16925
+ fitToViewport: !!opts.fitToViewport,
16926
+ viewportZoomFactor: opts.viewportZoomFactor,
16927
+ minZoom: opts.minZoom,
16928
+ maxZoom: opts.maxZoom
16929
+ });
16930
+ return {
16931
+ scrollX: appState.scrollX,
16932
+ scrollY: appState.scrollY,
16933
+ zoom: appState.zoom
16934
+ };
16935
+ }
16936
+ const { scrollX, scrollY } = calculateScrollCenter(targetElements, state);
16937
+ return { scrollX, scrollY, zoom: state.zoom };
16938
+ };
16939
+ var interpolateViewport = ({
16940
+ from,
16941
+ target,
16942
+ factor
16943
+ }) => {
16944
+ const zoom = from.zoom.value * Math.pow(
16945
+ target.zoom.value / from.zoom.value,
16946
+ factor
16947
+ );
16948
+ const fromCenterX = from.width / 2 / from.zoom.value - from.scrollX;
16949
+ const fromCenterY = from.height / 2 / from.zoom.value - from.scrollY;
16950
+ const toCenterX = from.width / 2 / target.zoom.value - target.scrollX;
16951
+ const toCenterY = from.height / 2 / target.zoom.value - target.scrollY;
16952
+ const centerX = fromCenterX + (toCenterX - fromCenterX) * factor;
16953
+ const centerY = fromCenterY + (toCenterY - fromCenterY) * factor;
16954
+ return {
16955
+ scrollX: from.width / 2 / zoom - centerX,
16956
+ scrollY: from.height / 2 / zoom - centerY,
16957
+ zoom: { value: zoom }
16958
+ };
16959
+ };
16960
+ var animateToViewport = (from, target, duration, onFrame) => {
16961
+ AnimationController.start(
16962
+ SCROLL_TO_CONTENT_ANIMATION_KEY,
16963
+ ({ deltaTime, state }) => {
16964
+ const elapsed = (state?.elapsed ?? 0) + deltaTime;
16965
+ const progress = Math.min(elapsed / duration, 1);
16966
+ const factor = easeOut(clamp6(progress, 0, 1));
16967
+ onFrame({
16968
+ ...interpolateViewport({ from, target, factor }),
16969
+ shouldCacheIgnoreZoom: progress < 1
16970
+ // ignore zoom caching while animating
16971
+ });
16972
+ return progress < 1 ? { elapsed } : null;
16973
+ }
16974
+ );
16975
+ };
16976
+
16977
+ // components/ElementCanvasButtons.tsx
16978
+ import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2 } from "@excalidraw/common";
16979
+ import { getElementAbsoluteCoords as getElementAbsoluteCoords5 } from "@excalidraw/element";
16980
+ import { jsx as jsx57 } from "react/jsx-runtime";
16981
+ var CONTAINER_PADDING = 5;
16982
+ var getContainerCoords2 = (element, appState, elementsMap) => {
16983
+ const [x1, y1] = getElementAbsoluteCoords5(element, elementsMap);
16984
+ const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords2(
16985
+ { sceneX: x1 + element.width, sceneY: y1 },
16986
+ appState
16987
+ );
16988
+ const x = viewportX - appState.offsetLeft + 10;
16989
+ const y = viewportY - appState.offsetTop;
16990
+ return { x, y };
16991
+ };
16992
+ var ElementCanvasButtons = ({
16993
+ children,
16994
+ element,
16995
+ elementsMap
16996
+ }) => {
16997
+ const appState = useExcalidrawAppState();
16998
+ if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
16999
+ return null;
17000
+ }
17001
+ const { x, y } = getContainerCoords2(element, appState, elementsMap);
17002
+ return /* @__PURE__ */ jsx57(
17003
+ "div",
17004
+ {
17005
+ className: "excalidraw-canvas-buttons",
17006
+ style: {
17007
+ top: `${y}px`,
17008
+ left: `${x}px`,
17009
+ // width: CONTAINER_WIDTH,
17010
+ padding: CONTAINER_PADDING
17011
+ },
17012
+ children
17013
+ }
17014
+ );
17015
+ };
17016
+
17017
+ // laserTrails.ts
17018
+ import { DEFAULT_LASER_COLOR, easeOut as easeOut2 } from "@excalidraw/common";
17019
+
16353
17020
  // animatedTrail.ts
17021
+ import {
17022
+ SVG_NS as SVG_NS2,
17023
+ getSvgPathFromStroke as getSvgPathFromStroke2,
17024
+ sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3
17025
+ } from "@excalidraw/common";
16354
17026
  var _AnimatedTrail = class _AnimatedTrail {
16355
17027
  constructor(app, options) {
16356
17028
  this.app = app;
@@ -16362,9 +17034,9 @@ var _AnimatedTrail = class _AnimatedTrail {
16362
17034
  __publicField(this, "trailAnimation");
16363
17035
  __publicField(this, "key");
16364
17036
  this.key = `animated-trail-${_AnimatedTrail.counter++}`;
16365
- this.trailElement = document.createElementNS(SVG_NS, "path");
17037
+ this.trailElement = document.createElementNS(SVG_NS2, "path");
16366
17038
  if (this.options.animateTrail) {
16367
- this.trailAnimation = document.createElementNS(SVG_NS, "animate");
17039
+ this.trailAnimation = document.createElementNS(SVG_NS2, "animate");
16368
17040
  this.trailAnimation.setAttribute("attributeName", "stroke-dashoffset");
16369
17041
  this.trailElement.setAttribute("stroke-dasharray", "7 7");
16370
17042
  this.trailElement.setAttribute("stroke-dashoffset", "10");
@@ -16522,7 +17194,7 @@ var LaserTrails = class {
16522
17194
  1 - (performance.now() - c.pressure) / DECAY_TIME
16523
17195
  );
16524
17196
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
16525
- return Math.min(easeOut(l), easeOut(t2));
17197
+ return Math.min(easeOut2(l), easeOut2(t2));
16526
17198
  }
16527
17199
  };
16528
17200
  }
@@ -16662,7 +17334,7 @@ import {
16662
17334
  getFontString as getFontString6,
16663
17335
  getFontFamilyString as getFontFamilyString3,
16664
17336
  isTestEnv as isTestEnv4,
16665
- MIME_TYPES as MIME_TYPES5,
17337
+ MIME_TYPES as MIME_TYPES6,
16666
17338
  applyDarkModeFilter as applyDarkModeFilter3,
16667
17339
  isRTL as isRTL2
16668
17340
  } from "@excalidraw/common";
@@ -16697,9 +17369,9 @@ import {
16697
17369
  isBoundToContainer as isBoundToContainer7,
16698
17370
  isTextElement as isTextElement8
16699
17371
  } from "@excalidraw/element";
16700
- var getTransform = (width, height, angle, appState, maxWidth, maxHeight) => {
17372
+ var getTransform = (width, height, angle2, appState, maxWidth, maxHeight) => {
16701
17373
  const { zoom } = appState;
16702
- const degree = 180 * angle / Math.PI;
17374
+ const degree = 180 * angle2 / Math.PI;
16703
17375
  let translateX = width * (zoom.value - 1) / 2;
16704
17376
  let translateY = height * (zoom.value - 1) / 2;
16705
17377
  if (width > maxWidth && zoom.value !== 1) {
@@ -16905,7 +17577,7 @@ var textWysiwyg = ({
16905
17577
  }
16906
17578
  height *= 1.05;
16907
17579
  const font = getFontString6(updatedTextElement);
16908
- const angle = getTextElementAngle(updatedTextElement, container);
17580
+ const angle2 = getTextElementAngle(updatedTextElement, container);
16909
17581
  const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value;
16910
17582
  Object.assign(editable.style, {
16911
17583
  font,
@@ -16918,7 +17590,7 @@ var textWysiwyg = ({
16918
17590
  transform: getTransform(
16919
17591
  width,
16920
17592
  height,
16921
- angle,
17593
+ angle2,
16922
17594
  appState,
16923
17595
  maxWidth,
16924
17596
  editorMaxHeight
@@ -16933,7 +17605,7 @@ var textWysiwyg = ({
16933
17605
  maxHeight: `${editorMaxHeight}px`
16934
17606
  });
16935
17607
  currentTextLayout = {
16936
- angle,
17608
+ angle: angle2,
16937
17609
  font,
16938
17610
  height: updatedTextElement.height,
16939
17611
  lineHeightPx: getLineHeightInPx2(
@@ -17042,7 +17714,7 @@ var textWysiwyg = ({
17042
17714
  editable.onpaste = async (event) => {
17043
17715
  const mimeTypes = parseDataTransferEventMimeTypes(event);
17044
17716
  let dataList = null;
17045
- if (mimeTypes.has(MIME_TYPES5.excalidrawClipboard) || mimeTypes.has(MIME_TYPES5.excalidraw)) {
17717
+ if (mimeTypes.has(MIME_TYPES6.excalidrawClipboard) || mimeTypes.has(MIME_TYPES6.excalidraw)) {
17046
17718
  event.preventDefault();
17047
17719
  dataList = await parseDataTransferEvent(event);
17048
17720
  try {
@@ -17063,7 +17735,7 @@ var textWysiwyg = ({
17063
17735
  }
17064
17736
  }
17065
17737
  dataList = dataList || await parseDataTransferEvent(event);
17066
- const textItem = dataList.findByType(MIME_TYPES5.text);
17738
+ const textItem = dataList.findByType(MIME_TYPES6.text);
17067
17739
  if (!textItem) {
17068
17740
  return;
17069
17741
  }
@@ -17455,7 +18127,7 @@ import {
17455
18127
  import { getFrameChildren as getFrameChildren4 } from "@excalidraw/element";
17456
18128
  import { selectGroupsForSelectedElements as selectGroupsForSelectedElements6 } from "@excalidraw/element";
17457
18129
  import { getContainerElement as getContainerElement4 } from "@excalidraw/element";
17458
- import { arrayToMap as arrayToMap25, easeOut as easeOut2, isShallowEqual as isShallowEqual3 } from "@excalidraw/common";
18130
+ import { arrayToMap as arrayToMap25, easeOut as easeOut3, isShallowEqual as isShallowEqual3 } from "@excalidraw/common";
17459
18131
 
17460
18132
  // lasso/utils.ts
17461
18133
  import { simplify as simplify2 } from "points-on-curve";
@@ -17567,7 +18239,7 @@ var LassoTrail = class extends AnimatedTrail {
17567
18239
  1 - (performance.now() - c.pressure) / DECAY_TIME
17568
18240
  );
17569
18241
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
17570
- return Math.min(easeOut2(l), easeOut2(t2));
18242
+ return Math.min(easeOut3(l), easeOut3(t2));
17571
18243
  },
17572
18244
  fill: () => "rgba(105,101,219,0.05)",
17573
18245
  stroke: () => "rgba(105,101,219)"
@@ -17735,7 +18407,7 @@ var actionSmartZoom = register({
17735
18407
  });
17736
18408
 
17737
18409
  // eraser/index.ts
17738
- import { arrayToMap as arrayToMap26, easeOut as easeOut3, THEME as THEME12 } from "@excalidraw/common";
18410
+ import { arrayToMap as arrayToMap26, easeOut as easeOut4, THEME as THEME12 } from "@excalidraw/common";
17739
18411
  import {
17740
18412
  computeBoundTextPosition as computeBoundTextPosition4,
17741
18413
  doBoundsIntersect as doBoundsIntersect3,
@@ -17775,7 +18447,7 @@ var EraserTrail = class extends AnimatedTrail {
17775
18447
  1 - (performance.now() - c.pressure) / DECAY_TIME
17776
18448
  );
17777
18449
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
17778
- return Math.min(easeOut3(l), easeOut3(t2));
18450
+ return Math.min(easeOut4(l), easeOut4(t2));
17779
18451
  },
17780
18452
  fill: () => app.state.theme === THEME12.LIGHT ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)"
17781
18453
  });
@@ -18165,7 +18837,7 @@ var createRadarAxisLabels = (labels, angles, centerX, centerY, radius, backgroun
18165
18837
  );
18166
18838
  const minLabelWidth = getApproxMinLineWidth(fontString, lineHeight);
18167
18839
  const axisLabels = labels.map((label, index) => {
18168
- const angle = angles[index];
18840
+ const angle2 = angles[index];
18169
18841
  const longestWordWidth = Math.max(
18170
18842
  0,
18171
18843
  ...label.trim().split(/\s+/).filter(Boolean).map((word) => measureText4(word, fontString, lineHeight).width)
@@ -18177,8 +18849,8 @@ var createRadarAxisLabels = (labels, angles, centerX, centerY, radius, backgroun
18177
18849
  );
18178
18850
  const displayLabel = getRadarDisplayText(label, fontString, maxLabelWidth);
18179
18851
  const metrics = measureText4(displayLabel, fontString, lineHeight);
18180
- const cos = Math.cos(angle);
18181
- const sin = Math.sin(angle);
18852
+ const cos = Math.cos(angle2);
18853
+ const sin = Math.sin(angle2);
18182
18854
  const textAlign = cos > RADAR_AXIS_LABEL_ALIGNMENT_THRESHOLD ? "left" : cos < -RADAR_AXIS_LABEL_ALIGNMENT_THRESHOLD ? "right" : "center";
18183
18855
  const centerAlignedXExtent = textAlign === "center" ? metrics.width / 2 : 0;
18184
18856
  const projectedExtent = Math.abs(cos) * centerAlignedXExtent + Math.abs(sin) * (metrics.height / 2);
@@ -18324,9 +18996,9 @@ var wrapOrEllipsifyTextToWidth = (text, maxWidth, fontString, lineHeight) => {
18324
18996
  text: ellipsifyTextToWidth(text, maxWidth, fontString, lineHeight)
18325
18997
  };
18326
18998
  };
18327
- var getRotatedBoundingBox = (width, height, angle) => {
18328
- const cos = Math.abs(Math.cos(angle));
18329
- const sin = Math.abs(Math.sin(angle));
18999
+ var getRotatedBoundingBox = (width, height, angle2) => {
19000
+ const cos = Math.abs(Math.cos(angle2));
19001
+ const sin = Math.abs(Math.sin(angle2));
18330
19002
  return {
18331
19003
  width: width * cos + height * sin,
18332
19004
  height: width * sin + height * cos
@@ -18900,9 +19572,9 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18900
19572
  const levelRatio = (levelIndex + 1) / RADAR_GRID_LEVELS;
18901
19573
  const levelRadius = radius * levelRatio;
18902
19574
  const points = angles.map(
18903
- (angle) => pointFrom24(
18904
- Math.cos(angle) * levelRadius,
18905
- Math.sin(angle) * levelRadius
19575
+ (angle2) => pointFrom24(
19576
+ Math.cos(angle2) * levelRadius,
19577
+ Math.sin(angle2) * levelRadius
18906
19578
  )
18907
19579
  );
18908
19580
  points.push(pointFrom24(points[0][0], points[0][1]));
@@ -18921,9 +19593,9 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18921
19593
  points
18922
19594
  });
18923
19595
  }) : [];
18924
- const spokes = angles.map((angle) => {
18925
- const px = Math.cos(angle) * radius;
18926
- const py = Math.sin(angle) * radius;
19596
+ const spokes = angles.map((angle2) => {
19597
+ const px = Math.cos(angle2) * radius;
19598
+ const py = Math.sin(angle2) * radius;
18927
19599
  return newLinearElement3({
18928
19600
  backgroundColor: "transparent",
18929
19601
  ...commonProps,
@@ -18939,12 +19611,12 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18939
19611
  });
18940
19612
  });
18941
19613
  const seriesPolygons = series.map((seriesData, index) => {
18942
- const points = angles.map((angle, axisIndex) => {
19614
+ const points = angles.map((angle2, axisIndex) => {
18943
19615
  const value = seriesData.values[axisIndex] ?? 0;
18944
19616
  const pointRadius = normalize(value, axisIndex) * radius;
18945
19617
  return pointFrom24(
18946
- Math.cos(angle) * pointRadius,
18947
- Math.sin(angle) * pointRadius
19618
+ Math.cos(angle2) * pointRadius,
19619
+ Math.sin(angle2) * pointRadius
18948
19620
  );
18949
19621
  });
18950
19622
  points.push(pointFrom24(points[0][0], points[0][1]));
@@ -19430,7 +20102,7 @@ var filterLinearConvertibleElements = (elements) => elements.filter(
19430
20102
  var THRESHOLD = 20;
19431
20103
  var isVert = (a, b) => a[0] === b[0];
19432
20104
  var isHorz = (a, b) => a[1] === b[1];
19433
- var dist = (a, b) => isVert(a, b) ? Math.abs(a[1] - b[1]) : Math.abs(a[0] - b[0]);
20105
+ var dist2 = (a, b) => isVert(a, b) ? Math.abs(a[1] - b[1]) : Math.abs(a[0] - b[0]);
19434
20106
  var convertLineToElbow = (line) => {
19435
20107
  const ortho = [line.points[0]];
19436
20108
  const src = sanitizePoints(line.points);
@@ -19464,8 +20136,8 @@ var convertLineToElbow = (line) => {
19464
20136
  const v1 = isVert(a, b);
19465
20137
  const v2 = isVert(b, c);
19466
20138
  if (v1 !== v2) {
19467
- const d1 = dist(a, b);
19468
- const d2 = dist(b, c);
20139
+ const d1 = dist2(a, b);
20140
+ const d2 = dist2(b, c);
19469
20141
  if (d1 < THRESHOLD || d2 < THRESHOLD) {
19470
20142
  if (d2 < d1) {
19471
20143
  if (v1) {
@@ -19522,7 +20194,7 @@ var convertElementType = (element, targetType, app) => {
19522
20194
  newElement5({
19523
20195
  ...element,
19524
20196
  type: targetType,
19525
- roundness: targetType === "diamond" && element.roundness ? {
20197
+ roundness: element.roundness ? {
19526
20198
  type: isUsingAdaptiveRadius3(targetType) ? ROUNDNESS6.ADAPTIVE_RADIUS : ROUNDNESS6.PROPORTIONAL_RADIUS
19527
20199
  } : element.roundness
19528
20200
  })
@@ -20645,7 +21317,7 @@ var SelectedShapeActions = ({
20645
21317
  canChangeBackgroundColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { children: renderAction("changeBackgroundColor") }),
20646
21318
  showFillIcons && renderAction("changeFillStyle"),
20647
21319
  (hasStrokeWidth(appState.activeTool.type) || targetElements.some((element) => hasStrokeWidth(element.type))) && renderAction("changeStrokeWidth"),
20648
- (appState.activeTool.type === "freedraw" || targetElements.some((element) => element.type === "freedraw")) && renderAction("changeStrokeShape"),
21320
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some((element) => hasFreedrawMode(element.type))) && renderAction("changeFreedrawMode"),
20649
21321
  (hasStrokeStyle(appState.activeTool.type) || targetElements.some((element) => hasStrokeStyle(element.type))) && /* @__PURE__ */ jsxs35(Fragment8, { children: [
20650
21322
  renderAction("changeStrokeStyle"),
20651
21323
  renderAction("changeSloppiness")
@@ -20778,6 +21450,13 @@ var CombinedShapeProperties = ({
20778
21450
  (hasStrokeWidth(appState.activeTool.type) || targetElements.some(
20779
21451
  (element) => hasStrokeWidth(element.type)
20780
21452
  )) && renderAction("changeStrokeWidth"),
21453
+ /* in compact UI the freedraw pressure setting is rendered as a
21454
+ standalone cycle button in the compact actions list; we render
21455
+ it in the combined properties popup as well for clarity
21456
+ */
21457
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some(
21458
+ (element) => hasFreedrawMode(element.type)
21459
+ )) && renderAction("changeFreedrawMode"),
20781
21460
  (hasStrokeStyle(appState.activeTool.type) || targetElements.some(
20782
21461
  (element) => hasStrokeStyle(element.type)
20783
21462
  )) && /* @__PURE__ */ jsxs35(Fragment8, { children: [
@@ -21105,6 +21784,7 @@ var CompactShapeActions = ({
21105
21784
  return /* @__PURE__ */ jsxs35("div", { className: "compact-shape-actions", children: [
21106
21785
  canChangeStrokeColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { className: clsx29("compact-action-item"), children: renderAction("changeStrokeColor") }),
21107
21786
  canChangeBackgroundColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { className: "compact-action-item", children: renderAction("changeBackgroundColor") }),
21787
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some((element) => hasFreedrawMode(element.type))) && /* @__PURE__ */ jsx75("div", { className: "compact-action-item", children: renderAction("changeFreedrawMode", { cycle: true }) }),
21108
21788
  /* @__PURE__ */ jsx75(
21109
21789
  CombinedShapeProperties,
21110
21790
  {
@@ -21293,6 +21973,7 @@ var ShapesSwitcher = ({
21293
21973
  const stylesPanelMode = useStylesPanelMode();
21294
21974
  const isFullStylesPanel = stylesPanelMode === "full";
21295
21975
  const isCompactStylesPanel = stylesPanelMode === "compact";
21976
+ const pendingPenDetectionRef = useRef16(false);
21296
21977
  const SELECTION_TOOLS2 = [
21297
21978
  {
21298
21979
  type: "selection",
@@ -21418,7 +22099,7 @@ var ShapesSwitcher = ({
21418
22099
  "data-testid": `toolbar-${value}`,
21419
22100
  onPointerDown: ({ pointerType }) => {
21420
22101
  if (!app.state.penDetected && pointerType === "pen") {
21421
- app.togglePenMode(true);
22102
+ pendingPenDetectionRef.current = true;
21422
22103
  }
21423
22104
  if (value === "selection") {
21424
22105
  if (app.state.activeTool.type === "selection") {
@@ -21428,16 +22109,14 @@ var ShapesSwitcher = ({
21428
22109
  }
21429
22110
  }
21430
22111
  },
21431
- onChange: ({ pointerType }) => {
22112
+ onChange: () => {
21432
22113
  if (app.state.activeTool.type !== value) {
21433
22114
  trackEvent("toolbar", value, "ui");
21434
22115
  }
21435
- if (value === "image") {
21436
- app.setActiveTool({
21437
- type: value
21438
- });
21439
- } else {
21440
- app.setActiveTool({ type: value });
22116
+ app.setActiveTool({ type: value });
22117
+ if (pendingPenDetectionRef.current) {
22118
+ pendingPenDetectionRef.current = false;
22119
+ requestAnimationFrame(() => app.togglePenMode(true));
21441
22120
  }
21442
22121
  }
21443
22122
  },
@@ -25250,7 +25929,7 @@ var CanvasGrid = ({
25250
25929
  var CanvasGrid_default = CanvasGrid;
25251
25930
 
25252
25931
  // components/Stats/Dimension.tsx
25253
- import { clamp as clamp5, round as round3 } from "@excalidraw/math";
25932
+ import { clamp as clamp7, round as round3 } from "@excalidraw/math";
25254
25933
  import { MIN_WIDTH_OR_HEIGHT } from "@excalidraw/common";
25255
25934
  import {
25256
25935
  MINIMAL_CROP_SIZE,
@@ -25305,7 +25984,7 @@ var handleDimensionChange = ({
25305
25984
  if (nextValue !== void 0) {
25306
25985
  if (property === "width") {
25307
25986
  const nextValueInNatural = nextValue * naturalToUncroppedWidthRatio;
25308
- const nextCropWidth2 = clamp5(
25987
+ const nextCropWidth2 = clamp7(
25309
25988
  nextValueInNatural,
25310
25989
  MIN_WIDTH,
25311
25990
  MAX_POSSIBLE_WIDTH
@@ -25317,7 +25996,7 @@ var handleDimensionChange = ({
25317
25996
  };
25318
25997
  } else if (property === "height") {
25319
25998
  const nextValueInNatural = nextValue * naturalToUncroppedHeightRatio;
25320
- const nextCropHeight2 = clamp5(
25999
+ const nextCropHeight2 = clamp7(
25321
26000
  nextValueInNatural,
25322
26001
  MIN_HEIGHT,
25323
26002
  MAX_POSSIBLE_HEIGHT
@@ -25337,12 +26016,12 @@ var handleDimensionChange = ({
25337
26016
  }
25338
26017
  const changeInWidth = property === "width" ? instantChange : 0;
25339
26018
  const changeInHeight = property === "height" ? instantChange : 0;
25340
- const nextCropWidth = clamp5(
26019
+ const nextCropWidth = clamp7(
25341
26020
  crop.width + changeInWidth,
25342
26021
  MIN_WIDTH,
25343
26022
  MAX_POSSIBLE_WIDTH
25344
26023
  );
25345
- const nextCropHeight = clamp5(
26024
+ const nextCropHeight = clamp7(
25346
26025
  crop.height + changeInHeight,
25347
26026
  MIN_WIDTH,
25348
26027
  MAX_POSSIBLE_HEIGHT
@@ -25696,7 +26375,7 @@ import { getBoundTextElement as getBoundTextElement13, handleBindTextResize as h
25696
26375
  import { isTextElement as isTextElement13 } from "@excalidraw/element";
25697
26376
 
25698
26377
  // ../utils/src/export.ts
25699
- import { MIME_TYPES as MIME_TYPES6 } from "@excalidraw/common";
26378
+ import { MIME_TYPES as MIME_TYPES7 } from "@excalidraw/common";
25700
26379
  var exportToCanvas2 = ({
25701
26380
  elements,
25702
26381
  appState,
@@ -25744,16 +26423,16 @@ var exportToCanvas2 = ({
25744
26423
  );
25745
26424
  };
25746
26425
  var exportToBlob = async (opts) => {
25747
- let { mimeType = MIME_TYPES6.png, quality } = opts;
25748
- if (mimeType === MIME_TYPES6.png && typeof quality === "number") {
25749
- console.warn(`"quality" will be ignored for "${MIME_TYPES6.png}" mimeType`);
26426
+ let { mimeType = MIME_TYPES7.png, quality } = opts;
26427
+ if (mimeType === MIME_TYPES7.png && typeof quality === "number") {
26428
+ console.warn(`"quality" will be ignored for "${MIME_TYPES7.png}" mimeType`);
25750
26429
  }
25751
26430
  if (mimeType === "image/jpg") {
25752
- mimeType = MIME_TYPES6.jpg;
26431
+ mimeType = MIME_TYPES7.jpg;
25753
26432
  }
25754
- if (mimeType === MIME_TYPES6.jpg && !opts.appState?.exportBackground) {
26433
+ if (mimeType === MIME_TYPES7.jpg && !opts.appState?.exportBackground) {
25755
26434
  console.warn(
25756
- `Defaulting "exportBackground" to "true" for "${MIME_TYPES6.jpg}" mimeType`
26435
+ `Defaulting "exportBackground" to "true" for "${MIME_TYPES7.jpg}" mimeType`
25757
26436
  );
25758
26437
  opts = {
25759
26438
  ...opts,
@@ -25768,7 +26447,7 @@ var exportToBlob = async (opts) => {
25768
26447
  if (!blob) {
25769
26448
  return reject(new Error("couldn't export to blob"));
25770
26449
  }
25771
- if (blob && mimeType === MIME_TYPES6.png && opts.appState?.exportEmbedScene) {
26450
+ if (blob && mimeType === MIME_TYPES7.png && opts.appState?.exportEmbedScene) {
25772
26451
  blob = await encodePngMetadata({
25773
26452
  blob,
25774
26453
  metadata: serializeAsJSON(
@@ -26469,7 +27148,7 @@ var MultiPosition = ({
26469
27148
  var MultiPosition_default = MultiPosition;
26470
27149
 
26471
27150
  // components/Stats/Position.tsx
26472
- import { clamp as clamp6, pointFrom as pointFrom29, pointRotateRads as pointRotateRads19, round as round4 } from "@excalidraw/math";
27151
+ import { clamp as clamp8, pointFrom as pointFrom29, pointRotateRads as pointRotateRads19, round as round4 } from "@excalidraw/math";
26473
27152
  import {
26474
27153
  getFlipAdjustedCropPosition,
26475
27154
  getUncroppedWidthAndHeight as getUncroppedWidthAndHeight2
@@ -26515,7 +27194,7 @@ var handlePositionChange2 = ({
26515
27194
  if (isFlippedByX) {
26516
27195
  nextCrop = {
26517
27196
  ...crop,
26518
- x: clamp6(
27197
+ x: clamp8(
26519
27198
  crop.naturalWidth - nextValueInNatural - crop.width,
26520
27199
  0,
26521
27200
  crop.naturalWidth - crop.width
@@ -26524,7 +27203,7 @@ var handlePositionChange2 = ({
26524
27203
  } else {
26525
27204
  nextCrop = {
26526
27205
  ...crop,
26527
- x: clamp6(
27206
+ x: clamp8(
26528
27207
  nextValue * (crop.naturalWidth / uncroppedWidth),
26529
27208
  0,
26530
27209
  crop.naturalWidth - crop.width
@@ -26535,7 +27214,7 @@ var handlePositionChange2 = ({
26535
27214
  if (property === "y") {
26536
27215
  nextCrop = {
26537
27216
  ...crop,
26538
- y: clamp6(
27217
+ y: clamp8(
26539
27218
  nextValue * (crop.naturalHeight / uncroppedHeight),
26540
27219
  0,
26541
27220
  crop.naturalHeight - crop.height
@@ -26551,8 +27230,8 @@ var handlePositionChange2 = ({
26551
27230
  const changeInY = (property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1);
26552
27231
  nextCrop = {
26553
27232
  ...crop,
26554
- x: clamp6(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
26555
- y: clamp6(crop.y + changeInY, 0, crop.naturalHeight - crop.height)
27233
+ x: clamp8(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
27234
+ y: clamp8(crop.y + changeInY, 0, crop.naturalHeight - crop.height)
26556
27235
  };
26557
27236
  scene.mutateElement(element, {
26558
27237
  crop: nextCrop
@@ -28794,7 +29473,6 @@ var LayerUI = ({
28794
29473
  renderWelcomeScreen && /* @__PURE__ */ jsx130(tunnels.WelcomeScreenMenuHintTunnel.Out, {})
28795
29474
  ] });
28796
29475
  const renderSelectedShapeActions = () => {
28797
- const isCompactMode = isCompactStylesPanel;
28798
29476
  return /* @__PURE__ */ jsx130(
28799
29477
  Section,
28800
29478
  {
@@ -28802,7 +29480,7 @@ var LayerUI = ({
28802
29480
  className: clsx55("selected-shape-actions zen-mode-transition", {
28803
29481
  "transition-left": appState.zenModeEnabled
28804
29482
  }),
28805
- children: isCompactMode ? /* @__PURE__ */ jsx130(
29483
+ children: isCompactStylesPanel ? /* @__PURE__ */ jsx130(
28806
29484
  Island,
28807
29485
  {
28808
29486
  className: clsx55("compact-shape-actions-island"),
@@ -28873,6 +29551,17 @@ var LayerUI = ({
28873
29551
  }),
28874
29552
  children: shouldRenderSelectedShapeActions && renderSelectedShapeActions()
28875
29553
  }
29554
+ ),
29555
+ isCompactStylesPanel && !appState.viewModeEnabled && shouldRenderSelectedShapeActions && /* @__PURE__ */ jsx130(
29556
+ PenModeButton,
29557
+ {
29558
+ zenModeEnabled: appState.zenModeEnabled,
29559
+ checked: appState.penMode,
29560
+ onChange: () => onPenModeToggle(null),
29561
+ title: t("toolBar.penMode"),
29562
+ isMobile: true,
29563
+ penDetected: appState.penDetected
29564
+ }
28876
29565
  )
28877
29566
  ]
28878
29567
  }
@@ -28907,7 +29596,7 @@ var LayerUI = ({
28907
29596
  ),
28908
29597
  heading,
28909
29598
  /* @__PURE__ */ jsxs67(Stack_default.Row, { gap: spacing.toolbarInnerRowGap, children: [
28910
- /* @__PURE__ */ jsx130(
29599
+ !isCompactStylesPanel && /* @__PURE__ */ jsx130(
28911
29600
  PenModeButton,
28912
29601
  {
28913
29602
  zenModeEnabled: appState.zenModeEnabled,
@@ -29295,7 +29984,7 @@ import {
29295
29984
 
29296
29985
  // renderer/interactiveScene.ts
29297
29986
  import {
29298
- clamp as clamp7,
29987
+ clamp as clamp9,
29299
29988
  pointFrom as pointFrom31,
29300
29989
  pointsEqual as pointsEqual8,
29301
29990
  bezierEquation as bezierEquation2,
@@ -29601,7 +30290,7 @@ var renderBindingHighlightForBindableElement_simple = (context, suggestedBinding
29601
30290
  context.rotate(suggestedBinding.element.angle);
29602
30291
  context.translate(-center[0], -center[1]);
29603
30292
  context.translate(suggestedBinding.element.x, suggestedBinding.element.y);
29604
- context.lineWidth = clamp7(1.75, suggestedBinding.element.strokeWidth, 4) / Math.max(0.25, appState.zoom.value);
30293
+ context.lineWidth = clamp9(1.75, suggestedBinding.element.strokeWidth, 4) / Math.max(0.25, appState.zoom.value);
29605
30294
  context.strokeStyle = appState.theme === THEME16.DARK ? `rgba(3, 93, 161, 1)` : `rgba(106, 189, 252, 1)`;
29606
30295
  switch (suggestedBinding.element.type) {
29607
30296
  case "ellipse":
@@ -29792,7 +30481,7 @@ var renderBindingHighlightForBindableElement_simple = (context, suggestedBinding
29792
30481
  var renderBindingHighlightForBindableElement_complex = (app, context, element, allElementsMap, appState, deltaTime, state) => {
29793
30482
  const countdownInProgress = app.state.bindMode === "orbit" && app.bindModeHandler !== null;
29794
30483
  const remainingTime = BIND_MODE_TIMEOUT - (state?.runtime ?? (countdownInProgress ? 0 : BIND_MODE_TIMEOUT));
29795
- const opacity = clamp7(1 / BIND_MODE_TIMEOUT * remainingTime, 1e-4, 1);
30484
+ const opacity = clamp9(1 / BIND_MODE_TIMEOUT * remainingTime, 1e-4, 1);
29796
30485
  const offset = element.strokeWidth / 2;
29797
30486
  const enclosingFrame = element.frameId && allElementsMap.get(element.frameId);
29798
30487
  if (enclosingFrame && isFrameLikeElement14(enclosingFrame)) {
@@ -29847,7 +30536,7 @@ var renderBindingHighlightForBindableElement_complex = (app, context, element, a
29847
30536
  element.x + appState.scrollX - offset,
29848
30537
  element.y + appState.scrollY - offset
29849
30538
  );
29850
- context.lineWidth = clamp7(2.5, element.strokeWidth * 1.75, 4) / Math.max(0.25, appState.zoom.value);
30539
+ context.lineWidth = clamp9(2.5, element.strokeWidth * 1.75, 4) / Math.max(0.25, appState.zoom.value);
29851
30540
  context.strokeStyle = appState.theme === THEME16.DARK ? `rgba(3, 93, 161, ${opacity / 2})` : `rgba(106, 189, 252, ${opacity / 2})`;
29852
30541
  switch (element.type) {
29853
30542
  case "ellipse":
@@ -30083,7 +30772,7 @@ var renderBindingHighlightForBindableElement = (app, context, suggestedBinding,
30083
30772
  };
30084
30773
  var renderSelectionBorder = (context, appState, elementProperties) => {
30085
30774
  const {
30086
- angle,
30775
+ angle: angle2,
30087
30776
  x1,
30088
30777
  y1,
30089
30778
  x2,
@@ -30121,7 +30810,7 @@ var renderSelectionBorder = (context, appState, elementProperties) => {
30121
30810
  elementHeight + linePadding * 2,
30122
30811
  cx,
30123
30812
  cy,
30124
- angle
30813
+ angle2
30125
30814
  );
30126
30815
  }
30127
30816
  context.restore();
@@ -30353,7 +31042,7 @@ var renderFocusPointIndicator = ({
30353
31042
  );
30354
31043
  }
30355
31044
  };
30356
- var renderTransformHandles = (context, renderConfig, appState, transformHandles, angle) => {
31045
+ var renderTransformHandles = (context, renderConfig, appState, transformHandles, angle2) => {
30357
31046
  Object.keys(transformHandles).forEach((key) => {
30358
31047
  const transformHandle = transformHandles[key];
30359
31048
  if (transformHandle !== void 0) {
@@ -30379,7 +31068,7 @@ var renderTransformHandles = (context, renderConfig, appState, transformHandles,
30379
31068
  height,
30380
31069
  x + width / 2,
30381
31070
  y + height / 2,
30382
- angle,
31071
+ angle2,
30383
31072
  true
30384
31073
  // fill before stroke
30385
31074
  );
@@ -30511,6 +31200,46 @@ var renderResetAutoResizeHandle = (text, context, appState, selectionColor, form
30511
31200
  context.stroke();
30512
31201
  context.restore();
30513
31202
  };
31203
+ var renderImageLoadingProgress = (context, visibleElements, elementsMap, app, appState, selectionColor) => {
31204
+ for (const element of visibleElements) {
31205
+ if (!isImageElement8(element) || !element.fileId || element.status === "error") {
31206
+ continue;
31207
+ }
31208
+ const progress = app.imageLoadingProgress.get(element.fileId);
31209
+ if (progress === void 0) {
31210
+ continue;
31211
+ }
31212
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords7(element, elementsMap);
31213
+ const centerX = (x1 + x2) / 2 + appState.scrollX;
31214
+ const centerY = (y1 + y2) / 2 + appState.scrollY;
31215
+ const zoom = appState.zoom.value;
31216
+ const radius = Math.min(14, Math.min(x2 - x1, y2 - y1) * zoom / 4) / zoom;
31217
+ const lineWidth = Math.min(3 / zoom, radius / 2);
31218
+ if (radius <= 0 || lineWidth <= 0) {
31219
+ continue;
31220
+ }
31221
+ context.save();
31222
+ context.lineWidth = lineWidth;
31223
+ context.lineCap = "round";
31224
+ context.strokeStyle = appState.theme === THEME16.DARK ? "rgba(255, 255, 255, 0.35)" : "rgba(0, 0, 0, 0.25)";
31225
+ context.beginPath();
31226
+ context.arc(centerX, centerY, radius, 0, Math.PI * 2);
31227
+ context.stroke();
31228
+ if (progress > 0) {
31229
+ context.strokeStyle = selectionColor;
31230
+ context.beginPath();
31231
+ context.arc(
31232
+ centerX,
31233
+ centerY,
31234
+ radius,
31235
+ -Math.PI / 2,
31236
+ -Math.PI / 2 + Math.PI * 2 * progress
31237
+ );
31238
+ context.stroke();
31239
+ }
31240
+ context.restore();
31241
+ }
31242
+ };
30514
31243
  var _renderInteractiveScene = ({
30515
31244
  app,
30516
31245
  canvas,
@@ -30868,6 +31597,14 @@ var _renderInteractiveScene = ({
30868
31597
  }
30869
31598
  });
30870
31599
  renderSnaps(context, appState);
31600
+ renderImageLoadingProgress(
31601
+ context,
31602
+ visibleElements,
31603
+ elementsMap,
31604
+ app,
31605
+ appState,
31606
+ renderConfig.selectionColor
31607
+ );
30871
31608
  context.restore();
30872
31609
  renderRemoteCursors({
30873
31610
  context,
@@ -30920,6 +31657,31 @@ var INTERACTIVE_SCENE_ANIMATION_KEY = "animateInteractiveScene";
30920
31657
  var InteractiveCanvas = (props) => {
30921
31658
  const isComponentMounted = useRef27(false);
30922
31659
  const rendererParams = useRef27(null);
31660
+ useEffect31(() => {
31661
+ let scheduledFrame = null;
31662
+ const unsubscribe = props.app.imageLoadingProgressEmitter.on(() => {
31663
+ if (scheduledFrame !== null) {
31664
+ return;
31665
+ }
31666
+ scheduledFrame = requestAnimationFrame(() => {
31667
+ scheduledFrame = null;
31668
+ if (!rendererParams.current) {
31669
+ return;
31670
+ }
31671
+ renderInteractiveScene({
31672
+ ...rendererParams.current,
31673
+ callback: () => {
31674
+ }
31675
+ });
31676
+ });
31677
+ });
31678
+ return () => {
31679
+ unsubscribe();
31680
+ if (scheduledFrame !== null) {
31681
+ cancelAnimationFrame(scheduledFrame);
31682
+ }
31683
+ };
31684
+ }, [props.app]);
30923
31685
  useEffect31(() => {
30924
31686
  if (!isComponentMounted.current) {
30925
31687
  isComponentMounted.current = true;
@@ -31099,6 +31861,54 @@ import { jsx as jsx134 } from "react/jsx-runtime";
31099
31861
  var StaticCanvas = (props) => {
31100
31862
  const wrapperRef = useRef28(null);
31101
31863
  const isComponentMounted = useRef28(false);
31864
+ const propsRef = useRef28(props);
31865
+ const renderCanvasRef = useRef28(() => {
31866
+ });
31867
+ const transitionFrameRef = useRef28(null);
31868
+ propsRef.current = props;
31869
+ const renderCanvas = React39.useCallback(() => {
31870
+ const currentProps = propsRef.current;
31871
+ const wrapper = wrapperRef.current;
31872
+ if (!wrapper) {
31873
+ return;
31874
+ }
31875
+ const gridColorBold = getComputedStyle(wrapper).getPropertyValue("--color-grid-bold").trim();
31876
+ const gridColorRegular = getComputedStyle(wrapper).getPropertyValue("--color-grid-regular").trim();
31877
+ renderStaticScene(
31878
+ {
31879
+ canvas: currentProps.canvas,
31880
+ rc: currentProps.rc,
31881
+ scale: currentProps.scale,
31882
+ elementsMap: currentProps.elementsMap,
31883
+ allElementsMap: currentProps.allElementsMap,
31884
+ visibleElements: currentProps.visibleElements,
31885
+ appState: currentProps.appState,
31886
+ renderConfig: {
31887
+ ...currentProps.renderConfig,
31888
+ gridColorBold,
31889
+ gridColorRegular
31890
+ }
31891
+ },
31892
+ isRenderThrottlingEnabled()
31893
+ );
31894
+ const transitionDuration = currentProps.renderConfig.imageTransitionDuration ?? 0;
31895
+ const hasActiveTransition = transitionDuration > 0 && currentProps.visibleElements.some((element) => {
31896
+ if (element.type !== "image" || !element.fileId) {
31897
+ return false;
31898
+ }
31899
+ const transitionStart = currentProps.renderConfig.imageCache.get(
31900
+ element.fileId
31901
+ )?.transitionStart;
31902
+ return transitionStart !== void 0 && performance.now() - transitionStart < transitionDuration;
31903
+ });
31904
+ if (hasActiveTransition && transitionFrameRef.current === null) {
31905
+ transitionFrameRef.current = requestAnimationFrame(() => {
31906
+ transitionFrameRef.current = null;
31907
+ renderCanvasRef.current();
31908
+ });
31909
+ }
31910
+ }, []);
31911
+ renderCanvasRef.current = renderCanvas;
31102
31912
  useEffect32(() => {
31103
31913
  props.canvas.style.width = `${props.appState.width}px`;
31104
31914
  props.canvas.style.height = `${props.appState.height}px`;
@@ -31116,26 +31926,34 @@ var StaticCanvas = (props) => {
31116
31926
  wrapper.replaceChildren(canvas);
31117
31927
  canvas.classList.add("excalidraw__canvas", "static");
31118
31928
  }
31119
- const gridColorBold = getComputedStyle(wrapper).getPropertyValue("--color-grid-bold").trim();
31120
- const gridColorRegular = getComputedStyle(wrapper).getPropertyValue("--color-grid-regular").trim();
31121
- renderStaticScene(
31122
- {
31123
- canvas,
31124
- rc: props.rc,
31125
- scale: props.scale,
31126
- elementsMap: props.elementsMap,
31127
- allElementsMap: props.allElementsMap,
31128
- visibleElements: props.visibleElements,
31129
- appState: props.appState,
31130
- renderConfig: {
31131
- ...props.renderConfig,
31132
- gridColorBold,
31133
- gridColorRegular
31134
- }
31135
- },
31136
- isRenderThrottlingEnabled()
31137
- );
31929
+ renderCanvas();
31138
31930
  });
31931
+ useEffect32(() => {
31932
+ let scheduledFrame = null;
31933
+ const unsubscribe = props.app.imagePlaceholderUpdateEmitter.on(() => {
31934
+ if (scheduledFrame !== null) {
31935
+ return;
31936
+ }
31937
+ scheduledFrame = requestAnimationFrame(() => {
31938
+ scheduledFrame = null;
31939
+ renderCanvas();
31940
+ });
31941
+ });
31942
+ return () => {
31943
+ unsubscribe();
31944
+ if (scheduledFrame !== null) {
31945
+ cancelAnimationFrame(scheduledFrame);
31946
+ }
31947
+ };
31948
+ }, [props.app, renderCanvas]);
31949
+ useEffect32(
31950
+ () => () => {
31951
+ if (transitionFrameRef.current !== null) {
31952
+ cancelAnimationFrame(transitionFrameRef.current);
31953
+ }
31954
+ },
31955
+ []
31956
+ );
31139
31957
  return /* @__PURE__ */ jsx134("div", { className: "excalidraw__canvas-wrapper", ref: wrapperRef });
31140
31958
  };
31141
31959
  var getRelevantAppStateProps2 = (appState) => {
@@ -31571,6 +32389,10 @@ var App = class _App extends React40.Component {
31571
32389
  __publicField(this, "excalidrawContainerValue");
31572
32390
  __publicField(this, "files", {});
31573
32391
  __publicField(this, "imageCache", /* @__PURE__ */ new Map());
32392
+ __publicField(this, "imageLoadingProgress", /* @__PURE__ */ new Map());
32393
+ __publicField(this, "imageLoadingProgressEmitter", new Emitter2());
32394
+ __publicField(this, "imagePlaceholderUpdateEmitter", new Emitter2());
32395
+ __publicField(this, "imageTransitionPlaceholders", /* @__PURE__ */ new Map());
31574
32396
  __publicField(this, "iFrameRefs", /* @__PURE__ */ new Map());
31575
32397
  /**
31576
32398
  * Indicates whether the embeddable's url has been validated for rendering.
@@ -32610,7 +33432,8 @@ var App = class _App extends React40.Component {
32610
33432
  this.setState((prevState) => {
32611
33433
  return {
32612
33434
  penMode: force ?? !prevState.penMode,
32613
- penDetected: true
33435
+ penDetected: true,
33436
+ currentItemStrokeVariability: !prevState.penDetected ? "variable" : prevState.currentItemStrokeVariability
32614
33437
  };
32615
33438
  });
32616
33439
  });
@@ -32635,120 +33458,92 @@ var App = class _App extends React40.Component {
32635
33458
  __publicField(this, "cancelInProgressAnimation", null);
32636
33459
  __publicField(this, "scrollToViewport", (target, opts) => {
32637
33460
  this.cancelInProgressAnimation?.();
33461
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
32638
33462
  const zoom = { value: getNormalizedZoom(target.zoom) };
32639
33463
  const animateDuration = opts?.duration ?? 500;
32640
33464
  if (opts?.animate && animateDuration > 0) {
32641
- const origScrollX = this.state.scrollX;
32642
- const origScrollY = this.state.scrollY;
32643
- const origZoom = this.state.zoom.value;
32644
- const cancel = easeToValuesRAF({
32645
- fromValues: {
32646
- scrollX: origScrollX,
32647
- scrollY: origScrollY,
32648
- zoom: origZoom
32649
- },
32650
- toValues: {
32651
- scrollX: target.scrollX,
32652
- scrollY: target.scrollY,
32653
- zoom: zoom.value
32654
- },
32655
- interpolateValue: (from, to, progress, key) => {
32656
- if (key === "zoom") {
32657
- return from * Math.pow(to / from, easeOut4(progress));
32658
- }
32659
- return void 0;
32660
- },
32661
- onStep: ({ scrollX, scrollY, zoom: zoom2 }) => {
32662
- this.setState({
32663
- scrollX,
32664
- scrollY,
32665
- zoom: { value: zoom2 }
32666
- });
32667
- },
32668
- onStart: () => {
32669
- this.setState({ shouldCacheIgnoreZoom: true });
32670
- },
32671
- onEnd: () => {
32672
- this.cancelInProgressAnimation = null;
32673
- this.setState({ shouldCacheIgnoreZoom: false });
32674
- },
32675
- onCancel: () => {
33465
+ const from = this.state;
33466
+ let startTime = null;
33467
+ let frameId = null;
33468
+ let cancelled = false;
33469
+ const targetViewport = {
33470
+ scrollX: target.scrollX,
33471
+ scrollY: target.scrollY,
33472
+ zoom
33473
+ };
33474
+ const step = (timestamp) => {
33475
+ if (cancelled) {
33476
+ return;
33477
+ }
33478
+ startTime ?? (startTime = timestamp);
33479
+ const progress = Math.min((timestamp - startTime) / animateDuration, 1);
33480
+ this.setState({
33481
+ ...interpolateViewport({
33482
+ from,
33483
+ target: targetViewport,
33484
+ factor: easeOut5(progress)
33485
+ }),
33486
+ shouldCacheIgnoreZoom: progress < 1
33487
+ });
33488
+ if (progress < 1) {
33489
+ frameId = requestAnimationFrame(step);
33490
+ } else {
32676
33491
  this.cancelInProgressAnimation = null;
32677
- this.setState({ shouldCacheIgnoreZoom: false });
32678
- },
32679
- duration: animateDuration
32680
- });
33492
+ }
33493
+ };
32681
33494
  this.cancelInProgressAnimation = () => {
32682
- cancel();
33495
+ cancelled = true;
33496
+ if (frameId !== null) {
33497
+ cancelAnimationFrame(frameId);
33498
+ }
32683
33499
  this.cancelInProgressAnimation = null;
33500
+ this.setState({ shouldCacheIgnoreZoom: false });
32684
33501
  };
33502
+ frameId = requestAnimationFrame(step);
32685
33503
  } else {
32686
33504
  this.setState({
32687
33505
  scrollX: target.scrollX,
32688
33506
  scrollY: target.scrollY,
32689
- zoom
33507
+ zoom,
33508
+ shouldCacheIgnoreZoom: false
32690
33509
  });
32691
33510
  }
32692
33511
  });
32693
- __publicField(this, "scrollToContent", (target = this.scene.getNonDeletedElements(), opts) => {
33512
+ __publicField(this, "scrollToContent", (target, opts) => {
33513
+ let elements;
32694
33514
  if (typeof target === "string") {
32695
- let id;
32696
- if (isElementLink2(target)) {
32697
- id = parseElementLinkFromURL(target);
32698
- } else {
32699
- id = target;
32700
- }
32701
- if (id) {
32702
- const elements = this.scene.getElementsFromId(id);
32703
- if (elements?.length) {
32704
- this.scrollToContent(elements, {
32705
- fitToContent: opts?.fitToContent ?? true,
32706
- animate: opts?.animate ?? true
32707
- });
32708
- } else if (isElementLink2(target)) {
32709
- this.setState({
32710
- toast: {
32711
- message: t("elementLink.notFound"),
32712
- duration: 3e3,
32713
- closable: true
32714
- }
32715
- });
32716
- }
32717
- }
32718
- return;
32719
- }
32720
- const targetElements = Array.isArray(target) ? target : [target];
32721
- let zoom = this.state.zoom;
32722
- let scrollX = this.state.scrollX;
32723
- let scrollY = this.state.scrollY;
32724
- if (opts?.fitToContent || opts?.fitToViewport) {
32725
- const { appState } = zoomToFit({
32726
- canvasOffsets: opts.canvasOffsets,
32727
- targetElements,
32728
- appState: this.state,
32729
- fitToViewport: !!opts?.fitToViewport,
32730
- viewportZoomFactor: opts?.viewportZoomFactor,
32731
- minZoom: opts?.minZoom,
32732
- maxZoom: opts?.maxZoom
32733
- });
32734
- zoom = appState.zoom;
32735
- scrollX = appState.scrollX;
32736
- scrollY = appState.scrollY;
33515
+ const id = isElementLink2(target) ? parseElementLinkFromURL(target) : target;
33516
+ elements = id ? this.scene.getElementsFromId(id) : [];
33517
+ } else if (Array.isArray(target)) {
33518
+ elements = target;
33519
+ } else if (target) {
33520
+ elements = [target];
32737
33521
  } else {
32738
- const scroll = calculateScrollCenter(targetElements, this.state);
32739
- scrollX = scroll.scrollX;
32740
- scrollY = scroll.scrollY;
33522
+ elements = this.scene.getNonDeletedElements();
32741
33523
  }
32742
- this.scrollToViewport(
32743
- {
32744
- scrollX,
32745
- scrollY,
32746
- zoom: zoom.value
32747
- },
32748
- {
32749
- animate: opts?.animate,
32750
- duration: opts?.duration
33524
+ if (!elements.length) {
33525
+ if (typeof target === "string" && isElementLink2(target)) {
33526
+ this.setState({
33527
+ toast: {
33528
+ message: t("elementLink.notFound"),
33529
+ duration: 3e3,
33530
+ closable: true
33531
+ }
33532
+ });
32751
33533
  }
33534
+ return;
33535
+ }
33536
+ const resolvedOpts = typeof target === "string" ? {
33537
+ ...opts,
33538
+ fitToViewport: void 0,
33539
+ fitToContent: opts?.fitToContent ?? true,
33540
+ animate: opts?.animate ?? true
33541
+ } : opts;
33542
+ scrollToElements(
33543
+ this.state,
33544
+ elements,
33545
+ this.setState.bind(this),
33546
+ resolvedOpts
32752
33547
  );
32753
33548
  });
32754
33549
  __publicField(this, "maybeUnfollowRemoteUser", () => {
@@ -32759,6 +33554,8 @@ var App = class _App extends React40.Component {
32759
33554
  /** use when changing scrollX/scrollY/zoom based on user interaction */
32760
33555
  __publicField(this, "translateCanvas", (state) => {
32761
33556
  this.cancelInProgressAnimation?.();
33557
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
33558
+ this.setState({ shouldCacheIgnoreZoom: false });
32762
33559
  this.maybeUnfollowRemoteUser();
32763
33560
  this.setState(state);
32764
33561
  });
@@ -32790,11 +33587,93 @@ var App = class _App extends React40.Component {
32790
33587
  __publicField(this, "addFiles", withBatchedUpdates(
32791
33588
  (files) => {
32792
33589
  const { addedFiles } = this.addMissingFiles(files);
33590
+ for (const fileId of Object.keys(addedFiles)) {
33591
+ const cacheEntry = this.imageCache.get(fileId);
33592
+ if (cacheEntry?.isPlaceholder && !(cacheEntry.image instanceof Promise)) {
33593
+ this.imageTransitionPlaceholders.set(fileId, cacheEntry.image);
33594
+ }
33595
+ }
32793
33596
  this.clearImageShapeCache(addedFiles);
32794
33597
  this.scene.triggerUpdate();
32795
- this.addNewImagesToImageCache();
33598
+ this.addNewImagesToImageCache().then(() => {
33599
+ for (const fileId of Object.keys(addedFiles)) {
33600
+ const cacheEntry = this.imageCache.get(fileId);
33601
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
33602
+ this.clearImageLoadingProgress(fileId);
33603
+ }
33604
+ this.imageTransitionPlaceholders.delete(fileId);
33605
+ }
33606
+ });
32796
33607
  }
32797
33608
  ));
33609
+ __publicField(this, "addImagePlaceholder", async (fileId, file2) => {
33610
+ if (this.files[fileId]) {
33611
+ return;
33612
+ }
33613
+ let normalizedFile = await normalizeFile(file2);
33614
+ if (!isSupportedImageFile(normalizedFile)) {
33615
+ throw new Error(t("errors.unsupportedFileType"));
33616
+ }
33617
+ if (normalizedFile.type === MIME_TYPES8.svg) {
33618
+ normalizedFile = SVGStringToFile(
33619
+ normalizeSVG(await normalizedFile.text()),
33620
+ normalizedFile.name
33621
+ );
33622
+ }
33623
+ const dataURL = await getDataURL(normalizedFile);
33624
+ if (this.files[fileId]) {
33625
+ return;
33626
+ }
33627
+ const imagePromise = loadHTMLImageElement(dataURL);
33628
+ const placeholderEntry = {
33629
+ image: imagePromise,
33630
+ mimeType: normalizedFile.type,
33631
+ isPlaceholder: true
33632
+ };
33633
+ this.imageCache.set(fileId, placeholderEntry);
33634
+ try {
33635
+ const image = await imagePromise;
33636
+ if (this.imageCache.get(fileId) !== placeholderEntry || this.files[fileId]) {
33637
+ return;
33638
+ }
33639
+ this.imageCache.set(fileId, { ...placeholderEntry, image });
33640
+ this.clearImageShapeCacheForFileId(fileId);
33641
+ this.imagePlaceholderUpdateEmitter.trigger();
33642
+ } catch (error) {
33643
+ if (this.imageCache.get(fileId) === placeholderEntry) {
33644
+ this.imageCache.delete(fileId);
33645
+ this.imagePlaceholderUpdateEmitter.trigger();
33646
+ }
33647
+ throw error;
33648
+ }
33649
+ });
33650
+ __publicField(this, "setImageLoadingProgress", (fileId, progress) => {
33651
+ if (progress === null) {
33652
+ this.clearImageLoadingProgress(fileId);
33653
+ return;
33654
+ }
33655
+ if (!Number.isFinite(progress)) {
33656
+ return;
33657
+ }
33658
+ const nextProgress = clamp10(progress, 0, 1);
33659
+ if (this.imageLoadingProgress.get(fileId) === nextProgress) {
33660
+ return;
33661
+ }
33662
+ this.imageLoadingProgress.set(fileId, nextProgress);
33663
+ this.imageLoadingProgressEmitter.trigger();
33664
+ });
33665
+ __publicField(this, "clearImageLoadingProgress", (fileId) => {
33666
+ if (this.imageLoadingProgress.delete(fileId)) {
33667
+ this.imageLoadingProgressEmitter.trigger();
33668
+ }
33669
+ });
33670
+ __publicField(this, "clearImageShapeCacheForFileId", (fileId) => {
33671
+ for (const element of this.scene.getNonDeletedElements()) {
33672
+ if (isInitializedImageElement3(element) && element.fileId === fileId) {
33673
+ ShapeCache4.delete(element);
33674
+ }
33675
+ }
33676
+ });
32798
33677
  __publicField(this, "addMissingFiles", (files, replace = false) => {
32799
33678
  const nextFiles = replace ? {} : { ...this.files };
32800
33679
  const addedFiles = {};
@@ -32805,11 +33684,11 @@ var App = class _App extends React40.Component {
32805
33684
  }
32806
33685
  addedFiles[fileData.id] = fileData;
32807
33686
  nextFiles[fileData.id] = fileData;
32808
- if (fileData.mimeType === MIME_TYPES7.svg) {
33687
+ if (fileData.mimeType === MIME_TYPES8.svg) {
32809
33688
  try {
32810
33689
  const restoredDataURL = getDataURL_sync(
32811
33690
  normalizeSVG(dataURLToString(fileData.dataURL)),
32812
- MIME_TYPES7.svg
33691
+ MIME_TYPES8.svg
32813
33692
  );
32814
33693
  if (fileData.dataURL !== restoredDataURL) {
32815
33694
  fileData.version = (fileData.version ?? 1) + 1;
@@ -33719,7 +34598,7 @@ var App = class _App extends React40.Component {
33719
34598
  strokeColor: this.state.currentItemStrokeColor,
33720
34599
  backgroundColor: this.state.currentItemBackgroundColor,
33721
34600
  fillStyle: this.state.currentItemFillStyle,
33722
- strokeWidth: this.state.currentItemStrokeWidth,
34601
+ strokeWidth: this.getCurrentItemStrokeWidth("text"),
33723
34602
  strokeStyle: this.state.currentItemStrokeStyle,
33724
34603
  roughness: this.state.currentItemRoughness,
33725
34604
  opacity: this.state.currentItemOpacity,
@@ -34278,42 +35157,7 @@ var App = class _App extends React40.Component {
34278
35157
  const { lastCommittedPoint } = selectedLinearElement;
34279
35158
  setCursorForShape(this.interactiveCanvas, this.state);
34280
35159
  if (lastPoint === lastCommittedPoint) {
34281
- const hoveredElement = isArrowElement13(this.state.newElement) && isBindingEnabled2(this.state) && getHoveredElementForBinding2(
34282
- pointFrom32(scenePointerX, scenePointerY),
34283
- this.scene.getNonDeletedElements(),
34284
- this.scene.getNonDeletedElementsMap(),
34285
- maxBindingDistance_simple3(this.state.zoom)
34286
- );
34287
- if (hoveredElement) {
34288
- this.actionManager.executeAction(actionFinalize, "ui", {
34289
- event: event.nativeEvent,
34290
- sceneCoords: {
34291
- x: scenePointerX,
34292
- y: scenePointerY
34293
- }
34294
- });
34295
- this.setState({ suggestedBinding: null });
34296
- if (!this.state.activeTool.locked) {
34297
- resetCursor(this.interactiveCanvas);
34298
- this.setState((prevState) => ({
34299
- newElement: null,
34300
- activeTool: updateActiveTool8(this.state, {
34301
- type: this.state.preferredSelectionTool.type
34302
- }),
34303
- selectedElementIds: makeNextSelectedElementIds3(
34304
- {
34305
- ...prevState.selectedElementIds,
34306
- [multiElement.id]: true
34307
- },
34308
- prevState
34309
- ),
34310
- selectedLinearElement: new LinearElementEditor11(
34311
- multiElement,
34312
- this.scene.getNonDeletedElementsMap()
34313
- )
34314
- }));
34315
- }
34316
- } else if (
35160
+ if (
34317
35161
  // if we haven't yet created a temp point and we're beyond commit-zone
34318
35162
  // threshold, add a point
34319
35163
  pointDistance9(
@@ -34321,6 +35165,24 @@ var App = class _App extends React40.Component {
34321
35165
  lastPoint
34322
35166
  ) >= LINE_CONFIRM_THRESHOLD2
34323
35167
  ) {
35168
+ this.store.scheduleCapture();
35169
+ flushSync2(() => {
35170
+ invariant16(
35171
+ this.state.selectedLinearElement?.initialState,
35172
+ "initialState must be set"
35173
+ );
35174
+ this.setState({
35175
+ selectedLinearElement: {
35176
+ ...this.state.selectedLinearElement,
35177
+ lastCommittedPoint: points[points.length - 1],
35178
+ selectedPointsIndices: [multiElement.points.length],
35179
+ initialState: {
35180
+ ...this.state.selectedLinearElement.initialState,
35181
+ lastClickedPoint: multiElement.points.length
35182
+ }
35183
+ }
35184
+ });
35185
+ });
34324
35186
  this.scene.mutateElement(
34325
35187
  multiElement,
34326
35188
  {
@@ -34331,21 +35193,6 @@ var App = class _App extends React40.Component {
34331
35193
  },
34332
35194
  { informMutation: false, isDragging: false }
34333
35195
  );
34334
- invariant16(
34335
- this.state.selectedLinearElement?.initialState,
34336
- "initialState must be set"
34337
- );
34338
- this.setState({
34339
- selectedLinearElement: {
34340
- ...this.state.selectedLinearElement,
34341
- lastCommittedPoint: points[points.length - 1],
34342
- selectedPointsIndices: [multiElement.points.length - 1],
34343
- initialState: {
34344
- ...this.state.selectedLinearElement.initialState,
34345
- lastClickedPoint: multiElement.points.length - 1
34346
- }
34347
- }
34348
- });
34349
35196
  } else {
34350
35197
  setCursor(this.interactiveCanvas, CURSOR_TYPE4.POINTER);
34351
35198
  }
@@ -34693,7 +35540,8 @@ var App = class _App extends React40.Component {
34693
35540
  this.setState((prevState) => {
34694
35541
  return {
34695
35542
  penMode: true,
34696
- penDetected: true
35543
+ penDetected: true,
35544
+ currentItemStrokeVariability: "variable"
34697
35545
  };
34698
35546
  });
34699
35547
  }
@@ -35466,6 +36314,7 @@ var App = class _App extends React40.Component {
35466
36314
  y: gridY
35467
36315
  });
35468
36316
  const simulatePressure = event.pressure === 0.5;
36317
+ const strokeVariability = this.state.currentItemStrokeVariability;
35469
36318
  const element = newFreeDrawElement({
35470
36319
  type: elementType,
35471
36320
  x: gridX,
@@ -35473,15 +36322,21 @@ var App = class _App extends React40.Component {
35473
36322
  strokeColor: this.state.currentItemStrokeColor,
35474
36323
  backgroundColor: this.state.currentItemBackgroundColor,
35475
36324
  fillStyle: this.state.currentItemFillStyle,
35476
- strokeWidth: this.state.currentItemStrokeWidth,
36325
+ strokeWidth: this.getCurrentItemStrokeWidth("freedraw"),
35477
36326
  strokeStyle: this.state.currentItemStrokeStyle,
35478
36327
  roughness: this.state.currentItemRoughness,
35479
36328
  opacity: this.state.currentItemOpacity,
35480
36329
  roundness: null,
35481
36330
  simulatePressure,
36331
+ strokeOptions: {
36332
+ variability: strokeVariability,
36333
+ streamline: event.pointerType !== "mouse" ? DEFAULT_STROKE_STREAMLINE_PRECISE : DEFAULT_STROKE_STREAMLINE2
36334
+ },
35482
36335
  locked: false,
35483
36336
  frameId: topLayerFrame ? topLayerFrame.id : null,
35484
36337
  points: [pointFrom32(0, 0)],
36338
+ // pressures are only consumed when rendering a real-pressure stroke, so
36339
+ // skip persisting them while pressure is being simulated
35485
36340
  pressures: simulatePressure ? [] : [event.pressure]
35486
36341
  });
35487
36342
  this.insertNewElement(element);
@@ -35520,7 +36375,7 @@ var App = class _App extends React40.Component {
35520
36375
  strokeColor: "transparent",
35521
36376
  backgroundColor: "transparent",
35522
36377
  fillStyle: this.state.currentItemFillStyle,
35523
- strokeWidth: this.state.currentItemStrokeWidth,
36378
+ strokeWidth: this.getCurrentItemStrokeWidth("iframe"),
35524
36379
  strokeStyle: this.state.currentItemStrokeStyle,
35525
36380
  roughness: this.state.currentItemRoughness,
35526
36381
  roundness: this.getCurrentItemRoundness("iframe"),
@@ -35560,7 +36415,7 @@ var App = class _App extends React40.Component {
35560
36415
  strokeColor: "transparent",
35561
36416
  backgroundColor: "transparent",
35562
36417
  fillStyle: this.state.currentItemFillStyle,
35563
- strokeWidth: this.state.currentItemStrokeWidth,
36418
+ strokeWidth: this.getCurrentItemStrokeWidth("embeddable"),
35564
36419
  strokeStyle: this.state.currentItemStrokeStyle,
35565
36420
  roughness: this.state.currentItemRoughness,
35566
36421
  roundness: this.getCurrentItemRoundness("embeddable"),
@@ -35594,7 +36449,7 @@ var App = class _App extends React40.Component {
35594
36449
  strokeColor: this.state.currentItemStrokeColor,
35595
36450
  backgroundColor: this.state.currentItemBackgroundColor,
35596
36451
  fillStyle: this.state.currentItemFillStyle,
35597
- strokeWidth: this.state.currentItemStrokeWidth,
36452
+ strokeWidth: this.getCurrentItemStrokeWidth("image"),
35598
36453
  strokeStyle: this.state.currentItemStrokeStyle,
35599
36454
  roughness: this.state.currentItemRoughness,
35600
36455
  roundness: null,
@@ -35651,21 +36506,41 @@ var App = class _App extends React40.Component {
35651
36506
  }
35652
36507
  const { x: rx, y: ry } = multiElement;
35653
36508
  const { lastCommittedPoint } = selectedLinearElement;
35654
- const hoveredElementForBinding = isBindingEnabled2(this.state) && getHoveredElementForBinding2(
35655
- pointFrom32(
35656
- this.lastPointerMoveCoords?.x ?? rx + multiElement.points[multiElement.points.length - 1][0],
35657
- this.lastPointerMoveCoords?.y ?? ry + multiElement.points[multiElement.points.length - 1][1]
35658
- ),
36509
+ const sceneCoords = viewportCoordsToSceneCoords3(event, this.state);
36510
+ const { start: start2, end } = isBindingElement4(multiElement) && isBindingEnabled2(this.state) ? getBindingStrategyForDraggingBindingElementEndpoints2(
36511
+ multiElement,
36512
+ /* @__PURE__ */ new Map([
36513
+ [
36514
+ multiElement.points.length - 1,
36515
+ {
36516
+ point: multiElement.points[multiElement.points.length - 1],
36517
+ isDragging: false
36518
+ }
36519
+ ]
36520
+ ]),
36521
+ sceneCoords.x,
36522
+ sceneCoords.y,
36523
+ this.scene.getNonDeletedElementsMap(),
35659
36524
  this.scene.getNonDeletedElements(),
35660
- this.scene.getNonDeletedElementsMap()
35661
- );
35662
- if (isBindingElement4(multiElement) && hoveredElementForBinding || multiElement.points.length > 1 && lastCommittedPoint && pointDistance9(
36525
+ this.state,
36526
+ {
36527
+ newArrow: Boolean(this.state.newElement),
36528
+ zoom: this.state.zoom
36529
+ }
36530
+ ) : { end: { mode: void 0 } };
36531
+ const elementsMap = this.scene.getNonDeletedElementsMap();
36532
+ const endOutsideSameElement = start2?.mode != null && end.mode != null && start2.element.id === end.element.id && !isPointInElement3(end.focusPoint, end.element, elementsMap);
36533
+ const boundOutsideFromElsewhere = end.mode === "orbit" && multiElement.startBinding?.elementId !== end.element?.id;
36534
+ const lastCommittedPointIsInsideCommitZone = lastCommittedPoint && pointDistance9(
35663
36535
  pointFrom32(
35664
36536
  pointerDownState.origin.x - rx,
35665
36537
  pointerDownState.origin.y - ry
35666
36538
  ),
35667
36539
  lastCommittedPoint
35668
- ) < LINE_CONFIRM_THRESHOLD2) {
36540
+ ) < LINE_CONFIRM_THRESHOLD2;
36541
+ if (boundOutsideFromElsewhere || // Outside -> orbit: Bind immediately
36542
+ endOutsideSameElement || // End outside the start's element: Bind immediately
36543
+ multiElement.points.length > 1 && lastCommittedPointIsInsideCommitZone) {
35669
36544
  this.actionManager.executeAction(actionFinalize, "ui", {
35670
36545
  event: event.nativeEvent,
35671
36546
  sceneCoords: {
@@ -35704,7 +36579,7 @@ var App = class _App extends React40.Component {
35704
36579
  strokeColor: this.state.currentItemStrokeColor,
35705
36580
  backgroundColor: this.state.currentItemBackgroundColor,
35706
36581
  fillStyle: this.state.currentItemFillStyle,
35707
- strokeWidth: this.state.currentItemStrokeWidth,
36582
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35708
36583
  strokeStyle: this.state.currentItemStrokeStyle,
35709
36584
  roughness: this.state.currentItemRoughness,
35710
36585
  opacity: this.state.currentItemOpacity,
@@ -35726,7 +36601,7 @@ var App = class _App extends React40.Component {
35726
36601
  strokeColor: this.state.currentItemStrokeColor,
35727
36602
  backgroundColor: this.state.currentItemBackgroundColor,
35728
36603
  fillStyle: this.state.currentItemFillStyle,
35729
- strokeWidth: this.state.currentItemStrokeWidth,
36604
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35730
36605
  strokeStyle: this.state.currentItemStrokeStyle,
35731
36606
  roughness: this.state.currentItemRoughness,
35732
36607
  opacity: this.state.currentItemOpacity,
@@ -35837,7 +36712,7 @@ var App = class _App extends React40.Component {
35837
36712
  strokeColor: this.state.currentItemStrokeColor,
35838
36713
  backgroundColor: this.state.currentItemBackgroundColor,
35839
36714
  fillStyle: this.state.currentItemFillStyle,
35840
- strokeWidth: this.state.currentItemStrokeWidth,
36715
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35841
36716
  strokeStyle: this.state.currentItemStrokeStyle,
35842
36717
  roughness: this.state.currentItemRoughness,
35843
36718
  opacity: this.state.currentItemOpacity,
@@ -35965,7 +36840,7 @@ var App = class _App extends React40.Component {
35965
36840
  let fileName = imageFile.name;
35966
36841
  const fileNeedsResizing = imageFile.size >= this.state.dontResizeLimitMBs * 1024 * 1024;
35967
36842
  setCursor(this.interactiveCanvas, "wait");
35968
- if (mimeType === MIME_TYPES7.svg) {
36843
+ if (mimeType === MIME_TYPES8.svg) {
35969
36844
  try {
35970
36845
  imageFile = SVGStringToFile(
35971
36846
  normalizeSVG(await imageFile.text()),
@@ -36030,8 +36905,15 @@ var App = class _App extends React40.Component {
36030
36905
  lastRetrieved: Date.now()
36031
36906
  }
36032
36907
  ]);
36908
+ if (this.imageCache.get(fileId)?.isPlaceholder) {
36909
+ const cacheEntry = this.imageCache.get(fileId);
36910
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
36911
+ this.imageTransitionPlaceholders.set(fileId, cacheEntry.image);
36912
+ }
36913
+ this.imageCache.delete(fileId);
36914
+ this.clearImageShapeCacheForFileId(fileId);
36915
+ }
36033
36916
  if (!this.imageCache.get(fileId)) {
36034
- this.addNewImagesToImageCache();
36035
36917
  const { erroredFiles } = await this.updateImageCache([
36036
36918
  initializedImageElement
36037
36919
  ]);
@@ -36040,18 +36922,33 @@ var App = class _App extends React40.Component {
36040
36922
  }
36041
36923
  }
36042
36924
  const imageHTML = await this.imageCache.get(fileId)?.image;
36043
- if (imageHTML && this.state.newElement?.id !== initializedImageElement.id) {
36925
+ let thumbHash = null;
36926
+ if (imageHTML) {
36927
+ try {
36928
+ thumbHash = generateThumbHash(imageHTML);
36929
+ } catch (error) {
36930
+ console.warn("Failed to generate image ThumbHash", error);
36931
+ }
36932
+ }
36933
+ if (thumbHash && this.files[fileId]) {
36934
+ this.files[fileId].thumbHash = thumbHash;
36935
+ }
36936
+ if (imageHTML) {
36044
36937
  initializedImageElement = this.getLatestInitializedImageElement(
36045
36938
  placeholderImageElement,
36046
36939
  fileId,
36047
- fileName
36048
- );
36049
- const naturalDimensions = this.getImageNaturalDimensions(
36050
- initializedImageElement,
36051
- imageHTML
36940
+ fileName,
36941
+ thumbHash
36052
36942
  );
36053
- Object.assign(initializedImageElement, naturalDimensions);
36943
+ if (this.state.newElement?.id !== initializedImageElement.id) {
36944
+ const naturalDimensions = this.getImageNaturalDimensions(
36945
+ initializedImageElement,
36946
+ imageHTML
36947
+ );
36948
+ Object.assign(initializedImageElement, naturalDimensions);
36949
+ }
36054
36950
  }
36951
+ this.clearImageLoadingProgress(fileId);
36055
36952
  resolve(initializedImageElement);
36056
36953
  } catch (error) {
36057
36954
  console.error(error);
@@ -36065,13 +36962,14 @@ var App = class _App extends React40.Component {
36065
36962
  * when the placeholder image could have been modified in the meantime,
36066
36963
  * and when you don't want to loose those modifications
36067
36964
  */
36068
- __publicField(this, "getLatestInitializedImageElement", (imagePlaceholder, fileId, fileName) => {
36965
+ __publicField(this, "getLatestInitializedImageElement", (imagePlaceholder, fileId, fileName, thumbHash = null) => {
36069
36966
  const latestImageElement = this.scene.getElement(imagePlaceholder.id) ?? imagePlaceholder;
36070
36967
  return newElementWith10(
36071
36968
  latestImageElement,
36072
36969
  {
36073
36970
  fileId,
36074
- fileName
36971
+ fileName,
36972
+ thumbHash
36075
36973
  }
36076
36974
  );
36077
36975
  });
@@ -36142,7 +37040,23 @@ var App = class _App extends React40.Component {
36142
37040
  fileIds: elements.map((element) => element.fileId),
36143
37041
  files
36144
37042
  });
37043
+ const transitionStart = performance.now();
37044
+ for (const fileId of updatedFiles.keys()) {
37045
+ const cacheEntry = this.imageCache.get(fileId);
37046
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
37047
+ this.imageCache.set(fileId, {
37048
+ ...cacheEntry,
37049
+ placeholderImage: this.imageTransitionPlaceholders.get(fileId),
37050
+ transitionStart
37051
+ });
37052
+ }
37053
+ this.imageTransitionPlaceholders.delete(fileId);
37054
+ }
36145
37055
  if (erroredFiles.size) {
37056
+ for (const fileId of erroredFiles.keys()) {
37057
+ this.imageTransitionPlaceholders.delete(fileId);
37058
+ this.clearImageLoadingProgress(fileId);
37059
+ }
36146
37060
  this.store.scheduleAction(CaptureUpdateAction41.NEVER);
36147
37061
  this.scene.replaceAllElements(
36148
37062
  this.scene.getElementsIncludingDeleted().map((element) => {
@@ -36264,7 +37178,7 @@ var App = class _App extends React40.Component {
36264
37178
  const fileItems = dataTransferList.getFiles();
36265
37179
  if (fileItems.length === 1) {
36266
37180
  const { file: file2, fileHandle } = fileItems[0];
36267
- if (file2 && (file2.type === MIME_TYPES7.png || file2.type === MIME_TYPES7.svg)) {
37181
+ if (file2 && (file2.type === MIME_TYPES8.png || file2.type === MIME_TYPES8.svg)) {
36268
37182
  try {
36269
37183
  const scene = await loadFromBlob(
36270
37184
  file2,
@@ -36306,11 +37220,11 @@ var App = class _App extends React40.Component {
36306
37220
  if (item.kind !== "string") {
36307
37221
  return;
36308
37222
  }
36309
- if (item.type === MIME_TYPES7.html) {
37223
+ if (item.type === MIME_TYPES8.html) {
36310
37224
  try {
36311
37225
  const doc = new DOMParser().parseFromString(
36312
37226
  item.value,
36313
- MIME_TYPES7.html
37227
+ MIME_TYPES8.html
36314
37228
  );
36315
37229
  for (const img of Array.from(
36316
37230
  doc.body.querySelectorAll("img")
@@ -36320,7 +37234,7 @@ var App = class _App extends React40.Component {
36320
37234
  }
36321
37235
  } catch {
36322
37236
  }
36323
- } else if (item.type === MIME_TYPES7.text) {
37237
+ } else if (item.type === MIME_TYPES8.text) {
36324
37238
  pushUnique(textImageSources, item.value);
36325
37239
  }
36326
37240
  });
@@ -36369,7 +37283,7 @@ var App = class _App extends React40.Component {
36369
37283
  await this.loadFileToCanvas(file2, fileHandle);
36370
37284
  }
36371
37285
  }
36372
- const textItem = dataTransferList.findByType(MIME_TYPES7.text);
37286
+ const textItem = dataTransferList.findByType(MIME_TYPES8.text);
36373
37287
  if (textItem) {
36374
37288
  const text = textItem.value;
36375
37289
  if (text && embeddableURLValidator2(text, this.props.validateEmbeddable) && (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(text) || getEmbedLink2(text)?.type === "video")) {
@@ -37013,6 +37927,8 @@ var App = class _App extends React40.Component {
37013
37927
  applyDeltas: this.applyDeltas,
37014
37928
  mutateElement: this.mutateElement,
37015
37929
  addFiles: this.addFiles,
37930
+ addImagePlaceholder: this.addImagePlaceholder,
37931
+ setImageLoadingProgress: this.setImageLoadingProgress,
37016
37932
  addImageElementsToScene: this.addImageElementsToScene,
37017
37933
  resetScene: this.resetScene,
37018
37934
  getSceneElementsIncludingDeleted: this.getSceneElementsIncludingDeleted,
@@ -37592,7 +38508,7 @@ var App = class _App extends React40.Component {
37592
38508
  const isActive = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "active";
37593
38509
  const isHovered = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "hover";
37594
38510
  const shouldScaleEmbeddableViewport = src?.type === "video";
37595
- const embeddableViewportScale = clamp8(
38511
+ const embeddableViewportScale = clamp10(
37596
38512
  shouldScaleEmbeddableViewport ? scale : 1,
37597
38513
  0.75,
37598
38514
  MAX_EMBEDDABLE_VIEWPORT_SCALE
@@ -37878,6 +38794,7 @@ var App = class _App extends React40.Component {
37878
38794
  /* @__PURE__ */ jsx137(
37879
38795
  StaticCanvas_default,
37880
38796
  {
38797
+ app: this,
37881
38798
  canvas: this.canvas,
37882
38799
  rc: this.rc,
37883
38800
  elementsMap,
@@ -37889,6 +38806,7 @@ var App = class _App extends React40.Component {
37889
38806
  appState: this.state,
37890
38807
  renderConfig: {
37891
38808
  imageCache: this.imageCache,
38809
+ imageTransitionDuration: this.props.imageOptions.placeholderTransitionDuration,
37892
38810
  isExporting: false,
37893
38811
  renderGrid: isGridModeEnabled(this),
37894
38812
  canvasBackgroundColor: this.state.viewBackgroundColor,
@@ -37910,6 +38828,7 @@ var App = class _App extends React40.Component {
37910
38828
  allElementsMap,
37911
38829
  renderConfig: {
37912
38830
  imageCache: this.imageCache,
38831
+ imageTransitionDuration: this.props.imageOptions.placeholderTransitionDuration,
37913
38832
  isExporting: false,
37914
38833
  renderGrid: false,
37915
38834
  canvasBackgroundColor: this.state.viewBackgroundColor,
@@ -38177,6 +39096,10 @@ var App = class _App extends React40.Component {
38177
39096
  this.renderer = new Renderer(this.scene);
38178
39097
  this.files = {};
38179
39098
  this.imageCache.clear();
39099
+ this.imageLoadingProgress.clear();
39100
+ this.imageTransitionPlaceholders.clear();
39101
+ this.imageLoadingProgressEmitter.clear();
39102
+ this.imagePlaceholderUpdateEmitter.clear();
38180
39103
  this.resizeObserver?.disconnect();
38181
39104
  this.unmounted = true;
38182
39105
  this.removeEventListeners();
@@ -38581,7 +39504,7 @@ var App = class _App extends React40.Component {
38581
39504
  strokeColor: this.state.currentItemStrokeColor,
38582
39505
  backgroundColor: this.state.currentItemBackgroundColor,
38583
39506
  fillStyle: this.state.currentItemFillStyle,
38584
- strokeWidth: this.state.currentItemStrokeWidth,
39507
+ strokeWidth: this.getCurrentItemStrokeWidth("text"),
38585
39508
  strokeStyle: this.state.currentItemStrokeStyle,
38586
39509
  roundness: null,
38587
39510
  roughness: this.state.currentItemRoughness,
@@ -39160,6 +40083,12 @@ var App = class _App extends React40.Component {
39160
40083
  type: isUsingAdaptiveRadius4(elementType) ? ROUNDNESS7.ADAPTIVE_RADIUS : ROUNDNESS7.PROPORTIONAL_RADIUS
39161
40084
  } : null;
39162
40085
  }
40086
+ getCurrentItemStrokeWidth(elementType) {
40087
+ return getStrokeWidthByKey3(
40088
+ elementType,
40089
+ this.state.currentItemStrokeWidthKey
40090
+ );
40091
+ }
39163
40092
  maybeCacheReferenceSnapPoints(event, selectedElements, recomputeAnyways = false) {
39164
40093
  if (isSnappingEnabled({
39165
40094
  event,
@@ -39498,12 +40427,12 @@ var App = class _App extends React40.Component {
39498
40427
  );
39499
40428
  const nextCrop = {
39500
40429
  ...crop,
39501
- x: clamp8(
40430
+ x: clamp10(
39502
40431
  crop.x - offsetVector[0] * Math.sign(croppingElement.scale[0]),
39503
40432
  0,
39504
40433
  image.naturalWidth - crop.width
39505
40434
  ),
39506
- y: clamp8(
40435
+ y: clamp10(
39507
40436
  crop.y - offsetVector[1] * Math.sign(croppingElement.scale[1]),
39508
40437
  0,
39509
40438
  image.naturalHeight - crop.height
@@ -40056,9 +40985,6 @@ var App = class _App extends React40.Component {
40056
40985
  return;
40057
40986
  }
40058
40987
  if (isLinearElement11(newElement7)) {
40059
- if (newElement7.points.length > 1 && newElement7.points[1][0] !== 0 && newElement7.points[1][1] !== 0) {
40060
- this.store.scheduleCapture();
40061
- }
40062
40988
  const pointerCoords = viewportCoordsToSceneCoords3(
40063
40989
  childEvent,
40064
40990
  this.state
@@ -40086,21 +41012,13 @@ var App = class _App extends React40.Component {
40086
41012
  );
40087
41013
  this.actionManager.executeAction(actionFinalize);
40088
41014
  } else {
40089
- const dx = pointerCoords.x - newElement7.x;
40090
- const dy = pointerCoords.y - newElement7.y;
40091
- this.scene.mutateElement(
40092
- newElement7,
40093
- {
40094
- points: [newElement7.points[0], pointFrom32(dx, dy)]
40095
- },
40096
- { informMutation: false, isDragging: false }
40097
- );
40098
41015
  this.setState({
40099
41016
  multiElement: newElement7,
40100
41017
  newElement: newElement7
40101
41018
  });
40102
41019
  }
40103
41020
  } else if (pointerDownState.drag.hasOccurred && !multiElement) {
41021
+ this.store.scheduleCapture();
40104
41022
  if (isLinearElement11(newElement7)) {
40105
41023
  this.actionManager.executeAction(actionFinalize, "ui", {
40106
41024
  event: childEvent,
@@ -41220,7 +42138,7 @@ import { isLinearElement as isLinearElement12 } from "@excalidraw/element";
41220
42138
  import {
41221
42139
  FONT_FAMILY as FONT_FAMILY7,
41222
42140
  THEME as THEME18,
41223
- MIME_TYPES as MIME_TYPES8,
42141
+ MIME_TYPES as MIME_TYPES9,
41224
42142
  ROUNDNESS as ROUNDNESS8,
41225
42143
  DEFAULT_LASER_COLOR as DEFAULT_LASER_COLOR2,
41226
42144
  UserIdleState as UserIdleState2,
@@ -41333,7 +42251,8 @@ var ExcalidrawBase = (props) => {
41333
42251
  }
41334
42252
  const normalizedImageOptions = {
41335
42253
  maxFileSizeBytes: imageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes,
41336
- maxWidthOrHeight: imageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight
42254
+ maxWidthOrHeight: imageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight,
42255
+ placeholderTransitionDuration: imageOptions?.placeholderTransitionDuration ?? DEFAULT_IMAGE_OPTIONS.placeholderTransitionDuration
41337
42256
  };
41338
42257
  const setExcalidrawAPI = useContext4(ExcalidrawAPISetContext);
41339
42258
  const onExcalidrawAPIRef = useRef31(onExcalidrawAPI);
@@ -41452,7 +42371,7 @@ var areEqual5 = (prevProps, nextProps) => {
41452
42371
  }
41453
42372
  return prevUIOptions[key] === nextUIOptions[key];
41454
42373
  });
41455
- const isImageOptionsSame = (prevImageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight) === (nextImageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight) && (prevImageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes) === (nextImageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes);
42374
+ const isImageOptionsSame = (prevImageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight) === (nextImageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight) && (prevImageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes) === (nextImageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes) && (prevImageOptions?.placeholderTransitionDuration ?? DEFAULT_IMAGE_OPTIONS.placeholderTransitionDuration) === (nextImageOptions?.placeholderTransitionDuration ?? DEFAULT_IMAGE_OPTIONS.placeholderTransitionDuration);
41456
42375
  return isUIOptionsSame && isImageOptionsSame && isShallowEqual9(prev, next);
41457
42376
  };
41458
42377
  var Excalidraw = React42.memo(ExcalidrawBase, areEqual5);
@@ -41474,7 +42393,7 @@ export {
41474
42393
  Fonts,
41475
42394
  FooterCenter_default as Footer,
41476
42395
  LiveCollaborationTrigger_default as LiveCollaborationTrigger,
41477
- MIME_TYPES8 as MIME_TYPES,
42396
+ MIME_TYPES9 as MIME_TYPES,
41478
42397
  MainMenu_default as MainMenu,
41479
42398
  ROUNDNESS8 as ROUNDNESS,
41480
42399
  Sidebar,
@@ -41496,6 +42415,7 @@ export {
41496
42415
  getFormFactor2 as getFormFactor,
41497
42416
  getNonDeletedElements13 as getNonDeletedElements,
41498
42417
  getSceneVersion,
42418
+ getStrokeWidthByKey4 as getStrokeWidthByKey,
41499
42419
  getTextFromElements3 as getTextFromElements,
41500
42420
  getVisibleSceneBounds2 as getVisibleSceneBounds,
41501
42421
  hashElementsVersion,