@excalidraw/excalidraw 0.17.1-c0b80a0 → 0.17.1-c329470

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/browser/dev/excalidraw-assets-dev/{chunk-JGDL4H2X.js → chunk-3DLVY5XU.js} +8272 -6864
  2. package/dist/browser/dev/excalidraw-assets-dev/chunk-3DLVY5XU.js.map +7 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/{chunk-V7NFEZA6.js → chunk-NOAEU4NM.js} +9 -2
  4. package/dist/browser/dev/excalidraw-assets-dev/chunk-NOAEU4NM.js.map +7 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js → en-7IBTMWBG.js} +2 -2
  6. package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js → image-N5AC7SEK.js} +2 -6
  7. package/dist/browser/dev/index.css +85 -50
  8. package/dist/browser/dev/index.css.map +3 -3
  9. package/dist/browser/dev/index.js +4375 -3766
  10. package/dist/browser/dev/index.js.map +4 -4
  11. package/dist/browser/prod/excalidraw-assets/{chunk-LDVEIXGO.js → chunk-7CSIPVOW.js} +2 -2
  12. package/dist/browser/prod/excalidraw-assets/chunk-TX3BU7T2.js +47 -0
  13. package/dist/browser/prod/excalidraw-assets/{en-UPNEHLDS.js → en-LOGQBETY.js} +1 -1
  14. package/dist/browser/prod/excalidraw-assets/image-3V4U7GZE.js +1 -0
  15. package/dist/browser/prod/index.css +1 -1
  16. package/dist/browser/prod/index.js +40 -40
  17. package/dist/dev/index.css +85 -50
  18. package/dist/dev/index.css.map +3 -3
  19. package/dist/dev/index.js +8688 -6706
  20. package/dist/dev/index.js.map +4 -4
  21. package/dist/{prod/locales/en-ZXYG7GCR.json → dev/locales/en-V6KXFSCK.json} +8 -1
  22. package/dist/excalidraw/actions/actionAlign.d.ts +7 -6
  23. package/dist/excalidraw/actions/actionAlign.js +14 -14
  24. package/dist/excalidraw/actions/actionClipboard.d.ts +7 -3
  25. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +7 -3
  26. package/dist/excalidraw/actions/actionDeleteSelected.js +103 -34
  27. package/dist/excalidraw/actions/actionDuplicateSelection.js +105 -95
  28. package/dist/excalidraw/actions/actionFlip.js +16 -7
  29. package/dist/excalidraw/actions/actionFrame.d.ts +493 -0
  30. package/dist/excalidraw/actions/actionFrame.js +45 -2
  31. package/dist/excalidraw/actions/actionGroup.js +6 -4
  32. package/dist/excalidraw/actions/actionProperties.js +145 -116
  33. package/dist/excalidraw/actions/actionSelectAll.js +4 -3
  34. package/dist/excalidraw/actions/shortcuts.d.ts +1 -1
  35. package/dist/excalidraw/actions/shortcuts.js +1 -0
  36. package/dist/excalidraw/actions/types.d.ts +1 -1
  37. package/dist/excalidraw/align.d.ts +2 -1
  38. package/dist/excalidraw/align.js +15 -6
  39. package/dist/excalidraw/clipboard.d.ts +27 -5
  40. package/dist/excalidraw/clipboard.js +55 -28
  41. package/dist/excalidraw/components/Actions.d.ts +2 -1
  42. package/dist/excalidraw/components/Actions.js +4 -2
  43. package/dist/excalidraw/components/ActiveConfirmDialog.d.ts +1 -1
  44. package/dist/excalidraw/components/ActiveConfirmDialog.js +2 -3
  45. package/dist/excalidraw/components/App.d.ts +1 -0
  46. package/dist/excalidraw/components/App.js +216 -111
  47. package/dist/excalidraw/components/ColorPicker/ColorInput.js +2 -3
  48. package/dist/excalidraw/components/ColorPicker/ColorPicker.js +2 -3
  49. package/dist/excalidraw/components/ColorPicker/CustomColorList.js +1 -1
  50. package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
  51. package/dist/excalidraw/components/ColorPicker/PickerColorList.js +1 -1
  52. package/dist/excalidraw/components/ColorPicker/ShadeList.js +1 -1
  53. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
  54. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
  55. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +3 -3
  56. package/dist/excalidraw/components/ConfirmDialog.js +17 -5
  57. package/dist/excalidraw/components/Dialog.js +2 -3
  58. package/dist/excalidraw/components/EyeDropper.d.ts +1 -1
  59. package/dist/excalidraw/components/EyeDropper.js +1 -1
  60. package/dist/excalidraw/components/IconPicker.d.ts +2 -2
  61. package/dist/excalidraw/components/IconPicker.js +56 -53
  62. package/dist/excalidraw/components/LayerUI.js +6 -6
  63. package/dist/excalidraw/components/LibraryMenu.d.ts +2 -16
  64. package/dist/excalidraw/components/LibraryMenu.js +70 -28
  65. package/dist/excalidraw/components/LibraryMenuHeaderContent.js +4 -5
  66. package/dist/excalidraw/components/MobileMenu.js +1 -1
  67. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirm.js +2 -3
  68. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
  69. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.js +2 -3
  70. package/dist/excalidraw/components/Range.d.ts +9 -0
  71. package/dist/excalidraw/components/Range.js +24 -0
  72. package/dist/excalidraw/components/SearchMenu.d.ts +1 -1
  73. package/dist/excalidraw/components/SearchMenu.js +3 -4
  74. package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  75. package/dist/excalidraw/components/Sidebar/Sidebar.js +2 -3
  76. package/dist/excalidraw/components/Stats/Collapsible.d.ts +2 -1
  77. package/dist/excalidraw/components/Stats/Collapsible.js +2 -2
  78. package/dist/excalidraw/components/Stats/Dimension.js +94 -8
  79. package/dist/excalidraw/components/Stats/MultiDimension.js +8 -5
  80. package/dist/excalidraw/components/Stats/Position.js +63 -3
  81. package/dist/excalidraw/components/Stats/index.js +21 -4
  82. package/dist/excalidraw/components/Stats/utils.d.ts +1 -1
  83. package/dist/excalidraw/components/Stats/utils.js +2 -55
  84. package/dist/excalidraw/components/TTDDialog/TTDDialog.js +1 -1
  85. package/dist/excalidraw/components/ToolButton.js +4 -9
  86. package/dist/excalidraw/components/hoc/withInternalFallback.js +3 -3
  87. package/dist/excalidraw/components/hyperlink/Hyperlink.js +6 -12
  88. package/dist/excalidraw/components/icons.d.ts +9 -0
  89. package/dist/excalidraw/components/icons.js +4 -4
  90. package/dist/excalidraw/components/main-menu/DefaultItems.js +2 -3
  91. package/dist/excalidraw/constants.d.ts +5 -1
  92. package/dist/excalidraw/constants.js +9 -1
  93. package/dist/excalidraw/context/tunnels.d.ts +2 -1
  94. package/dist/excalidraw/context/tunnels.js +3 -1
  95. package/dist/excalidraw/data/blob.d.ts +1 -0
  96. package/dist/excalidraw/data/blob.js +7 -3
  97. package/dist/excalidraw/data/filesystem.d.ts +2 -1
  98. package/dist/excalidraw/data/filesystem.js +1 -0
  99. package/dist/excalidraw/data/image.d.ts +0 -6
  100. package/dist/excalidraw/data/image.js +1 -43
  101. package/dist/excalidraw/data/index.js +6 -6
  102. package/dist/excalidraw/data/library.d.ts +9 -3
  103. package/dist/excalidraw/data/library.js +43 -6
  104. package/dist/excalidraw/data/restore.js +26 -8
  105. package/dist/excalidraw/data/url.d.ts +0 -1
  106. package/dist/excalidraw/data/url.js +2 -4
  107. package/dist/excalidraw/editor-jotai.d.ts +56 -0
  108. package/dist/excalidraw/editor-jotai.js +8 -0
  109. package/dist/excalidraw/element/binding.d.ts +9 -6
  110. package/dist/excalidraw/element/binding.js +124 -44
  111. package/dist/excalidraw/element/bounds.js +10 -0
  112. package/dist/excalidraw/element/cropElement.d.ts +5 -0
  113. package/dist/excalidraw/element/cropElement.js +28 -1
  114. package/dist/excalidraw/element/dragElements.js +13 -7
  115. package/dist/excalidraw/element/elbowArrow.d.ts +16 -0
  116. package/dist/excalidraw/element/elbowArrow.js +1268 -0
  117. package/dist/excalidraw/element/embeddable.js +4 -5
  118. package/dist/excalidraw/element/flowchart.d.ts +1 -1
  119. package/dist/excalidraw/element/flowchart.js +25 -9
  120. package/dist/excalidraw/element/heading.d.ts +5 -1
  121. package/dist/excalidraw/element/heading.js +5 -1
  122. package/dist/excalidraw/element/image.js +19 -5
  123. package/dist/excalidraw/element/linearElementEditor.d.ts +9 -10
  124. package/dist/excalidraw/element/linearElementEditor.js +97 -38
  125. package/dist/excalidraw/element/mutateElement.d.ts +3 -1
  126. package/dist/excalidraw/element/mutateElement.js +31 -4
  127. package/dist/excalidraw/element/newElement.d.ts +8 -12
  128. package/dist/excalidraw/element/newElement.js +36 -21
  129. package/dist/excalidraw/element/resizeElements.d.ts +20 -5
  130. package/dist/excalidraw/element/resizeElements.js +593 -361
  131. package/dist/excalidraw/element/sortElements.js +1 -4
  132. package/dist/excalidraw/element/types.d.ts +23 -1
  133. package/dist/excalidraw/fonts/Fonts.d.ts +0 -16
  134. package/dist/excalidraw/fonts/Fonts.js +6 -31
  135. package/dist/excalidraw/frame.d.ts +11 -5
  136. package/dist/excalidraw/frame.js +146 -35
  137. package/dist/excalidraw/groups.js +3 -0
  138. package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
  139. package/dist/excalidraw/hooks/useLibraryItemSvg.js +2 -3
  140. package/dist/excalidraw/hooks/useScrollPosition.js +1 -1
  141. package/dist/excalidraw/i18n.js +3 -4
  142. package/dist/excalidraw/index.js +3 -4
  143. package/dist/excalidraw/locales/en.json +8 -1
  144. package/dist/excalidraw/renderer/interactiveScene.js +43 -32
  145. package/dist/excalidraw/renderer/staticScene.js +6 -4
  146. package/dist/excalidraw/renderer/staticSvgScene.js +1 -1
  147. package/dist/excalidraw/scene/Shape.js +40 -17
  148. package/dist/excalidraw/scene/comparisons.d.ts +0 -477
  149. package/dist/excalidraw/scene/comparisons.js +0 -37
  150. package/dist/excalidraw/scene/export.d.ts +7 -0
  151. package/dist/excalidraw/scene/export.js +107 -43
  152. package/dist/excalidraw/scene/index.d.ts +1 -1
  153. package/dist/excalidraw/scene/index.js +1 -1
  154. package/dist/excalidraw/scene/selection.js +4 -1
  155. package/dist/excalidraw/types.d.ts +15 -0
  156. package/dist/excalidraw/utility-types.d.ts +1 -0
  157. package/dist/excalidraw/utils.d.ts +8 -1
  158. package/dist/excalidraw/utils.js +9 -0
  159. package/dist/excalidraw/visualdebug.d.ts +8 -1
  160. package/dist/excalidraw/visualdebug.js +3 -0
  161. package/dist/math/line.d.ts +19 -0
  162. package/dist/math/line.js +32 -3
  163. package/dist/math/point.d.ts +10 -0
  164. package/dist/math/point.js +12 -1
  165. package/dist/prod/index.css +1 -1
  166. package/dist/prod/index.js +29 -44
  167. package/dist/{dev/locales/en-ZXYG7GCR.json → prod/locales/en-V6KXFSCK.json} +8 -1
  168. package/package.json +5 -2
  169. package/dist/browser/dev/excalidraw-assets-dev/chunk-JGDL4H2X.js.map +0 -7
  170. package/dist/browser/dev/excalidraw-assets-dev/chunk-V7NFEZA6.js.map +0 -7
  171. package/dist/browser/prod/excalidraw-assets/chunk-S2XKB3DE.js +0 -62
  172. package/dist/browser/prod/excalidraw-assets/image-OFI2YYMP.js +0 -1
  173. package/dist/excalidraw/element/routing.d.ts +0 -12
  174. package/dist/excalidraw/element/routing.js +0 -642
  175. package/dist/excalidraw/jotai.d.ts +0 -34
  176. package/dist/excalidraw/jotai.js +0 -18
  177. /package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js.map → en-7IBTMWBG.js.map} +0 -0
  178. /package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js.map → image-N5AC7SEK.js.map} +0 -0
@@ -1,7 +1,6 @@
1
1
  import { loadLibraryFromBlob } from "./blob";
2
2
  import { restoreLibraryItems } from "./restore";
3
- import { atom } from "jotai";
4
- import { jotaiStore } from "../jotai";
3
+ import { atom, editorJotaiStore } from "../editor-jotai";
5
4
  import { getCommonBoundingBox } from "../element/bounds";
6
5
  import { AbortError } from "../errors";
7
6
  import { t } from "../i18n";
@@ -12,6 +11,19 @@ import { arrayToMap, cloneJSON, preventUnload, promiseTry, resolvablePromise, }
12
11
  import { Emitter } from "../emitter";
13
12
  import { Queue } from "../queue";
14
13
  import { hashElementsVersion, hashString } from "../element";
14
+ import { toValidURL } from "./url";
15
+ /**
16
+ * format: hostname or hostname/pathname
17
+ *
18
+ * Both hostname and pathname are matched partially,
19
+ * hostname from the end, pathname from the start, with subdomain/path
20
+ * boundaries
21
+ **/
22
+ const ALLOWED_LIBRARY_URLS = [
23
+ "excalidraw.com",
24
+ // when installing from github PRs
25
+ "raw.githubusercontent.com/excalidraw/excalidraw-libraries",
26
+ ];
15
27
  const onLibraryUpdateEmitter = new Emitter();
16
28
  export const libraryItemsAtom = atom({ status: "loaded", isInitialized: false, libraryItems: [] });
17
29
  const cloneLibraryItems = (libraryItems) => cloneJSON(libraryItems);
@@ -83,14 +95,14 @@ class Library {
83
95
  };
84
96
  notifyListeners = () => {
85
97
  if (this.updateQueue.length > 0) {
86
- jotaiStore.set(libraryItemsAtom, (s) => ({
98
+ editorJotaiStore.set(libraryItemsAtom, (s) => ({
87
99
  status: "loading",
88
100
  libraryItems: this.currLibraryItems,
89
101
  isInitialized: s.isInitialized,
90
102
  }));
91
103
  }
92
104
  else {
93
- jotaiStore.set(libraryItemsAtom, {
105
+ editorJotaiStore.set(libraryItemsAtom, {
94
106
  status: "loaded",
95
107
  libraryItems: this.currLibraryItems,
96
108
  isInitialized: true,
@@ -112,7 +124,7 @@ class Library {
112
124
  destroy = () => {
113
125
  this.updateQueue = [];
114
126
  this.currLibraryItems = [];
115
- jotaiStore.set(libraryItemSvgsCache, new Map());
127
+ editorJotaiStore.set(libraryItemSvgsCache, new Map());
116
128
  // TODO uncomment after/if we make jotai store scoped to each excal instance
117
129
  // jotaiStore.set(libraryItemsAtom, {
118
130
  // status: "loading",
@@ -311,6 +323,23 @@ export const distributeLibraryItemsOnSquareGrid = (libraryItems) => {
311
323
  }
312
324
  return resElements;
313
325
  };
326
+ export const validateLibraryUrl = (libraryUrl,
327
+ /**
328
+ * @returns `true` if the URL is valid, throws otherwise.
329
+ */
330
+ validator = ALLOWED_LIBRARY_URLS) => {
331
+ if (typeof validator === "function"
332
+ ? validator(libraryUrl)
333
+ : validator.some((allowedUrlDef) => {
334
+ const allowedUrl = new URL(`https://${allowedUrlDef.replace(/^https?:\/\//, "")}`);
335
+ const { hostname, pathname } = new URL(libraryUrl);
336
+ return (new RegExp(`(^|\\.)${allowedUrl.hostname}$`).test(hostname) &&
337
+ new RegExp(`^${allowedUrl.pathname.replace(/\/+$/, "")}(/+|$)`).test(pathname));
338
+ })) {
339
+ return true;
340
+ }
341
+ throw new Error(`Invalid or disallowed library URL: "${libraryUrl}"`);
342
+ };
314
343
  export const parseLibraryTokensFromUrl = () => {
315
344
  const libraryUrl =
316
345
  // current
@@ -424,7 +453,10 @@ export const useHandleLibrary = (opts) => {
424
453
  const importLibraryFromURL = async ({ libraryUrl, idToken, }) => {
425
454
  const libraryPromise = new Promise(async (resolve, reject) => {
426
455
  try {
427
- const request = await fetch(decodeURIComponent(libraryUrl));
456
+ libraryUrl = decodeURIComponent(libraryUrl);
457
+ libraryUrl = toValidURL(libraryUrl);
458
+ validateLibraryUrl(libraryUrl, optsRef.current.validateLibraryUrl);
459
+ const request = await fetch(libraryUrl);
428
460
  const blob = await request.blob();
429
461
  resolve(blob);
430
462
  }
@@ -452,6 +484,11 @@ export const useHandleLibrary = (opts) => {
452
484
  });
453
485
  }
454
486
  catch (error) {
487
+ excalidrawAPI.updateScene({
488
+ appState: {
489
+ errorMessage: error.message,
490
+ },
491
+ });
455
492
  throw error;
456
493
  }
457
494
  finally {
@@ -43,14 +43,20 @@ const repairBinding = (element, binding) => {
43
43
  if (!binding) {
44
44
  return null;
45
45
  }
46
- return {
47
- ...binding,
48
- focus: binding.focus || 0,
49
- ...(isElbowArrow(element) && isFixedPointBinding(binding)
46
+ const focus = binding.focus || 0;
47
+ if (isElbowArrow(element)) {
48
+ const fixedPointBinding = isFixedPointBinding(binding)
50
49
  ? {
50
+ ...binding,
51
+ focus,
51
52
  fixedPoint: normalizeFixedPoint(binding.fixedPoint ?? [0, 0]),
52
53
  }
53
- : {}),
54
+ : null;
55
+ return fixedPointBinding;
56
+ }
57
+ return {
58
+ ...binding,
59
+ focus,
54
60
  };
55
61
  };
56
62
  const restoreElementWithProperties = (element, extra) => {
@@ -205,8 +211,7 @@ const restoreElement = (element) => {
205
211
  if (points[0][0] !== 0 || points[0][1] !== 0) {
206
212
  ({ points, x, y } = LinearElementEditor.getNormalizedPoints(element));
207
213
  }
208
- // TODO: Separate arrow from linear element
209
- return restoreElementWithProperties(element, {
214
+ const base = {
210
215
  type: element.type,
211
216
  startBinding: repairBinding(element, element.startBinding),
212
217
  endBinding: repairBinding(element, element.endBinding),
@@ -218,7 +223,19 @@ const restoreElement = (element) => {
218
223
  y,
219
224
  elbowed: element.elbowed,
220
225
  ...getSizeFromPoints(points),
221
- });
226
+ };
227
+ // TODO: Separate arrow from linear element
228
+ return isElbowArrow(element)
229
+ ? restoreElementWithProperties(element, {
230
+ ...base,
231
+ elbowed: true,
232
+ startBinding: repairBinding(element, element.startBinding),
233
+ endBinding: repairBinding(element, element.endBinding),
234
+ fixedSegments: element.fixedSegments,
235
+ startIsSpecial: element.startIsSpecial,
236
+ endIsSpecial: element.endIsSpecial,
237
+ })
238
+ : restoreElementWithProperties(element, base);
222
239
  }
223
240
  // generic elements
224
241
  case "ellipse":
@@ -433,6 +450,7 @@ export const restoreAppState = (appState, localAppState) => {
433
450
  : nextAppState.openSidebar,
434
451
  gridSize: getNormalizedGridSize(isFiniteNumber(appState.gridSize) ? appState.gridSize : DEFAULT_GRID_SIZE),
435
452
  gridStep: getNormalizedGridStep(isFiniteNumber(appState.gridStep) ? appState.gridStep : DEFAULT_GRID_STEP),
453
+ editingFrame: null,
436
454
  };
437
455
  };
438
456
  export const restore = (data,
@@ -1,4 +1,3 @@
1
- export declare const sanitizeHTMLAttribute: (html: string) => string;
2
1
  export declare const normalizeLink: (link: string) => string;
3
2
  export declare const isLocalLink: (link: string | null) => boolean;
4
3
  /**
@@ -1,13 +1,11 @@
1
1
  import { sanitizeUrl } from "@braintree/sanitize-url";
2
- export const sanitizeHTMLAttribute = (html) => {
3
- return html.replace(/"/g, """);
4
- };
2
+ import { escapeDoubleQuotes } from "../utils";
5
3
  export const normalizeLink = (link) => {
6
4
  link = link.trim();
7
5
  if (!link) {
8
6
  return link;
9
7
  }
10
- return sanitizeUrl(sanitizeHTMLAttribute(link));
8
+ return sanitizeUrl(escapeDoubleQuotes(link));
11
9
  };
12
10
  export const isLocalLink = (link) => {
13
11
  return !!(link?.includes(location.origin) || link?.startsWith("/"));
@@ -0,0 +1,56 @@
1
+ import { atom, createStore, type PrimitiveAtom } from "jotai";
2
+ import { createIsolation } from "jotai-scope";
3
+ export { atom, PrimitiveAtom };
4
+ export declare const useAtom: typeof import("jotai").useAtom, useSetAtom: typeof import("jotai").useSetAtom, useAtomValue: typeof import("jotai").useAtomValue, useStore: (options?: {
5
+ store?: {
6
+ get: <Value>(atom: import("jotai").Atom<Value>) => Value;
7
+ set: <Value_1, Args extends unknown[], Result>(atom: import("jotai").WritableAtom<Value_1, Args, Result>, ...args: Args) => Result;
8
+ sub: (atom: import("jotai").Atom<unknown>, listener: () => void) => () => void;
9
+ } | ({
10
+ get: <Value_2>(atom: import("jotai").Atom<Value_2>) => Value_2;
11
+ set: <Value_1_1, Args_1 extends unknown[], Result_1>(atom: import("jotai").WritableAtom<Value_1_1, Args_1, Result_1>, ...args: Args_1) => Result_1;
12
+ sub: (atom: import("jotai").Atom<unknown>, listener: () => void) => () => void;
13
+ } & {
14
+ dev4_get_internal_weak_map: () => WeakMap<import("jotai").Atom<unknown>, {
15
+ readonly d: Map<import("jotai").Atom<unknown>, number>;
16
+ readonly p: Set<import("jotai").Atom<unknown>>;
17
+ n: number;
18
+ m?: {
19
+ readonly l: Set<() => void>;
20
+ readonly d: Set<import("jotai").Atom<unknown>>;
21
+ readonly t: Set<import("jotai").Atom<unknown>>;
22
+ u?: (() => void) | undefined;
23
+ } | undefined;
24
+ v?: unknown;
25
+ e?: unknown;
26
+ }>;
27
+ dev4_get_mounted_atoms: () => Set<import("jotai").Atom<unknown>>;
28
+ dev4_restore_atoms: (values: Iterable<readonly [import("jotai").Atom<unknown>, unknown]>) => void;
29
+ }) | undefined;
30
+ } | undefined) => {
31
+ get: <Value_3>(atom: import("jotai").Atom<Value_3>) => Value_3;
32
+ set: <Value_1_2, Args_2 extends unknown[], Result_2>(atom: import("jotai").WritableAtom<Value_1_2, Args_2, Result_2>, ...args: Args_2) => Result_2;
33
+ sub: (atom: import("jotai").Atom<unknown>, listener: () => void) => () => void;
34
+ } | ({
35
+ get: <Value_4>(atom: import("jotai").Atom<Value_4>) => Value_4;
36
+ set: <Value_1_3, Args_3 extends unknown[], Result_3>(atom: import("jotai").WritableAtom<Value_1_3, Args_3, Result_3>, ...args: Args_3) => Result_3;
37
+ sub: (atom: import("jotai").Atom<unknown>, listener: () => void) => () => void;
38
+ } & {
39
+ dev4_get_internal_weak_map: () => WeakMap<import("jotai").Atom<unknown>, {
40
+ readonly d: Map<import("jotai").Atom<unknown>, number>;
41
+ readonly p: Set<import("jotai").Atom<unknown>>;
42
+ n: number;
43
+ m?: {
44
+ readonly l: Set<() => void>;
45
+ readonly d: Set<import("jotai").Atom<unknown>>;
46
+ readonly t: Set<import("jotai").Atom<unknown>>;
47
+ u?: (() => void) | undefined;
48
+ } | undefined;
49
+ v?: unknown;
50
+ e?: unknown;
51
+ }>;
52
+ dev4_get_mounted_atoms: () => Set<import("jotai").Atom<unknown>>;
53
+ dev4_restore_atoms: (values: Iterable<readonly [import("jotai").Atom<unknown>, unknown]>) => void;
54
+ });
55
+ export declare const EditorJotaiProvider: ReturnType<typeof createIsolation>["Provider"];
56
+ export declare const editorJotaiStore: ReturnType<typeof createStore>;
@@ -0,0 +1,8 @@
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import { atom, createStore } from "jotai";
3
+ import { createIsolation } from "jotai-scope";
4
+ const jotai = createIsolation();
5
+ export { atom };
6
+ export const { useAtom, useSetAtom, useAtomValue, useStore } = jotai;
7
+ export const EditorJotaiProvider = jotai.Provider;
8
+ export const editorJotaiStore = createStore();
@@ -15,9 +15,11 @@ export type SuggestedPointBinding = [
15
15
  export declare const shouldEnableBindingForPointerEvent: (event: React.PointerEvent<HTMLElement>) => boolean;
16
16
  export declare const isBindingEnabled: (appState: AppState) => boolean;
17
17
  export declare const FIXED_BINDING_DISTANCE = 5;
18
+ export declare const BINDING_HIGHLIGHT_THICKNESS = 10;
19
+ export declare const BINDING_HIGHLIGHT_OFFSET = 4;
18
20
  export declare const bindOrUnbindLinearElement: (linearElement: NonDeleted<ExcalidrawLinearElement>, startBindingElement: ExcalidrawBindableElement | null | "keep", endBindingElement: ExcalidrawBindableElement | null | "keep", elementsMap: NonDeletedSceneElementsMap, scene: Scene) => void;
19
- export declare const bindOrUnbindLinearElements: (selectedElements: NonDeleted<ExcalidrawLinearElement>[], elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], scene: Scene, isBindingEnabled: boolean, draggingPoints: readonly number[] | null) => void;
20
- export declare const getSuggestedBindingsForArrows: (selectedElements: NonDeleted<ExcalidrawElement>[], elementsMap: NonDeletedSceneElementsMap) => SuggestedBinding[];
21
+ export declare const bindOrUnbindLinearElements: (selectedElements: NonDeleted<ExcalidrawLinearElement>[], elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], scene: Scene, isBindingEnabled: boolean, draggingPoints: readonly number[] | null, zoom?: AppState["zoom"]) => void;
22
+ export declare const getSuggestedBindingsForArrows: (selectedElements: NonDeleted<ExcalidrawElement>[], elementsMap: NonDeletedSceneElementsMap, zoom: AppState["zoom"]) => SuggestedBinding[];
21
23
  export declare const maybeBindLinearElement: (linearElement: NonDeleted<ExcalidrawLinearElement>, appState: AppState, pointerCoords: {
22
24
  x: number;
23
25
  y: number;
@@ -27,7 +29,7 @@ export declare const isLinearElementSimpleAndAlreadyBound: (linearElement: NonDe
27
29
  export declare const getHoveredElementForBinding: (pointerCoords: {
28
30
  x: number;
29
31
  y: number;
30
- }, elements: readonly NonDeletedExcalidrawElement[], elementsMap: NonDeletedSceneElementsMap, fullShape?: boolean) => NonDeleted<ExcalidrawBindableElement> | null;
32
+ }, elements: readonly NonDeletedExcalidrawElement[], elementsMap: NonDeletedSceneElementsMap, zoom?: AppState["zoom"], fullShape?: boolean, considerAllElements?: boolean) => NonDeleted<ExcalidrawBindableElement> | null;
31
33
  export declare const updateBoundElements: (changedElement: NonDeletedExcalidrawElement, elementsMap: NonDeletedSceneElementsMap | SceneElementsMap, options?: {
32
34
  simultaneouslyUpdated?: readonly ExcalidrawElement[];
33
35
  newSize?: {
@@ -36,7 +38,7 @@ export declare const updateBoundElements: (changedElement: NonDeletedExcalidrawE
36
38
  };
37
39
  changedElements?: Map<string, OrderedExcalidrawElement>;
38
40
  }) => void;
39
- export declare const getHeadingForElbowArrowSnap: (p: Readonly<GlobalPoint>, otherPoint: Readonly<GlobalPoint>, bindableElement: ExcalidrawBindableElement | undefined | null, aabb: Bounds | undefined | null, elementsMap: ElementsMap, origPoint: GlobalPoint) => Heading;
41
+ export declare const getHeadingForElbowArrowSnap: (p: Readonly<GlobalPoint>, otherPoint: Readonly<GlobalPoint>, bindableElement: ExcalidrawBindableElement | undefined | null, aabb: Bounds | undefined | null, elementsMap: ElementsMap, origPoint: GlobalPoint, zoom?: AppState["zoom"]) => Heading;
40
42
  export declare const bindPointToSnapToElementOutline: (p: Readonly<GlobalPoint>, otherPoint: Readonly<GlobalPoint>, bindableElement: ExcalidrawBindableElement | undefined, elementsMap: ElementsMap) => GlobalPoint;
41
43
  export declare const avoidRectangularCorner: (element: ExcalidrawBindableElement, p: GlobalPoint) => GlobalPoint;
42
44
  export declare const snapToMid: (element: ExcalidrawBindableElement, p: GlobalPoint, tolerance?: number) => GlobalPoint;
@@ -48,8 +50,8 @@ export declare const fixBindingsAfterDeletion: (sceneElements: readonly Excalidr
48
50
  export declare const bindingBorderTest: (element: NonDeleted<ExcalidrawBindableElement>, { x, y }: {
49
51
  x: number;
50
52
  y: number;
51
- }, elementsMap: NonDeletedSceneElementsMap, fullShape?: boolean) => boolean;
52
- export declare const maxBindingGap: (element: ExcalidrawElement, elementWidth: number, elementHeight: number) => number;
53
+ }, elementsMap: NonDeletedSceneElementsMap, zoom?: AppState["zoom"], fullShape?: boolean) => boolean;
54
+ export declare const maxBindingGap: (element: ExcalidrawElement, elementWidth: number, elementHeight: number, zoom?: AppState["zoom"]) => number;
53
55
  export declare const distanceToBindableElement: (element: ExcalidrawBindableElement, point: GlobalPoint, elementsMap: ElementsMap) => number;
54
56
  export declare const bindingProperties: Set<BindableProp | BindingProp>;
55
57
  export type BindableProp = "boundElements";
@@ -93,5 +95,6 @@ export declare class BindableElement {
93
95
  static rebindAffected: (elements: ElementsMap, bindableElement: ExcalidrawElement | undefined, updateElementWith: (affected: ExcalidrawElement, updates: ElementUpdate<ExcalidrawElement>) => void) => void;
94
96
  }
95
97
  export declare const getGlobalFixedPointForBindableElement: (fixedPointRatio: [number, number], element: ExcalidrawBindableElement) => GlobalPoint;
98
+ export declare const getGlobalFixedPoints: (arrow: ExcalidrawElbowArrowElement, elementsMap: ElementsMap) => [GlobalPoint, GlobalPoint];
96
99
  export declare const getArrowLocalFixedPoints: (arrow: ExcalidrawElbowArrowElement, elementsMap: ElementsMap) => LocalPoint[];
97
100
  export declare const normalizeFixedPoint: <T extends FixedPoint | null>(fixedPoint: T) => T extends null ? null : FixedPoint;
@@ -5,11 +5,10 @@ import * as GALine from "../../math/ga/galines";
5
5
  import * as GATransform from "../../math/ga/gatransforms";
6
6
  import { getCenterForBounds, getElementAbsoluteCoords } from "./bounds";
7
7
  import { isPointOnShape } from "../../utils/collision";
8
- import { getElementAtPosition } from "../scene";
9
8
  import { isArrowElement, isBindableElement, isBindingElement, isBoundToContainer, isElbowArrow, isFixedPointBinding, isFrameLikeElement, isLinearElement, isRectangularElement, isTextElement, } from "./typeChecks";
10
9
  import { mutateElement } from "./mutateElement";
11
10
  import { LinearElementEditor } from "./linearElementEditor";
12
- import { arrayToMap, tupleToCoors } from "../utils";
11
+ import { arrayToMap, isBindingFallthroughEnabled, tupleToCoors, } from "../utils";
13
12
  import { KEYS } from "../keys";
14
13
  import { getBoundTextElement, handleBindTextResize } from "./textElement";
15
14
  import { aabbForElement, getElementShape, pointInsideBounds } from "../shapes";
@@ -23,6 +22,8 @@ export const isBindingEnabled = (appState) => {
23
22
  return appState.isBindingEnabled;
24
23
  };
25
24
  export const FIXED_BINDING_DISTANCE = 5;
25
+ export const BINDING_HIGHLIGHT_THICKNESS = 10;
26
+ export const BINDING_HIGHLIGHT_OFFSET = 4;
26
27
  const getNonDeletedElements = (scene, ids) => {
27
28
  const result = [];
28
29
  ids.forEach((id) => {
@@ -81,7 +82,7 @@ unboundFromElementIds, elementsMap) => {
81
82
  boundToElementIds.add(bindableElement.id);
82
83
  }
83
84
  };
84
- const getOriginalBindingIfStillCloseOfLinearElementEdge = (linearElement, edge, elementsMap) => {
85
+ const getOriginalBindingIfStillCloseOfLinearElementEdge = (linearElement, edge, elementsMap, zoom) => {
85
86
  const coors = getLinearElementEdgeCoors(linearElement, edge, elementsMap);
86
87
  const elementId = edge === "start"
87
88
  ? linearElement.startBinding?.elementId
@@ -89,64 +90,64 @@ const getOriginalBindingIfStillCloseOfLinearElementEdge = (linearElement, edge,
89
90
  if (elementId) {
90
91
  const element = elementsMap.get(elementId);
91
92
  if (isBindableElement(element) &&
92
- bindingBorderTest(element, coors, elementsMap)) {
93
+ bindingBorderTest(element, coors, elementsMap, zoom)) {
93
94
  return element;
94
95
  }
95
96
  }
96
97
  return null;
97
98
  };
98
- const getOriginalBindingsIfStillCloseToArrowEnds = (linearElement, elementsMap) => ["start", "end"].map((edge) => getOriginalBindingIfStillCloseOfLinearElementEdge(linearElement, edge, elementsMap));
99
- const getBindingStrategyForDraggingArrowEndpoints = (selectedElement, isBindingEnabled, draggingPoints, elementsMap, elements) => {
99
+ const getOriginalBindingsIfStillCloseToArrowEnds = (linearElement, elementsMap, zoom) => ["start", "end"].map((edge) => getOriginalBindingIfStillCloseOfLinearElementEdge(linearElement, edge, elementsMap, zoom));
100
+ const getBindingStrategyForDraggingArrowEndpoints = (selectedElement, isBindingEnabled, draggingPoints, elementsMap, elements, zoom) => {
100
101
  const startIdx = 0;
101
102
  const endIdx = selectedElement.points.length - 1;
102
103
  const startDragged = draggingPoints.findIndex((i) => i === startIdx) > -1;
103
104
  const endDragged = draggingPoints.findIndex((i) => i === endIdx) > -1;
104
105
  const start = startDragged
105
106
  ? isBindingEnabled
106
- ? getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements)
107
+ ? getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements, zoom)
107
108
  : null // If binding is disabled and start is dragged, break all binds
108
109
  : // We have to update the focus and gap of the binding, so let's rebind
109
- getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements);
110
+ getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements, zoom);
110
111
  const end = endDragged
111
112
  ? isBindingEnabled
112
- ? getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements)
113
+ ? getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements, zoom)
113
114
  : null // If binding is disabled and end is dragged, break all binds
114
115
  : // We have to update the focus and gap of the binding, so let's rebind
115
- getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements);
116
+ getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements, zoom);
116
117
  return [start, end];
117
118
  };
118
- const getBindingStrategyForDraggingArrowOrJoints = (selectedElement, elementsMap, elements, isBindingEnabled) => {
119
- const [startIsClose, endIsClose] = getOriginalBindingsIfStillCloseToArrowEnds(selectedElement, elementsMap);
119
+ const getBindingStrategyForDraggingArrowOrJoints = (selectedElement, elementsMap, elements, isBindingEnabled, zoom) => {
120
+ const [startIsClose, endIsClose] = getOriginalBindingsIfStillCloseToArrowEnds(selectedElement, elementsMap, zoom);
120
121
  const start = startIsClose
121
122
  ? isBindingEnabled
122
- ? getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements)
123
+ ? getElligibleElementForBindingElement(selectedElement, "start", elementsMap, elements, zoom)
123
124
  : null
124
125
  : null;
125
126
  const end = endIsClose
126
127
  ? isBindingEnabled
127
- ? getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements)
128
+ ? getElligibleElementForBindingElement(selectedElement, "end", elementsMap, elements, zoom)
128
129
  : null
129
130
  : null;
130
131
  return [start, end];
131
132
  };
132
- export const bindOrUnbindLinearElements = (selectedElements, elementsMap, elements, scene, isBindingEnabled, draggingPoints) => {
133
+ export const bindOrUnbindLinearElements = (selectedElements, elementsMap, elements, scene, isBindingEnabled, draggingPoints, zoom) => {
133
134
  selectedElements.forEach((selectedElement) => {
134
135
  const [start, end] = draggingPoints?.length
135
136
  ? // The arrow edge points are dragged (i.e. start, end)
136
- getBindingStrategyForDraggingArrowEndpoints(selectedElement, isBindingEnabled, draggingPoints ?? [], elementsMap, elements)
137
+ getBindingStrategyForDraggingArrowEndpoints(selectedElement, isBindingEnabled, draggingPoints ?? [], elementsMap, elements, zoom)
137
138
  : // The arrow itself (the shaft) or the inner joins are dragged
138
- getBindingStrategyForDraggingArrowOrJoints(selectedElement, elementsMap, elements, isBindingEnabled);
139
+ getBindingStrategyForDraggingArrowOrJoints(selectedElement, elementsMap, elements, isBindingEnabled, zoom);
139
140
  bindOrUnbindLinearElement(selectedElement, start, end, elementsMap, scene);
140
141
  });
141
142
  };
142
- export const getSuggestedBindingsForArrows = (selectedElements, elementsMap) => {
143
+ export const getSuggestedBindingsForArrows = (selectedElements, elementsMap, zoom) => {
143
144
  // HOT PATH: Bail out if selected elements list is too large
144
145
  if (selectedElements.length > 50) {
145
146
  return [];
146
147
  }
147
148
  return (selectedElements
148
149
  .filter(isLinearElement)
149
- .flatMap((element) => getOriginalBindingsIfStillCloseToArrowEnds(element, elementsMap))
150
+ .flatMap((element) => getOriginalBindingsIfStillCloseToArrowEnds(element, elementsMap, zoom))
150
151
  .filter((element) => element !== null)
151
152
  // Filter out bind candidates which are in the
152
153
  // same selection / group with the arrow
@@ -159,20 +160,31 @@ export const maybeBindLinearElement = (linearElement, appState, pointerCoords, e
159
160
  if (appState.startBoundElement != null) {
160
161
  bindLinearElement(linearElement, appState.startBoundElement, "start", elementsMap);
161
162
  }
162
- const hoveredElement = getHoveredElementForBinding(pointerCoords, elements, elementsMap, isElbowArrow(linearElement) && isElbowArrow(linearElement));
163
+ const hoveredElement = getHoveredElementForBinding(pointerCoords, elements, elementsMap, appState.zoom, isElbowArrow(linearElement), isElbowArrow(linearElement));
163
164
  if (hoveredElement !== null) {
164
165
  if (!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(linearElement, hoveredElement, "end")) {
165
166
  bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
166
167
  }
167
168
  }
168
169
  };
170
+ const normalizePointBinding = (binding, hoveredElement) => {
171
+ let gap = binding.gap;
172
+ const maxGap = maxBindingGap(hoveredElement, hoveredElement.width, hoveredElement.height);
173
+ if (gap > maxGap) {
174
+ gap = BINDING_HIGHLIGHT_THICKNESS + BINDING_HIGHLIGHT_OFFSET;
175
+ }
176
+ return {
177
+ ...binding,
178
+ gap,
179
+ };
180
+ };
169
181
  export const bindLinearElement = (linearElement, hoveredElement, startOrEnd, elementsMap) => {
170
182
  if (!isArrowElement(linearElement)) {
171
183
  return;
172
184
  }
173
185
  const binding = {
174
186
  elementId: hoveredElement.id,
175
- ...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd, elementsMap),
187
+ ...normalizePointBinding(calculateFocusAndGap(linearElement, hoveredElement, startOrEnd, elementsMap), hoveredElement),
176
188
  ...(isElbowArrow(linearElement)
177
189
  ? calculateFixedPointForElbowArrowBinding(linearElement, hoveredElement, startOrEnd, elementsMap)
178
190
  : { fixedPoint: null }),
@@ -209,14 +221,81 @@ const unbindLinearElement = (linearElement, startOrEnd) => {
209
221
  mutateElement(linearElement, { [field]: null });
210
222
  return binding.elementId;
211
223
  };
212
- export const getHoveredElementForBinding = (pointerCoords, elements, elementsMap, fullShape) => {
213
- const hoveredElement = getElementAtPosition(elements, (element) => isBindableElement(element, false) &&
214
- bindingBorderTest(element, pointerCoords, elementsMap,
224
+ export const getHoveredElementForBinding = (pointerCoords, elements, elementsMap, zoom, fullShape, considerAllElements) => {
225
+ if (considerAllElements) {
226
+ let cullRest = false;
227
+ const candidateElements = getAllElementsAtPositionForBinding(elements, (element) => isBindableElement(element, false) &&
228
+ bindingBorderTest(element, pointerCoords, elementsMap, zoom, (fullShape ||
229
+ !isBindingFallthroughEnabled(element)) &&
230
+ // disable fullshape snapping for frame elements so we
231
+ // can bind to frame children
232
+ !isFrameLikeElement(element))).filter((element) => {
233
+ if (cullRest) {
234
+ return false;
235
+ }
236
+ if (!isBindingFallthroughEnabled(element)) {
237
+ cullRest = true;
238
+ }
239
+ return true;
240
+ });
241
+ // Return early if there are no candidates or just one candidate
242
+ if (!candidateElements || candidateElements.length === 0) {
243
+ return null;
244
+ }
245
+ if (candidateElements.length === 1) {
246
+ return candidateElements[0];
247
+ }
248
+ // Prefer the shape with the border being tested (if any)
249
+ const borderTestElements = candidateElements.filter((element) => bindingBorderTest(element, pointerCoords, elementsMap, zoom, false));
250
+ if (borderTestElements.length === 1) {
251
+ return borderTestElements[0];
252
+ }
253
+ // Prefer smaller shapes
254
+ return candidateElements
255
+ .sort((a, b) => b.width ** 2 + b.height ** 2 - (a.width ** 2 + a.height ** 2))
256
+ .pop();
257
+ }
258
+ const hoveredElement = getElementAtPositionForBinding(elements, (element) => isBindableElement(element, false) &&
259
+ bindingBorderTest(element, pointerCoords, elementsMap, zoom,
215
260
  // disable fullshape snapping for frame elements so we
216
261
  // can bind to frame children
217
- fullShape && !isFrameLikeElement(element)));
262
+ (fullShape || !isBindingFallthroughEnabled(element)) &&
263
+ !isFrameLikeElement(element)));
218
264
  return hoveredElement;
219
265
  };
266
+ const getElementAtPositionForBinding = (elements, isAtPositionFn) => {
267
+ let hitElement = null;
268
+ // We need to to hit testing from front (end of the array) to back (beginning of the array)
269
+ // because array is ordered from lower z-index to highest and we want element z-index
270
+ // with higher z-index
271
+ for (let index = elements.length - 1; index >= 0; --index) {
272
+ const element = elements[index];
273
+ if (element.isDeleted) {
274
+ continue;
275
+ }
276
+ if (isAtPositionFn(element)) {
277
+ hitElement = element;
278
+ break;
279
+ }
280
+ }
281
+ return hitElement;
282
+ };
283
+ const getAllElementsAtPositionForBinding = (elements, isAtPositionFn) => {
284
+ const elementsAtPosition = [];
285
+ // We need to to hit testing from front (end of the array) to back (beginning of the array)
286
+ // because array is ordered from lower z-index to highest and we want element z-index
287
+ // with higher z-index
288
+ for (let index = elements.length - 1; index >= 0; --index) {
289
+ const element = elements[index];
290
+ if (element.isDeleted) {
291
+ continue;
292
+ }
293
+ if (isAtPositionFn(element)) {
294
+ elementsAtPosition.push(element);
295
+ }
296
+ }
297
+ return elementsAtPosition;
298
+ };
220
299
  const calculateFocusAndGap = (linearElement, hoveredElement, startOrEnd, elementsMap) => {
221
300
  const direction = startOrEnd === "start" ? -1 : 1;
222
301
  const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
@@ -234,7 +313,7 @@ const calculateFocusAndGap = (linearElement, hoveredElement, startOrEnd, element
234
313
  // done before the `changedElement` is updated, and the `newSize` is passed
235
314
  // in explicitly.
236
315
  export const updateBoundElements = (changedElement, elementsMap, options) => {
237
- const { newSize, simultaneouslyUpdated, changedElements } = options ?? {};
316
+ const { newSize, simultaneouslyUpdated } = options ?? {};
238
317
  const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(simultaneouslyUpdated);
239
318
  if (!isBindableElement(changedElement)) {
240
319
  return;
@@ -253,7 +332,7 @@ export const updateBoundElements = (changedElement, elementsMap, options) => {
253
332
  };
254
333
  // `linearElement` is being moved/scaled already, just update the binding
255
334
  if (simultaneouslyUpdatedElementIds.has(element.id)) {
256
- mutateElement(element, bindings);
335
+ mutateElement(element, bindings, true);
257
336
  return;
258
337
  }
259
338
  const updates = bindableElementsVisitor(elementsMap, element, (bindableElement, bindingProp) => {
@@ -271,15 +350,13 @@ export const updateBoundElements = (changedElement, elementsMap, options) => {
271
350
  }
272
351
  return null;
273
352
  }).filter((update) => update !== null);
274
- LinearElementEditor.movePoints(element, updates, elementsMap, {
353
+ LinearElementEditor.movePoints(element, updates, {
275
354
  ...(changedElement.id === element.startBinding?.elementId
276
355
  ? { startBinding: bindings.startBinding }
277
356
  : {}),
278
357
  ...(changedElement.id === element.endBinding?.elementId
279
358
  ? { endBinding: bindings.endBinding }
280
359
  : {}),
281
- }, {
282
- changedElements,
283
360
  });
284
361
  const boundText = getBoundTextElement(element, elementsMap);
285
362
  if (boundText && !boundText.isDeleted) {
@@ -294,21 +371,20 @@ const doesNeedUpdate = (boundElement, changedElement) => {
294
371
  const getSimultaneouslyUpdatedElementIds = (simultaneouslyUpdated) => {
295
372
  return new Set((simultaneouslyUpdated || []).map((element) => element.id));
296
373
  };
297
- export const getHeadingForElbowArrowSnap = (p, otherPoint, bindableElement, aabb, elementsMap, origPoint) => {
374
+ export const getHeadingForElbowArrowSnap = (p, otherPoint, bindableElement, aabb, elementsMap, origPoint, zoom) => {
298
375
  const otherPointHeading = vectorToHeading(vectorFromPoint(otherPoint, p));
299
376
  if (!bindableElement || !aabb) {
300
377
  return otherPointHeading;
301
378
  }
302
- const distance = getDistanceForBinding(origPoint, bindableElement, elementsMap);
379
+ const distance = getDistanceForBinding(origPoint, bindableElement, elementsMap, zoom);
303
380
  if (!distance) {
304
381
  return vectorToHeading(vectorFromPoint(p, pointFrom(bindableElement.x + bindableElement.width / 2, bindableElement.y + bindableElement.height / 2)));
305
382
  }
306
- const pointHeading = headingForPointFromElement(bindableElement, aabb, p);
307
- return pointHeading;
383
+ return headingForPointFromElement(bindableElement, aabb, p);
308
384
  };
309
- const getDistanceForBinding = (point, bindableElement, elementsMap) => {
385
+ const getDistanceForBinding = (point, bindableElement, elementsMap, zoom) => {
310
386
  const distance = distanceToBindableElement(bindableElement, point, elementsMap);
311
- const bindDistance = maxBindingGap(bindableElement, bindableElement.width, bindableElement.height);
387
+ const bindDistance = maxBindingGap(bindableElement, bindableElement.width, bindableElement.height, zoom);
312
388
  return distance > bindDistance ? null : distance;
313
389
  };
314
390
  export const bindPointToSnapToElementOutline = (p, otherPoint, bindableElement, elementsMap) => {
@@ -493,8 +569,8 @@ const maybeCalculateNewGapWhenScaling = (changedElement, currentBinding, newSize
493
569
  (newWidth < newHeight ? newWidth / width : newHeight / height)));
494
570
  return { ...currentBinding, gap: newGap };
495
571
  };
496
- const getElligibleElementForBindingElement = (linearElement, startOrEnd, elementsMap, elements) => {
497
- return getHoveredElementForBinding(getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap), elements, elementsMap);
572
+ const getElligibleElementForBindingElement = (linearElement, startOrEnd, elementsMap, elements, zoom) => {
573
+ return getHoveredElementForBinding(getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap), elements, elementsMap, zoom, isElbowArrow(linearElement), isElbowArrow(linearElement));
498
574
  };
499
575
  const getLinearElementEdgeCoors = (linearElement, startOrEnd, elementsMap) => {
500
576
  const index = startOrEnd === "start" ? 0 : -1;
@@ -593,19 +669,23 @@ const newBoundElements = (boundElements, idsToRemove, elementsToAdd = []) => {
593
669
  nextBoundElements.push(...elementsToAdd.map((x) => ({ id: x.id, type: x.type })));
594
670
  return nextBoundElements;
595
671
  };
596
- export const bindingBorderTest = (element, { x, y }, elementsMap, fullShape) => {
597
- const threshold = maxBindingGap(element, element.width, element.height);
672
+ export const bindingBorderTest = (element, { x, y }, elementsMap, zoom, fullShape) => {
673
+ const threshold = maxBindingGap(element, element.width, element.height, zoom);
598
674
  const shape = getElementShape(element, elementsMap);
599
675
  return (isPointOnShape(pointFrom(x, y), shape, threshold) ||
600
676
  (fullShape === true &&
601
677
  pointInsideBounds(pointFrom(x, y), aabbForElement(element))));
602
678
  };
603
- export const maxBindingGap = (element, elementWidth, elementHeight) => {
679
+ export const maxBindingGap = (element, elementWidth, elementHeight, zoom) => {
680
+ const zoomValue = zoom?.value && zoom.value < 1 ? zoom.value : 1;
604
681
  // Aligns diamonds with rectangles
605
682
  const shapeRatio = element.type === "diamond" ? 1 / Math.sqrt(2) : 1;
606
683
  const smallerDimension = shapeRatio * Math.min(elementWidth, elementHeight);
607
- // We make the bindable boundary bigger for bigger elements
608
- return Math.max(16, Math.min(0.25 * smallerDimension, 32));
684
+ return Math.max(16,
685
+ // bigger bindable boundary for bigger elements
686
+ Math.min(0.25 * smallerDimension, 32),
687
+ // keep in sync with the zoomed highlight
688
+ BINDING_HIGHLIGHT_THICKNESS / zoomValue + BINDING_HIGHLIGHT_OFFSET);
609
689
  };
610
690
  export const distanceToBindableElement = (element, point, elementsMap) => {
611
691
  switch (element.type) {
@@ -1165,7 +1245,7 @@ export const getGlobalFixedPointForBindableElement = (fixedPointRatio, element)
1165
1245
  const [fixedX, fixedY] = normalizeFixedPoint(fixedPointRatio);
1166
1246
  return pointRotateRads(pointFrom(element.x + element.width * fixedX, element.y + element.height * fixedY), pointFrom(element.x + element.width / 2, element.y + element.height / 2), element.angle);
1167
1247
  };
1168
- const getGlobalFixedPoints = (arrow, elementsMap) => {
1248
+ export const getGlobalFixedPoints = (arrow, elementsMap) => {
1169
1249
  const startElement = arrow.startBinding &&
1170
1250
  elementsMap.get(arrow.startBinding.elementId);
1171
1251
  const endElement = arrow.endBinding &&