@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.
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-JGDL4H2X.js → chunk-3DLVY5XU.js} +8272 -6864
- package/dist/browser/dev/excalidraw-assets-dev/chunk-3DLVY5XU.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-V7NFEZA6.js → chunk-NOAEU4NM.js} +9 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-NOAEU4NM.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js → en-7IBTMWBG.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js → image-N5AC7SEK.js} +2 -6
- package/dist/browser/dev/index.css +85 -50
- package/dist/browser/dev/index.css.map +3 -3
- package/dist/browser/dev/index.js +4375 -3766
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/{chunk-LDVEIXGO.js → chunk-7CSIPVOW.js} +2 -2
- package/dist/browser/prod/excalidraw-assets/chunk-TX3BU7T2.js +47 -0
- package/dist/browser/prod/excalidraw-assets/{en-UPNEHLDS.js → en-LOGQBETY.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-3V4U7GZE.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +40 -40
- package/dist/dev/index.css +85 -50
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +8688 -6706
- package/dist/dev/index.js.map +4 -4
- package/dist/{prod/locales/en-ZXYG7GCR.json → dev/locales/en-V6KXFSCK.json} +8 -1
- package/dist/excalidraw/actions/actionAlign.d.ts +7 -6
- package/dist/excalidraw/actions/actionAlign.js +14 -14
- package/dist/excalidraw/actions/actionClipboard.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.js +103 -34
- package/dist/excalidraw/actions/actionDuplicateSelection.js +105 -95
- package/dist/excalidraw/actions/actionFlip.js +16 -7
- package/dist/excalidraw/actions/actionFrame.d.ts +493 -0
- package/dist/excalidraw/actions/actionFrame.js +45 -2
- package/dist/excalidraw/actions/actionGroup.js +6 -4
- package/dist/excalidraw/actions/actionProperties.js +145 -116
- package/dist/excalidraw/actions/actionSelectAll.js +4 -3
- package/dist/excalidraw/actions/shortcuts.d.ts +1 -1
- package/dist/excalidraw/actions/shortcuts.js +1 -0
- package/dist/excalidraw/actions/types.d.ts +1 -1
- package/dist/excalidraw/align.d.ts +2 -1
- package/dist/excalidraw/align.js +15 -6
- package/dist/excalidraw/clipboard.d.ts +27 -5
- package/dist/excalidraw/clipboard.js +55 -28
- package/dist/excalidraw/components/Actions.d.ts +2 -1
- package/dist/excalidraw/components/Actions.js +4 -2
- package/dist/excalidraw/components/ActiveConfirmDialog.d.ts +1 -1
- package/dist/excalidraw/components/ActiveConfirmDialog.js +2 -3
- package/dist/excalidraw/components/App.d.ts +1 -0
- package/dist/excalidraw/components/App.js +216 -111
- package/dist/excalidraw/components/ColorPicker/ColorInput.js +2 -3
- package/dist/excalidraw/components/ColorPicker/ColorPicker.js +2 -3
- package/dist/excalidraw/components/ColorPicker/CustomColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
- package/dist/excalidraw/components/ColorPicker/PickerColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/ShadeList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +3 -3
- package/dist/excalidraw/components/ConfirmDialog.js +17 -5
- package/dist/excalidraw/components/Dialog.js +2 -3
- package/dist/excalidraw/components/EyeDropper.d.ts +1 -1
- package/dist/excalidraw/components/EyeDropper.js +1 -1
- package/dist/excalidraw/components/IconPicker.d.ts +2 -2
- package/dist/excalidraw/components/IconPicker.js +56 -53
- package/dist/excalidraw/components/LayerUI.js +6 -6
- package/dist/excalidraw/components/LibraryMenu.d.ts +2 -16
- package/dist/excalidraw/components/LibraryMenu.js +70 -28
- package/dist/excalidraw/components/LibraryMenuHeaderContent.js +4 -5
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirm.js +2 -3
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.js +2 -3
- package/dist/excalidraw/components/Range.d.ts +9 -0
- package/dist/excalidraw/components/Range.js +24 -0
- package/dist/excalidraw/components/SearchMenu.d.ts +1 -1
- package/dist/excalidraw/components/SearchMenu.js +3 -4
- package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/excalidraw/components/Sidebar/Sidebar.js +2 -3
- package/dist/excalidraw/components/Stats/Collapsible.d.ts +2 -1
- package/dist/excalidraw/components/Stats/Collapsible.js +2 -2
- package/dist/excalidraw/components/Stats/Dimension.js +94 -8
- package/dist/excalidraw/components/Stats/MultiDimension.js +8 -5
- package/dist/excalidraw/components/Stats/Position.js +63 -3
- package/dist/excalidraw/components/Stats/index.js +21 -4
- package/dist/excalidraw/components/Stats/utils.d.ts +1 -1
- package/dist/excalidraw/components/Stats/utils.js +2 -55
- package/dist/excalidraw/components/TTDDialog/TTDDialog.js +1 -1
- package/dist/excalidraw/components/ToolButton.js +4 -9
- package/dist/excalidraw/components/hoc/withInternalFallback.js +3 -3
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +6 -12
- package/dist/excalidraw/components/icons.d.ts +9 -0
- package/dist/excalidraw/components/icons.js +4 -4
- package/dist/excalidraw/components/main-menu/DefaultItems.js +2 -3
- package/dist/excalidraw/constants.d.ts +5 -1
- package/dist/excalidraw/constants.js +9 -1
- package/dist/excalidraw/context/tunnels.d.ts +2 -1
- package/dist/excalidraw/context/tunnels.js +3 -1
- package/dist/excalidraw/data/blob.d.ts +1 -0
- package/dist/excalidraw/data/blob.js +7 -3
- package/dist/excalidraw/data/filesystem.d.ts +2 -1
- package/dist/excalidraw/data/filesystem.js +1 -0
- package/dist/excalidraw/data/image.d.ts +0 -6
- package/dist/excalidraw/data/image.js +1 -43
- package/dist/excalidraw/data/index.js +6 -6
- package/dist/excalidraw/data/library.d.ts +9 -3
- package/dist/excalidraw/data/library.js +43 -6
- package/dist/excalidraw/data/restore.js +26 -8
- package/dist/excalidraw/data/url.d.ts +0 -1
- package/dist/excalidraw/data/url.js +2 -4
- package/dist/excalidraw/editor-jotai.d.ts +56 -0
- package/dist/excalidraw/editor-jotai.js +8 -0
- package/dist/excalidraw/element/binding.d.ts +9 -6
- package/dist/excalidraw/element/binding.js +124 -44
- package/dist/excalidraw/element/bounds.js +10 -0
- package/dist/excalidraw/element/cropElement.d.ts +5 -0
- package/dist/excalidraw/element/cropElement.js +28 -1
- package/dist/excalidraw/element/dragElements.js +13 -7
- package/dist/excalidraw/element/elbowArrow.d.ts +16 -0
- package/dist/excalidraw/element/elbowArrow.js +1268 -0
- package/dist/excalidraw/element/embeddable.js +4 -5
- package/dist/excalidraw/element/flowchart.d.ts +1 -1
- package/dist/excalidraw/element/flowchart.js +25 -9
- package/dist/excalidraw/element/heading.d.ts +5 -1
- package/dist/excalidraw/element/heading.js +5 -1
- package/dist/excalidraw/element/image.js +19 -5
- package/dist/excalidraw/element/linearElementEditor.d.ts +9 -10
- package/dist/excalidraw/element/linearElementEditor.js +97 -38
- package/dist/excalidraw/element/mutateElement.d.ts +3 -1
- package/dist/excalidraw/element/mutateElement.js +31 -4
- package/dist/excalidraw/element/newElement.d.ts +8 -12
- package/dist/excalidraw/element/newElement.js +36 -21
- package/dist/excalidraw/element/resizeElements.d.ts +20 -5
- package/dist/excalidraw/element/resizeElements.js +593 -361
- package/dist/excalidraw/element/sortElements.js +1 -4
- package/dist/excalidraw/element/types.d.ts +23 -1
- package/dist/excalidraw/fonts/Fonts.d.ts +0 -16
- package/dist/excalidraw/fonts/Fonts.js +6 -31
- package/dist/excalidraw/frame.d.ts +11 -5
- package/dist/excalidraw/frame.js +146 -35
- package/dist/excalidraw/groups.js +3 -0
- package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
- package/dist/excalidraw/hooks/useLibraryItemSvg.js +2 -3
- package/dist/excalidraw/hooks/useScrollPosition.js +1 -1
- package/dist/excalidraw/i18n.js +3 -4
- package/dist/excalidraw/index.js +3 -4
- package/dist/excalidraw/locales/en.json +8 -1
- package/dist/excalidraw/renderer/interactiveScene.js +43 -32
- package/dist/excalidraw/renderer/staticScene.js +6 -4
- package/dist/excalidraw/renderer/staticSvgScene.js +1 -1
- package/dist/excalidraw/scene/Shape.js +40 -17
- package/dist/excalidraw/scene/comparisons.d.ts +0 -477
- package/dist/excalidraw/scene/comparisons.js +0 -37
- package/dist/excalidraw/scene/export.d.ts +7 -0
- package/dist/excalidraw/scene/export.js +107 -43
- package/dist/excalidraw/scene/index.d.ts +1 -1
- package/dist/excalidraw/scene/index.js +1 -1
- package/dist/excalidraw/scene/selection.js +4 -1
- package/dist/excalidraw/types.d.ts +15 -0
- package/dist/excalidraw/utility-types.d.ts +1 -0
- package/dist/excalidraw/utils.d.ts +8 -1
- package/dist/excalidraw/utils.js +9 -0
- package/dist/excalidraw/visualdebug.d.ts +8 -1
- package/dist/excalidraw/visualdebug.js +3 -0
- package/dist/math/line.d.ts +19 -0
- package/dist/math/line.js +32 -3
- package/dist/math/point.d.ts +10 -0
- package/dist/math/point.js +12 -1
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +29 -44
- package/dist/{dev/locales/en-ZXYG7GCR.json → prod/locales/en-V6KXFSCK.json} +8 -1
- package/package.json +5 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-JGDL4H2X.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-V7NFEZA6.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-S2XKB3DE.js +0 -62
- package/dist/browser/prod/excalidraw-assets/image-OFI2YYMP.js +0 -1
- package/dist/excalidraw/element/routing.d.ts +0 -12
- package/dist/excalidraw/element/routing.js +0 -642
- package/dist/excalidraw/jotai.d.ts +0 -34
- package/dist/excalidraw/jotai.js +0 -18
- /package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js.map → en-7IBTMWBG.js.map} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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,13 +1,11 @@
|
|
|
1
1
|
import { sanitizeUrl } from "@braintree/sanitize-url";
|
|
2
|
-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
608
|
-
|
|
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 &&
|