@myoc/excalidraw 0.19.519 → 0.19.521

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 (89) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/dist/dev/{chunk-ZGRXNVW4.js → chunk-IU2VFRFU.js} +4 -1
  3. package/dist/dev/chunk-IU2VFRFU.js.map +7 -0
  4. package/dist/dev/{chunk-RIK6B6HD.js → chunk-MN6E45MP.js} +62 -26
  5. package/dist/dev/chunk-MN6E45MP.js.map +7 -0
  6. package/dist/dev/{chunk-MCPNWEHU.js → chunk-P7I4FGOG.js} +2 -2
  7. package/dist/dev/data/{image-IRC25PM5.js → image-ZYGVHYLC.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 +1454 -452
  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-VHQT4IVX.js → chunk-3UXKIWVY.js} +2 -2
  16. package/dist/prod/chunk-BNYGVLQX.js +4 -0
  17. package/dist/prod/{chunk-QW7MIEK6.js → chunk-ZYOK2JIN.js} +1 -1
  18. package/dist/prod/data/image-OFIWDJ6Z.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/clipboard.d.ts +34 -0
  60. package/dist/types/excalidraw/components/App.d.ts +12 -28
  61. package/dist/types/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
  62. package/dist/types/excalidraw/components/ConvertElementTypePopup.d.ts +1 -1
  63. package/dist/types/excalidraw/components/EyeDropper.d.ts +1 -1
  64. package/dist/types/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
  65. package/dist/types/excalidraw/components/SearchMenu.d.ts +1 -1
  66. package/dist/types/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  67. package/dist/types/excalidraw/components/canvases/StaticCanvas.d.ts +2 -1
  68. package/dist/types/excalidraw/components/icons.d.ts +2 -0
  69. package/dist/types/excalidraw/data/blob.d.ts +2 -1
  70. package/dist/types/excalidraw/data/json.d.ts +2 -1
  71. package/dist/types/excalidraw/index.d.ts +4 -2
  72. package/dist/types/excalidraw/renderer/animation.d.ts +1 -0
  73. package/dist/types/excalidraw/scene/Renderer.d.ts +2 -0
  74. package/dist/types/excalidraw/scene/index.d.ts +1 -1
  75. package/dist/types/excalidraw/scene/types.d.ts +1 -0
  76. package/dist/types/excalidraw/scroll.d.ts +46 -0
  77. package/dist/types/excalidraw/types.d.ts +18 -3
  78. package/dist/types/laser-pointer/src/index.d.ts +2 -0
  79. package/dist/types/laser-pointer/src/math.d.ts +16 -0
  80. package/dist/types/laser-pointer/src/simplify.d.ts +2 -0
  81. package/dist/types/laser-pointer/src/state.d.ts +35 -0
  82. package/package.json +4 -4
  83. package/dist/dev/chunk-RIK6B6HD.js.map +0 -7
  84. package/dist/dev/chunk-ZGRXNVW4.js.map +0 -7
  85. package/dist/prod/chunk-FL5QOM6C.js +0 -4
  86. package/dist/prod/data/image-7LG744DI.js +0 -1
  87. /package/dist/dev/{chunk-MCPNWEHU.js.map → chunk-P7I4FGOG.js.map} +0 -0
  88. /package/dist/dev/data/{image-IRC25PM5.js.map → image-ZYGVHYLC.js.map} +0 -0
  89. /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-MN6E45MP.js";
61
62
  import {
62
63
  define_import_meta_env_default
63
- } from "./chunk-MCPNWEHU.js";
64
+ } from "./chunk-P7I4FGOG.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,
@@ -9352,6 +9558,133 @@ var getDataTransferFiles = function() {
9352
9558
  (item) => item.kind === "file"
9353
9559
  );
9354
9560
  };
9561
+ var captureDragEventData = (event) => {
9562
+ const dataTransfer = isClipboardEvent(event) ? event.clipboardData : event.dataTransfer;
9563
+ const types = Array.from(dataTransfer?.types ?? []);
9564
+ const dataTypes = Array.from(
9565
+ /* @__PURE__ */ new Set([
9566
+ ...types.filter((type) => type !== "Files"),
9567
+ MIME_TYPES2.text,
9568
+ MIME_TYPES2.html,
9569
+ "text/uri-list",
9570
+ "public.url",
9571
+ "public.plain-text",
9572
+ "text/x-moz-url"
9573
+ ])
9574
+ );
9575
+ const data = {};
9576
+ for (const type of dataTypes) {
9577
+ try {
9578
+ const value = dataTransfer?.getData(type);
9579
+ if (value) {
9580
+ data[type] = value;
9581
+ }
9582
+ } catch {
9583
+ }
9584
+ }
9585
+ return {
9586
+ items: Array.from(dataTransfer?.items ?? []).map((item) => ({
9587
+ kind: item.kind,
9588
+ type: item.type
9589
+ })),
9590
+ files: Array.from(dataTransfer?.files ?? []).map((file2) => ({
9591
+ name: file2.name,
9592
+ type: file2.type,
9593
+ size: file2.size,
9594
+ lastModified: file2.lastModified
9595
+ })),
9596
+ types,
9597
+ data
9598
+ };
9599
+ };
9600
+ var getParsedRawDragItems = (dataTransferList) => dataTransferList.map((item) => {
9601
+ if (item.kind === "string") {
9602
+ return {
9603
+ kind: item.kind,
9604
+ type: item.type,
9605
+ value: item.value
9606
+ };
9607
+ }
9608
+ return {
9609
+ kind: item.kind,
9610
+ type: item.type,
9611
+ file: {
9612
+ name: item.file.name,
9613
+ type: item.file.type,
9614
+ size: item.file.size,
9615
+ lastModified: item.file.lastModified
9616
+ }
9617
+ };
9618
+ });
9619
+ var parseDragImageMetadata = (dataTransferList, imageCount, dragDataSnapshot) => {
9620
+ const htmlImageSources = [];
9621
+ const textImageSources = [];
9622
+ const imageAlts = [];
9623
+ const pushUnique = (values, value) => {
9624
+ const trimmedValue = value?.trim();
9625
+ if (trimmedValue && !values.includes(trimmedValue)) {
9626
+ values.push(trimmedValue);
9627
+ }
9628
+ };
9629
+ const pushUriList = (values, value) => {
9630
+ value.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).forEach((line) => pushUnique(values, line));
9631
+ };
9632
+ dataTransferList.forEach((item) => {
9633
+ if (item.kind !== "string") {
9634
+ return;
9635
+ }
9636
+ if (item.type === MIME_TYPES2.html) {
9637
+ try {
9638
+ const doc = new DOMParser().parseFromString(item.value, MIME_TYPES2.html);
9639
+ for (const img of Array.from(doc.body.querySelectorAll("img"))) {
9640
+ pushUnique(htmlImageSources, img.getAttribute("src"));
9641
+ pushUnique(imageAlts, img.getAttribute("alt"));
9642
+ }
9643
+ } catch {
9644
+ }
9645
+ } else if (item.type === MIME_TYPES2.text) {
9646
+ pushUnique(textImageSources, item.value);
9647
+ } else if (item.type === "text/uri-list") {
9648
+ pushUriList(textImageSources, item.value);
9649
+ }
9650
+ });
9651
+ const imageSources = [
9652
+ ...htmlImageSources,
9653
+ ...textImageSources.filter((src) => !htmlImageSources.includes(src))
9654
+ ];
9655
+ const imageDetails = Array.from(
9656
+ { length: imageCount },
9657
+ () => ({})
9658
+ );
9659
+ const rawDragData = {
9660
+ ...dragDataSnapshot ?? {
9661
+ items: [],
9662
+ files: [],
9663
+ types: [],
9664
+ data: {}
9665
+ },
9666
+ parsedItems: getParsedRawDragItems(dataTransferList)
9667
+ };
9668
+ imageDetails.forEach((detail, index) => {
9669
+ const source = imageSources[index];
9670
+ const alt = imageAlts[index];
9671
+ if (source) {
9672
+ detail.src = source;
9673
+ }
9674
+ if (alt) {
9675
+ detail.alt = alt;
9676
+ }
9677
+ detail.rawDragData = rawDragData;
9678
+ });
9679
+ const additionalUrls = imageSources.slice(imageDetails.length);
9680
+ const firstImageDetails = imageDetails[0];
9681
+ if (firstImageDetails && additionalUrls.length > 0) {
9682
+ additionalUrls.forEach((url, index) => {
9683
+ firstImageDetails[`additionalUrl${index + 1}`] = url;
9684
+ });
9685
+ }
9686
+ return imageDetails;
9687
+ };
9355
9688
  var parseDataTransferEventMimeTypes = (event) => {
9356
9689
  let items = void 0;
9357
9690
  if (isClipboardEvent(event)) {
@@ -9617,7 +9950,7 @@ var exportCanvas = async (type, elements, appState, files, {
9617
9950
  let blob = canvasToBlob(tempCanvas);
9618
9951
  if (appState.exportEmbedScene) {
9619
9952
  blob = blob.then(
9620
- (blob2) => import("./data/image-IRC25PM5.js").then(
9953
+ (blob2) => import("./data/image-ZYGVHYLC.js").then(
9621
9954
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9622
9955
  blob: blob2,
9623
9956
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -11812,7 +12145,8 @@ import {
11812
12145
  TEXT_ALIGN,
11813
12146
  VERTICAL_ALIGN as VERTICAL_ALIGN2,
11814
12147
  arrayToMap as arrayToMap13,
11815
- getFontString
12148
+ getFontString,
12149
+ getStrokeWidthByKey as getStrokeWidthByKey2
11816
12150
  } from "@excalidraw/common";
11817
12151
  import {
11818
12152
  getOriginalContainerHeightFromCache,
@@ -11999,7 +12333,10 @@ var actionWrapTextInContainer = register({
11999
12333
  fillStyle: appState.currentItemFillStyle,
12000
12334
  strokeColor: appState.currentItemStrokeColor,
12001
12335
  roughness: appState.currentItemRoughness,
12002
- strokeWidth: appState.currentItemStrokeWidth,
12336
+ strokeWidth: getStrokeWidthByKey2(
12337
+ "rectangle",
12338
+ appState.currentItemStrokeWidthKey
12339
+ ),
12003
12340
  strokeStyle: appState.currentItemStrokeStyle,
12004
12341
  roundness: appState.currentItemRoundness === "round" ? {
12005
12342
  type: isUsingAdaptiveRadius2("rectangle") ? ROUNDNESS2.ADAPTIVE_RADIUS : ROUNDNESS2.PROPORTIONAL_RADIUS
@@ -12647,6 +12984,355 @@ import {
12647
12984
  import { simplify } from "points-on-curve";
12648
12985
  import { getStroke } from "perfect-freehand";
12649
12986
 
12987
+ // ../laser-pointer/src/math.ts
12988
+ function add([ax, ay, ar], [bx, by, br]) {
12989
+ return [ax + bx, ay + by, ar + br];
12990
+ }
12991
+ function sub([ax, ay, ar], [bx, by, br]) {
12992
+ return [ax - bx, ay - by, ar - br];
12993
+ }
12994
+ function smul([x, y, r], s) {
12995
+ return [x * s, y * s, r * s];
12996
+ }
12997
+ function norm([x, y, r]) {
12998
+ return [x / Math.sqrt(x ** 2 + y ** 2), y / Math.sqrt(x ** 2 + y ** 2), r];
12999
+ }
13000
+ function rot([x, y, r], rad) {
13001
+ return [
13002
+ Math.cos(rad) * x - Math.sin(rad) * y,
13003
+ Math.sin(rad) * x + Math.cos(rad) * y,
13004
+ r
13005
+ ];
13006
+ }
13007
+ function plerp(a, b, t2) {
13008
+ return add(a, smul(sub(b, a), t2));
13009
+ }
13010
+ function angle(p, p1, p2) {
13011
+ return Math.atan2(p2[1] - p[1], p2[0] - p[0]) - Math.atan2(p1[1] - p[1], p1[0] - p[0]);
13012
+ }
13013
+ function normAngle(a) {
13014
+ return Math.atan2(Math.sin(a), Math.cos(a));
13015
+ }
13016
+ function mag([x, y]) {
13017
+ return Math.sqrt(x ** 2 + y ** 2);
13018
+ }
13019
+ function dist([ax, ay], [bx, by]) {
13020
+ return Math.sqrt((bx - ax) ** 2 + (by - ay) ** 2);
13021
+ }
13022
+ function runLength(ps) {
13023
+ if (ps.length < 2) {
13024
+ return 0;
13025
+ }
13026
+ let len = 0;
13027
+ for (let i = 1; i <= ps.length - 1; i++) {
13028
+ len += dist(ps[i - 1], ps[i]);
13029
+ }
13030
+ len += dist(ps[ps.length - 2], ps[ps.length - 1]);
13031
+ return len;
13032
+ }
13033
+ var clamp2 = (v, min, max) => Math.max(min, Math.min(max, v));
13034
+ function distancePointToSegment(p3, p1, p2) {
13035
+ const sMag = dist(p1, p2);
13036
+ if (sMag === 0) {
13037
+ return dist(p3, p1);
13038
+ }
13039
+ const u = clamp2(
13040
+ ((p3[0] - p1[0]) * (p2[0] - p1[0]) + (p3[1] - p1[1]) * (p2[1] - p1[1])) / sMag ** 2,
13041
+ 0,
13042
+ 1
13043
+ );
13044
+ const pi = [
13045
+ p1[0] + u * (p2[0] - p1[0]),
13046
+ p1[1] + u * (p2[1] - p1[1]),
13047
+ p3[2]
13048
+ ];
13049
+ return dist(pi, p3);
13050
+ }
13051
+
13052
+ // ../laser-pointer/src/simplify.ts
13053
+ function douglasPeucker(points, epsilon) {
13054
+ if (epsilon === 0) {
13055
+ return points;
13056
+ }
13057
+ if (points.length <= 2) {
13058
+ return points;
13059
+ }
13060
+ const first = points[0];
13061
+ const last = points[points.length - 1];
13062
+ const [maxDistance, maxIndex] = points.reduce(
13063
+ ([maxDistance2, maxIndex2], point, index) => {
13064
+ const distance3 = distancePointToSegment(point, first, last);
13065
+ return distance3 > maxDistance2 ? [distance3, index] : [maxDistance2, maxIndex2];
13066
+ },
13067
+ [0, -1]
13068
+ );
13069
+ if (maxDistance >= epsilon) {
13070
+ const maxIndexPoint = points[maxIndex];
13071
+ return [
13072
+ ...douglasPeucker(
13073
+ [first, ...points.slice(1, maxIndex), maxIndexPoint],
13074
+ epsilon
13075
+ ).slice(0, -1),
13076
+ maxIndexPoint,
13077
+ ...douglasPeucker(
13078
+ [maxIndexPoint, ...points.slice(maxIndex, -1), last],
13079
+ epsilon
13080
+ ).slice(1)
13081
+ ];
13082
+ }
13083
+ return [first, last];
13084
+ }
13085
+
13086
+ // ../laser-pointer/src/state.ts
13087
+ var _LaserPointer = class _LaserPointer {
13088
+ constructor(options) {
13089
+ __publicField(this, "options");
13090
+ __publicField(this, "originalPoints", []);
13091
+ __publicField(this, "stablePoints", []);
13092
+ __publicField(this, "tailPoints", []);
13093
+ __publicField(this, "isFresh", true);
13094
+ this.options = Object.assign({}, _LaserPointer.defaults, options);
13095
+ }
13096
+ get lastPoint() {
13097
+ return this.tailPoints[this.tailPoints.length - 1] ?? this.stablePoints[this.stablePoints.length - 1];
13098
+ }
13099
+ addPoint(point) {
13100
+ const lastPoint = this.originalPoints[this.originalPoints.length - 1];
13101
+ if (lastPoint && lastPoint[0] === point[0] && lastPoint[1] === point[1]) {
13102
+ return;
13103
+ }
13104
+ this.originalPoints.push(point);
13105
+ if (this.isFresh) {
13106
+ this.isFresh = false;
13107
+ this.stablePoints.push(point);
13108
+ return;
13109
+ }
13110
+ if (this.options.streamline > 0) {
13111
+ point = plerp(this.lastPoint, point, 1 - this.options.streamline);
13112
+ }
13113
+ this.tailPoints.push(point);
13114
+ if (runLength(this.tailPoints) > _LaserPointer.constants.maxTailLength) {
13115
+ this.stabilizeTail();
13116
+ }
13117
+ }
13118
+ close() {
13119
+ this.stabilizeTail();
13120
+ }
13121
+ stabilizeTail() {
13122
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "tail") {
13123
+ throw new Error("Not implemented yet");
13124
+ } else {
13125
+ this.stablePoints.push(...this.tailPoints);
13126
+ this.tailPoints = [];
13127
+ }
13128
+ }
13129
+ getSize(sizeOverride, pressure, index, totalLength, runningLength) {
13130
+ return (sizeOverride ?? this.options.size) * this.options.sizeMapping({
13131
+ pressure,
13132
+ runningLength,
13133
+ currentIndex: index,
13134
+ totalLength
13135
+ });
13136
+ }
13137
+ getStrokeOutline(sizeOverride) {
13138
+ if (this.isFresh) {
13139
+ return [];
13140
+ }
13141
+ let points = [...this.stablePoints, ...this.tailPoints];
13142
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "input") {
13143
+ points = douglasPeucker(points, this.options.simplify);
13144
+ }
13145
+ const len = points.length;
13146
+ if (len === 0) {
13147
+ return [];
13148
+ }
13149
+ if (len === 1) {
13150
+ const c = points[0];
13151
+ const size = this.getSize(sizeOverride, c[2], 0, len, 0);
13152
+ if (size < 0.5) {
13153
+ return [];
13154
+ }
13155
+ const ps = [];
13156
+ for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
13157
+ ps.push(add(c, smul(rot([1, 0, 0], theta), size)));
13158
+ }
13159
+ ps.push(
13160
+ add(
13161
+ c,
13162
+ smul(
13163
+ [1, 0, 0],
13164
+ this.getSize(sizeOverride, c[2], 0, len, 0)
13165
+ )
13166
+ )
13167
+ );
13168
+ return ps;
13169
+ }
13170
+ if (len === 2) {
13171
+ const c = points[0];
13172
+ const n = points[1];
13173
+ const cSize = this.getSize(sizeOverride, c[2], 0, len, 0);
13174
+ const nSize = this.getSize(sizeOverride, n[2], 0, len, 0);
13175
+ if (cSize < 0.5 || nSize < 0.5) {
13176
+ return [];
13177
+ }
13178
+ const ps = [];
13179
+ const pAngle = angle(c, [c[0], c[1] - 100, c[2]], n);
13180
+ for (let theta = pAngle; theta <= Math.PI + pAngle; theta += Math.PI / 16) {
13181
+ ps.push(add(c, smul(rot([1, 0, 0], theta), cSize)));
13182
+ }
13183
+ for (let theta = Math.PI + pAngle; theta <= Math.PI * 2 + pAngle; theta += Math.PI / 16) {
13184
+ ps.push(add(n, smul(rot([1, 0, 0], theta), nSize)));
13185
+ }
13186
+ ps.push(ps[0]);
13187
+ return ps;
13188
+ }
13189
+ const forwardPoints = [];
13190
+ const backwardPoints = [];
13191
+ let speed = 0;
13192
+ let prevSpeed = 0;
13193
+ let visibleStartIndex = 0;
13194
+ let runningLength = 0;
13195
+ for (let i = 1; i < len - 1; i++) {
13196
+ const p = points[i - 1];
13197
+ const c = points[i];
13198
+ const n = points[i + 1];
13199
+ const pressure = c[2];
13200
+ const d = dist(p, c);
13201
+ runningLength += d;
13202
+ speed = prevSpeed + (d - prevSpeed) * 0.2;
13203
+ const cSize = this.getSize(sizeOverride, pressure, i, len, runningLength);
13204
+ if (cSize === 0) {
13205
+ visibleStartIndex = i + 1;
13206
+ continue;
13207
+ }
13208
+ const dirPC = norm(sub(p, c));
13209
+ const dirNC = norm(sub(n, c));
13210
+ const p1dirPC = rot(dirPC, Math.PI / 2);
13211
+ const p2dirPC = rot(dirPC, -Math.PI / 2);
13212
+ const p1dirNC = rot(dirNC, Math.PI / 2);
13213
+ const p2dirNC = rot(dirNC, -Math.PI / 2);
13214
+ const p1PC = add(c, smul(p1dirPC, cSize));
13215
+ const p2PC = add(c, smul(p2dirPC, cSize));
13216
+ const p1NC = add(c, smul(p1dirNC, cSize));
13217
+ const p2NC = add(c, smul(p2dirNC, cSize));
13218
+ const ftdir = add(p1dirPC, p2dirNC);
13219
+ const btdir = add(p2dirPC, p1dirNC);
13220
+ const paPC = add(
13221
+ c,
13222
+ smul(mag(ftdir) === 0 ? dirPC : norm(ftdir), cSize)
13223
+ );
13224
+ const paNC = add(
13225
+ c,
13226
+ smul(mag(btdir) === 0 ? dirNC : norm(btdir), cSize)
13227
+ );
13228
+ const cAngle = normAngle(angle(c, p, n));
13229
+ const D_ANGLE = _LaserPointer.constants.cornerDetectionMaxAngle / 180 * Math.PI * _LaserPointer.constants.cornerDetectionVariance(speed);
13230
+ if (Math.abs(cAngle) < D_ANGLE) {
13231
+ const tAngle = Math.abs(normAngle(Math.PI - cAngle));
13232
+ if (tAngle === 0) {
13233
+ continue;
13234
+ }
13235
+ if (cAngle < 0) {
13236
+ backwardPoints.push(p2PC, paNC);
13237
+ for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
13238
+ forwardPoints.push(add(c, rot(smul(p1dirPC, cSize), theta)));
13239
+ }
13240
+ for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
13241
+ backwardPoints.push(add(c, rot(smul(p1dirPC, cSize), theta)));
13242
+ }
13243
+ backwardPoints.push(paNC, p1NC);
13244
+ } else {
13245
+ forwardPoints.push(p1PC, paPC);
13246
+ for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
13247
+ backwardPoints.push(
13248
+ add(c, rot(smul(p1dirPC, -cSize), -theta))
13249
+ );
13250
+ }
13251
+ for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
13252
+ forwardPoints.push(
13253
+ add(c, rot(smul(p1dirPC, -cSize), -theta))
13254
+ );
13255
+ }
13256
+ forwardPoints.push(paPC, p2NC);
13257
+ }
13258
+ } else {
13259
+ forwardPoints.push(paPC);
13260
+ backwardPoints.push(paNC);
13261
+ }
13262
+ prevSpeed = speed;
13263
+ }
13264
+ if (visibleStartIndex >= len - 2) {
13265
+ if (this.options.keepHead) {
13266
+ const c = points[len - 1];
13267
+ const ps = [];
13268
+ for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
13269
+ ps.push(
13270
+ add(
13271
+ c,
13272
+ smul(rot([1, 0, 0], theta), this.options.size)
13273
+ )
13274
+ );
13275
+ }
13276
+ ps.push(add(c, smul([1, 0, 0], this.options.size)));
13277
+ return ps;
13278
+ }
13279
+ return [];
13280
+ }
13281
+ const first = points[visibleStartIndex];
13282
+ const second = points[visibleStartIndex + 1];
13283
+ const penultimate = points[len - 2];
13284
+ const ultimate = points[len - 1];
13285
+ const dirFS = norm(sub(second, first));
13286
+ const dirPU = norm(sub(penultimate, ultimate));
13287
+ const ppdirFS = rot(dirFS, -Math.PI / 2);
13288
+ const ppdirPU = rot(dirPU, Math.PI / 2);
13289
+ const startCapSize = this.getSize(sizeOverride, first[2], 0, len, 0);
13290
+ const startCap = [];
13291
+ const endCapSize = this.options.keepHead ? this.options.size : this.getSize(sizeOverride, penultimate[2], len - 2, len, runningLength);
13292
+ const endCap = [];
13293
+ if (startCapSize > 0.1) {
13294
+ for (let theta = 0; theta <= Math.PI; theta += Math.PI / 16) {
13295
+ startCap.unshift(
13296
+ add(first, rot(smul(ppdirFS, startCapSize), -theta))
13297
+ );
13298
+ }
13299
+ startCap.unshift(add(first, smul(ppdirFS, -startCapSize)));
13300
+ } else {
13301
+ startCap.push(first);
13302
+ }
13303
+ for (let theta = 0; theta <= Math.PI * 3; theta += Math.PI / 16) {
13304
+ endCap.push(add(ultimate, rot(smul(ppdirPU, -endCapSize), -theta)));
13305
+ }
13306
+ const strokeOutline = [
13307
+ ...startCap,
13308
+ ...forwardPoints,
13309
+ ...endCap.reverse(),
13310
+ ...backwardPoints.reverse()
13311
+ ];
13312
+ if (startCap.length > 0) {
13313
+ strokeOutline.push(startCap[0]);
13314
+ }
13315
+ if (this.options.simplify > 0 && this.options.simplifyPhase === "output") {
13316
+ return douglasPeucker(strokeOutline, this.options.simplify);
13317
+ }
13318
+ return strokeOutline;
13319
+ }
13320
+ };
13321
+ __publicField(_LaserPointer, "defaults", {
13322
+ size: 2,
13323
+ streamline: 0.45,
13324
+ simplify: 0.1,
13325
+ simplifyPhase: "output",
13326
+ keepHead: false,
13327
+ sizeMapping: () => 1
13328
+ });
13329
+ __publicField(_LaserPointer, "constants", {
13330
+ cornerDetectionMaxAngle: 75,
13331
+ cornerDetectionVariance: (s) => s > 35 ? 0.5 : 1,
13332
+ maxTailLength: 50
13333
+ });
13334
+ var LaserPointer = _LaserPointer;
13335
+
12650
13336
  // ../utils/src/shape.ts
12651
13337
  import { pointsOnBezierCurves } from "points-on-curve";
12652
13338
  import { invariant as invariant4 } from "@excalidraw/common";
@@ -12693,7 +13379,8 @@ import {
12693
13379
  assertNever as assertNever2,
12694
13380
  COLOR_PALETTE as COLOR_PALETTE3,
12695
13381
  LINE_POLYGON_POINT_MERGE_DISTANCE,
12696
- applyDarkModeFilter as applyDarkModeFilter2
13382
+ applyDarkModeFilter as applyDarkModeFilter2,
13383
+ DEFAULT_STROKE_STREAMLINE
12697
13384
  } from "@excalidraw/common";
12698
13385
  import { RoughGenerator } from "roughjs/bin/generator";
12699
13386
 
@@ -12711,7 +13398,7 @@ import {
12711
13398
  ELEMENT_READY_TO_ERASE_OPACITY,
12712
13399
  FRAME_STYLE,
12713
13400
  DARK_THEME_FILTER,
12714
- MIME_TYPES as MIME_TYPES4,
13401
+ MIME_TYPES as MIME_TYPES5,
12715
13402
  THEME as THEME9,
12716
13403
  distance,
12717
13404
  getFontString as getFontString4,
@@ -12770,7 +13457,7 @@ import {
12770
13457
  } from "@excalidraw/common";
12771
13458
  import {
12772
13459
  PRECISION as PRECISION2,
12773
- clamp as clamp3,
13460
+ clamp as clamp4,
12774
13461
  lineSegment as lineSegment4,
12775
13462
  pointDistance as pointDistance4,
12776
13463
  pointDistanceSq,
@@ -12968,7 +13655,7 @@ var headingIsHorizontal = (a) => compareHeading(a, HEADING_RIGHT) || compareHead
12968
13655
 
12969
13656
  // ../element/src/elbowArrow.ts
12970
13657
  import {
12971
- clamp as clamp2,
13658
+ clamp as clamp3,
12972
13659
  pointDistance as pointDistance3,
12973
13660
  pointFrom as pointFrom9,
12974
13661
  pointScaleFromOrigin as pointScaleFromOrigin2,
@@ -13005,6 +13692,7 @@ import { pointsEqual as pointsEqual6 } from "@excalidraw/math";
13005
13692
 
13006
13693
  // ../element/src/zindex.ts
13007
13694
  import { arrayToMap as arrayToMap20, findIndex, findLastIndex } from "@excalidraw/common";
13695
+ import { isFiniteNumber } from "@excalidraw/math";
13008
13696
 
13009
13697
  // ../element/src/selection.ts
13010
13698
  import { arrayToMap as arrayToMap19, isShallowEqual } from "@excalidraw/common";
@@ -13276,29 +13964,29 @@ var getArrowheadPoints = (element, shape, position, arrowhead, offsetMultiplier
13276
13964
  const diameter = Math.hypot(ys - ty, xs - tx) + element.strokeWidth - 2;
13277
13965
  return [tx, ty, diameter];
13278
13966
  }
13279
- const angle = getArrowheadAngle(arrowhead);
13967
+ const angle2 = getArrowheadAngle(arrowhead);
13280
13968
  if (arrowhead === "cardinality_many" || arrowhead === "cardinality_one_or_many") {
13281
13969
  const [x32, y32] = pointRotateRads9(
13282
13970
  pointFrom13(tx, ty),
13283
13971
  pointFrom13(xs, ys),
13284
- degreesToRadians(-angle)
13972
+ degreesToRadians(-angle2)
13285
13973
  );
13286
13974
  const [x42, y42] = pointRotateRads9(
13287
13975
  pointFrom13(tx, ty),
13288
13976
  pointFrom13(xs, ys),
13289
- degreesToRadians(angle)
13977
+ degreesToRadians(angle2)
13290
13978
  );
13291
13979
  return [xs, ys, x32, y32, x42, y42];
13292
13980
  }
13293
13981
  const [x3, y3] = pointRotateRads9(
13294
13982
  pointFrom13(xs, ys),
13295
13983
  pointFrom13(tx, ty),
13296
- -angle * Math.PI / 180
13984
+ -angle2 * Math.PI / 180
13297
13985
  );
13298
13986
  const [x4, y4] = pointRotateRads9(
13299
13987
  pointFrom13(xs, ys),
13300
13988
  pointFrom13(tx, ty),
13301
- degreesToRadians(angle)
13989
+ degreesToRadians(angle2)
13302
13990
  );
13303
13991
  if (arrowhead === "diamond" || arrowhead === "diamond_outline") {
13304
13992
  let ox;
@@ -13334,17 +14022,21 @@ import {
13334
14022
  vectorAdd as vectorAdd2,
13335
14023
  vectorScale as vectorScale7,
13336
14024
  pointFromVector as pointFromVector6,
13337
- clamp as clamp4,
14025
+ clamp as clamp5,
13338
14026
  isCloseTo
13339
14027
  } from "@excalidraw/math";
13340
14028
 
14029
+ // ../element/src/image.ts
14030
+ import { MIME_TYPES as MIME_TYPES4, SVG_NS } from "@excalidraw/common";
14031
+ import { rgbaToThumbHash, thumbHashToRGBA } from "thumbhash";
14032
+
13341
14033
  // ../element/src/renderElement.ts
13342
14034
  var IMAGE_PLACEHOLDER_IMG = typeof document !== "undefined" ? document.createElement("img") : { src: "" };
13343
- IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES4.svg},${encodeURIComponent(
14035
+ IMAGE_PLACEHOLDER_IMG.src = `data:${MIME_TYPES5.svg},${encodeURIComponent(
13344
14036
  `<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
14037
  )}`;
13346
14038
  var IMAGE_ERROR_PLACEHOLDER_IMG = typeof document !== "undefined" ? document.createElement("img") : { src: "" };
13347
- IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES4.svg},${encodeURIComponent(
14039
+ IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES5.svg},${encodeURIComponent(
13348
14040
  `<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
14041
  )}`;
13350
14042
  var elementWithCanvasCache = /* @__PURE__ */ new WeakMap();
@@ -13948,19 +14640,46 @@ var getFreeDrawSvgPath = (element) => {
13948
14640
  getFreedrawOutlinePoints(element)
13949
14641
  );
13950
14642
  };
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]];
14643
+ var VARIABLE_WIDTH_FREEDRAW = {
14644
+ /** Stroke size relative to `strokeWidth` for pressure-sensitive strokes. */
14645
+ SIZE_FACTOR: 4.25,
14646
+ THINNING: 0.6,
14647
+ SMOOTHING: 0.5
14648
+ };
14649
+ var CONSTANT_WIDTH_FREEDRAW = {
14650
+ /** Stroke size relative to `strokeWidth` for uniform (laser) strokes. */
14651
+ SIZE_FACTOR: 1.4
14652
+ };
14653
+ var getFreedrawStreamline = (element) => element.strokeOptions?.streamline ?? DEFAULT_STROKE_STREAMLINE;
14654
+ var getVariableWidthFreedrawOutline = (element) => {
14655
+ const inputPoints = element.simulatePressure ? element.points : element.points.length ? element.points.map(
14656
+ ([x, y], i) => [x, y, element.pressures[i]]
14657
+ ) : [[0, 0, 0.5]];
13953
14658
  return getStroke(inputPoints, {
13954
14659
  simulatePressure: element.simulatePressure,
13955
- size: element.strokeWidth * 4.25,
13956
- thinning: 0.6,
13957
- smoothing: 0.5,
13958
- streamline: 0.5,
14660
+ size: element.strokeWidth * VARIABLE_WIDTH_FREEDRAW.SIZE_FACTOR,
14661
+ thinning: VARIABLE_WIDTH_FREEDRAW.THINNING,
14662
+ smoothing: VARIABLE_WIDTH_FREEDRAW.SMOOTHING,
14663
+ streamline: getFreedrawStreamline(element),
13959
14664
  easing: (t2) => Math.sin(t2 * Math.PI / 2),
13960
14665
  // https://easings.net/#easeOutSine
13961
14666
  last: true
13962
14667
  });
13963
14668
  };
14669
+ var createLaserPointer = (element) => new LaserPointer({
14670
+ size: element.strokeWidth * CONSTANT_WIDTH_FREEDRAW.SIZE_FACTOR,
14671
+ streamline: getFreedrawStreamline(element),
14672
+ simplify: 0,
14673
+ sizeMapping: (details) => Math.max(0.1, details.pressure)
14674
+ });
14675
+ var getConstantWidthFreedrawOutline = (element) => {
14676
+ const laserPointer = createLaserPointer(element);
14677
+ element.points.map(([x, y]) => laserPointer.addPoint([x, y, 1]));
14678
+ return laserPointer.getStrokeOutline().map(([x, y]) => [x, y]);
14679
+ };
14680
+ var getFreedrawOutlinePoints = (element) => {
14681
+ return element.strokeOptions?.variability === "constant" ? getConstantWidthFreedrawOutline(element) : getVariableWidthFreedrawOutline(element);
14682
+ };
13964
14683
  var med = (A, B) => {
13965
14684
  return [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
13966
14685
  };
@@ -16170,56 +16889,9 @@ var Renderer = class {
16170
16889
  }
16171
16890
  };
16172
16891
 
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";
16892
+ // scroll.ts
16893
+ import { easeOut } from "@excalidraw/common";
16894
+ import { clamp as clamp6 } from "@excalidraw/math";
16223
16895
 
16224
16896
  // reactUtils.ts
16225
16897
  import { version as ReactVersion } from "react";
@@ -16345,12 +17017,139 @@ var _AnimationController = class _AnimationController {
16345
17017
  _AnimationController.animations.delete(key);
16346
17018
  _AnimationController.cancelScheduledFrameIfIdle();
16347
17019
  }
17020
+ static reset() {
17021
+ _AnimationController.animations.clear();
17022
+ _AnimationController.cancelScheduledFrame();
17023
+ }
16348
17024
  };
16349
17025
  __publicField(_AnimationController, "scheduledFrame", null);
16350
17026
  __publicField(_AnimationController, "animations", /* @__PURE__ */ new Map());
16351
17027
  var AnimationController = _AnimationController;
16352
17028
 
17029
+ // scroll.ts
17030
+ var SCROLL_TO_CONTENT_ANIMATION_KEY = "animateScrollToContent";
17031
+ var DEFAULT_ANIMATION_DURATION = 500;
17032
+ var scrollToElements = (state, target, onFrame, opts) => {
17033
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
17034
+ const viewport = getTargetViewport(state, target, opts);
17035
+ if (opts?.animate) {
17036
+ animateToViewport(
17037
+ state,
17038
+ viewport,
17039
+ opts.duration ?? DEFAULT_ANIMATION_DURATION,
17040
+ onFrame
17041
+ );
17042
+ } else {
17043
+ onFrame({ ...viewport, shouldCacheIgnoreZoom: false });
17044
+ }
17045
+ };
17046
+ var getTargetViewport = (state, targetElements, opts) => {
17047
+ if (opts?.fitToContent || opts?.fitToViewport) {
17048
+ const { appState } = zoomToFit({
17049
+ canvasOffsets: opts.canvasOffsets,
17050
+ targetElements,
17051
+ appState: state,
17052
+ fitToViewport: !!opts.fitToViewport,
17053
+ viewportZoomFactor: opts.viewportZoomFactor,
17054
+ minZoom: opts.minZoom,
17055
+ maxZoom: opts.maxZoom
17056
+ });
17057
+ return {
17058
+ scrollX: appState.scrollX,
17059
+ scrollY: appState.scrollY,
17060
+ zoom: appState.zoom
17061
+ };
17062
+ }
17063
+ const { scrollX, scrollY } = calculateScrollCenter(targetElements, state);
17064
+ return { scrollX, scrollY, zoom: state.zoom };
17065
+ };
17066
+ var interpolateViewport = ({
17067
+ from,
17068
+ target,
17069
+ factor
17070
+ }) => {
17071
+ const zoom = from.zoom.value * Math.pow(
17072
+ target.zoom.value / from.zoom.value,
17073
+ factor
17074
+ );
17075
+ const fromCenterX = from.width / 2 / from.zoom.value - from.scrollX;
17076
+ const fromCenterY = from.height / 2 / from.zoom.value - from.scrollY;
17077
+ const toCenterX = from.width / 2 / target.zoom.value - target.scrollX;
17078
+ const toCenterY = from.height / 2 / target.zoom.value - target.scrollY;
17079
+ const centerX = fromCenterX + (toCenterX - fromCenterX) * factor;
17080
+ const centerY = fromCenterY + (toCenterY - fromCenterY) * factor;
17081
+ return {
17082
+ scrollX: from.width / 2 / zoom - centerX,
17083
+ scrollY: from.height / 2 / zoom - centerY,
17084
+ zoom: { value: zoom }
17085
+ };
17086
+ };
17087
+ var animateToViewport = (from, target, duration, onFrame) => {
17088
+ AnimationController.start(
17089
+ SCROLL_TO_CONTENT_ANIMATION_KEY,
17090
+ ({ deltaTime, state }) => {
17091
+ const elapsed = (state?.elapsed ?? 0) + deltaTime;
17092
+ const progress = Math.min(elapsed / duration, 1);
17093
+ const factor = easeOut(clamp6(progress, 0, 1));
17094
+ onFrame({
17095
+ ...interpolateViewport({ from, target, factor }),
17096
+ shouldCacheIgnoreZoom: progress < 1
17097
+ // ignore zoom caching while animating
17098
+ });
17099
+ return progress < 1 ? { elapsed } : null;
17100
+ }
17101
+ );
17102
+ };
17103
+
17104
+ // components/ElementCanvasButtons.tsx
17105
+ import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2 } from "@excalidraw/common";
17106
+ import { getElementAbsoluteCoords as getElementAbsoluteCoords5 } from "@excalidraw/element";
17107
+ import { jsx as jsx57 } from "react/jsx-runtime";
17108
+ var CONTAINER_PADDING = 5;
17109
+ var getContainerCoords2 = (element, appState, elementsMap) => {
17110
+ const [x1, y1] = getElementAbsoluteCoords5(element, elementsMap);
17111
+ const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords2(
17112
+ { sceneX: x1 + element.width, sceneY: y1 },
17113
+ appState
17114
+ );
17115
+ const x = viewportX - appState.offsetLeft + 10;
17116
+ const y = viewportY - appState.offsetTop;
17117
+ return { x, y };
17118
+ };
17119
+ var ElementCanvasButtons = ({
17120
+ children,
17121
+ element,
17122
+ elementsMap
17123
+ }) => {
17124
+ const appState = useExcalidrawAppState();
17125
+ if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
17126
+ return null;
17127
+ }
17128
+ const { x, y } = getContainerCoords2(element, appState, elementsMap);
17129
+ return /* @__PURE__ */ jsx57(
17130
+ "div",
17131
+ {
17132
+ className: "excalidraw-canvas-buttons",
17133
+ style: {
17134
+ top: `${y}px`,
17135
+ left: `${x}px`,
17136
+ // width: CONTAINER_WIDTH,
17137
+ padding: CONTAINER_PADDING
17138
+ },
17139
+ children
17140
+ }
17141
+ );
17142
+ };
17143
+
17144
+ // laserTrails.ts
17145
+ import { DEFAULT_LASER_COLOR, easeOut as easeOut2 } from "@excalidraw/common";
17146
+
16353
17147
  // animatedTrail.ts
17148
+ import {
17149
+ SVG_NS as SVG_NS2,
17150
+ getSvgPathFromStroke as getSvgPathFromStroke2,
17151
+ sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3
17152
+ } from "@excalidraw/common";
16354
17153
  var _AnimatedTrail = class _AnimatedTrail {
16355
17154
  constructor(app, options) {
16356
17155
  this.app = app;
@@ -16362,9 +17161,9 @@ var _AnimatedTrail = class _AnimatedTrail {
16362
17161
  __publicField(this, "trailAnimation");
16363
17162
  __publicField(this, "key");
16364
17163
  this.key = `animated-trail-${_AnimatedTrail.counter++}`;
16365
- this.trailElement = document.createElementNS(SVG_NS, "path");
17164
+ this.trailElement = document.createElementNS(SVG_NS2, "path");
16366
17165
  if (this.options.animateTrail) {
16367
- this.trailAnimation = document.createElementNS(SVG_NS, "animate");
17166
+ this.trailAnimation = document.createElementNS(SVG_NS2, "animate");
16368
17167
  this.trailAnimation.setAttribute("attributeName", "stroke-dashoffset");
16369
17168
  this.trailElement.setAttribute("stroke-dasharray", "7 7");
16370
17169
  this.trailElement.setAttribute("stroke-dashoffset", "10");
@@ -16522,7 +17321,7 @@ var LaserTrails = class {
16522
17321
  1 - (performance.now() - c.pressure) / DECAY_TIME
16523
17322
  );
16524
17323
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
16525
- return Math.min(easeOut(l), easeOut(t2));
17324
+ return Math.min(easeOut2(l), easeOut2(t2));
16526
17325
  }
16527
17326
  };
16528
17327
  }
@@ -16662,7 +17461,7 @@ import {
16662
17461
  getFontString as getFontString6,
16663
17462
  getFontFamilyString as getFontFamilyString3,
16664
17463
  isTestEnv as isTestEnv4,
16665
- MIME_TYPES as MIME_TYPES5,
17464
+ MIME_TYPES as MIME_TYPES6,
16666
17465
  applyDarkModeFilter as applyDarkModeFilter3,
16667
17466
  isRTL as isRTL2
16668
17467
  } from "@excalidraw/common";
@@ -16697,9 +17496,9 @@ import {
16697
17496
  isBoundToContainer as isBoundToContainer7,
16698
17497
  isTextElement as isTextElement8
16699
17498
  } from "@excalidraw/element";
16700
- var getTransform = (width, height, angle, appState, maxWidth, maxHeight) => {
17499
+ var getTransform = (width, height, angle2, appState, maxWidth, maxHeight) => {
16701
17500
  const { zoom } = appState;
16702
- const degree = 180 * angle / Math.PI;
17501
+ const degree = 180 * angle2 / Math.PI;
16703
17502
  let translateX = width * (zoom.value - 1) / 2;
16704
17503
  let translateY = height * (zoom.value - 1) / 2;
16705
17504
  if (width > maxWidth && zoom.value !== 1) {
@@ -16905,7 +17704,7 @@ var textWysiwyg = ({
16905
17704
  }
16906
17705
  height *= 1.05;
16907
17706
  const font = getFontString6(updatedTextElement);
16908
- const angle = getTextElementAngle(updatedTextElement, container);
17707
+ const angle2 = getTextElementAngle(updatedTextElement, container);
16909
17708
  const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value;
16910
17709
  Object.assign(editable.style, {
16911
17710
  font,
@@ -16918,7 +17717,7 @@ var textWysiwyg = ({
16918
17717
  transform: getTransform(
16919
17718
  width,
16920
17719
  height,
16921
- angle,
17720
+ angle2,
16922
17721
  appState,
16923
17722
  maxWidth,
16924
17723
  editorMaxHeight
@@ -16933,7 +17732,7 @@ var textWysiwyg = ({
16933
17732
  maxHeight: `${editorMaxHeight}px`
16934
17733
  });
16935
17734
  currentTextLayout = {
16936
- angle,
17735
+ angle: angle2,
16937
17736
  font,
16938
17737
  height: updatedTextElement.height,
16939
17738
  lineHeightPx: getLineHeightInPx2(
@@ -17042,7 +17841,7 @@ var textWysiwyg = ({
17042
17841
  editable.onpaste = async (event) => {
17043
17842
  const mimeTypes = parseDataTransferEventMimeTypes(event);
17044
17843
  let dataList = null;
17045
- if (mimeTypes.has(MIME_TYPES5.excalidrawClipboard) || mimeTypes.has(MIME_TYPES5.excalidraw)) {
17844
+ if (mimeTypes.has(MIME_TYPES6.excalidrawClipboard) || mimeTypes.has(MIME_TYPES6.excalidraw)) {
17046
17845
  event.preventDefault();
17047
17846
  dataList = await parseDataTransferEvent(event);
17048
17847
  try {
@@ -17063,7 +17862,7 @@ var textWysiwyg = ({
17063
17862
  }
17064
17863
  }
17065
17864
  dataList = dataList || await parseDataTransferEvent(event);
17066
- const textItem = dataList.findByType(MIME_TYPES5.text);
17865
+ const textItem = dataList.findByType(MIME_TYPES6.text);
17067
17866
  if (!textItem) {
17068
17867
  return;
17069
17868
  }
@@ -17455,7 +18254,7 @@ import {
17455
18254
  import { getFrameChildren as getFrameChildren4 } from "@excalidraw/element";
17456
18255
  import { selectGroupsForSelectedElements as selectGroupsForSelectedElements6 } from "@excalidraw/element";
17457
18256
  import { getContainerElement as getContainerElement4 } from "@excalidraw/element";
17458
- import { arrayToMap as arrayToMap25, easeOut as easeOut2, isShallowEqual as isShallowEqual3 } from "@excalidraw/common";
18257
+ import { arrayToMap as arrayToMap25, easeOut as easeOut3, isShallowEqual as isShallowEqual3 } from "@excalidraw/common";
17459
18258
 
17460
18259
  // lasso/utils.ts
17461
18260
  import { simplify as simplify2 } from "points-on-curve";
@@ -17567,7 +18366,7 @@ var LassoTrail = class extends AnimatedTrail {
17567
18366
  1 - (performance.now() - c.pressure) / DECAY_TIME
17568
18367
  );
17569
18368
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
17570
- return Math.min(easeOut2(l), easeOut2(t2));
18369
+ return Math.min(easeOut3(l), easeOut3(t2));
17571
18370
  },
17572
18371
  fill: () => "rgba(105,101,219,0.05)",
17573
18372
  stroke: () => "rgba(105,101,219)"
@@ -17735,7 +18534,7 @@ var actionSmartZoom = register({
17735
18534
  });
17736
18535
 
17737
18536
  // eraser/index.ts
17738
- import { arrayToMap as arrayToMap26, easeOut as easeOut3, THEME as THEME12 } from "@excalidraw/common";
18537
+ import { arrayToMap as arrayToMap26, easeOut as easeOut4, THEME as THEME12 } from "@excalidraw/common";
17739
18538
  import {
17740
18539
  computeBoundTextPosition as computeBoundTextPosition4,
17741
18540
  doBoundsIntersect as doBoundsIntersect3,
@@ -17775,7 +18574,7 @@ var EraserTrail = class extends AnimatedTrail {
17775
18574
  1 - (performance.now() - c.pressure) / DECAY_TIME
17776
18575
  );
17777
18576
  const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
17778
- return Math.min(easeOut3(l), easeOut3(t2));
18577
+ return Math.min(easeOut4(l), easeOut4(t2));
17779
18578
  },
17780
18579
  fill: () => app.state.theme === THEME12.LIGHT ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)"
17781
18580
  });
@@ -18165,7 +18964,7 @@ var createRadarAxisLabels = (labels, angles, centerX, centerY, radius, backgroun
18165
18964
  );
18166
18965
  const minLabelWidth = getApproxMinLineWidth(fontString, lineHeight);
18167
18966
  const axisLabels = labels.map((label, index) => {
18168
- const angle = angles[index];
18967
+ const angle2 = angles[index];
18169
18968
  const longestWordWidth = Math.max(
18170
18969
  0,
18171
18970
  ...label.trim().split(/\s+/).filter(Boolean).map((word) => measureText4(word, fontString, lineHeight).width)
@@ -18177,8 +18976,8 @@ var createRadarAxisLabels = (labels, angles, centerX, centerY, radius, backgroun
18177
18976
  );
18178
18977
  const displayLabel = getRadarDisplayText(label, fontString, maxLabelWidth);
18179
18978
  const metrics = measureText4(displayLabel, fontString, lineHeight);
18180
- const cos = Math.cos(angle);
18181
- const sin = Math.sin(angle);
18979
+ const cos = Math.cos(angle2);
18980
+ const sin = Math.sin(angle2);
18182
18981
  const textAlign = cos > RADAR_AXIS_LABEL_ALIGNMENT_THRESHOLD ? "left" : cos < -RADAR_AXIS_LABEL_ALIGNMENT_THRESHOLD ? "right" : "center";
18183
18982
  const centerAlignedXExtent = textAlign === "center" ? metrics.width / 2 : 0;
18184
18983
  const projectedExtent = Math.abs(cos) * centerAlignedXExtent + Math.abs(sin) * (metrics.height / 2);
@@ -18324,9 +19123,9 @@ var wrapOrEllipsifyTextToWidth = (text, maxWidth, fontString, lineHeight) => {
18324
19123
  text: ellipsifyTextToWidth(text, maxWidth, fontString, lineHeight)
18325
19124
  };
18326
19125
  };
18327
- var getRotatedBoundingBox = (width, height, angle) => {
18328
- const cos = Math.abs(Math.cos(angle));
18329
- const sin = Math.abs(Math.sin(angle));
19126
+ var getRotatedBoundingBox = (width, height, angle2) => {
19127
+ const cos = Math.abs(Math.cos(angle2));
19128
+ const sin = Math.abs(Math.sin(angle2));
18330
19129
  return {
18331
19130
  width: width * cos + height * sin,
18332
19131
  height: width * sin + height * cos
@@ -18900,9 +19699,9 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18900
19699
  const levelRatio = (levelIndex + 1) / RADAR_GRID_LEVELS;
18901
19700
  const levelRadius = radius * levelRatio;
18902
19701
  const points = angles.map(
18903
- (angle) => pointFrom24(
18904
- Math.cos(angle) * levelRadius,
18905
- Math.sin(angle) * levelRadius
19702
+ (angle2) => pointFrom24(
19703
+ Math.cos(angle2) * levelRadius,
19704
+ Math.sin(angle2) * levelRadius
18906
19705
  )
18907
19706
  );
18908
19707
  points.push(pointFrom24(points[0][0], points[0][1]));
@@ -18921,9 +19720,9 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18921
19720
  points
18922
19721
  });
18923
19722
  }) : [];
18924
- const spokes = angles.map((angle) => {
18925
- const px = Math.cos(angle) * radius;
18926
- const py = Math.sin(angle) * radius;
19723
+ const spokes = angles.map((angle2) => {
19724
+ const px = Math.cos(angle2) * radius;
19725
+ const py = Math.sin(angle2) * radius;
18927
19726
  return newLinearElement3({
18928
19727
  backgroundColor: "transparent",
18929
19728
  ...commonProps,
@@ -18939,12 +19738,12 @@ var renderRadarChart = (spreadsheet, x, y, colorSeed) => {
18939
19738
  });
18940
19739
  });
18941
19740
  const seriesPolygons = series.map((seriesData, index) => {
18942
- const points = angles.map((angle, axisIndex) => {
19741
+ const points = angles.map((angle2, axisIndex) => {
18943
19742
  const value = seriesData.values[axisIndex] ?? 0;
18944
19743
  const pointRadius = normalize(value, axisIndex) * radius;
18945
19744
  return pointFrom24(
18946
- Math.cos(angle) * pointRadius,
18947
- Math.sin(angle) * pointRadius
19745
+ Math.cos(angle2) * pointRadius,
19746
+ Math.sin(angle2) * pointRadius
18948
19747
  );
18949
19748
  });
18950
19749
  points.push(pointFrom24(points[0][0], points[0][1]));
@@ -19430,7 +20229,7 @@ var filterLinearConvertibleElements = (elements) => elements.filter(
19430
20229
  var THRESHOLD = 20;
19431
20230
  var isVert = (a, b) => a[0] === b[0];
19432
20231
  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]);
20232
+ var dist2 = (a, b) => isVert(a, b) ? Math.abs(a[1] - b[1]) : Math.abs(a[0] - b[0]);
19434
20233
  var convertLineToElbow = (line) => {
19435
20234
  const ortho = [line.points[0]];
19436
20235
  const src = sanitizePoints(line.points);
@@ -19464,8 +20263,8 @@ var convertLineToElbow = (line) => {
19464
20263
  const v1 = isVert(a, b);
19465
20264
  const v2 = isVert(b, c);
19466
20265
  if (v1 !== v2) {
19467
- const d1 = dist(a, b);
19468
- const d2 = dist(b, c);
20266
+ const d1 = dist2(a, b);
20267
+ const d2 = dist2(b, c);
19469
20268
  if (d1 < THRESHOLD || d2 < THRESHOLD) {
19470
20269
  if (d2 < d1) {
19471
20270
  if (v1) {
@@ -19522,7 +20321,7 @@ var convertElementType = (element, targetType, app) => {
19522
20321
  newElement5({
19523
20322
  ...element,
19524
20323
  type: targetType,
19525
- roundness: targetType === "diamond" && element.roundness ? {
20324
+ roundness: element.roundness ? {
19526
20325
  type: isUsingAdaptiveRadius3(targetType) ? ROUNDNESS6.ADAPTIVE_RADIUS : ROUNDNESS6.PROPORTIONAL_RADIUS
19527
20326
  } : element.roundness
19528
20327
  })
@@ -20645,7 +21444,7 @@ var SelectedShapeActions = ({
20645
21444
  canChangeBackgroundColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { children: renderAction("changeBackgroundColor") }),
20646
21445
  showFillIcons && renderAction("changeFillStyle"),
20647
21446
  (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"),
21447
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some((element) => hasFreedrawMode(element.type))) && renderAction("changeFreedrawMode"),
20649
21448
  (hasStrokeStyle(appState.activeTool.type) || targetElements.some((element) => hasStrokeStyle(element.type))) && /* @__PURE__ */ jsxs35(Fragment8, { children: [
20650
21449
  renderAction("changeStrokeStyle"),
20651
21450
  renderAction("changeSloppiness")
@@ -20778,6 +21577,13 @@ var CombinedShapeProperties = ({
20778
21577
  (hasStrokeWidth(appState.activeTool.type) || targetElements.some(
20779
21578
  (element) => hasStrokeWidth(element.type)
20780
21579
  )) && renderAction("changeStrokeWidth"),
21580
+ /* in compact UI the freedraw pressure setting is rendered as a
21581
+ standalone cycle button in the compact actions list; we render
21582
+ it in the combined properties popup as well for clarity
21583
+ */
21584
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some(
21585
+ (element) => hasFreedrawMode(element.type)
21586
+ )) && renderAction("changeFreedrawMode"),
20781
21587
  (hasStrokeStyle(appState.activeTool.type) || targetElements.some(
20782
21588
  (element) => hasStrokeStyle(element.type)
20783
21589
  )) && /* @__PURE__ */ jsxs35(Fragment8, { children: [
@@ -21105,6 +21911,7 @@ var CompactShapeActions = ({
21105
21911
  return /* @__PURE__ */ jsxs35("div", { className: "compact-shape-actions", children: [
21106
21912
  canChangeStrokeColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { className: clsx29("compact-action-item"), children: renderAction("changeStrokeColor") }),
21107
21913
  canChangeBackgroundColor(appState, targetElements) && /* @__PURE__ */ jsx75("div", { className: "compact-action-item", children: renderAction("changeBackgroundColor") }),
21914
+ (hasFreedrawMode(appState.activeTool.type) || targetElements.some((element) => hasFreedrawMode(element.type))) && /* @__PURE__ */ jsx75("div", { className: "compact-action-item", children: renderAction("changeFreedrawMode", { cycle: true }) }),
21108
21915
  /* @__PURE__ */ jsx75(
21109
21916
  CombinedShapeProperties,
21110
21917
  {
@@ -21293,6 +22100,7 @@ var ShapesSwitcher = ({
21293
22100
  const stylesPanelMode = useStylesPanelMode();
21294
22101
  const isFullStylesPanel = stylesPanelMode === "full";
21295
22102
  const isCompactStylesPanel = stylesPanelMode === "compact";
22103
+ const pendingPenDetectionRef = useRef16(false);
21296
22104
  const SELECTION_TOOLS2 = [
21297
22105
  {
21298
22106
  type: "selection",
@@ -21418,7 +22226,7 @@ var ShapesSwitcher = ({
21418
22226
  "data-testid": `toolbar-${value}`,
21419
22227
  onPointerDown: ({ pointerType }) => {
21420
22228
  if (!app.state.penDetected && pointerType === "pen") {
21421
- app.togglePenMode(true);
22229
+ pendingPenDetectionRef.current = true;
21422
22230
  }
21423
22231
  if (value === "selection") {
21424
22232
  if (app.state.activeTool.type === "selection") {
@@ -21428,16 +22236,14 @@ var ShapesSwitcher = ({
21428
22236
  }
21429
22237
  }
21430
22238
  },
21431
- onChange: ({ pointerType }) => {
22239
+ onChange: () => {
21432
22240
  if (app.state.activeTool.type !== value) {
21433
22241
  trackEvent("toolbar", value, "ui");
21434
22242
  }
21435
- if (value === "image") {
21436
- app.setActiveTool({
21437
- type: value
21438
- });
21439
- } else {
21440
- app.setActiveTool({ type: value });
22243
+ app.setActiveTool({ type: value });
22244
+ if (pendingPenDetectionRef.current) {
22245
+ pendingPenDetectionRef.current = false;
22246
+ requestAnimationFrame(() => app.togglePenMode(true));
21441
22247
  }
21442
22248
  }
21443
22249
  },
@@ -25250,7 +26056,7 @@ var CanvasGrid = ({
25250
26056
  var CanvasGrid_default = CanvasGrid;
25251
26057
 
25252
26058
  // components/Stats/Dimension.tsx
25253
- import { clamp as clamp5, round as round3 } from "@excalidraw/math";
26059
+ import { clamp as clamp7, round as round3 } from "@excalidraw/math";
25254
26060
  import { MIN_WIDTH_OR_HEIGHT } from "@excalidraw/common";
25255
26061
  import {
25256
26062
  MINIMAL_CROP_SIZE,
@@ -25305,7 +26111,7 @@ var handleDimensionChange = ({
25305
26111
  if (nextValue !== void 0) {
25306
26112
  if (property === "width") {
25307
26113
  const nextValueInNatural = nextValue * naturalToUncroppedWidthRatio;
25308
- const nextCropWidth2 = clamp5(
26114
+ const nextCropWidth2 = clamp7(
25309
26115
  nextValueInNatural,
25310
26116
  MIN_WIDTH,
25311
26117
  MAX_POSSIBLE_WIDTH
@@ -25317,7 +26123,7 @@ var handleDimensionChange = ({
25317
26123
  };
25318
26124
  } else if (property === "height") {
25319
26125
  const nextValueInNatural = nextValue * naturalToUncroppedHeightRatio;
25320
- const nextCropHeight2 = clamp5(
26126
+ const nextCropHeight2 = clamp7(
25321
26127
  nextValueInNatural,
25322
26128
  MIN_HEIGHT,
25323
26129
  MAX_POSSIBLE_HEIGHT
@@ -25337,12 +26143,12 @@ var handleDimensionChange = ({
25337
26143
  }
25338
26144
  const changeInWidth = property === "width" ? instantChange : 0;
25339
26145
  const changeInHeight = property === "height" ? instantChange : 0;
25340
- const nextCropWidth = clamp5(
26146
+ const nextCropWidth = clamp7(
25341
26147
  crop.width + changeInWidth,
25342
26148
  MIN_WIDTH,
25343
26149
  MAX_POSSIBLE_WIDTH
25344
26150
  );
25345
- const nextCropHeight = clamp5(
26151
+ const nextCropHeight = clamp7(
25346
26152
  crop.height + changeInHeight,
25347
26153
  MIN_WIDTH,
25348
26154
  MAX_POSSIBLE_HEIGHT
@@ -25696,7 +26502,7 @@ import { getBoundTextElement as getBoundTextElement13, handleBindTextResize as h
25696
26502
  import { isTextElement as isTextElement13 } from "@excalidraw/element";
25697
26503
 
25698
26504
  // ../utils/src/export.ts
25699
- import { MIME_TYPES as MIME_TYPES6 } from "@excalidraw/common";
26505
+ import { MIME_TYPES as MIME_TYPES7 } from "@excalidraw/common";
25700
26506
  var exportToCanvas2 = ({
25701
26507
  elements,
25702
26508
  appState,
@@ -25744,16 +26550,16 @@ var exportToCanvas2 = ({
25744
26550
  );
25745
26551
  };
25746
26552
  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`);
26553
+ let { mimeType = MIME_TYPES7.png, quality } = opts;
26554
+ if (mimeType === MIME_TYPES7.png && typeof quality === "number") {
26555
+ console.warn(`"quality" will be ignored for "${MIME_TYPES7.png}" mimeType`);
25750
26556
  }
25751
26557
  if (mimeType === "image/jpg") {
25752
- mimeType = MIME_TYPES6.jpg;
26558
+ mimeType = MIME_TYPES7.jpg;
25753
26559
  }
25754
- if (mimeType === MIME_TYPES6.jpg && !opts.appState?.exportBackground) {
26560
+ if (mimeType === MIME_TYPES7.jpg && !opts.appState?.exportBackground) {
25755
26561
  console.warn(
25756
- `Defaulting "exportBackground" to "true" for "${MIME_TYPES6.jpg}" mimeType`
26562
+ `Defaulting "exportBackground" to "true" for "${MIME_TYPES7.jpg}" mimeType`
25757
26563
  );
25758
26564
  opts = {
25759
26565
  ...opts,
@@ -25768,7 +26574,7 @@ var exportToBlob = async (opts) => {
25768
26574
  if (!blob) {
25769
26575
  return reject(new Error("couldn't export to blob"));
25770
26576
  }
25771
- if (blob && mimeType === MIME_TYPES6.png && opts.appState?.exportEmbedScene) {
26577
+ if (blob && mimeType === MIME_TYPES7.png && opts.appState?.exportEmbedScene) {
25772
26578
  blob = await encodePngMetadata({
25773
26579
  blob,
25774
26580
  metadata: serializeAsJSON(
@@ -26469,7 +27275,7 @@ var MultiPosition = ({
26469
27275
  var MultiPosition_default = MultiPosition;
26470
27276
 
26471
27277
  // components/Stats/Position.tsx
26472
- import { clamp as clamp6, pointFrom as pointFrom29, pointRotateRads as pointRotateRads19, round as round4 } from "@excalidraw/math";
27278
+ import { clamp as clamp8, pointFrom as pointFrom29, pointRotateRads as pointRotateRads19, round as round4 } from "@excalidraw/math";
26473
27279
  import {
26474
27280
  getFlipAdjustedCropPosition,
26475
27281
  getUncroppedWidthAndHeight as getUncroppedWidthAndHeight2
@@ -26515,7 +27321,7 @@ var handlePositionChange2 = ({
26515
27321
  if (isFlippedByX) {
26516
27322
  nextCrop = {
26517
27323
  ...crop,
26518
- x: clamp6(
27324
+ x: clamp8(
26519
27325
  crop.naturalWidth - nextValueInNatural - crop.width,
26520
27326
  0,
26521
27327
  crop.naturalWidth - crop.width
@@ -26524,7 +27330,7 @@ var handlePositionChange2 = ({
26524
27330
  } else {
26525
27331
  nextCrop = {
26526
27332
  ...crop,
26527
- x: clamp6(
27333
+ x: clamp8(
26528
27334
  nextValue * (crop.naturalWidth / uncroppedWidth),
26529
27335
  0,
26530
27336
  crop.naturalWidth - crop.width
@@ -26535,7 +27341,7 @@ var handlePositionChange2 = ({
26535
27341
  if (property === "y") {
26536
27342
  nextCrop = {
26537
27343
  ...crop,
26538
- y: clamp6(
27344
+ y: clamp8(
26539
27345
  nextValue * (crop.naturalHeight / uncroppedHeight),
26540
27346
  0,
26541
27347
  crop.naturalHeight - crop.height
@@ -26551,8 +27357,8 @@ var handlePositionChange2 = ({
26551
27357
  const changeInY = (property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1);
26552
27358
  nextCrop = {
26553
27359
  ...crop,
26554
- x: clamp6(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
26555
- y: clamp6(crop.y + changeInY, 0, crop.naturalHeight - crop.height)
27360
+ x: clamp8(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
27361
+ y: clamp8(crop.y + changeInY, 0, crop.naturalHeight - crop.height)
26556
27362
  };
26557
27363
  scene.mutateElement(element, {
26558
27364
  crop: nextCrop
@@ -28794,7 +29600,6 @@ var LayerUI = ({
28794
29600
  renderWelcomeScreen && /* @__PURE__ */ jsx130(tunnels.WelcomeScreenMenuHintTunnel.Out, {})
28795
29601
  ] });
28796
29602
  const renderSelectedShapeActions = () => {
28797
- const isCompactMode = isCompactStylesPanel;
28798
29603
  return /* @__PURE__ */ jsx130(
28799
29604
  Section,
28800
29605
  {
@@ -28802,7 +29607,7 @@ var LayerUI = ({
28802
29607
  className: clsx55("selected-shape-actions zen-mode-transition", {
28803
29608
  "transition-left": appState.zenModeEnabled
28804
29609
  }),
28805
- children: isCompactMode ? /* @__PURE__ */ jsx130(
29610
+ children: isCompactStylesPanel ? /* @__PURE__ */ jsx130(
28806
29611
  Island,
28807
29612
  {
28808
29613
  className: clsx55("compact-shape-actions-island"),
@@ -28873,6 +29678,17 @@ var LayerUI = ({
28873
29678
  }),
28874
29679
  children: shouldRenderSelectedShapeActions && renderSelectedShapeActions()
28875
29680
  }
29681
+ ),
29682
+ isCompactStylesPanel && !appState.viewModeEnabled && shouldRenderSelectedShapeActions && /* @__PURE__ */ jsx130(
29683
+ PenModeButton,
29684
+ {
29685
+ zenModeEnabled: appState.zenModeEnabled,
29686
+ checked: appState.penMode,
29687
+ onChange: () => onPenModeToggle(null),
29688
+ title: t("toolBar.penMode"),
29689
+ isMobile: true,
29690
+ penDetected: appState.penDetected
29691
+ }
28876
29692
  )
28877
29693
  ]
28878
29694
  }
@@ -28907,7 +29723,7 @@ var LayerUI = ({
28907
29723
  ),
28908
29724
  heading,
28909
29725
  /* @__PURE__ */ jsxs67(Stack_default.Row, { gap: spacing.toolbarInnerRowGap, children: [
28910
- /* @__PURE__ */ jsx130(
29726
+ !isCompactStylesPanel && /* @__PURE__ */ jsx130(
28911
29727
  PenModeButton,
28912
29728
  {
28913
29729
  zenModeEnabled: appState.zenModeEnabled,
@@ -29295,7 +30111,7 @@ import {
29295
30111
 
29296
30112
  // renderer/interactiveScene.ts
29297
30113
  import {
29298
- clamp as clamp7,
30114
+ clamp as clamp9,
29299
30115
  pointFrom as pointFrom31,
29300
30116
  pointsEqual as pointsEqual8,
29301
30117
  bezierEquation as bezierEquation2,
@@ -29601,7 +30417,7 @@ var renderBindingHighlightForBindableElement_simple = (context, suggestedBinding
29601
30417
  context.rotate(suggestedBinding.element.angle);
29602
30418
  context.translate(-center[0], -center[1]);
29603
30419
  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);
30420
+ context.lineWidth = clamp9(1.75, suggestedBinding.element.strokeWidth, 4) / Math.max(0.25, appState.zoom.value);
29605
30421
  context.strokeStyle = appState.theme === THEME16.DARK ? `rgba(3, 93, 161, 1)` : `rgba(106, 189, 252, 1)`;
29606
30422
  switch (suggestedBinding.element.type) {
29607
30423
  case "ellipse":
@@ -29792,7 +30608,7 @@ var renderBindingHighlightForBindableElement_simple = (context, suggestedBinding
29792
30608
  var renderBindingHighlightForBindableElement_complex = (app, context, element, allElementsMap, appState, deltaTime, state) => {
29793
30609
  const countdownInProgress = app.state.bindMode === "orbit" && app.bindModeHandler !== null;
29794
30610
  const remainingTime = BIND_MODE_TIMEOUT - (state?.runtime ?? (countdownInProgress ? 0 : BIND_MODE_TIMEOUT));
29795
- const opacity = clamp7(1 / BIND_MODE_TIMEOUT * remainingTime, 1e-4, 1);
30611
+ const opacity = clamp9(1 / BIND_MODE_TIMEOUT * remainingTime, 1e-4, 1);
29796
30612
  const offset = element.strokeWidth / 2;
29797
30613
  const enclosingFrame = element.frameId && allElementsMap.get(element.frameId);
29798
30614
  if (enclosingFrame && isFrameLikeElement14(enclosingFrame)) {
@@ -29847,7 +30663,7 @@ var renderBindingHighlightForBindableElement_complex = (app, context, element, a
29847
30663
  element.x + appState.scrollX - offset,
29848
30664
  element.y + appState.scrollY - offset
29849
30665
  );
29850
- context.lineWidth = clamp7(2.5, element.strokeWidth * 1.75, 4) / Math.max(0.25, appState.zoom.value);
30666
+ context.lineWidth = clamp9(2.5, element.strokeWidth * 1.75, 4) / Math.max(0.25, appState.zoom.value);
29851
30667
  context.strokeStyle = appState.theme === THEME16.DARK ? `rgba(3, 93, 161, ${opacity / 2})` : `rgba(106, 189, 252, ${opacity / 2})`;
29852
30668
  switch (element.type) {
29853
30669
  case "ellipse":
@@ -30083,7 +30899,7 @@ var renderBindingHighlightForBindableElement = (app, context, suggestedBinding,
30083
30899
  };
30084
30900
  var renderSelectionBorder = (context, appState, elementProperties) => {
30085
30901
  const {
30086
- angle,
30902
+ angle: angle2,
30087
30903
  x1,
30088
30904
  y1,
30089
30905
  x2,
@@ -30121,7 +30937,7 @@ var renderSelectionBorder = (context, appState, elementProperties) => {
30121
30937
  elementHeight + linePadding * 2,
30122
30938
  cx,
30123
30939
  cy,
30124
- angle
30940
+ angle2
30125
30941
  );
30126
30942
  }
30127
30943
  context.restore();
@@ -30353,7 +31169,7 @@ var renderFocusPointIndicator = ({
30353
31169
  );
30354
31170
  }
30355
31171
  };
30356
- var renderTransformHandles = (context, renderConfig, appState, transformHandles, angle) => {
31172
+ var renderTransformHandles = (context, renderConfig, appState, transformHandles, angle2) => {
30357
31173
  Object.keys(transformHandles).forEach((key) => {
30358
31174
  const transformHandle = transformHandles[key];
30359
31175
  if (transformHandle !== void 0) {
@@ -30379,7 +31195,7 @@ var renderTransformHandles = (context, renderConfig, appState, transformHandles,
30379
31195
  height,
30380
31196
  x + width / 2,
30381
31197
  y + height / 2,
30382
- angle,
31198
+ angle2,
30383
31199
  true
30384
31200
  // fill before stroke
30385
31201
  );
@@ -30511,6 +31327,46 @@ var renderResetAutoResizeHandle = (text, context, appState, selectionColor, form
30511
31327
  context.stroke();
30512
31328
  context.restore();
30513
31329
  };
31330
+ var renderImageLoadingProgress = (context, visibleElements, elementsMap, app, appState, selectionColor) => {
31331
+ for (const element of visibleElements) {
31332
+ if (!isImageElement8(element) || !element.fileId || element.status === "error") {
31333
+ continue;
31334
+ }
31335
+ const progress = app.imageLoadingProgress.get(element.fileId);
31336
+ if (progress === void 0) {
31337
+ continue;
31338
+ }
31339
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords7(element, elementsMap);
31340
+ const centerX = (x1 + x2) / 2 + appState.scrollX;
31341
+ const centerY = (y1 + y2) / 2 + appState.scrollY;
31342
+ const zoom = appState.zoom.value;
31343
+ const radius = Math.min(14, Math.min(x2 - x1, y2 - y1) * zoom / 4) / zoom;
31344
+ const lineWidth = Math.min(3 / zoom, radius / 2);
31345
+ if (radius <= 0 || lineWidth <= 0) {
31346
+ continue;
31347
+ }
31348
+ context.save();
31349
+ context.lineWidth = lineWidth;
31350
+ context.lineCap = "round";
31351
+ context.strokeStyle = appState.theme === THEME16.DARK ? "rgba(255, 255, 255, 0.35)" : "rgba(0, 0, 0, 0.25)";
31352
+ context.beginPath();
31353
+ context.arc(centerX, centerY, radius, 0, Math.PI * 2);
31354
+ context.stroke();
31355
+ if (progress > 0) {
31356
+ context.strokeStyle = selectionColor;
31357
+ context.beginPath();
31358
+ context.arc(
31359
+ centerX,
31360
+ centerY,
31361
+ radius,
31362
+ -Math.PI / 2,
31363
+ -Math.PI / 2 + Math.PI * 2 * progress
31364
+ );
31365
+ context.stroke();
31366
+ }
31367
+ context.restore();
31368
+ }
31369
+ };
30514
31370
  var _renderInteractiveScene = ({
30515
31371
  app,
30516
31372
  canvas,
@@ -30868,6 +31724,14 @@ var _renderInteractiveScene = ({
30868
31724
  }
30869
31725
  });
30870
31726
  renderSnaps(context, appState);
31727
+ renderImageLoadingProgress(
31728
+ context,
31729
+ visibleElements,
31730
+ elementsMap,
31731
+ app,
31732
+ appState,
31733
+ renderConfig.selectionColor
31734
+ );
30871
31735
  context.restore();
30872
31736
  renderRemoteCursors({
30873
31737
  context,
@@ -30920,6 +31784,31 @@ var INTERACTIVE_SCENE_ANIMATION_KEY = "animateInteractiveScene";
30920
31784
  var InteractiveCanvas = (props) => {
30921
31785
  const isComponentMounted = useRef27(false);
30922
31786
  const rendererParams = useRef27(null);
31787
+ useEffect31(() => {
31788
+ let scheduledFrame = null;
31789
+ const unsubscribe = props.app.imageLoadingProgressEmitter.on(() => {
31790
+ if (scheduledFrame !== null) {
31791
+ return;
31792
+ }
31793
+ scheduledFrame = requestAnimationFrame(() => {
31794
+ scheduledFrame = null;
31795
+ if (!rendererParams.current) {
31796
+ return;
31797
+ }
31798
+ renderInteractiveScene({
31799
+ ...rendererParams.current,
31800
+ callback: () => {
31801
+ }
31802
+ });
31803
+ });
31804
+ });
31805
+ return () => {
31806
+ unsubscribe();
31807
+ if (scheduledFrame !== null) {
31808
+ cancelAnimationFrame(scheduledFrame);
31809
+ }
31810
+ };
31811
+ }, [props.app]);
30923
31812
  useEffect31(() => {
30924
31813
  if (!isComponentMounted.current) {
30925
31814
  isComponentMounted.current = true;
@@ -31099,6 +31988,54 @@ import { jsx as jsx134 } from "react/jsx-runtime";
31099
31988
  var StaticCanvas = (props) => {
31100
31989
  const wrapperRef = useRef28(null);
31101
31990
  const isComponentMounted = useRef28(false);
31991
+ const propsRef = useRef28(props);
31992
+ const renderCanvasRef = useRef28(() => {
31993
+ });
31994
+ const transitionFrameRef = useRef28(null);
31995
+ propsRef.current = props;
31996
+ const renderCanvas = React39.useCallback(() => {
31997
+ const currentProps = propsRef.current;
31998
+ const wrapper = wrapperRef.current;
31999
+ if (!wrapper) {
32000
+ return;
32001
+ }
32002
+ const gridColorBold = getComputedStyle(wrapper).getPropertyValue("--color-grid-bold").trim();
32003
+ const gridColorRegular = getComputedStyle(wrapper).getPropertyValue("--color-grid-regular").trim();
32004
+ renderStaticScene(
32005
+ {
32006
+ canvas: currentProps.canvas,
32007
+ rc: currentProps.rc,
32008
+ scale: currentProps.scale,
32009
+ elementsMap: currentProps.elementsMap,
32010
+ allElementsMap: currentProps.allElementsMap,
32011
+ visibleElements: currentProps.visibleElements,
32012
+ appState: currentProps.appState,
32013
+ renderConfig: {
32014
+ ...currentProps.renderConfig,
32015
+ gridColorBold,
32016
+ gridColorRegular
32017
+ }
32018
+ },
32019
+ isRenderThrottlingEnabled()
32020
+ );
32021
+ const transitionDuration = currentProps.renderConfig.imageTransitionDuration ?? 0;
32022
+ const hasActiveTransition = transitionDuration > 0 && currentProps.visibleElements.some((element) => {
32023
+ if (element.type !== "image" || !element.fileId) {
32024
+ return false;
32025
+ }
32026
+ const transitionStart = currentProps.renderConfig.imageCache.get(
32027
+ element.fileId
32028
+ )?.transitionStart;
32029
+ return transitionStart !== void 0 && performance.now() - transitionStart < transitionDuration;
32030
+ });
32031
+ if (hasActiveTransition && transitionFrameRef.current === null) {
32032
+ transitionFrameRef.current = requestAnimationFrame(() => {
32033
+ transitionFrameRef.current = null;
32034
+ renderCanvasRef.current();
32035
+ });
32036
+ }
32037
+ }, []);
32038
+ renderCanvasRef.current = renderCanvas;
31102
32039
  useEffect32(() => {
31103
32040
  props.canvas.style.width = `${props.appState.width}px`;
31104
32041
  props.canvas.style.height = `${props.appState.height}px`;
@@ -31116,26 +32053,34 @@ var StaticCanvas = (props) => {
31116
32053
  wrapper.replaceChildren(canvas);
31117
32054
  canvas.classList.add("excalidraw__canvas", "static");
31118
32055
  }
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
- );
32056
+ renderCanvas();
31138
32057
  });
32058
+ useEffect32(() => {
32059
+ let scheduledFrame = null;
32060
+ const unsubscribe = props.app.imagePlaceholderUpdateEmitter.on(() => {
32061
+ if (scheduledFrame !== null) {
32062
+ return;
32063
+ }
32064
+ scheduledFrame = requestAnimationFrame(() => {
32065
+ scheduledFrame = null;
32066
+ renderCanvas();
32067
+ });
32068
+ });
32069
+ return () => {
32070
+ unsubscribe();
32071
+ if (scheduledFrame !== null) {
32072
+ cancelAnimationFrame(scheduledFrame);
32073
+ }
32074
+ };
32075
+ }, [props.app, renderCanvas]);
32076
+ useEffect32(
32077
+ () => () => {
32078
+ if (transitionFrameRef.current !== null) {
32079
+ cancelAnimationFrame(transitionFrameRef.current);
32080
+ }
32081
+ },
32082
+ []
32083
+ );
31139
32084
  return /* @__PURE__ */ jsx134("div", { className: "excalidraw__canvas-wrapper", ref: wrapperRef });
31140
32085
  };
31141
32086
  var getRelevantAppStateProps2 = (appState) => {
@@ -31571,6 +32516,10 @@ var App = class _App extends React40.Component {
31571
32516
  __publicField(this, "excalidrawContainerValue");
31572
32517
  __publicField(this, "files", {});
31573
32518
  __publicField(this, "imageCache", /* @__PURE__ */ new Map());
32519
+ __publicField(this, "imageLoadingProgress", /* @__PURE__ */ new Map());
32520
+ __publicField(this, "imageLoadingProgressEmitter", new Emitter2());
32521
+ __publicField(this, "imagePlaceholderUpdateEmitter", new Emitter2());
32522
+ __publicField(this, "imageTransitionPlaceholders", /* @__PURE__ */ new Map());
31574
32523
  __publicField(this, "iFrameRefs", /* @__PURE__ */ new Map());
31575
32524
  /**
31576
32525
  * Indicates whether the embeddable's url has been validated for rendering.
@@ -32610,7 +33559,8 @@ var App = class _App extends React40.Component {
32610
33559
  this.setState((prevState) => {
32611
33560
  return {
32612
33561
  penMode: force ?? !prevState.penMode,
32613
- penDetected: true
33562
+ penDetected: true,
33563
+ currentItemStrokeVariability: !prevState.penDetected ? "variable" : prevState.currentItemStrokeVariability
32614
33564
  };
32615
33565
  });
32616
33566
  });
@@ -32635,120 +33585,92 @@ var App = class _App extends React40.Component {
32635
33585
  __publicField(this, "cancelInProgressAnimation", null);
32636
33586
  __publicField(this, "scrollToViewport", (target, opts) => {
32637
33587
  this.cancelInProgressAnimation?.();
33588
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
32638
33589
  const zoom = { value: getNormalizedZoom(target.zoom) };
32639
33590
  const animateDuration = opts?.duration ?? 500;
32640
33591
  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: () => {
33592
+ const from = this.state;
33593
+ let startTime = null;
33594
+ let frameId = null;
33595
+ let cancelled = false;
33596
+ const targetViewport = {
33597
+ scrollX: target.scrollX,
33598
+ scrollY: target.scrollY,
33599
+ zoom
33600
+ };
33601
+ const step = (timestamp) => {
33602
+ if (cancelled) {
33603
+ return;
33604
+ }
33605
+ startTime ?? (startTime = timestamp);
33606
+ const progress = Math.min((timestamp - startTime) / animateDuration, 1);
33607
+ this.setState({
33608
+ ...interpolateViewport({
33609
+ from,
33610
+ target: targetViewport,
33611
+ factor: easeOut5(progress)
33612
+ }),
33613
+ shouldCacheIgnoreZoom: progress < 1
33614
+ });
33615
+ if (progress < 1) {
33616
+ frameId = requestAnimationFrame(step);
33617
+ } else {
32676
33618
  this.cancelInProgressAnimation = null;
32677
- this.setState({ shouldCacheIgnoreZoom: false });
32678
- },
32679
- duration: animateDuration
32680
- });
33619
+ }
33620
+ };
32681
33621
  this.cancelInProgressAnimation = () => {
32682
- cancel();
33622
+ cancelled = true;
33623
+ if (frameId !== null) {
33624
+ cancelAnimationFrame(frameId);
33625
+ }
32683
33626
  this.cancelInProgressAnimation = null;
33627
+ this.setState({ shouldCacheIgnoreZoom: false });
32684
33628
  };
33629
+ frameId = requestAnimationFrame(step);
32685
33630
  } else {
32686
33631
  this.setState({
32687
33632
  scrollX: target.scrollX,
32688
33633
  scrollY: target.scrollY,
32689
- zoom
33634
+ zoom,
33635
+ shouldCacheIgnoreZoom: false
32690
33636
  });
32691
33637
  }
32692
33638
  });
32693
- __publicField(this, "scrollToContent", (target = this.scene.getNonDeletedElements(), opts) => {
33639
+ __publicField(this, "scrollToContent", (target, opts) => {
33640
+ let elements;
32694
33641
  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;
33642
+ const id = isElementLink2(target) ? parseElementLinkFromURL(target) : target;
33643
+ elements = id ? this.scene.getElementsFromId(id) : [];
33644
+ } else if (Array.isArray(target)) {
33645
+ elements = target;
33646
+ } else if (target) {
33647
+ elements = [target];
32737
33648
  } else {
32738
- const scroll = calculateScrollCenter(targetElements, this.state);
32739
- scrollX = scroll.scrollX;
32740
- scrollY = scroll.scrollY;
33649
+ elements = this.scene.getNonDeletedElements();
32741
33650
  }
32742
- this.scrollToViewport(
32743
- {
32744
- scrollX,
32745
- scrollY,
32746
- zoom: zoom.value
32747
- },
32748
- {
32749
- animate: opts?.animate,
32750
- duration: opts?.duration
33651
+ if (!elements.length) {
33652
+ if (typeof target === "string" && isElementLink2(target)) {
33653
+ this.setState({
33654
+ toast: {
33655
+ message: t("elementLink.notFound"),
33656
+ duration: 3e3,
33657
+ closable: true
33658
+ }
33659
+ });
32751
33660
  }
33661
+ return;
33662
+ }
33663
+ const resolvedOpts = typeof target === "string" ? {
33664
+ ...opts,
33665
+ fitToViewport: void 0,
33666
+ fitToContent: opts?.fitToContent ?? true,
33667
+ animate: opts?.animate ?? true
33668
+ } : opts;
33669
+ scrollToElements(
33670
+ this.state,
33671
+ elements,
33672
+ this.setState.bind(this),
33673
+ resolvedOpts
32752
33674
  );
32753
33675
  });
32754
33676
  __publicField(this, "maybeUnfollowRemoteUser", () => {
@@ -32759,6 +33681,8 @@ var App = class _App extends React40.Component {
32759
33681
  /** use when changing scrollX/scrollY/zoom based on user interaction */
32760
33682
  __publicField(this, "translateCanvas", (state) => {
32761
33683
  this.cancelInProgressAnimation?.();
33684
+ AnimationController.cancel(SCROLL_TO_CONTENT_ANIMATION_KEY);
33685
+ this.setState({ shouldCacheIgnoreZoom: false });
32762
33686
  this.maybeUnfollowRemoteUser();
32763
33687
  this.setState(state);
32764
33688
  });
@@ -32790,11 +33714,93 @@ var App = class _App extends React40.Component {
32790
33714
  __publicField(this, "addFiles", withBatchedUpdates(
32791
33715
  (files) => {
32792
33716
  const { addedFiles } = this.addMissingFiles(files);
33717
+ for (const fileId of Object.keys(addedFiles)) {
33718
+ const cacheEntry = this.imageCache.get(fileId);
33719
+ if (cacheEntry?.isPlaceholder && !(cacheEntry.image instanceof Promise)) {
33720
+ this.imageTransitionPlaceholders.set(fileId, cacheEntry.image);
33721
+ }
33722
+ }
32793
33723
  this.clearImageShapeCache(addedFiles);
32794
33724
  this.scene.triggerUpdate();
32795
- this.addNewImagesToImageCache();
33725
+ this.addNewImagesToImageCache().then(() => {
33726
+ for (const fileId of Object.keys(addedFiles)) {
33727
+ const cacheEntry = this.imageCache.get(fileId);
33728
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
33729
+ this.clearImageLoadingProgress(fileId);
33730
+ }
33731
+ this.imageTransitionPlaceholders.delete(fileId);
33732
+ }
33733
+ });
32796
33734
  }
32797
33735
  ));
33736
+ __publicField(this, "addImagePlaceholder", async (fileId, file2) => {
33737
+ if (this.files[fileId]) {
33738
+ return;
33739
+ }
33740
+ let normalizedFile = await normalizeFile(file2);
33741
+ if (!isSupportedImageFile(normalizedFile)) {
33742
+ throw new Error(t("errors.unsupportedFileType"));
33743
+ }
33744
+ if (normalizedFile.type === MIME_TYPES8.svg) {
33745
+ normalizedFile = SVGStringToFile(
33746
+ normalizeSVG(await normalizedFile.text()),
33747
+ normalizedFile.name
33748
+ );
33749
+ }
33750
+ const dataURL = await getDataURL(normalizedFile);
33751
+ if (this.files[fileId]) {
33752
+ return;
33753
+ }
33754
+ const imagePromise = loadHTMLImageElement(dataURL);
33755
+ const placeholderEntry = {
33756
+ image: imagePromise,
33757
+ mimeType: normalizedFile.type,
33758
+ isPlaceholder: true
33759
+ };
33760
+ this.imageCache.set(fileId, placeholderEntry);
33761
+ try {
33762
+ const image = await imagePromise;
33763
+ if (this.imageCache.get(fileId) !== placeholderEntry || this.files[fileId]) {
33764
+ return;
33765
+ }
33766
+ this.imageCache.set(fileId, { ...placeholderEntry, image });
33767
+ this.clearImageShapeCacheForFileId(fileId);
33768
+ this.imagePlaceholderUpdateEmitter.trigger();
33769
+ } catch (error) {
33770
+ if (this.imageCache.get(fileId) === placeholderEntry) {
33771
+ this.imageCache.delete(fileId);
33772
+ this.imagePlaceholderUpdateEmitter.trigger();
33773
+ }
33774
+ throw error;
33775
+ }
33776
+ });
33777
+ __publicField(this, "setImageLoadingProgress", (fileId, progress) => {
33778
+ if (progress === null) {
33779
+ this.clearImageLoadingProgress(fileId);
33780
+ return;
33781
+ }
33782
+ if (!Number.isFinite(progress)) {
33783
+ return;
33784
+ }
33785
+ const nextProgress = clamp10(progress, 0, 1);
33786
+ if (this.imageLoadingProgress.get(fileId) === nextProgress) {
33787
+ return;
33788
+ }
33789
+ this.imageLoadingProgress.set(fileId, nextProgress);
33790
+ this.imageLoadingProgressEmitter.trigger();
33791
+ });
33792
+ __publicField(this, "clearImageLoadingProgress", (fileId) => {
33793
+ if (this.imageLoadingProgress.delete(fileId)) {
33794
+ this.imageLoadingProgressEmitter.trigger();
33795
+ }
33796
+ });
33797
+ __publicField(this, "clearImageShapeCacheForFileId", (fileId) => {
33798
+ for (const element of this.scene.getNonDeletedElements()) {
33799
+ if (isInitializedImageElement3(element) && element.fileId === fileId) {
33800
+ ShapeCache4.delete(element);
33801
+ }
33802
+ }
33803
+ });
32798
33804
  __publicField(this, "addMissingFiles", (files, replace = false) => {
32799
33805
  const nextFiles = replace ? {} : { ...this.files };
32800
33806
  const addedFiles = {};
@@ -32805,11 +33811,11 @@ var App = class _App extends React40.Component {
32805
33811
  }
32806
33812
  addedFiles[fileData.id] = fileData;
32807
33813
  nextFiles[fileData.id] = fileData;
32808
- if (fileData.mimeType === MIME_TYPES7.svg) {
33814
+ if (fileData.mimeType === MIME_TYPES8.svg) {
32809
33815
  try {
32810
33816
  const restoredDataURL = getDataURL_sync(
32811
33817
  normalizeSVG(dataURLToString(fileData.dataURL)),
32812
- MIME_TYPES7.svg
33818
+ MIME_TYPES8.svg
32813
33819
  );
32814
33820
  if (fileData.dataURL !== restoredDataURL) {
32815
33821
  fileData.version = (fileData.version ?? 1) + 1;
@@ -33719,7 +34725,7 @@ var App = class _App extends React40.Component {
33719
34725
  strokeColor: this.state.currentItemStrokeColor,
33720
34726
  backgroundColor: this.state.currentItemBackgroundColor,
33721
34727
  fillStyle: this.state.currentItemFillStyle,
33722
- strokeWidth: this.state.currentItemStrokeWidth,
34728
+ strokeWidth: this.getCurrentItemStrokeWidth("text"),
33723
34729
  strokeStyle: this.state.currentItemStrokeStyle,
33724
34730
  roughness: this.state.currentItemRoughness,
33725
34731
  opacity: this.state.currentItemOpacity,
@@ -34278,42 +35284,7 @@ var App = class _App extends React40.Component {
34278
35284
  const { lastCommittedPoint } = selectedLinearElement;
34279
35285
  setCursorForShape(this.interactiveCanvas, this.state);
34280
35286
  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 (
35287
+ if (
34317
35288
  // if we haven't yet created a temp point and we're beyond commit-zone
34318
35289
  // threshold, add a point
34319
35290
  pointDistance9(
@@ -34321,6 +35292,24 @@ var App = class _App extends React40.Component {
34321
35292
  lastPoint
34322
35293
  ) >= LINE_CONFIRM_THRESHOLD2
34323
35294
  ) {
35295
+ this.store.scheduleCapture();
35296
+ flushSync2(() => {
35297
+ invariant16(
35298
+ this.state.selectedLinearElement?.initialState,
35299
+ "initialState must be set"
35300
+ );
35301
+ this.setState({
35302
+ selectedLinearElement: {
35303
+ ...this.state.selectedLinearElement,
35304
+ lastCommittedPoint: points[points.length - 1],
35305
+ selectedPointsIndices: [multiElement.points.length],
35306
+ initialState: {
35307
+ ...this.state.selectedLinearElement.initialState,
35308
+ lastClickedPoint: multiElement.points.length
35309
+ }
35310
+ }
35311
+ });
35312
+ });
34324
35313
  this.scene.mutateElement(
34325
35314
  multiElement,
34326
35315
  {
@@ -34331,21 +35320,6 @@ var App = class _App extends React40.Component {
34331
35320
  },
34332
35321
  { informMutation: false, isDragging: false }
34333
35322
  );
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
35323
  } else {
34350
35324
  setCursor(this.interactiveCanvas, CURSOR_TYPE4.POINTER);
34351
35325
  }
@@ -34693,7 +35667,8 @@ var App = class _App extends React40.Component {
34693
35667
  this.setState((prevState) => {
34694
35668
  return {
34695
35669
  penMode: true,
34696
- penDetected: true
35670
+ penDetected: true,
35671
+ currentItemStrokeVariability: "variable"
34697
35672
  };
34698
35673
  });
34699
35674
  }
@@ -35466,6 +36441,7 @@ var App = class _App extends React40.Component {
35466
36441
  y: gridY
35467
36442
  });
35468
36443
  const simulatePressure = event.pressure === 0.5;
36444
+ const strokeVariability = this.state.currentItemStrokeVariability;
35469
36445
  const element = newFreeDrawElement({
35470
36446
  type: elementType,
35471
36447
  x: gridX,
@@ -35473,15 +36449,21 @@ var App = class _App extends React40.Component {
35473
36449
  strokeColor: this.state.currentItemStrokeColor,
35474
36450
  backgroundColor: this.state.currentItemBackgroundColor,
35475
36451
  fillStyle: this.state.currentItemFillStyle,
35476
- strokeWidth: this.state.currentItemStrokeWidth,
36452
+ strokeWidth: this.getCurrentItemStrokeWidth("freedraw"),
35477
36453
  strokeStyle: this.state.currentItemStrokeStyle,
35478
36454
  roughness: this.state.currentItemRoughness,
35479
36455
  opacity: this.state.currentItemOpacity,
35480
36456
  roundness: null,
35481
36457
  simulatePressure,
36458
+ strokeOptions: {
36459
+ variability: strokeVariability,
36460
+ streamline: event.pointerType !== "mouse" ? DEFAULT_STROKE_STREAMLINE_PRECISE : DEFAULT_STROKE_STREAMLINE2
36461
+ },
35482
36462
  locked: false,
35483
36463
  frameId: topLayerFrame ? topLayerFrame.id : null,
35484
36464
  points: [pointFrom32(0, 0)],
36465
+ // pressures are only consumed when rendering a real-pressure stroke, so
36466
+ // skip persisting them while pressure is being simulated
35485
36467
  pressures: simulatePressure ? [] : [event.pressure]
35486
36468
  });
35487
36469
  this.insertNewElement(element);
@@ -35520,7 +36502,7 @@ var App = class _App extends React40.Component {
35520
36502
  strokeColor: "transparent",
35521
36503
  backgroundColor: "transparent",
35522
36504
  fillStyle: this.state.currentItemFillStyle,
35523
- strokeWidth: this.state.currentItemStrokeWidth,
36505
+ strokeWidth: this.getCurrentItemStrokeWidth("iframe"),
35524
36506
  strokeStyle: this.state.currentItemStrokeStyle,
35525
36507
  roughness: this.state.currentItemRoughness,
35526
36508
  roundness: this.getCurrentItemRoundness("iframe"),
@@ -35560,7 +36542,7 @@ var App = class _App extends React40.Component {
35560
36542
  strokeColor: "transparent",
35561
36543
  backgroundColor: "transparent",
35562
36544
  fillStyle: this.state.currentItemFillStyle,
35563
- strokeWidth: this.state.currentItemStrokeWidth,
36545
+ strokeWidth: this.getCurrentItemStrokeWidth("embeddable"),
35564
36546
  strokeStyle: this.state.currentItemStrokeStyle,
35565
36547
  roughness: this.state.currentItemRoughness,
35566
36548
  roundness: this.getCurrentItemRoundness("embeddable"),
@@ -35594,7 +36576,7 @@ var App = class _App extends React40.Component {
35594
36576
  strokeColor: this.state.currentItemStrokeColor,
35595
36577
  backgroundColor: this.state.currentItemBackgroundColor,
35596
36578
  fillStyle: this.state.currentItemFillStyle,
35597
- strokeWidth: this.state.currentItemStrokeWidth,
36579
+ strokeWidth: this.getCurrentItemStrokeWidth("image"),
35598
36580
  strokeStyle: this.state.currentItemStrokeStyle,
35599
36581
  roughness: this.state.currentItemRoughness,
35600
36582
  roundness: null,
@@ -35651,21 +36633,41 @@ var App = class _App extends React40.Component {
35651
36633
  }
35652
36634
  const { x: rx, y: ry } = multiElement;
35653
36635
  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
- ),
36636
+ const sceneCoords = viewportCoordsToSceneCoords3(event, this.state);
36637
+ const { start: start2, end } = isBindingElement4(multiElement) && isBindingEnabled2(this.state) ? getBindingStrategyForDraggingBindingElementEndpoints2(
36638
+ multiElement,
36639
+ /* @__PURE__ */ new Map([
36640
+ [
36641
+ multiElement.points.length - 1,
36642
+ {
36643
+ point: multiElement.points[multiElement.points.length - 1],
36644
+ isDragging: false
36645
+ }
36646
+ ]
36647
+ ]),
36648
+ sceneCoords.x,
36649
+ sceneCoords.y,
36650
+ this.scene.getNonDeletedElementsMap(),
35659
36651
  this.scene.getNonDeletedElements(),
35660
- this.scene.getNonDeletedElementsMap()
35661
- );
35662
- if (isBindingElement4(multiElement) && hoveredElementForBinding || multiElement.points.length > 1 && lastCommittedPoint && pointDistance9(
36652
+ this.state,
36653
+ {
36654
+ newArrow: Boolean(this.state.newElement),
36655
+ zoom: this.state.zoom
36656
+ }
36657
+ ) : { end: { mode: void 0 } };
36658
+ const elementsMap = this.scene.getNonDeletedElementsMap();
36659
+ const endOutsideSameElement = start2?.mode != null && end.mode != null && start2.element.id === end.element.id && !isPointInElement3(end.focusPoint, end.element, elementsMap);
36660
+ const boundOutsideFromElsewhere = end.mode === "orbit" && multiElement.startBinding?.elementId !== end.element?.id;
36661
+ const lastCommittedPointIsInsideCommitZone = lastCommittedPoint && pointDistance9(
35663
36662
  pointFrom32(
35664
36663
  pointerDownState.origin.x - rx,
35665
36664
  pointerDownState.origin.y - ry
35666
36665
  ),
35667
36666
  lastCommittedPoint
35668
- ) < LINE_CONFIRM_THRESHOLD2) {
36667
+ ) < LINE_CONFIRM_THRESHOLD2;
36668
+ if (boundOutsideFromElsewhere || // Outside -> orbit: Bind immediately
36669
+ endOutsideSameElement || // End outside the start's element: Bind immediately
36670
+ multiElement.points.length > 1 && lastCommittedPointIsInsideCommitZone) {
35669
36671
  this.actionManager.executeAction(actionFinalize, "ui", {
35670
36672
  event: event.nativeEvent,
35671
36673
  sceneCoords: {
@@ -35704,7 +36706,7 @@ var App = class _App extends React40.Component {
35704
36706
  strokeColor: this.state.currentItemStrokeColor,
35705
36707
  backgroundColor: this.state.currentItemBackgroundColor,
35706
36708
  fillStyle: this.state.currentItemFillStyle,
35707
- strokeWidth: this.state.currentItemStrokeWidth,
36709
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35708
36710
  strokeStyle: this.state.currentItemStrokeStyle,
35709
36711
  roughness: this.state.currentItemRoughness,
35710
36712
  opacity: this.state.currentItemOpacity,
@@ -35726,7 +36728,7 @@ var App = class _App extends React40.Component {
35726
36728
  strokeColor: this.state.currentItemStrokeColor,
35727
36729
  backgroundColor: this.state.currentItemBackgroundColor,
35728
36730
  fillStyle: this.state.currentItemFillStyle,
35729
- strokeWidth: this.state.currentItemStrokeWidth,
36731
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35730
36732
  strokeStyle: this.state.currentItemStrokeStyle,
35731
36733
  roughness: this.state.currentItemRoughness,
35732
36734
  opacity: this.state.currentItemOpacity,
@@ -35837,7 +36839,7 @@ var App = class _App extends React40.Component {
35837
36839
  strokeColor: this.state.currentItemStrokeColor,
35838
36840
  backgroundColor: this.state.currentItemBackgroundColor,
35839
36841
  fillStyle: this.state.currentItemFillStyle,
35840
- strokeWidth: this.state.currentItemStrokeWidth,
36842
+ strokeWidth: this.getCurrentItemStrokeWidth(elementType),
35841
36843
  strokeStyle: this.state.currentItemStrokeStyle,
35842
36844
  roughness: this.state.currentItemRoughness,
35843
36845
  opacity: this.state.currentItemOpacity,
@@ -35965,7 +36967,7 @@ var App = class _App extends React40.Component {
35965
36967
  let fileName = imageFile.name;
35966
36968
  const fileNeedsResizing = imageFile.size >= this.state.dontResizeLimitMBs * 1024 * 1024;
35967
36969
  setCursor(this.interactiveCanvas, "wait");
35968
- if (mimeType === MIME_TYPES7.svg) {
36970
+ if (mimeType === MIME_TYPES8.svg) {
35969
36971
  try {
35970
36972
  imageFile = SVGStringToFile(
35971
36973
  normalizeSVG(await imageFile.text()),
@@ -36030,8 +37032,15 @@ var App = class _App extends React40.Component {
36030
37032
  lastRetrieved: Date.now()
36031
37033
  }
36032
37034
  ]);
37035
+ if (this.imageCache.get(fileId)?.isPlaceholder) {
37036
+ const cacheEntry = this.imageCache.get(fileId);
37037
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
37038
+ this.imageTransitionPlaceholders.set(fileId, cacheEntry.image);
37039
+ }
37040
+ this.imageCache.delete(fileId);
37041
+ this.clearImageShapeCacheForFileId(fileId);
37042
+ }
36033
37043
  if (!this.imageCache.get(fileId)) {
36034
- this.addNewImagesToImageCache();
36035
37044
  const { erroredFiles } = await this.updateImageCache([
36036
37045
  initializedImageElement
36037
37046
  ]);
@@ -36040,18 +37049,33 @@ var App = class _App extends React40.Component {
36040
37049
  }
36041
37050
  }
36042
37051
  const imageHTML = await this.imageCache.get(fileId)?.image;
36043
- if (imageHTML && this.state.newElement?.id !== initializedImageElement.id) {
37052
+ let thumbHash = null;
37053
+ if (imageHTML) {
37054
+ try {
37055
+ thumbHash = generateThumbHash(imageHTML);
37056
+ } catch (error) {
37057
+ console.warn("Failed to generate image ThumbHash", error);
37058
+ }
37059
+ }
37060
+ if (thumbHash && this.files[fileId]) {
37061
+ this.files[fileId].thumbHash = thumbHash;
37062
+ }
37063
+ if (imageHTML) {
36044
37064
  initializedImageElement = this.getLatestInitializedImageElement(
36045
37065
  placeholderImageElement,
36046
37066
  fileId,
36047
- fileName
36048
- );
36049
- const naturalDimensions = this.getImageNaturalDimensions(
36050
- initializedImageElement,
36051
- imageHTML
37067
+ fileName,
37068
+ thumbHash
36052
37069
  );
36053
- Object.assign(initializedImageElement, naturalDimensions);
37070
+ if (this.state.newElement?.id !== initializedImageElement.id) {
37071
+ const naturalDimensions = this.getImageNaturalDimensions(
37072
+ initializedImageElement,
37073
+ imageHTML
37074
+ );
37075
+ Object.assign(initializedImageElement, naturalDimensions);
37076
+ }
36054
37077
  }
37078
+ this.clearImageLoadingProgress(fileId);
36055
37079
  resolve(initializedImageElement);
36056
37080
  } catch (error) {
36057
37081
  console.error(error);
@@ -36065,13 +37089,14 @@ var App = class _App extends React40.Component {
36065
37089
  * when the placeholder image could have been modified in the meantime,
36066
37090
  * and when you don't want to loose those modifications
36067
37091
  */
36068
- __publicField(this, "getLatestInitializedImageElement", (imagePlaceholder, fileId, fileName) => {
37092
+ __publicField(this, "getLatestInitializedImageElement", (imagePlaceholder, fileId, fileName, thumbHash = null) => {
36069
37093
  const latestImageElement = this.scene.getElement(imagePlaceholder.id) ?? imagePlaceholder;
36070
37094
  return newElementWith10(
36071
37095
  latestImageElement,
36072
37096
  {
36073
37097
  fileId,
36074
- fileName
37098
+ fileName,
37099
+ thumbHash
36075
37100
  }
36076
37101
  );
36077
37102
  });
@@ -36142,7 +37167,23 @@ var App = class _App extends React40.Component {
36142
37167
  fileIds: elements.map((element) => element.fileId),
36143
37168
  files
36144
37169
  });
37170
+ const transitionStart = performance.now();
37171
+ for (const fileId of updatedFiles.keys()) {
37172
+ const cacheEntry = this.imageCache.get(fileId);
37173
+ if (cacheEntry && !(cacheEntry.image instanceof Promise)) {
37174
+ this.imageCache.set(fileId, {
37175
+ ...cacheEntry,
37176
+ placeholderImage: this.imageTransitionPlaceholders.get(fileId),
37177
+ transitionStart
37178
+ });
37179
+ }
37180
+ this.imageTransitionPlaceholders.delete(fileId);
37181
+ }
36145
37182
  if (erroredFiles.size) {
37183
+ for (const fileId of erroredFiles.keys()) {
37184
+ this.imageTransitionPlaceholders.delete(fileId);
37185
+ this.clearImageLoadingProgress(fileId);
37186
+ }
36146
37187
  this.store.scheduleAction(CaptureUpdateAction41.NEVER);
36147
37188
  this.scene.replaceAllElements(
36148
37189
  this.scene.getElementsIncludingDeleted().map((element) => {
@@ -36256,6 +37297,7 @@ var App = class _App extends React40.Component {
36256
37297
  });
36257
37298
  });
36258
37299
  __publicField(this, "handleAppOnDrop", async (event) => {
37300
+ const dragDataSnapshot = captureDragEventData(event);
36259
37301
  const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords3(
36260
37302
  event,
36261
37303
  this.state
@@ -36264,7 +37306,7 @@ var App = class _App extends React40.Component {
36264
37306
  const fileItems = dataTransferList.getFiles();
36265
37307
  if (fileItems.length === 1) {
36266
37308
  const { file: file2, fileHandle } = fileItems[0];
36267
- if (file2 && (file2.type === MIME_TYPES7.png || file2.type === MIME_TYPES7.svg)) {
37309
+ if (file2 && (file2.type === MIME_TYPES8.png || file2.type === MIME_TYPES8.svg)) {
36268
37310
  try {
36269
37311
  const scene = await loadFromBlob(
36270
37312
  file2,
@@ -36292,65 +37334,14 @@ var App = class _App extends React40.Component {
36292
37334
  const imageFiles = fileItems.map((data) => data.file).filter((file2) => isSupportedImageFile(file2));
36293
37335
  if (imageFiles.length > 0) {
36294
37336
  if (this.isToolSupported("image")) {
36295
- const parseDroppedImageDetails = () => {
36296
- const htmlImageSources = [];
36297
- const textImageSources = [];
36298
- const imageAlts = [];
36299
- const pushUnique = (values, value) => {
36300
- const trimmedValue = value?.trim();
36301
- if (trimmedValue && !values.includes(trimmedValue)) {
36302
- values.push(trimmedValue);
36303
- }
36304
- };
36305
- dataTransferList.forEach((item) => {
36306
- if (item.kind !== "string") {
36307
- return;
36308
- }
36309
- if (item.type === MIME_TYPES7.html) {
36310
- try {
36311
- const doc = new DOMParser().parseFromString(
36312
- item.value,
36313
- MIME_TYPES7.html
36314
- );
36315
- for (const img of Array.from(
36316
- doc.body.querySelectorAll("img")
36317
- )) {
36318
- pushUnique(htmlImageSources, img.getAttribute("src"));
36319
- pushUnique(imageAlts, img.getAttribute("alt"));
36320
- }
36321
- } catch {
36322
- }
36323
- } else if (item.type === MIME_TYPES7.text) {
36324
- pushUnique(textImageSources, item.value);
36325
- }
36326
- });
36327
- const imageSources = [
36328
- ...htmlImageSources,
36329
- ...textImageSources.filter(
36330
- (src) => !htmlImageSources.includes(src)
36331
- )
36332
- ];
36333
- const imageDetails = imageFiles.map(() => ({}));
36334
- imageDetails.forEach((detail, index) => {
36335
- const source = imageSources[index];
36336
- const alt = imageAlts[index];
36337
- if (source) {
36338
- detail.src = source;
36339
- }
36340
- if (alt) {
36341
- detail.alt = alt;
36342
- }
36343
- });
36344
- const additionalUrls = imageSources.slice(imageDetails.length);
36345
- const firstImageDetails = imageDetails[0];
36346
- if (firstImageDetails && additionalUrls.length > 0) {
36347
- additionalUrls.forEach((url, index) => {
36348
- firstImageDetails[`additionalUrl${index + 1}`] = url;
36349
- });
36350
- }
36351
- return imageDetails;
36352
- };
36353
- const droppedImageDetails = parseDroppedImageDetails();
37337
+ const droppedImageDetails = parseDragImageMetadata(
37338
+ dataTransferList,
37339
+ imageFiles.length,
37340
+ dragDataSnapshot
37341
+ );
37342
+ if (this.props.showDropEventDebugAlert) {
37343
+ alert(`${JSON.stringify(droppedImageDetails)}`);
37344
+ }
36354
37345
  return this.insertImages(
36355
37346
  imageFiles.map((file2, index) => ({
36356
37347
  file: file2,
@@ -36369,7 +37360,7 @@ var App = class _App extends React40.Component {
36369
37360
  await this.loadFileToCanvas(file2, fileHandle);
36370
37361
  }
36371
37362
  }
36372
- const textItem = dataTransferList.findByType(MIME_TYPES7.text);
37363
+ const textItem = dataTransferList.findByType(MIME_TYPES8.text);
36373
37364
  if (textItem) {
36374
37365
  const text = textItem.value;
36375
37366
  if (text && embeddableURLValidator2(text, this.props.validateEmbeddable) && (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(text) || getEmbedLink2(text)?.type === "video")) {
@@ -37013,6 +38004,8 @@ var App = class _App extends React40.Component {
37013
38004
  applyDeltas: this.applyDeltas,
37014
38005
  mutateElement: this.mutateElement,
37015
38006
  addFiles: this.addFiles,
38007
+ addImagePlaceholder: this.addImagePlaceholder,
38008
+ setImageLoadingProgress: this.setImageLoadingProgress,
37016
38009
  addImageElementsToScene: this.addImageElementsToScene,
37017
38010
  resetScene: this.resetScene,
37018
38011
  getSceneElementsIncludingDeleted: this.getSceneElementsIncludingDeleted,
@@ -37592,7 +38585,7 @@ var App = class _App extends React40.Component {
37592
38585
  const isActive = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "active";
37593
38586
  const isHovered = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "hover";
37594
38587
  const shouldScaleEmbeddableViewport = src?.type === "video";
37595
- const embeddableViewportScale = clamp8(
38588
+ const embeddableViewportScale = clamp10(
37596
38589
  shouldScaleEmbeddableViewport ? scale : 1,
37597
38590
  0.75,
37598
38591
  MAX_EMBEDDABLE_VIEWPORT_SCALE
@@ -37878,6 +38871,7 @@ var App = class _App extends React40.Component {
37878
38871
  /* @__PURE__ */ jsx137(
37879
38872
  StaticCanvas_default,
37880
38873
  {
38874
+ app: this,
37881
38875
  canvas: this.canvas,
37882
38876
  rc: this.rc,
37883
38877
  elementsMap,
@@ -37889,6 +38883,7 @@ var App = class _App extends React40.Component {
37889
38883
  appState: this.state,
37890
38884
  renderConfig: {
37891
38885
  imageCache: this.imageCache,
38886
+ imageTransitionDuration: this.props.imageOptions.placeholderTransitionDuration,
37892
38887
  isExporting: false,
37893
38888
  renderGrid: isGridModeEnabled(this),
37894
38889
  canvasBackgroundColor: this.state.viewBackgroundColor,
@@ -37910,6 +38905,7 @@ var App = class _App extends React40.Component {
37910
38905
  allElementsMap,
37911
38906
  renderConfig: {
37912
38907
  imageCache: this.imageCache,
38908
+ imageTransitionDuration: this.props.imageOptions.placeholderTransitionDuration,
37913
38909
  isExporting: false,
37914
38910
  renderGrid: false,
37915
38911
  canvasBackgroundColor: this.state.viewBackgroundColor,
@@ -38177,6 +39173,10 @@ var App = class _App extends React40.Component {
38177
39173
  this.renderer = new Renderer(this.scene);
38178
39174
  this.files = {};
38179
39175
  this.imageCache.clear();
39176
+ this.imageLoadingProgress.clear();
39177
+ this.imageTransitionPlaceholders.clear();
39178
+ this.imageLoadingProgressEmitter.clear();
39179
+ this.imagePlaceholderUpdateEmitter.clear();
38180
39180
  this.resizeObserver?.disconnect();
38181
39181
  this.unmounted = true;
38182
39182
  this.removeEventListeners();
@@ -38581,7 +39581,7 @@ var App = class _App extends React40.Component {
38581
39581
  strokeColor: this.state.currentItemStrokeColor,
38582
39582
  backgroundColor: this.state.currentItemBackgroundColor,
38583
39583
  fillStyle: this.state.currentItemFillStyle,
38584
- strokeWidth: this.state.currentItemStrokeWidth,
39584
+ strokeWidth: this.getCurrentItemStrokeWidth("text"),
38585
39585
  strokeStyle: this.state.currentItemStrokeStyle,
38586
39586
  roundness: null,
38587
39587
  roughness: this.state.currentItemRoughness,
@@ -39160,6 +40160,12 @@ var App = class _App extends React40.Component {
39160
40160
  type: isUsingAdaptiveRadius4(elementType) ? ROUNDNESS7.ADAPTIVE_RADIUS : ROUNDNESS7.PROPORTIONAL_RADIUS
39161
40161
  } : null;
39162
40162
  }
40163
+ getCurrentItemStrokeWidth(elementType) {
40164
+ return getStrokeWidthByKey3(
40165
+ elementType,
40166
+ this.state.currentItemStrokeWidthKey
40167
+ );
40168
+ }
39163
40169
  maybeCacheReferenceSnapPoints(event, selectedElements, recomputeAnyways = false) {
39164
40170
  if (isSnappingEnabled({
39165
40171
  event,
@@ -39498,12 +40504,12 @@ var App = class _App extends React40.Component {
39498
40504
  );
39499
40505
  const nextCrop = {
39500
40506
  ...crop,
39501
- x: clamp8(
40507
+ x: clamp10(
39502
40508
  crop.x - offsetVector[0] * Math.sign(croppingElement.scale[0]),
39503
40509
  0,
39504
40510
  image.naturalWidth - crop.width
39505
40511
  ),
39506
- y: clamp8(
40512
+ y: clamp10(
39507
40513
  crop.y - offsetVector[1] * Math.sign(croppingElement.scale[1]),
39508
40514
  0,
39509
40515
  image.naturalHeight - crop.height
@@ -40056,9 +41062,6 @@ var App = class _App extends React40.Component {
40056
41062
  return;
40057
41063
  }
40058
41064
  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
41065
  const pointerCoords = viewportCoordsToSceneCoords3(
40063
41066
  childEvent,
40064
41067
  this.state
@@ -40086,21 +41089,13 @@ var App = class _App extends React40.Component {
40086
41089
  );
40087
41090
  this.actionManager.executeAction(actionFinalize);
40088
41091
  } 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
41092
  this.setState({
40099
41093
  multiElement: newElement7,
40100
41094
  newElement: newElement7
40101
41095
  });
40102
41096
  }
40103
41097
  } else if (pointerDownState.drag.hasOccurred && !multiElement) {
41098
+ this.store.scheduleCapture();
40104
41099
  if (isLinearElement11(newElement7)) {
40105
41100
  this.actionManager.executeAction(actionFinalize, "ui", {
40106
41101
  event: childEvent,
@@ -41220,7 +42215,7 @@ import { isLinearElement as isLinearElement12 } from "@excalidraw/element";
41220
42215
  import {
41221
42216
  FONT_FAMILY as FONT_FAMILY7,
41222
42217
  THEME as THEME18,
41223
- MIME_TYPES as MIME_TYPES8,
42218
+ MIME_TYPES as MIME_TYPES9,
41224
42219
  ROUNDNESS as ROUNDNESS8,
41225
42220
  DEFAULT_LASER_COLOR as DEFAULT_LASER_COLOR2,
41226
42221
  UserIdleState as UserIdleState2,
@@ -41312,7 +42307,8 @@ var ExcalidrawBase = (props) => {
41312
42307
  strokeColorTopPicks,
41313
42308
  backgroundColorTopPicks,
41314
42309
  renderScrollbars,
41315
- imageOptions
42310
+ imageOptions,
42311
+ showDropEventDebugAlert
41316
42312
  } = props;
41317
42313
  const canvasActions = props.UIOptions?.canvasActions;
41318
42314
  const UIOptions = {
@@ -41333,7 +42329,8 @@ var ExcalidrawBase = (props) => {
41333
42329
  }
41334
42330
  const normalizedImageOptions = {
41335
42331
  maxFileSizeBytes: imageOptions?.maxFileSizeBytes ?? DEFAULT_IMAGE_OPTIONS.maxFileSizeBytes,
41336
- maxWidthOrHeight: imageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight
42332
+ maxWidthOrHeight: imageOptions?.maxWidthOrHeight ?? DEFAULT_IMAGE_OPTIONS.maxWidthOrHeight,
42333
+ placeholderTransitionDuration: imageOptions?.placeholderTransitionDuration ?? DEFAULT_IMAGE_OPTIONS.placeholderTransitionDuration
41337
42334
  };
41338
42335
  const setExcalidrawAPI = useContext4(ExcalidrawAPISetContext);
41339
42336
  const onExcalidrawAPIRef = useRef31(onExcalidrawAPI);
@@ -41410,6 +42407,7 @@ var ExcalidrawBase = (props) => {
41410
42407
  backgroundColorTopPicks,
41411
42408
  renderScrollbars,
41412
42409
  imageOptions: normalizedImageOptions,
42410
+ showDropEventDebugAlert,
41413
42411
  children
41414
42412
  }
41415
42413
  ) }) });
@@ -41452,7 +42450,7 @@ var areEqual5 = (prevProps, nextProps) => {
41452
42450
  }
41453
42451
  return prevUIOptions[key] === nextUIOptions[key];
41454
42452
  });
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);
42453
+ 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
42454
  return isUIOptionsSame && isImageOptionsSame && isShallowEqual9(prev, next);
41457
42455
  };
41458
42456
  var Excalidraw = React42.memo(ExcalidrawBase, areEqual5);
@@ -41474,7 +42472,7 @@ export {
41474
42472
  Fonts,
41475
42473
  FooterCenter_default as Footer,
41476
42474
  LiveCollaborationTrigger_default as LiveCollaborationTrigger,
41477
- MIME_TYPES8 as MIME_TYPES,
42475
+ MIME_TYPES9 as MIME_TYPES,
41478
42476
  MainMenu_default as MainMenu,
41479
42477
  ROUNDNESS8 as ROUNDNESS,
41480
42478
  Sidebar,
@@ -41484,6 +42482,7 @@ export {
41484
42482
  WelcomeScreen_default as WelcomeScreen,
41485
42483
  applyDarkModeFilter5 as applyDarkModeFilter,
41486
42484
  bumpVersion3 as bumpVersion,
42485
+ captureDragEventData,
41487
42486
  convertToExcalidrawElements2 as convertToExcalidrawElements,
41488
42487
  defaultLang,
41489
42488
  elementsOverlappingBBox3 as elementsOverlappingBBox,
@@ -41496,6 +42495,7 @@ export {
41496
42495
  getFormFactor2 as getFormFactor,
41497
42496
  getNonDeletedElements13 as getNonDeletedElements,
41498
42497
  getSceneVersion,
42498
+ getStrokeWidthByKey4 as getStrokeWidthByKey,
41499
42499
  getTextFromElements3 as getTextFromElements,
41500
42500
  getVisibleSceneBounds2 as getVisibleSceneBounds,
41501
42501
  hashElementsVersion,
@@ -41509,6 +42509,8 @@ export {
41509
42509
  mutateElement7 as mutateElement,
41510
42510
  newElementWith11 as newElementWith,
41511
42511
  normalizeLink4 as normalizeLink,
42512
+ parseDataTransferEvent,
42513
+ parseDragImageMetadata,
41512
42514
  reconcileElements,
41513
42515
  renderSpreadsheet,
41514
42516
  restoreAppState,