@myoc/excalidraw 0.19.512 → 0.19.513

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/dev/index.js CHANGED
@@ -57,10 +57,10 @@ import {
57
57
  saveAsJSON,
58
58
  serializeAsJSON,
59
59
  strokeRectWithRotation_simple
60
- } from "./chunk-OBLRXIUK.js";
60
+ } from "./chunk-BI6BDZVY.js";
61
61
  import {
62
62
  define_import_meta_env_default
63
- } from "./chunk-QHLG4OQL.js";
63
+ } from "./chunk-FW62QUKV.js";
64
64
  import {
65
65
  en_default
66
66
  } from "./chunk-ZGRXNVW4.js";
@@ -90,257 +90,257 @@ import {
90
90
  // components/App.tsx
91
91
  import clsx57 from "clsx";
92
92
  import throttle2 from "lodash.throttle";
93
+ import { nanoid } from "nanoid";
93
94
  import React40, { useContext as useContext3 } from "react";
94
95
  import { flushSync as flushSync2 } from "react-dom";
95
96
  import rough3 from "roughjs/bin/rough";
96
- import { nanoid } from "nanoid";
97
97
  import {
98
98
  clamp as clamp8,
99
- pointFrom as pointFrom34,
100
99
  pointDistance as pointDistance9,
101
- vector as vector3,
100
+ pointFrom as pointFrom34,
102
101
  pointRotateRads as pointRotateRads23,
103
- vectorFromPoint as vectorFromPoint10,
104
- vectorSubtract as vectorSubtract2,
102
+ vector as vector3,
105
103
  vectorDot,
106
- vectorNormalize as vectorNormalize5
104
+ vectorFromPoint as vectorFromPoint10,
105
+ vectorNormalize as vectorNormalize5,
106
+ vectorSubtract as vectorSubtract2
107
107
  } from "@excalidraw/math";
108
108
  import {
109
- COLOR_PALETTE as COLOR_PALETTE6,
110
- CODES as CODES11,
111
- shouldResizeFromCenter,
112
- shouldMaintainAspectRatio,
113
- shouldRotateWithDiscreteAngle as shouldRotateWithDiscreteAngle3,
114
- isArrowKey as isArrowKey2,
115
- KEYS as KEYS50,
109
+ addEventListener as addEventListener2,
116
110
  APP_NAME,
111
+ AppEventBus,
112
+ applyDarkModeFilter as applyDarkModeFilter4,
113
+ arrayToMap as arrayToMap30,
114
+ ARROW_TYPE as ARROW_TYPE2,
115
+ BIND_MODE_TIMEOUT as BIND_MODE_TIMEOUT2,
116
+ CLASSES as CLASSES10,
117
+ CODES as CODES11,
118
+ COLOR_PALETTE as COLOR_PALETTE6,
119
+ createUserAgentDescriptor,
117
120
  CURSOR_TYPE as CURSOR_TYPE4,
121
+ debounce as debounce3,
122
+ DEFAULT_COLLISION_THRESHOLD,
123
+ DEFAULT_REDUCED_GLOBAL_ALPHA as DEFAULT_REDUCED_GLOBAL_ALPHA2,
124
+ DEFAULT_TEXT_ALIGN as DEFAULT_TEXT_ALIGN2,
118
125
  DEFAULT_TRANSFORM_HANDLE_SPACING as DEFAULT_TRANSFORM_HANDLE_SPACING3,
119
126
  DEFAULT_VERTICAL_ALIGN,
127
+ deriveStylesPanelMode as deriveStylesPanelMode2,
128
+ distance as distance2,
129
+ DOUBLE_TAP_POSITION_THRESHOLD,
120
130
  DRAGGING_THRESHOLD as DRAGGING_THRESHOLD3,
131
+ easeOut as easeOut4,
132
+ easeToValuesRAF,
121
133
  ELEMENT_SHIFT_TRANSLATE_AMOUNT,
122
134
  ELEMENT_TRANSLATE_AMOUNT,
135
+ Emitter as Emitter2,
123
136
  EVENT as EVENT10,
124
137
  FRAME_STYLE as FRAME_STYLE4,
138
+ getDateTime,
139
+ getFeatureFlag as getFeatureFlag4,
140
+ getFontString as getFontString11,
141
+ getFormFactor,
142
+ getGridPoint as getGridPoint2,
143
+ getLineHeight as getLineHeight6,
144
+ getNearestScrollableContainer,
125
145
  IMAGE_MIME_TYPES as IMAGE_MIME_TYPES2,
126
146
  IMAGE_RENDER_TIMEOUT,
147
+ invariant as invariant16,
148
+ isArrowKey as isArrowKey2,
149
+ isBrave,
150
+ isDevEnv as isDevEnv9,
151
+ isInputLike,
152
+ isIOS,
153
+ isLocalLink as isLocalLink2,
154
+ isSafari as isSafari2,
155
+ isSelectionLikeTool,
156
+ isShallowEqual as isShallowEqual8,
157
+ isTestEnv as isTestEnv5,
158
+ isToolIcon,
159
+ isTransparent as isTransparent6,
160
+ isWritableElement as isWritableElement4,
161
+ KEYS as KEYS50,
127
162
  LINE_CONFIRM_THRESHOLD as LINE_CONFIRM_THRESHOLD2,
163
+ loadDesktopUIModePreference,
164
+ matchKey as matchKey3,
128
165
  MIME_TYPES as MIME_TYPES7,
166
+ MINIMUM_ARROW_SIZE,
129
167
  MQ_RIGHT_SIDEBAR_MIN_WIDTH,
168
+ muteFSAbortError,
169
+ normalizeEOL as normalizeEOL2,
170
+ normalizeLink as normalizeLink3,
171
+ oneOf,
130
172
  POINTER_BUTTON as POINTER_BUTTON2,
173
+ POINTER_EVENTS,
174
+ randomInteger as randomInteger4,
131
175
  ROUNDNESS as ROUNDNESS7,
176
+ sceneCoordsToViewportCoords as sceneCoordsToViewportCoords7,
132
177
  SCROLL_TIMEOUT,
178
+ setDesktopUIMode,
179
+ shouldMaintainAspectRatio,
180
+ shouldResizeFromCenter,
181
+ shouldRotateWithDiscreteAngle as shouldRotateWithDiscreteAngle3,
182
+ supportsResizeObserver as supportsResizeObserver2,
133
183
  TAP_TWICE_TIMEOUT,
134
184
  TEXT_TO_CENTER_SNAP_THRESHOLD,
135
185
  THEME as THEME17,
136
- TOUCH_CTX_MENU_TIMEOUT,
137
- VERTICAL_ALIGN as VERTICAL_ALIGN5,
138
- YOUTUBE_STATES,
139
- ZOOM_STEP as ZOOM_STEP2,
140
- POINTER_EVENTS,
141
186
  TOOL_TYPE as TOOL_TYPE3,
142
- supportsResizeObserver as supportsResizeObserver2,
143
- DEFAULT_COLLISION_THRESHOLD,
144
- DEFAULT_TEXT_ALIGN as DEFAULT_TEXT_ALIGN2,
145
- ARROW_TYPE as ARROW_TYPE2,
146
- DEFAULT_REDUCED_GLOBAL_ALPHA as DEFAULT_REDUCED_GLOBAL_ALPHA2,
147
- isLocalLink as isLocalLink2,
148
- normalizeLink as normalizeLink3,
187
+ TOUCH_CTX_MENU_TIMEOUT,
149
188
  toValidURL,
150
- getGridPoint as getGridPoint2,
151
- getLineHeight as getLineHeight6,
152
- debounce as debounce3,
153
- distance as distance2,
154
- getFontString as getFontString11,
155
- getNearestScrollableContainer,
156
- isInputLike,
157
- isToolIcon,
158
- isWritableElement as isWritableElement4,
159
- sceneCoordsToViewportCoords as sceneCoordsToViewportCoords7,
160
189
  tupleToCoors,
161
- viewportCoordsToSceneCoords as viewportCoordsToSceneCoords3,
162
- wrapEvent as wrapEvent2,
163
- updateObject as updateObject2,
164
190
  updateActiveTool as updateActiveTool8,
165
- isTransparent as isTransparent6,
166
- easeToValuesRAF,
167
- muteFSAbortError,
168
- isTestEnv as isTestEnv5,
169
- isDevEnv as isDevEnv9,
170
- easeOut as easeOut4,
191
+ updateObject as updateObject2,
171
192
  updateStable,
172
- addEventListener as addEventListener2,
173
- normalizeEOL as normalizeEOL2,
174
- getDateTime,
175
- isShallowEqual as isShallowEqual8,
176
- arrayToMap as arrayToMap30,
177
- applyDarkModeFilter as applyDarkModeFilter4,
178
- AppEventBus,
179
- randomInteger as randomInteger4,
180
- CLASSES as CLASSES10,
181
- Emitter as Emitter2,
182
- MINIMUM_ARROW_SIZE,
183
- DOUBLE_TAP_POSITION_THRESHOLD,
184
- BIND_MODE_TIMEOUT as BIND_MODE_TIMEOUT2,
185
- invariant as invariant16,
186
- getFeatureFlag as getFeatureFlag4,
187
- createUserAgentDescriptor,
188
- getFormFactor,
189
- deriveStylesPanelMode as deriveStylesPanelMode2,
190
- isIOS,
191
- isBrave,
192
- isSafari as isSafari2,
193
- loadDesktopUIModePreference,
194
- setDesktopUIMode,
195
- isSelectionLikeTool,
196
- oneOf,
197
- matchKey as matchKey3
193
+ VERTICAL_ALIGN as VERTICAL_ALIGN5,
194
+ viewportCoordsToSceneCoords as viewportCoordsToSceneCoords3,
195
+ wrapEvent as wrapEvent2,
196
+ YOUTUBE_STATES,
197
+ ZOOM_STEP as ZOOM_STEP2
198
198
  } from "@excalidraw/common";
199
199
  import {
200
- getObservedAppState,
201
- getCommonBounds as getCommonBounds11,
202
- getElementAbsoluteCoords as getElementAbsoluteCoords8,
200
+ updateImageCache as _updateImageCache,
201
+ addElementsToFrame as addElementsToFrame2,
202
+ bindOrUnbindBindingElement as bindOrUnbindBindingElement2,
203
203
  bindOrUnbindBindingElements as bindOrUnbindBindingElements2,
204
- fixBindingsAfterDeletion as fixBindingsAfterDeletion2,
205
- getHoveredElementForBinding as getHoveredElementForBinding2,
206
- isBindingEnabled as isBindingEnabled2,
207
- updateBoundElements as updateBoundElements4,
208
- newElementWith as newElementWith10,
209
- newFrameElement as newFrameElement2,
210
- newFreeDrawElement,
211
- newEmbeddableElement,
212
- newMagicFrameElement,
213
- newIframeElement,
214
- newArrowElement as newArrowElement2,
215
- newElement as newElement6,
216
- newImageElement,
217
- newLinearElement as newLinearElement5,
218
- newTextElement as newTextElement5,
219
- refreshTextDimensions,
204
+ calculateFixedPointForNonElbowArrowBinding as calculateFixedPointForNonElbowArrowBinding2,
205
+ CaptureUpdateAction as CaptureUpdateAction41,
206
+ convertToExcalidrawElements,
207
+ createSrcDoc,
208
+ cropElement,
220
209
  deepCopyElement as deepCopyElement4,
210
+ doBoundsIntersect as doBoundsIntersect4,
211
+ dragNewElement,
212
+ dragSelectedElements,
221
213
  duplicateElements as duplicateElements2,
214
+ editGroupForSelectedElement,
215
+ elementOverlapsWithFrame as elementOverlapsWithFrame2,
216
+ embeddableURLValidator as embeddableURLValidator2,
217
+ excludeElementsInFramesFromSelection,
218
+ filterElementsEligibleAsFrameChildren,
219
+ fixBindingsAfterDeletion as fixBindingsAfterDeletion2,
220
+ FlowChartCreator,
221
+ FlowChartNavigator,
222
+ getActiveTextElement as getActiveTextElement2,
223
+ getApproxMinLineHeight,
224
+ getApproxMinLineWidth as getApproxMinLineWidth2,
225
+ getBoundTextElement as getBoundTextElement15,
226
+ getCommonBounds as getCommonBounds11,
227
+ getCommonFrameId as getCommonFrameId2,
228
+ getContainerCenter,
229
+ getContainerElement as getContainerElement5,
230
+ getContainingFrame as getContainingFrame3,
231
+ getCornerRadius as getCornerRadius2,
232
+ getCursorForResizingElement,
233
+ getDragOffsetXY,
234
+ getElementAbsoluteCoords as getElementAbsoluteCoords8,
235
+ getElementBounds as getElementBounds5,
236
+ getElementsInGroup as getElementsInGroup10,
237
+ getElementsInNewFrame,
238
+ getElementsInResizingFrame as getElementsInResizingFrame4,
239
+ getElementsOverlappingFrame as getElementsOverlappingFrame2,
240
+ getElementWithTransformHandleType,
241
+ getEmbedLink as getEmbedLink2,
242
+ getFrameChildren as getFrameChildren6,
243
+ getFrameChildrenInsertionIndex as getFrameChildrenInsertionIndex2,
244
+ getFrameLikeTitle,
245
+ getHoveredElementForBinding as getHoveredElementForBinding2,
246
+ getInitializedImageElements,
247
+ getLineHeightInPx as getLineHeightInPx3,
248
+ getLinkDirectionFromKey,
249
+ getMinTextElementWidth,
250
+ getNormalizedDimensions,
251
+ getObservedAppState,
252
+ getRenderOpacity,
253
+ getResizeArrowDirection,
254
+ getResizeOffsetXY,
255
+ getSelectedGroupIdForElement,
256
+ getSelectedGroupIds as getSelectedGroupIds4,
257
+ getSelectionStateForElements as getSelectionStateForElements2,
258
+ getSnapOutlineMidPoint as getSnapOutlineMidPoint2,
259
+ getTransformHandleTypeFromCoords,
260
+ getUncroppedWidthAndHeight as getUncroppedWidthAndHeight4,
261
+ getVisibleSceneBounds,
262
+ handleFocusPointDrag,
263
+ handleFocusPointHover,
264
+ handleFocusPointPointerDown,
265
+ handleFocusPointPointerUp,
266
+ hasBoundingBox as hasBoundingBox2,
222
267
  hasBoundTextElement as hasBoundTextElement9,
268
+ hitElementBoundingBox as hitElementBoundingBox2,
269
+ hitElementBoundingBoxOnly,
270
+ hitElementBoundText,
271
+ hitElementItself as hitElementItself3,
223
272
  isArrowElement as isArrowElement14,
273
+ isBindableElement as isBindableElement3,
224
274
  isBindingElement as isBindingElement4,
225
275
  isBindingElementType,
276
+ isBindingEnabled as isBindingEnabled2,
226
277
  isBoundToContainer as isBoundToContainer9,
278
+ isCursorInFrame,
279
+ isElbowArrow as isElbowArrow10,
280
+ isElementCompletelyInViewport as isElementCompletelyInViewport2,
281
+ isElementInFrame,
282
+ isElementInGroup as isElementInGroup2,
283
+ isElementInViewport as isElementInViewport3,
284
+ isElementLink as isElementLink2,
285
+ isEligibleFrameChildType,
286
+ isEmbeddableElement as isEmbeddableElement4,
287
+ isFlowchartNodeElement as isFlowchartNodeElement2,
227
288
  isFrameLikeElement as isFrameLikeElement15,
289
+ isIframeElement as isIframeElement2,
290
+ isIframeLikeElement as isIframeLikeElement2,
228
291
  isImageElement as isImageElement9,
229
- isEmbeddableElement as isEmbeddableElement4,
230
292
  isInitializedImageElement as isInitializedImageElement3,
293
+ isInvisiblySmallElement as isInvisiblySmallElement3,
231
294
  isLinearElement as isLinearElement12,
232
295
  isLinearElementType as isLinearElementType2,
233
- isUsingAdaptiveRadius as isUsingAdaptiveRadius4,
234
- isIframeElement as isIframeElement2,
235
- isIframeLikeElement as isIframeLikeElement2,
296
+ isLineElement as isLineElement8,
236
297
  isMagicFrameElement as isMagicFrameElement2,
298
+ isMeasureTextSupported,
299
+ isNonDeletedElement,
300
+ isPathALoop as isPathALoop3,
301
+ isPointInElement as isPointInElement3,
302
+ isSelectedViaGroup as isSelectedViaGroup2,
303
+ isSimpleArrow,
237
304
  isTextBindableContainer as isTextBindableContainer3,
238
- isElbowArrow as isElbowArrow10,
239
- isFlowchartNodeElement as isFlowchartNodeElement2,
240
- isBindableElement as isBindableElement3,
241
305
  isTextElement as isTextElement19,
242
- getNormalizedDimensions,
243
- isElementCompletelyInViewport as isElementCompletelyInViewport2,
244
- isElementInViewport as isElementInViewport3,
245
- isInvisiblySmallElement as isInvisiblySmallElement3,
246
- getCornerRadius as getCornerRadius2,
247
- isPathALoop as isPathALoop3,
248
- createSrcDoc,
249
- embeddableURLValidator as embeddableURLValidator2,
306
+ isUsingAdaptiveRadius as isUsingAdaptiveRadius4,
307
+ isValidTextContainer,
308
+ makeNextSelectedElementIds as makeNextSelectedElementIds3,
309
+ maxBindingDistance_simple as maxBindingDistance_simple3,
310
+ maybeHandleArrowPointlikeDrag,
250
311
  maybeParseEmbedSrc,
251
- getEmbedLink as getEmbedLink2,
252
- getInitializedImageElements,
312
+ measureText as measureText8,
313
+ mutateElement as mutateElement6,
314
+ newArrowElement as newArrowElement2,
315
+ newElement as newElement6,
316
+ newElementWith as newElementWith10,
317
+ newEmbeddableElement,
318
+ newFrameElement as newFrameElement2,
319
+ newFreeDrawElement,
320
+ newIframeElement,
321
+ newImageElement,
322
+ newLinearElement as newLinearElement5,
323
+ newMagicFrameElement,
324
+ newTextElement as newTextElement5,
253
325
  normalizeSVG,
254
- updateImageCache as _updateImageCache,
255
- getBoundTextElement as getBoundTextElement15,
256
- getContainerCenter,
257
- getContainerElement as getContainerElement5,
258
- isValidTextContainer,
326
+ normalizeText as normalizeText2,
327
+ parseElementLinkFromURL,
328
+ positionElementsOnGrid,
259
329
  redrawTextBoundingBox as redrawTextBoundingBox8,
260
- hasBoundingBox as hasBoundingBox2,
261
- getCommonFrameId as getCommonFrameId2,
262
- getFrameChildren as getFrameChildren6,
263
- getFrameChildrenInsertionIndex as getFrameChildrenInsertionIndex2,
264
- isCursorInFrame,
265
- addElementsToFrame as addElementsToFrame2,
266
- replaceAllElementsInFrame as replaceAllElementsInFrame4,
330
+ refreshTextDimensions,
267
331
  removeElementsFromFrame as removeElementsFromFrame2,
268
- getElementsInResizingFrame as getElementsInResizingFrame4,
269
- getElementsInNewFrame,
270
- getContainingFrame as getContainingFrame3,
271
- elementOverlapsWithFrame as elementOverlapsWithFrame2,
272
- updateFrameMembershipOfSelectedElements as updateFrameMembershipOfSelectedElements6,
273
- isElementInFrame,
274
- getFrameLikeTitle,
275
- getElementsOverlappingFrame as getElementsOverlappingFrame2,
276
- filterElementsEligibleAsFrameChildren,
277
- hitElementBoundText,
278
- hitElementBoundingBoxOnly,
279
- hitElementItself as hitElementItself3,
280
- getVisibleSceneBounds,
281
- FlowChartCreator,
282
- FlowChartNavigator,
283
- getLinkDirectionFromKey,
284
- cropElement,
285
- wrapText as wrapText5,
286
- isElementLink as isElementLink2,
287
- parseElementLinkFromURL,
288
- isMeasureTextSupported,
289
- normalizeText as normalizeText2,
290
- measureText as measureText8,
291
- getLineHeightInPx as getLineHeightInPx3,
292
- getApproxMinLineWidth as getApproxMinLineWidth2,
293
- getApproxMinLineHeight,
294
- getMinTextElementWidth,
295
- ShapeCache as ShapeCache4,
296
- getRenderOpacity,
297
- editGroupForSelectedElement,
298
- getElementsInGroup as getElementsInGroup10,
299
- getSelectedGroupIdForElement,
300
- getSelectedGroupIds as getSelectedGroupIds4,
301
- isElementInGroup as isElementInGroup2,
302
- isSelectedViaGroup as isSelectedViaGroup2,
332
+ replaceAllElementsInFrame as replaceAllElementsInFrame4,
333
+ Scene,
303
334
  selectGroupsForSelectedElements as selectGroupsForSelectedElements7,
335
+ ShapeCache as ShapeCache4,
336
+ Store,
337
+ StoreDelta as StoreDelta2,
304
338
  syncInvalidIndices,
305
339
  syncMovedIndices as syncMovedIndices5,
306
- excludeElementsInFramesFromSelection,
307
- getSelectionStateForElements as getSelectionStateForElements2,
308
- makeNextSelectedElementIds as makeNextSelectedElementIds3,
309
- getResizeOffsetXY,
310
- getResizeArrowDirection,
311
340
  transformElements,
312
- getCursorForResizingElement,
313
- getElementWithTransformHandleType,
314
- getTransformHandleTypeFromCoords,
315
- dragNewElement,
316
- dragSelectedElements,
317
- getDragOffsetXY,
318
- isNonDeletedElement,
319
- Scene,
320
- Store,
321
- CaptureUpdateAction as CaptureUpdateAction41,
322
- hitElementBoundingBox as hitElementBoundingBox2,
323
- isLineElement as isLineElement8,
324
- isSimpleArrow,
325
- StoreDelta as StoreDelta2,
326
- positionElementsOnGrid,
327
- calculateFixedPointForNonElbowArrowBinding as calculateFixedPointForNonElbowArrowBinding2,
328
- bindOrUnbindBindingElement as bindOrUnbindBindingElement2,
329
- mutateElement as mutateElement6,
330
- getElementBounds as getElementBounds5,
331
- doBoundsIntersect as doBoundsIntersect4,
332
- isPointInElement as isPointInElement3,
333
- maxBindingDistance_simple as maxBindingDistance_simple3,
334
- convertToExcalidrawElements,
335
- getSnapOutlineMidPoint as getSnapOutlineMidPoint2,
336
- handleFocusPointDrag,
337
- handleFocusPointHover,
338
- handleFocusPointPointerDown,
339
- handleFocusPointPointerUp,
340
- maybeHandleArrowPointlikeDrag,
341
- getUncroppedWidthAndHeight as getUncroppedWidthAndHeight4,
342
- getActiveTextElement as getActiveTextElement2,
343
- isEligibleFrameChildType
341
+ updateBoundElements as updateBoundElements4,
342
+ updateFrameMembershipOfSelectedElements as updateFrameMembershipOfSelectedElements6,
343
+ wrapText as wrapText5
344
344
  } from "@excalidraw/element";
345
345
  import { LinearElementEditor as LinearElementEditor11 } from "@excalidraw/element/linearElementEditor";
346
346
  import { findShapeByKey } from "@excalidraw/element/shapes";
@@ -9611,7 +9611,7 @@ var exportCanvas = async (type, elements, appState, files, {
9611
9611
  let blob = canvasToBlob(tempCanvas);
9612
9612
  if (appState.exportEmbedScene) {
9613
9613
  blob = blob.then(
9614
- (blob2) => import("./data/image-R7FQP7SA.js").then(
9614
+ (blob2) => import("./data/image-WGZL7YPK.js").then(
9615
9615
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9616
9616
  blob: blob2,
9617
9617
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -15025,980 +15025,428 @@ var getCenter = (pointers) => {
15025
15025
  var getDistance = ([a, b]) => Math.hypot(a.x - b.x, a.y - b.y);
15026
15026
  var sum = (array, mapper) => array.reduce((acc, item) => acc + mapper(item), 0);
15027
15027
 
15028
- // snapping.ts
15029
- import {
15030
- pointFrom as pointFrom18,
15031
- pointRotateRads as pointRotateRads14,
15032
- rangeInclusive,
15033
- rangeIntersection,
15034
- rangesOverlap
15035
- } from "@excalidraw/math";
15036
- import { TOOL_TYPE, KEYS as KEYS34 } from "@excalidraw/common";
15037
- import {
15038
- getCommonBounds as getCommonBounds4,
15039
- getDraggedElementsBounds,
15040
- getElementAbsoluteCoords as getElementAbsoluteCoords4
15041
- } from "@excalidraw/element";
15042
- import { isBoundToContainer as isBoundToContainer6 } from "@excalidraw/element";
15043
- import { getMaximumGroups } from "@excalidraw/element";
15044
- import {
15045
- getSelectedElements as getSelectedElements4,
15046
- getVisibleAndNonSelectedElements
15047
- } from "@excalidraw/element";
15048
- var SNAP_DISTANCE = 8;
15049
- var VISIBLE_GAPS_LIMIT_PER_AXIS = 99999;
15050
- var getSnapDistance = (zoomValue) => {
15051
- return SNAP_DISTANCE / zoomValue;
15052
- };
15053
- var _SnapCache = class _SnapCache {
15054
- };
15055
- __publicField(_SnapCache, "referenceSnapPoints", null);
15056
- __publicField(_SnapCache, "visibleGaps", null);
15057
- __publicField(_SnapCache, "setReferenceSnapPoints", (snapPoints) => {
15058
- _SnapCache.referenceSnapPoints = snapPoints;
15059
- });
15060
- __publicField(_SnapCache, "getReferenceSnapPoints", () => {
15061
- return _SnapCache.referenceSnapPoints;
15062
- });
15063
- __publicField(_SnapCache, "setVisibleGaps", (gaps) => {
15064
- _SnapCache.visibleGaps = gaps;
15065
- });
15066
- __publicField(_SnapCache, "getVisibleGaps", () => {
15067
- return _SnapCache.visibleGaps;
15068
- });
15069
- __publicField(_SnapCache, "destroy", () => {
15070
- _SnapCache.referenceSnapPoints = null;
15071
- _SnapCache.visibleGaps = null;
15072
- });
15073
- var SnapCache = _SnapCache;
15074
- var isGridModeEnabled = (app) => app.props.gridModeEnabled ?? app.state.gridModeEnabled;
15075
- var isSnappingEnabled = ({
15076
- event,
15077
- app,
15078
- selectedElements
15079
- }) => {
15080
- if (event) {
15081
- const isLassoDragging = app.state.activeTool.type === "lasso" && app.state.selectedElementsAreBeingDragged;
15082
- return (app.state.activeTool.type !== "lasso" || isLassoDragging) && (app.state.objectsSnapModeEnabled && !event[KEYS34.CTRL_OR_CMD] || !app.state.objectsSnapModeEnabled && event[KEYS34.CTRL_OR_CMD] && !isGridModeEnabled(app));
15083
- }
15084
- if (selectedElements.length === 1 && selectedElements[0].type === "arrow") {
15085
- return false;
15086
- }
15087
- return app.state.objectsSnapModeEnabled;
15088
- };
15089
- var areRoughlyEqual = (a, b, precision = 0.01) => {
15090
- return Math.abs(a - b) <= precision;
15091
- };
15092
- var getElementsCorners = (elements, elementsMap, {
15093
- omitCenter,
15094
- boundingBoxCorners,
15095
- dragOffset
15096
- } = {
15097
- omitCenter: false,
15098
- boundingBoxCorners: false
15099
- }) => {
15100
- let result = [];
15101
- if (elements.length === 1) {
15102
- const element = elements[0];
15103
- let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords4(
15104
- element,
15105
- elementsMap
15106
- );
15107
- if (dragOffset) {
15108
- x1 += dragOffset.x;
15109
- x2 += dragOffset.x;
15110
- cx += dragOffset.x;
15111
- y1 += dragOffset.y;
15112
- y2 += dragOffset.y;
15113
- cy += dragOffset.y;
15114
- }
15115
- const halfWidth = (x2 - x1) / 2;
15116
- const halfHeight = (y2 - y1) / 2;
15117
- if ((element.type === "diamond" || element.type === "ellipse") && !boundingBoxCorners) {
15118
- const leftMid = pointRotateRads14(
15119
- pointFrom18(x1, y1 + halfHeight),
15120
- pointFrom18(cx, cy),
15121
- element.angle
15122
- );
15123
- const topMid = pointRotateRads14(
15124
- pointFrom18(x1 + halfWidth, y1),
15125
- pointFrom18(cx, cy),
15126
- element.angle
15127
- );
15128
- const rightMid = pointRotateRads14(
15129
- pointFrom18(x2, y1 + halfHeight),
15130
- pointFrom18(cx, cy),
15131
- element.angle
15132
- );
15133
- const bottomMid = pointRotateRads14(
15134
- pointFrom18(x1 + halfWidth, y2),
15135
- pointFrom18(cx, cy),
15136
- element.angle
15137
- );
15138
- const center = pointFrom18(cx, cy);
15139
- result = omitCenter ? [leftMid, topMid, rightMid, bottomMid] : [leftMid, topMid, rightMid, bottomMid, center];
15140
- } else {
15141
- const topLeft = pointRotateRads14(
15142
- pointFrom18(x1, y1),
15143
- pointFrom18(cx, cy),
15144
- element.angle
15145
- );
15146
- const topRight = pointRotateRads14(
15147
- pointFrom18(x2, y1),
15148
- pointFrom18(cx, cy),
15149
- element.angle
15150
- );
15151
- const bottomLeft = pointRotateRads14(
15152
- pointFrom18(x1, y2),
15153
- pointFrom18(cx, cy),
15154
- element.angle
15155
- );
15156
- const bottomRight = pointRotateRads14(
15157
- pointFrom18(x2, y2),
15158
- pointFrom18(cx, cy),
15159
- element.angle
15160
- );
15161
- const center = pointFrom18(cx, cy);
15162
- result = omitCenter ? [topLeft, topRight, bottomLeft, bottomRight] : [topLeft, topRight, bottomLeft, bottomRight, center];
15163
- }
15164
- } else if (elements.length > 1) {
15165
- const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
15166
- elements,
15167
- dragOffset ?? { x: 0, y: 0 }
15168
- );
15169
- const width = maxX - minX;
15170
- const height = maxY - minY;
15171
- const topLeft = pointFrom18(minX, minY);
15172
- const topRight = pointFrom18(maxX, minY);
15173
- const bottomLeft = pointFrom18(minX, maxY);
15174
- const bottomRight = pointFrom18(maxX, maxY);
15175
- const center = pointFrom18(minX + width / 2, minY + height / 2);
15176
- result = omitCenter ? [topLeft, topRight, bottomLeft, bottomRight] : [topLeft, topRight, bottomLeft, bottomRight, center];
15177
- }
15178
- return result.map((p) => pointFrom18(round(p[0]), round(p[1])));
15179
- };
15180
- var getReferenceElements = (elements, selectedElements, appState, elementsMap) => getVisibleAndNonSelectedElements(
15181
- elements,
15182
- selectedElements,
15183
- appState,
15184
- elementsMap
15185
- );
15186
- var getVisibleGaps = (elements, selectedElements, appState, elementsMap) => {
15187
- const referenceElements = getReferenceElements(
15188
- elements,
15189
- selectedElements,
15190
- appState,
15191
- elementsMap
15192
- );
15193
- const referenceBounds = getMaximumGroups(referenceElements, elementsMap).filter(
15194
- (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer6(elementsGroup[0]))
15195
- ).map(
15196
- (group) => getCommonBounds4(group).map(
15197
- (bound) => round(bound)
15198
- )
15199
- );
15200
- const horizontallySorted = referenceBounds.sort((a, b) => a[0] - b[0]);
15201
- const horizontalGaps = [];
15202
- let c = 0;
15203
- horizontal:
15204
- for (let i = 0; i < horizontallySorted.length; i++) {
15205
- const startBounds = horizontallySorted[i];
15206
- for (let j = i + 1; j < horizontallySorted.length; j++) {
15207
- if (++c > VISIBLE_GAPS_LIMIT_PER_AXIS) {
15208
- break horizontal;
15209
- }
15210
- const endBounds = horizontallySorted[j];
15211
- const [, startMinY, startMaxX, startMaxY] = startBounds;
15212
- const [endMinX, endMinY, , endMaxY] = endBounds;
15213
- if (startMaxX < endMinX && rangesOverlap(
15214
- rangeInclusive(startMinY, startMaxY),
15215
- rangeInclusive(endMinY, endMaxY)
15216
- )) {
15217
- horizontalGaps.push({
15218
- startBounds,
15219
- endBounds,
15220
- startSide: [
15221
- pointFrom18(startMaxX, startMinY),
15222
- pointFrom18(startMaxX, startMaxY)
15223
- ],
15224
- endSide: [pointFrom18(endMinX, endMinY), pointFrom18(endMinX, endMaxY)],
15225
- length: endMinX - startMaxX,
15226
- overlap: rangeIntersection(
15227
- rangeInclusive(startMinY, startMaxY),
15228
- rangeInclusive(endMinY, endMaxY)
15229
- )
15230
- });
15231
- }
15232
- }
15233
- }
15234
- const verticallySorted = referenceBounds.sort((a, b) => a[1] - b[1]);
15235
- const verticalGaps = [];
15236
- c = 0;
15237
- vertical:
15238
- for (let i = 0; i < verticallySorted.length; i++) {
15239
- const startBounds = verticallySorted[i];
15240
- for (let j = i + 1; j < verticallySorted.length; j++) {
15241
- if (++c > VISIBLE_GAPS_LIMIT_PER_AXIS) {
15242
- break vertical;
15243
- }
15244
- const endBounds = verticallySorted[j];
15245
- const [startMinX, , startMaxX, startMaxY] = startBounds;
15246
- const [endMinX, endMinY, endMaxX] = endBounds;
15247
- if (startMaxY < endMinY && rangesOverlap(
15248
- rangeInclusive(startMinX, startMaxX),
15249
- rangeInclusive(endMinX, endMaxX)
15250
- )) {
15251
- verticalGaps.push({
15252
- startBounds,
15253
- endBounds,
15254
- startSide: [
15255
- pointFrom18(startMinX, startMaxY),
15256
- pointFrom18(startMaxX, startMaxY)
15257
- ],
15258
- endSide: [pointFrom18(endMinX, endMinY), pointFrom18(endMaxX, endMinY)],
15259
- length: endMinY - startMaxY,
15260
- overlap: rangeIntersection(
15261
- rangeInclusive(startMinX, startMaxX),
15262
- rangeInclusive(endMinX, endMaxX)
15263
- )
15264
- });
15265
- }
15266
- }
15267
- }
15268
- return {
15269
- horizontalGaps,
15270
- verticalGaps
15271
- };
15272
- };
15273
- var getGapSnaps = (selectedElements, dragOffset, app, event, nearestSnapsX, nearestSnapsY, minOffset) => {
15274
- if (!isSnappingEnabled({ app, event, selectedElements })) {
15275
- return [];
15276
- }
15277
- if (selectedElements.length === 0) {
15278
- return [];
15279
- }
15280
- const visibleGaps = SnapCache.getVisibleGaps();
15281
- if (visibleGaps) {
15282
- const { horizontalGaps, verticalGaps } = visibleGaps;
15283
- const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
15284
- selectedElements,
15285
- dragOffset
15286
- ).map((bound) => round(bound));
15287
- const centerX = (minX + maxX) / 2;
15288
- const centerY = (minY + maxY) / 2;
15289
- for (const gap of horizontalGaps) {
15290
- if (!rangesOverlap(rangeInclusive(minY, maxY), gap.overlap)) {
15291
- continue;
15292
- }
15293
- const gapMidX = gap.startSide[0][0] + gap.length / 2;
15294
- const centerOffset = round(gapMidX - centerX);
15295
- const gapIsLargerThanSelection = gap.length > maxX - minX;
15296
- if (gapIsLargerThanSelection && Math.abs(centerOffset) <= minOffset.x) {
15297
- if (Math.abs(centerOffset) < minOffset.x) {
15298
- nearestSnapsX.length = 0;
15299
- }
15300
- minOffset.x = Math.abs(centerOffset);
15301
- const snap = {
15302
- type: "gap",
15303
- direction: "center_horizontal",
15304
- gap,
15305
- offset: centerOffset
15306
- };
15307
- nearestSnapsX.push(snap);
15308
- continue;
15309
- }
15310
- const [, , endMaxX] = gap.endBounds;
15311
- const distanceToEndElementX = minX - endMaxX;
15312
- const sideOffsetRight = round(gap.length - distanceToEndElementX);
15313
- if (Math.abs(sideOffsetRight) <= minOffset.x) {
15314
- if (Math.abs(sideOffsetRight) < minOffset.x) {
15315
- nearestSnapsX.length = 0;
15316
- }
15317
- minOffset.x = Math.abs(sideOffsetRight);
15318
- const snap = {
15319
- type: "gap",
15320
- direction: "side_right",
15321
- gap,
15322
- offset: sideOffsetRight
15323
- };
15324
- nearestSnapsX.push(snap);
15325
- continue;
15326
- }
15327
- const [startMinX, , ,] = gap.startBounds;
15328
- const distanceToStartElementX = startMinX - maxX;
15329
- const sideOffsetLeft = round(distanceToStartElementX - gap.length);
15330
- if (Math.abs(sideOffsetLeft) <= minOffset.x) {
15331
- if (Math.abs(sideOffsetLeft) < minOffset.x) {
15332
- nearestSnapsX.length = 0;
15333
- }
15334
- minOffset.x = Math.abs(sideOffsetLeft);
15335
- const snap = {
15336
- type: "gap",
15337
- direction: "side_left",
15338
- gap,
15339
- offset: sideOffsetLeft
15340
- };
15341
- nearestSnapsX.push(snap);
15342
- continue;
15343
- }
15344
- }
15345
- for (const gap of verticalGaps) {
15346
- if (!rangesOverlap(rangeInclusive(minX, maxX), gap.overlap)) {
15347
- continue;
15348
- }
15349
- const gapMidY = gap.startSide[0][1] + gap.length / 2;
15350
- const centerOffset = round(gapMidY - centerY);
15351
- const gapIsLargerThanSelection = gap.length > maxY - minY;
15352
- if (gapIsLargerThanSelection && Math.abs(centerOffset) <= minOffset.y) {
15353
- if (Math.abs(centerOffset) < minOffset.y) {
15354
- nearestSnapsY.length = 0;
15355
- }
15356
- minOffset.y = Math.abs(centerOffset);
15357
- const snap = {
15358
- type: "gap",
15359
- direction: "center_vertical",
15360
- gap,
15361
- offset: centerOffset
15362
- };
15363
- nearestSnapsY.push(snap);
15364
- continue;
15365
- }
15366
- const [, startMinY, ,] = gap.startBounds;
15367
- const distanceToStartElementY = startMinY - maxY;
15368
- const sideOffsetTop = round(distanceToStartElementY - gap.length);
15369
- if (Math.abs(sideOffsetTop) <= minOffset.y) {
15370
- if (Math.abs(sideOffsetTop) < minOffset.y) {
15371
- nearestSnapsY.length = 0;
15372
- }
15373
- minOffset.y = Math.abs(sideOffsetTop);
15374
- const snap = {
15375
- type: "gap",
15376
- direction: "side_top",
15377
- gap,
15378
- offset: sideOffsetTop
15379
- };
15380
- nearestSnapsY.push(snap);
15381
- continue;
15382
- }
15383
- const [, , , endMaxY] = gap.endBounds;
15384
- const distanceToEndElementY = round(minY - endMaxY);
15385
- const sideOffsetBottom = gap.length - distanceToEndElementY;
15386
- if (Math.abs(sideOffsetBottom) <= minOffset.y) {
15387
- if (Math.abs(sideOffsetBottom) < minOffset.y) {
15388
- nearestSnapsY.length = 0;
15389
- }
15390
- minOffset.y = Math.abs(sideOffsetBottom);
15391
- const snap = {
15392
- type: "gap",
15393
- direction: "side_bottom",
15394
- gap,
15395
- offset: sideOffsetBottom
15396
- };
15397
- nearestSnapsY.push(snap);
15398
- continue;
15399
- }
15400
- }
15401
- }
15402
- };
15403
- var getReferenceSnapPoints = (elements, selectedElements, appState, elementsMap) => {
15404
- const referenceElements = getReferenceElements(
15405
- elements,
15406
- selectedElements,
15407
- appState,
15408
- elementsMap
15409
- );
15410
- return getMaximumGroups(referenceElements, elementsMap).filter(
15411
- (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer6(elementsGroup[0]))
15412
- ).flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
15413
- };
15414
- var getPointSnaps = (selectedElements, selectionSnapPoints, app, event, nearestSnapsX, nearestSnapsY, minOffset) => {
15415
- if (!isSnappingEnabled({ app, event, selectedElements }) || selectedElements.length === 0 && selectionSnapPoints.length === 0) {
15416
- return [];
15417
- }
15418
- const referenceSnapPoints = SnapCache.getReferenceSnapPoints();
15419
- if (referenceSnapPoints) {
15420
- for (const thisSnapPoint of selectionSnapPoints) {
15421
- for (const otherSnapPoint of referenceSnapPoints) {
15422
- const offsetX = otherSnapPoint[0] - thisSnapPoint[0];
15423
- const offsetY = otherSnapPoint[1] - thisSnapPoint[1];
15424
- if (Math.abs(offsetX) <= minOffset.x) {
15425
- if (Math.abs(offsetX) < minOffset.x) {
15426
- nearestSnapsX.length = 0;
15427
- }
15428
- nearestSnapsX.push({
15429
- type: "point",
15430
- points: [thisSnapPoint, otherSnapPoint],
15431
- offset: offsetX
15432
- });
15433
- minOffset.x = Math.abs(offsetX);
15434
- }
15435
- if (Math.abs(offsetY) <= minOffset.y) {
15436
- if (Math.abs(offsetY) < minOffset.y) {
15437
- nearestSnapsY.length = 0;
15438
- }
15439
- nearestSnapsY.push({
15440
- type: "point",
15441
- points: [thisSnapPoint, otherSnapPoint],
15442
- offset: offsetY
15443
- });
15444
- minOffset.y = Math.abs(offsetY);
15445
- }
15446
- }
15447
- }
15448
- }
15449
- };
15450
- var snapDraggedElements = (elements, dragOffset, app, event, elementsMap) => {
15451
- const appState = app.state;
15452
- const selectedElements = getSelectedElements4(elements, appState);
15453
- if (!isSnappingEnabled({ app, event, selectedElements }) || selectedElements.length === 0) {
15454
- return {
15455
- snapOffset: {
15456
- x: 0,
15457
- y: 0
15458
- },
15459
- snapLines: []
15460
- };
15461
- }
15462
- dragOffset.x = round(dragOffset.x);
15463
- dragOffset.y = round(dragOffset.y);
15464
- const nearestSnapsX = [];
15465
- const nearestSnapsY = [];
15466
- const snapDistance = getSnapDistance(appState.zoom.value);
15467
- const minOffset = {
15468
- x: snapDistance,
15469
- y: snapDistance
15470
- };
15471
- const selectionPoints = getElementsCorners(selectedElements, elementsMap, {
15472
- dragOffset
15473
- });
15474
- getPointSnaps(
15475
- selectedElements,
15476
- selectionPoints,
15477
- app,
15478
- event,
15479
- nearestSnapsX,
15480
- nearestSnapsY,
15481
- minOffset
15482
- );
15483
- getGapSnaps(
15484
- selectedElements,
15485
- dragOffset,
15486
- app,
15487
- event,
15488
- nearestSnapsX,
15489
- nearestSnapsY,
15490
- minOffset
15491
- );
15492
- const snapOffset = {
15493
- x: nearestSnapsX[0]?.offset ?? 0,
15494
- y: nearestSnapsY[0]?.offset ?? 0
15495
- };
15496
- minOffset.x = 0;
15497
- minOffset.y = 0;
15498
- nearestSnapsX.length = 0;
15499
- nearestSnapsY.length = 0;
15500
- const newDragOffset = {
15501
- x: round(dragOffset.x + snapOffset.x),
15502
- y: round(dragOffset.y + snapOffset.y)
15503
- };
15504
- getPointSnaps(
15505
- selectedElements,
15506
- getElementsCorners(selectedElements, elementsMap, {
15507
- dragOffset: newDragOffset
15508
- }),
15509
- app,
15510
- event,
15511
- nearestSnapsX,
15512
- nearestSnapsY,
15513
- minOffset
15514
- );
15515
- getGapSnaps(
15516
- selectedElements,
15517
- newDragOffset,
15518
- app,
15519
- event,
15520
- nearestSnapsX,
15521
- nearestSnapsY,
15522
- minOffset
15523
- );
15524
- const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
15525
- const gapSnapLines = createGapSnapLines(
15526
- selectedElements,
15527
- newDragOffset,
15528
- [...nearestSnapsX, ...nearestSnapsY].filter(
15529
- (snap) => snap.type === "gap"
15530
- )
15531
- );
15532
- return {
15533
- snapOffset,
15534
- snapLines: [...pointSnapLines, ...gapSnapLines]
15535
- };
15536
- };
15537
- var round = (x) => {
15538
- const decimalPlaces = 6;
15539
- return Math.round(x * 10 ** decimalPlaces) / 10 ** decimalPlaces;
15540
- };
15541
- var dedupePoints = (points) => {
15542
- const map = /* @__PURE__ */ new Map();
15543
- for (const point of points) {
15544
- const key = point.join(",");
15545
- if (!map.has(key)) {
15546
- map.set(key, point);
15547
- }
15548
- }
15549
- return Array.from(map.values());
15550
- };
15551
- var createPointSnapLines = (nearestSnapsX, nearestSnapsY) => {
15552
- const snapsX = {};
15553
- const snapsY = {};
15554
- if (nearestSnapsX.length > 0) {
15555
- for (const snap of nearestSnapsX) {
15556
- if (snap.type === "point") {
15557
- const key = round(snap.points[0][0]);
15558
- if (!snapsX[key]) {
15559
- snapsX[key] = [];
15560
- }
15561
- snapsX[key].push(
15562
- ...snap.points.map(
15563
- (p) => pointFrom18(round(p[0]), round(p[1]))
15564
- )
15565
- );
15566
- }
15567
- }
15568
- }
15569
- if (nearestSnapsY.length > 0) {
15570
- for (const snap of nearestSnapsY) {
15571
- if (snap.type === "point") {
15572
- const key = round(snap.points[0][1]);
15573
- if (!snapsY[key]) {
15574
- snapsY[key] = [];
15575
- }
15576
- snapsY[key].push(
15577
- ...snap.points.map(
15578
- (p) => pointFrom18(round(p[0]), round(p[1]))
15579
- )
15580
- );
15581
- }
15582
- }
15583
- }
15584
- return Object.entries(snapsX).map(([key, points]) => {
15585
- return {
15586
- type: "points",
15587
- points: dedupePoints(
15588
- points.map((p) => {
15589
- return pointFrom18(Number(key), p[1]);
15590
- }).sort((a, b) => a[1] - b[1])
15591
- )
15592
- };
15593
- }).concat(
15594
- Object.entries(snapsY).map(([key, points]) => {
15595
- return {
15596
- type: "points",
15597
- points: dedupePoints(
15598
- points.map((p) => {
15599
- return pointFrom18(p[0], Number(key));
15600
- }).sort((a, b) => a[0] - b[0])
15601
- )
15602
- };
15603
- })
15028
+ // components/ElementCanvasButtons.tsx
15029
+ import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2 } from "@excalidraw/common";
15030
+ import { getElementAbsoluteCoords as getElementAbsoluteCoords4 } from "@excalidraw/element";
15031
+ import { jsx as jsx57 } from "react/jsx-runtime";
15032
+ var CONTAINER_PADDING = 5;
15033
+ var getContainerCoords2 = (element, appState, elementsMap) => {
15034
+ const [x1, y1] = getElementAbsoluteCoords4(element, elementsMap);
15035
+ const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords2(
15036
+ { sceneX: x1 + element.width, sceneY: y1 },
15037
+ appState
15604
15038
  );
15039
+ const x = viewportX - appState.offsetLeft + 10;
15040
+ const y = viewportY - appState.offsetTop;
15041
+ return { x, y };
15605
15042
  };
15606
- var dedupeGapSnapLines = (gapSnapLines) => {
15607
- const map = /* @__PURE__ */ new Map();
15608
- for (const gapSnapLine of gapSnapLines) {
15609
- const key = gapSnapLine.points.flat().map((point) => [round(point)]).join(",");
15610
- if (!map.has(key)) {
15611
- map.set(key, gapSnapLine);
15612
- }
15043
+ var ElementCanvasButtons = ({
15044
+ children,
15045
+ element,
15046
+ elementsMap
15047
+ }) => {
15048
+ const appState = useExcalidrawAppState();
15049
+ if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
15050
+ return null;
15613
15051
  }
15614
- return Array.from(map.values());
15615
- };
15616
- var createGapSnapLines = (selectedElements, dragOffset, gapSnaps) => {
15617
- const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
15618
- selectedElements,
15619
- dragOffset
15620
- );
15621
- const gapSnapLines = [];
15622
- for (const gapSnap of gapSnaps) {
15623
- const [startMinX, startMinY, startMaxX, startMaxY] = gapSnap.gap.startBounds;
15624
- const [endMinX, endMinY, endMaxX, endMaxY] = gapSnap.gap.endBounds;
15625
- const verticalIntersection = rangeIntersection(
15626
- rangeInclusive(minY, maxY),
15627
- gapSnap.gap.overlap
15628
- );
15629
- const horizontalGapIntersection = rangeIntersection(
15630
- rangeInclusive(minX, maxX),
15631
- gapSnap.gap.overlap
15632
- );
15633
- switch (gapSnap.direction) {
15634
- case "center_horizontal": {
15635
- if (verticalIntersection) {
15636
- const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
15637
- gapSnapLines.push(
15638
- {
15639
- type: "gap",
15640
- direction: "horizontal",
15641
- points: [
15642
- pointFrom18(gapSnap.gap.startSide[0][0], gapLineY),
15643
- pointFrom18(minX, gapLineY)
15644
- ]
15645
- },
15646
- {
15647
- type: "gap",
15648
- direction: "horizontal",
15649
- points: [
15650
- pointFrom18(maxX, gapLineY),
15651
- pointFrom18(gapSnap.gap.endSide[0][0], gapLineY)
15652
- ]
15653
- }
15654
- );
15655
- }
15656
- break;
15657
- }
15658
- case "center_vertical": {
15659
- if (horizontalGapIntersection) {
15660
- const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
15661
- gapSnapLines.push(
15662
- {
15663
- type: "gap",
15664
- direction: "vertical",
15665
- points: [
15666
- pointFrom18(gapLineX, gapSnap.gap.startSide[0][1]),
15667
- pointFrom18(gapLineX, minY)
15668
- ]
15669
- },
15670
- {
15671
- type: "gap",
15672
- direction: "vertical",
15673
- points: [
15674
- pointFrom18(gapLineX, maxY),
15675
- pointFrom18(gapLineX, gapSnap.gap.endSide[0][1])
15676
- ]
15677
- }
15678
- );
15679
- }
15680
- break;
15681
- }
15682
- case "side_right": {
15683
- if (verticalIntersection) {
15684
- const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
15685
- gapSnapLines.push(
15686
- {
15687
- type: "gap",
15688
- direction: "horizontal",
15689
- points: [
15690
- pointFrom18(startMaxX, gapLineY),
15691
- pointFrom18(endMinX, gapLineY)
15692
- ]
15693
- },
15694
- {
15695
- type: "gap",
15696
- direction: "horizontal",
15697
- points: [pointFrom18(endMaxX, gapLineY), pointFrom18(minX, gapLineY)]
15698
- }
15699
- );
15700
- }
15701
- break;
15702
- }
15703
- case "side_left": {
15704
- if (verticalIntersection) {
15705
- const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
15706
- gapSnapLines.push(
15707
- {
15708
- type: "gap",
15709
- direction: "horizontal",
15710
- points: [
15711
- pointFrom18(maxX, gapLineY),
15712
- pointFrom18(startMinX, gapLineY)
15713
- ]
15714
- },
15715
- {
15716
- type: "gap",
15717
- direction: "horizontal",
15718
- points: [
15719
- pointFrom18(startMaxX, gapLineY),
15720
- pointFrom18(endMinX, gapLineY)
15721
- ]
15722
- }
15723
- );
15724
- }
15725
- break;
15726
- }
15727
- case "side_top": {
15728
- if (horizontalGapIntersection) {
15729
- const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
15730
- gapSnapLines.push(
15731
- {
15732
- type: "gap",
15733
- direction: "vertical",
15734
- points: [
15735
- pointFrom18(gapLineX, maxY),
15736
- pointFrom18(gapLineX, startMinY)
15737
- ]
15738
- },
15739
- {
15740
- type: "gap",
15741
- direction: "vertical",
15742
- points: [
15743
- pointFrom18(gapLineX, startMaxY),
15744
- pointFrom18(gapLineX, endMinY)
15745
- ]
15746
- }
15747
- );
15748
- }
15749
- break;
15750
- }
15751
- case "side_bottom": {
15752
- if (horizontalGapIntersection) {
15753
- const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
15754
- gapSnapLines.push(
15755
- {
15756
- type: "gap",
15757
- direction: "vertical",
15758
- points: [
15759
- pointFrom18(gapLineX, startMaxY),
15760
- pointFrom18(gapLineX, endMinY)
15761
- ]
15762
- },
15763
- {
15764
- type: "gap",
15765
- direction: "vertical",
15766
- points: [pointFrom18(gapLineX, endMaxY), pointFrom18(gapLineX, minY)]
15767
- }
15052
+ const { x, y } = getContainerCoords2(element, appState, elementsMap);
15053
+ return /* @__PURE__ */ jsx57(
15054
+ "div",
15055
+ {
15056
+ className: "excalidraw-canvas-buttons",
15057
+ style: {
15058
+ top: `${y}px`,
15059
+ left: `${x}px`,
15060
+ // width: CONTAINER_WIDTH,
15061
+ padding: CONTAINER_PADDING
15062
+ },
15063
+ children
15064
+ }
15065
+ );
15066
+ };
15067
+
15068
+ // laserTrails.ts
15069
+ import { DEFAULT_LASER_COLOR, easeOut } from "@excalidraw/common";
15070
+
15071
+ // animatedTrail.ts
15072
+ import { LaserPointer } from "@excalidraw/laser-pointer";
15073
+ import {
15074
+ SVG_NS,
15075
+ getSvgPathFromStroke as getSvgPathFromStroke2,
15076
+ sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3
15077
+ } from "@excalidraw/common";
15078
+
15079
+ // reactUtils.ts
15080
+ import { version as ReactVersion } from "react";
15081
+ import { unstable_batchedUpdates } from "react-dom";
15082
+ import { throttleRAF } from "@excalidraw/common";
15083
+ var withBatchedUpdates = (func) => (event) => {
15084
+ unstable_batchedUpdates(func, event);
15085
+ };
15086
+ var withBatchedUpdatesThrottled = (func) => {
15087
+ return throttleRAF((event) => {
15088
+ unstable_batchedUpdates(func, event);
15089
+ });
15090
+ };
15091
+ var isRenderThrottlingEnabled = (() => {
15092
+ let IS_REACT_18_AND_UP;
15093
+ try {
15094
+ const version = ReactVersion.split(".");
15095
+ IS_REACT_18_AND_UP = Number(version[0]) > 17;
15096
+ } catch {
15097
+ IS_REACT_18_AND_UP = false;
15098
+ }
15099
+ let hasWarned = false;
15100
+ return () => {
15101
+ if (window.EXCALIDRAW_THROTTLE_RENDER === true) {
15102
+ if (!IS_REACT_18_AND_UP) {
15103
+ if (!hasWarned) {
15104
+ hasWarned = true;
15105
+ console.warn(
15106
+ "Excalidraw: render throttling is disabled on React versions < 18."
15768
15107
  );
15769
15108
  }
15770
- break;
15109
+ return false;
15771
15110
  }
15111
+ return true;
15112
+ }
15113
+ return false;
15114
+ };
15115
+ })();
15116
+
15117
+ // renderer/animation.ts
15118
+ var _AnimationController = class _AnimationController {
15119
+ static start(key, animation) {
15120
+ if (_AnimationController.animations.has(key)) {
15121
+ return;
15122
+ }
15123
+ const initialState = animation({
15124
+ deltaTime: 0,
15125
+ state: void 0
15126
+ });
15127
+ if (initialState) {
15128
+ _AnimationController.animations.set(key, {
15129
+ animation,
15130
+ lastTime: 0,
15131
+ state: initialState
15132
+ });
15133
+ _AnimationController.scheduleNextFrame();
15772
15134
  }
15773
15135
  }
15774
- return dedupeGapSnapLines(
15775
- gapSnapLines.map((gapSnapLine) => {
15776
- return {
15777
- ...gapSnapLine,
15778
- points: gapSnapLine.points.map(
15779
- (p) => pointFrom18(round(p[0]), round(p[1]))
15780
- )
15136
+ static scheduleNextFrame() {
15137
+ if (_AnimationController.scheduledFrame) {
15138
+ return;
15139
+ }
15140
+ if (isRenderThrottlingEnabled()) {
15141
+ _AnimationController.scheduledFrame = {
15142
+ id: requestAnimationFrame(_AnimationController.tick),
15143
+ type: "raf"
15781
15144
  };
15782
- })
15783
- );
15784
- };
15785
- var snapResizingElements = (selectedElements, selectedOriginalElements, app, event, dragOffset, transformHandle) => {
15786
- if (!isSnappingEnabled({ event, selectedElements, app }) || selectedElements.length === 0 || selectedElements.length === 1 && !areRoughlyEqual(selectedElements[0].angle, 0)) {
15787
- return {
15788
- snapOffset: { x: 0, y: 0 },
15789
- snapLines: []
15790
- };
15145
+ } else {
15146
+ _AnimationController.scheduledFrame = {
15147
+ id: setTimeout(_AnimationController.tick, 0),
15148
+ type: "timeout"
15149
+ };
15150
+ }
15791
15151
  }
15792
- let [minX, minY, maxX, maxY] = getCommonBounds4(selectedOriginalElements);
15793
- if (transformHandle) {
15794
- if (transformHandle.includes("e")) {
15795
- maxX += dragOffset.x;
15796
- } else if (transformHandle.includes("w")) {
15797
- minX += dragOffset.x;
15152
+ static cancelScheduledFrame() {
15153
+ if (!_AnimationController.scheduledFrame) {
15154
+ return;
15798
15155
  }
15799
- if (transformHandle.includes("n")) {
15800
- minY += dragOffset.y;
15801
- } else if (transformHandle.includes("s")) {
15802
- maxY += dragOffset.y;
15156
+ if (_AnimationController.scheduledFrame.type === "raf") {
15157
+ cancelAnimationFrame(_AnimationController.scheduledFrame.id);
15158
+ } else {
15159
+ clearTimeout(_AnimationController.scheduledFrame.id);
15803
15160
  }
15161
+ _AnimationController.scheduledFrame = null;
15804
15162
  }
15805
- const selectionSnapPoints = [];
15806
- if (transformHandle) {
15807
- switch (transformHandle) {
15808
- case "e": {
15809
- selectionSnapPoints.push(pointFrom18(maxX, minY), pointFrom18(maxX, maxY));
15810
- break;
15811
- }
15812
- case "w": {
15813
- selectionSnapPoints.push(pointFrom18(minX, minY), pointFrom18(minX, maxY));
15814
- break;
15815
- }
15816
- case "n": {
15817
- selectionSnapPoints.push(pointFrom18(minX, minY), pointFrom18(maxX, minY));
15818
- break;
15819
- }
15820
- case "s": {
15821
- selectionSnapPoints.push(pointFrom18(minX, maxY), pointFrom18(maxX, maxY));
15822
- break;
15823
- }
15824
- case "ne": {
15825
- selectionSnapPoints.push(pointFrom18(maxX, minY));
15826
- break;
15827
- }
15828
- case "nw": {
15829
- selectionSnapPoints.push(pointFrom18(minX, minY));
15830
- break;
15831
- }
15832
- case "se": {
15833
- selectionSnapPoints.push(pointFrom18(maxX, maxY));
15834
- break;
15163
+ static cancelScheduledFrameIfIdle() {
15164
+ if (_AnimationController.animations.size > 0) {
15165
+ return false;
15166
+ }
15167
+ _AnimationController.cancelScheduledFrame();
15168
+ return true;
15169
+ }
15170
+ static tick() {
15171
+ _AnimationController.scheduledFrame = null;
15172
+ if (_AnimationController.animations.size > 0) {
15173
+ for (const [key, animation] of _AnimationController.animations) {
15174
+ const now = performance.now();
15175
+ const deltaTime = animation.lastTime === 0 ? 0 : now - animation.lastTime;
15176
+ const state = animation.animation({
15177
+ deltaTime,
15178
+ state: animation.state
15179
+ });
15180
+ if (!state) {
15181
+ _AnimationController.animations.delete(key);
15182
+ if (_AnimationController.cancelScheduledFrameIfIdle()) {
15183
+ return;
15184
+ }
15185
+ } else {
15186
+ animation.lastTime = now;
15187
+ animation.state = state;
15188
+ }
15835
15189
  }
15836
- case "sw": {
15837
- selectionSnapPoints.push(pointFrom18(minX, maxY));
15838
- break;
15190
+ if (_AnimationController.cancelScheduledFrameIfIdle()) {
15191
+ return;
15839
15192
  }
15193
+ _AnimationController.scheduleNextFrame();
15840
15194
  }
15841
15195
  }
15842
- const snapDistance = getSnapDistance(app.state.zoom.value);
15843
- const minOffset = {
15844
- x: snapDistance,
15845
- y: snapDistance
15846
- };
15847
- const nearestSnapsX = [];
15848
- const nearestSnapsY = [];
15849
- getPointSnaps(
15850
- selectedOriginalElements,
15851
- selectionSnapPoints,
15852
- app,
15853
- event,
15854
- nearestSnapsX,
15855
- nearestSnapsY,
15856
- minOffset
15857
- );
15858
- const snapOffset = {
15859
- x: nearestSnapsX[0]?.offset ?? 0,
15860
- y: nearestSnapsY[0]?.offset ?? 0
15861
- };
15862
- minOffset.x = 0;
15863
- minOffset.y = 0;
15864
- nearestSnapsX.length = 0;
15865
- nearestSnapsY.length = 0;
15866
- const [x1, y1, x2, y2] = getCommonBounds4(selectedElements).map(
15867
- (bound) => round(bound)
15868
- );
15869
- const corners = [
15870
- pointFrom18(x1, y1),
15871
- pointFrom18(x1, y2),
15872
- pointFrom18(x2, y1),
15873
- pointFrom18(x2, y2)
15874
- ];
15875
- getPointSnaps(
15876
- selectedElements,
15877
- corners,
15878
- app,
15879
- event,
15880
- nearestSnapsX,
15881
- nearestSnapsY,
15882
- minOffset
15883
- );
15884
- const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
15885
- return {
15886
- snapOffset,
15887
- snapLines: pointSnapLines
15888
- };
15196
+ static running(key) {
15197
+ return _AnimationController.animations.has(key);
15198
+ }
15199
+ static cancel(key) {
15200
+ _AnimationController.animations.delete(key);
15201
+ _AnimationController.cancelScheduledFrameIfIdle();
15202
+ }
15889
15203
  };
15890
- var snapNewElement = (newElement7, app, event, origin, dragOffset, elementsMap) => {
15891
- if (!isSnappingEnabled({ event, selectedElements: [newElement7], app })) {
15892
- return {
15893
- snapOffset: { x: 0, y: 0 },
15894
- snapLines: []
15895
- };
15204
+ __publicField(_AnimationController, "scheduledFrame", null);
15205
+ __publicField(_AnimationController, "animations", /* @__PURE__ */ new Map());
15206
+ var AnimationController = _AnimationController;
15207
+
15208
+ // animatedTrail.ts
15209
+ var _AnimatedTrail = class _AnimatedTrail {
15210
+ constructor(app, options) {
15211
+ this.app = app;
15212
+ this.options = options;
15213
+ __publicField(this, "currentTrail");
15214
+ __publicField(this, "pastTrails", []);
15215
+ __publicField(this, "container");
15216
+ __publicField(this, "trailElement");
15217
+ __publicField(this, "trailAnimation");
15218
+ __publicField(this, "key");
15219
+ this.key = `animated-trail-${_AnimatedTrail.counter++}`;
15220
+ this.trailElement = document.createElementNS(SVG_NS, "path");
15221
+ if (this.options.animateTrail) {
15222
+ this.trailAnimation = document.createElementNS(SVG_NS, "animate");
15223
+ this.trailAnimation.setAttribute("attributeName", "stroke-dashoffset");
15224
+ this.trailElement.setAttribute("stroke-dasharray", "7 7");
15225
+ this.trailElement.setAttribute("stroke-dashoffset", "10");
15226
+ this.trailAnimation.setAttribute("from", "0");
15227
+ this.trailAnimation.setAttribute("to", `-14`);
15228
+ this.trailAnimation.setAttribute("dur", "0.3s");
15229
+ this.trailElement.appendChild(this.trailAnimation);
15230
+ }
15231
+ }
15232
+ get hasCurrentTrail() {
15233
+ return !!this.currentTrail;
15234
+ }
15235
+ hasLastPoint(x, y) {
15236
+ if (this.currentTrail) {
15237
+ const len = this.currentTrail.originalPoints.length;
15238
+ return this.currentTrail.originalPoints[len - 1][0] === x && this.currentTrail.originalPoints[len - 1][1] === y;
15239
+ }
15240
+ return false;
15241
+ }
15242
+ cleanup() {
15243
+ this.pastTrails = [];
15244
+ this.currentTrail = void 0;
15245
+ if (this.trailElement.parentNode === this.container) {
15246
+ this.container?.removeChild(this.trailElement);
15247
+ }
15248
+ }
15249
+ start(container) {
15250
+ if (container) {
15251
+ this.container = container;
15252
+ }
15253
+ if (this.trailElement.parentNode !== this.container && this.container) {
15254
+ this.container.appendChild(this.trailElement);
15255
+ }
15256
+ if (!AnimationController.running(this.key)) {
15257
+ AnimationController.start(this.key, () => {
15258
+ const needsNext = this.onFrame();
15259
+ if (needsNext) {
15260
+ return { keep: true };
15261
+ }
15262
+ this.cleanup();
15263
+ return null;
15264
+ });
15265
+ }
15266
+ }
15267
+ stop() {
15268
+ AnimationController.cancel(this.key);
15269
+ this.cleanup();
15270
+ }
15271
+ startPath(x, y) {
15272
+ this.currentTrail = new LaserPointer(this.options);
15273
+ this.currentTrail.addPoint([x, y, performance.now()]);
15274
+ this.update();
15275
+ }
15276
+ addPointToPath(x, y) {
15277
+ if (this.currentTrail) {
15278
+ this.currentTrail.addPoint([x, y, performance.now()]);
15279
+ this.update();
15280
+ }
15281
+ }
15282
+ endPath() {
15283
+ if (this.currentTrail) {
15284
+ this.currentTrail.close();
15285
+ this.currentTrail.options.keepHead = false;
15286
+ this.pastTrails.push(this.currentTrail);
15287
+ this.currentTrail = void 0;
15288
+ this.update();
15289
+ }
15290
+ }
15291
+ getCurrentTrail() {
15292
+ return this.currentTrail;
15293
+ }
15294
+ clearTrails() {
15295
+ this.pastTrails = [];
15296
+ this.currentTrail = void 0;
15297
+ this.update();
15298
+ }
15299
+ update() {
15300
+ this.start();
15301
+ if (this.trailAnimation) {
15302
+ this.trailAnimation.setAttribute("begin", "indefinite");
15303
+ this.trailAnimation.setAttribute("repeatCount", "indefinite");
15304
+ }
15305
+ }
15306
+ onFrame() {
15307
+ const paths = [];
15308
+ for (const trail of this.pastTrails) {
15309
+ paths.push(this.drawTrail(trail, this.app.state));
15310
+ }
15311
+ if (this.currentTrail) {
15312
+ const currentPath = this.drawTrail(this.currentTrail, this.app.state);
15313
+ paths.push(currentPath);
15314
+ }
15315
+ this.pastTrails = this.pastTrails.filter(
15316
+ (t2) => t2.getStrokeOutline(t2.options.size / this.app.state.zoom.value).length !== 0
15317
+ );
15318
+ if (paths.length === 0) {
15319
+ this.trailElement.setAttribute("d", "");
15320
+ return false;
15321
+ }
15322
+ const svgPaths = paths.join(" ").trim();
15323
+ this.trailElement.setAttribute("d", svgPaths);
15324
+ if (this.trailAnimation) {
15325
+ this.trailElement.setAttribute(
15326
+ "fill",
15327
+ (this.options.fill ?? (() => "black"))(this)
15328
+ );
15329
+ this.trailElement.setAttribute(
15330
+ "stroke",
15331
+ (this.options.stroke ?? (() => "black"))(this)
15332
+ );
15333
+ } else {
15334
+ this.trailElement.setAttribute(
15335
+ "fill",
15336
+ (this.options.fill ?? (() => "black"))(this)
15337
+ );
15338
+ }
15339
+ return true;
15340
+ }
15341
+ drawTrail(trail, state) {
15342
+ const _stroke = trail.getStrokeOutline(trail.options.size / state.zoom.value).map(([x, y]) => {
15343
+ const result = sceneCoordsToViewportCoords3(
15344
+ { sceneX: x, sceneY: y },
15345
+ state
15346
+ );
15347
+ return [result.x, result.y];
15348
+ });
15349
+ const stroke = this.trailAnimation ? _stroke.slice(0, _stroke.length / 2) : _stroke;
15350
+ return getSvgPathFromStroke2(stroke, true);
15896
15351
  }
15897
- const selectionSnapPoints = [
15898
- pointFrom18(origin.x + dragOffset.x, origin.y + dragOffset.y)
15899
- ];
15900
- const snapDistance = getSnapDistance(app.state.zoom.value);
15901
- const minOffset = {
15902
- x: snapDistance,
15903
- y: snapDistance
15904
- };
15905
- const nearestSnapsX = [];
15906
- const nearestSnapsY = [];
15907
- getPointSnaps(
15908
- [newElement7],
15909
- selectionSnapPoints,
15910
- app,
15911
- event,
15912
- nearestSnapsX,
15913
- nearestSnapsY,
15914
- minOffset
15915
- );
15916
- const snapOffset = {
15917
- x: nearestSnapsX[0]?.offset ?? 0,
15918
- y: nearestSnapsY[0]?.offset ?? 0
15919
- };
15920
- minOffset.x = 0;
15921
- minOffset.y = 0;
15922
- nearestSnapsX.length = 0;
15923
- nearestSnapsY.length = 0;
15924
- const corners = getElementsCorners([newElement7], elementsMap, {
15925
- boundingBoxCorners: true,
15926
- omitCenter: true
15927
- });
15928
- getPointSnaps(
15929
- [newElement7],
15930
- corners,
15931
- app,
15932
- event,
15933
- nearestSnapsX,
15934
- nearestSnapsY,
15935
- minOffset
15936
- );
15937
- const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
15938
- return {
15939
- snapOffset,
15940
- snapLines: pointSnapLines
15941
- };
15942
15352
  };
15943
- var getSnapLinesAtPointer = (elements, app, pointer, event, elementsMap) => {
15944
- if (!isSnappingEnabled({ event, selectedElements: [], app })) {
15353
+ __publicField(_AnimatedTrail, "counter", 0);
15354
+ var AnimatedTrail = _AnimatedTrail;
15355
+
15356
+ // laserTrails.ts
15357
+ var LaserTrails = class {
15358
+ constructor(app) {
15359
+ this.app = app;
15360
+ __publicField(this, "localTrail");
15361
+ __publicField(this, "collabTrails", /* @__PURE__ */ new Map());
15362
+ __publicField(this, "container");
15363
+ this.localTrail = new AnimatedTrail(app, {
15364
+ ...this.getTrailOptions(),
15365
+ fill: () => DEFAULT_LASER_COLOR
15366
+ });
15367
+ }
15368
+ getTrailOptions() {
15945
15369
  return {
15946
- originOffset: { x: 0, y: 0 },
15947
- snapLines: []
15370
+ simplify: 0,
15371
+ streamline: 0.4,
15372
+ sizeMapping: (c) => {
15373
+ const DECAY_TIME = 1e3;
15374
+ const DECAY_LENGTH = 50;
15375
+ const t2 = Math.max(
15376
+ 0,
15377
+ 1 - (performance.now() - c.pressure) / DECAY_TIME
15378
+ );
15379
+ const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
15380
+ return Math.min(easeOut(l), easeOut(t2));
15381
+ }
15948
15382
  };
15949
15383
  }
15950
- const referenceElements = getVisibleAndNonSelectedElements(
15951
- elements,
15952
- [],
15953
- app.state,
15954
- elementsMap
15955
- );
15956
- const snapDistance = getSnapDistance(app.state.zoom.value);
15957
- const minOffset = {
15958
- x: snapDistance,
15959
- y: snapDistance
15960
- };
15961
- const horizontalSnapLines = [];
15962
- const verticalSnapLines = [];
15963
- for (const referenceElement of referenceElements) {
15964
- const corners = getElementsCorners([referenceElement], elementsMap);
15965
- for (const corner of corners) {
15966
- const offsetX = corner[0] - pointer.x;
15967
- if (Math.abs(offsetX) <= Math.abs(minOffset.x)) {
15968
- if (Math.abs(offsetX) < Math.abs(minOffset.x)) {
15969
- verticalSnapLines.length = 0;
15970
- }
15971
- verticalSnapLines.push({
15972
- type: "pointer",
15973
- points: [corner, pointFrom18(corner[0], pointer.y)],
15974
- direction: "vertical"
15384
+ startPath(x, y) {
15385
+ this.localTrail.startPath(x, y);
15386
+ }
15387
+ addPointToPath(x, y) {
15388
+ this.localTrail.addPointToPath(x, y);
15389
+ }
15390
+ endPath() {
15391
+ this.localTrail.endPath();
15392
+ }
15393
+ start(container) {
15394
+ this.container = container;
15395
+ this.localTrail.start(container);
15396
+ }
15397
+ stop() {
15398
+ this.localTrail.stop();
15399
+ this.stopCollabTrails();
15400
+ this.container = void 0;
15401
+ }
15402
+ stopCollabTrails(collaborators) {
15403
+ for (const [key, trail] of this.collabTrails) {
15404
+ const collaborator = collaborators?.get(key);
15405
+ if (!collaborator) {
15406
+ trail.stop();
15407
+ this.collabTrails.delete(key);
15408
+ }
15409
+ }
15410
+ }
15411
+ updateCollabTrails(collaborators) {
15412
+ this.stopCollabTrails(collaborators);
15413
+ if (!this.container || collaborators.size === 0) {
15414
+ return;
15415
+ }
15416
+ for (const [key, collaborator] of collaborators.entries()) {
15417
+ if (collaborator.isCurrentUser) {
15418
+ continue;
15419
+ }
15420
+ let trail = this.collabTrails.get(key);
15421
+ if (!trail) {
15422
+ trail = new AnimatedTrail(this.app, {
15423
+ ...this.getTrailOptions(),
15424
+ fill: () => collaborator.pointer?.laserColor || getClientColor(key, collaborator)
15975
15425
  });
15976
- minOffset.x = offsetX;
15426
+ trail.start(this.container);
15427
+ this.collabTrails.set(key, trail);
15977
15428
  }
15978
- const offsetY = corner[1] - pointer.y;
15979
- if (Math.abs(offsetY) <= Math.abs(minOffset.y)) {
15980
- if (Math.abs(offsetY) < Math.abs(minOffset.y)) {
15981
- horizontalSnapLines.length = 0;
15429
+ if (collaborator.pointer && collaborator.pointer.tool === "laser") {
15430
+ const buttonDown = collaborator.button === "down";
15431
+ const buttonUp = collaborator.button === "up";
15432
+ const hasTrail = trail.hasCurrentTrail;
15433
+ if (buttonDown && !hasTrail) {
15434
+ trail.startPath(collaborator.pointer.x, collaborator.pointer.y);
15435
+ }
15436
+ const lastPointOriginal = !trail.hasLastPoint(
15437
+ collaborator.pointer.x,
15438
+ collaborator.pointer.y
15439
+ );
15440
+ if (buttonDown && lastPointOriginal) {
15441
+ trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
15442
+ }
15443
+ if (buttonUp && hasTrail) {
15444
+ trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
15445
+ trail.endPath();
15982
15446
  }
15983
- horizontalSnapLines.push({
15984
- type: "pointer",
15985
- points: [corner, pointFrom18(pointer.x, corner[1])],
15986
- direction: "horizontal"
15987
- });
15988
- minOffset.y = offsetY;
15989
15447
  }
15990
15448
  }
15991
15449
  }
15992
- return {
15993
- originOffset: {
15994
- x: verticalSnapLines.length > 0 ? verticalSnapLines[0].points[0][0] - pointer.x : 0,
15995
- y: horizontalSnapLines.length > 0 ? horizontalSnapLines[0].points[0][1] - pointer.y : 0
15996
- },
15997
- snapLines: [...verticalSnapLines, ...horizontalSnapLines]
15998
- };
15999
- };
16000
- var isActiveToolNonLinearSnappable = (activeToolType) => {
16001
- return activeToolType === TOOL_TYPE.rectangle || activeToolType === TOOL_TYPE.ellipse || activeToolType === TOOL_TYPE.diamond || activeToolType === TOOL_TYPE.frame || activeToolType === TOOL_TYPE.magicframe || activeToolType === TOOL_TYPE.image || activeToolType === TOOL_TYPE.text;
16002
15450
  };
16003
15451
 
16004
15452
  // scene/Renderer.ts
@@ -16107,481 +15555,1102 @@ var Renderer = class {
16107
15555
  )) {
16108
15556
  visibleElements.push(element);
16109
15557
  }
16110
- }
16111
- return visibleElements;
16112
- }
16113
- getRenderableElementsMap({
16114
- elements,
16115
- editingTextElement,
16116
- newElement: newElement7
16117
- }) {
16118
- const elementsMap = toBrandedType(/* @__PURE__ */ new Map());
16119
- const newElementCanvasElement = newElement7?.frameId ? null : newElement7;
16120
- for (const element of elements) {
16121
- if (newElementCanvasElement?.id === element.id) {
15558
+ }
15559
+ return visibleElements;
15560
+ }
15561
+ getRenderableElementsMap({
15562
+ elements,
15563
+ editingTextElement,
15564
+ newElement: newElement7
15565
+ }) {
15566
+ const elementsMap = toBrandedType(/* @__PURE__ */ new Map());
15567
+ const newElementCanvasElement = newElement7?.frameId ? null : newElement7;
15568
+ for (const element of elements) {
15569
+ if (newElementCanvasElement?.id === element.id) {
15570
+ continue;
15571
+ }
15572
+ if (!editingTextElement || editingTextElement.type !== "text" || element.id !== editingTextElement.id) {
15573
+ elementsMap.set(element.id, element);
15574
+ }
15575
+ }
15576
+ return { elementsMap, newElementCanvasElement };
15577
+ }
15578
+ sortSelectedElementsIntoHighlightedFrame({
15579
+ visibleElements,
15580
+ selectedElements,
15581
+ frameToHighlight
15582
+ }) {
15583
+ if (!selectedElements.length) {
15584
+ return visibleElements;
15585
+ }
15586
+ const selectedElementsMap = arrayToMap24(selectedElements);
15587
+ const deselectedElements = visibleElements.filter(
15588
+ (element) => !selectedElementsMap.has(element.id)
15589
+ );
15590
+ const insertionIndex = getFrameChildrenInsertionIndex(
15591
+ deselectedElements,
15592
+ frameToHighlight.id
15593
+ );
15594
+ if (insertionIndex === null) {
15595
+ return visibleElements;
15596
+ }
15597
+ return [
15598
+ ...deselectedElements.slice(0, insertionIndex),
15599
+ ...selectedElements,
15600
+ ...deselectedElements.slice(insertionIndex)
15601
+ ];
15602
+ }
15603
+ // NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
15604
+ // safe to break TS contract here (for upstream cases)
15605
+ destroy() {
15606
+ renderStaticSceneThrottled.cancel();
15607
+ this._getRenderableElements.clear();
15608
+ }
15609
+ };
15610
+
15611
+ // scene/scrollbars.ts
15612
+ import { getGlobalCSSVariable } from "@excalidraw/common";
15613
+ import { getCommonBounds as getCommonBounds4 } from "@excalidraw/element";
15614
+ var SCROLLBAR_MARGIN = 4;
15615
+ var SCROLLBAR_WIDTH = 6;
15616
+ var SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
15617
+ var getScrollBars = (elements, viewportWidth, viewportHeight, appState) => {
15618
+ if (!elements.size) {
15619
+ return {
15620
+ horizontal: null,
15621
+ vertical: null
15622
+ };
15623
+ }
15624
+ const [elementsMinX, elementsMinY, elementsMaxX, elementsMaxY] = getCommonBounds4(elements);
15625
+ const viewportWidthWithZoom = viewportWidth / appState.zoom.value;
15626
+ const viewportHeightWithZoom = viewportHeight / appState.zoom.value;
15627
+ const safeArea = {
15628
+ top: parseInt(getGlobalCSSVariable("sat")) || 0,
15629
+ bottom: parseInt(getGlobalCSSVariable("sab")) || 0,
15630
+ left: parseInt(getGlobalCSSVariable("sal")) || 0,
15631
+ right: parseInt(getGlobalCSSVariable("sar")) || 0
15632
+ };
15633
+ const isRTL3 = getLanguage().rtl;
15634
+ const viewportMinX = -appState.scrollX + safeArea.left;
15635
+ const viewportMinY = -appState.scrollY + safeArea.top;
15636
+ const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right;
15637
+ const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom;
15638
+ const sceneMinX = Math.min(elementsMinX, viewportMinX);
15639
+ const sceneMinY = Math.min(elementsMinY, viewportMinY);
15640
+ const sceneMaxX = Math.max(elementsMaxX, viewportMaxX);
15641
+ const sceneMaxY = Math.max(elementsMaxY, viewportMaxY);
15642
+ const sceneWidth = elementsMaxX - elementsMinX;
15643
+ const sceneHeight = elementsMaxY - elementsMinY;
15644
+ const extendedSceneWidth = sceneMaxX - sceneMinX;
15645
+ const extendedSceneHeight = sceneMaxY - sceneMinY;
15646
+ const scrollWidthOffset = Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right) + SCROLLBAR_WIDTH * 2;
15647
+ const scrollbarWidth = viewportWidth * (viewportWidthWithZoom / extendedSceneWidth) - scrollWidthOffset;
15648
+ const scrollbarHeightOffset = Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom) + SCROLLBAR_WIDTH * 2;
15649
+ const scrollbarHeight = viewportHeight * (viewportHeightWithZoom / extendedSceneHeight) - scrollbarHeightOffset;
15650
+ const horizontalDeltaMultiplier = extendedSceneWidth > sceneWidth ? extendedSceneWidth * appState.zoom.value / (scrollbarWidth + scrollWidthOffset) : viewportWidth / (scrollbarWidth + scrollWidthOffset);
15651
+ const verticalDeltaMultiplier = extendedSceneHeight > sceneHeight ? extendedSceneHeight * appState.zoom.value / (scrollbarHeight + scrollbarHeightOffset) : viewportHeight / (scrollbarHeight + scrollbarHeightOffset);
15652
+ return {
15653
+ horizontal: viewportMinX === sceneMinX && viewportMaxX === sceneMaxX ? null : {
15654
+ x: Math.max(safeArea.left, SCROLLBAR_MARGIN) + SCROLLBAR_WIDTH + (viewportMinX - sceneMinX) / extendedSceneWidth * viewportWidth,
15655
+ y: viewportHeight - SCROLLBAR_WIDTH - Math.max(SCROLLBAR_MARGIN, safeArea.bottom),
15656
+ width: scrollbarWidth,
15657
+ height: SCROLLBAR_WIDTH,
15658
+ deltaMultiplier: horizontalDeltaMultiplier
15659
+ },
15660
+ vertical: viewportMinY === sceneMinY && viewportMaxY === sceneMaxY ? null : {
15661
+ x: isRTL3 ? Math.max(safeArea.left, SCROLLBAR_MARGIN) : viewportWidth - SCROLLBAR_WIDTH - Math.max(safeArea.right, SCROLLBAR_MARGIN),
15662
+ y: Math.max(safeArea.top, SCROLLBAR_MARGIN) + SCROLLBAR_WIDTH + (viewportMinY - sceneMinY) / extendedSceneHeight * viewportHeight,
15663
+ width: SCROLLBAR_WIDTH,
15664
+ height: scrollbarHeight,
15665
+ deltaMultiplier: verticalDeltaMultiplier
15666
+ }
15667
+ };
15668
+ };
15669
+ var isOverScrollBars = (scrollBars, x, y) => {
15670
+ const [isOverHorizontal, isOverVertical] = [
15671
+ scrollBars.horizontal,
15672
+ scrollBars.vertical
15673
+ ].map((scrollBar) => {
15674
+ return scrollBar != null && scrollBar.x <= x && x <= scrollBar.x + scrollBar.width && scrollBar.y <= y && y <= scrollBar.y + scrollBar.height;
15675
+ });
15676
+ const isOverEither = isOverHorizontal || isOverVertical;
15677
+ return { isOverEither, isOverHorizontal, isOverVertical };
15678
+ };
15679
+
15680
+ // snapping.ts
15681
+ import {
15682
+ pointFrom as pointFrom18,
15683
+ pointRotateRads as pointRotateRads14,
15684
+ rangeInclusive,
15685
+ rangeIntersection,
15686
+ rangesOverlap
15687
+ } from "@excalidraw/math";
15688
+ import { TOOL_TYPE, KEYS as KEYS34 } from "@excalidraw/common";
15689
+ import {
15690
+ getCommonBounds as getCommonBounds5,
15691
+ getDraggedElementsBounds,
15692
+ getElementAbsoluteCoords as getElementAbsoluteCoords5
15693
+ } from "@excalidraw/element";
15694
+ import { isBoundToContainer as isBoundToContainer6 } from "@excalidraw/element";
15695
+ import { getMaximumGroups } from "@excalidraw/element";
15696
+ import {
15697
+ getSelectedElements as getSelectedElements4,
15698
+ getVisibleAndNonSelectedElements
15699
+ } from "@excalidraw/element";
15700
+ var SNAP_DISTANCE = 8;
15701
+ var VISIBLE_GAPS_LIMIT_PER_AXIS = 99999;
15702
+ var getSnapDistance = (zoomValue) => {
15703
+ return SNAP_DISTANCE / zoomValue;
15704
+ };
15705
+ var _SnapCache = class _SnapCache {
15706
+ };
15707
+ __publicField(_SnapCache, "referenceSnapPoints", null);
15708
+ __publicField(_SnapCache, "visibleGaps", null);
15709
+ __publicField(_SnapCache, "setReferenceSnapPoints", (snapPoints) => {
15710
+ _SnapCache.referenceSnapPoints = snapPoints;
15711
+ });
15712
+ __publicField(_SnapCache, "getReferenceSnapPoints", () => {
15713
+ return _SnapCache.referenceSnapPoints;
15714
+ });
15715
+ __publicField(_SnapCache, "setVisibleGaps", (gaps) => {
15716
+ _SnapCache.visibleGaps = gaps;
15717
+ });
15718
+ __publicField(_SnapCache, "getVisibleGaps", () => {
15719
+ return _SnapCache.visibleGaps;
15720
+ });
15721
+ __publicField(_SnapCache, "destroy", () => {
15722
+ _SnapCache.referenceSnapPoints = null;
15723
+ _SnapCache.visibleGaps = null;
15724
+ });
15725
+ var SnapCache = _SnapCache;
15726
+ var isGridModeEnabled = (app) => app.props.gridModeEnabled ?? app.state.gridModeEnabled;
15727
+ var isSnappingEnabled = ({
15728
+ event,
15729
+ app,
15730
+ selectedElements
15731
+ }) => {
15732
+ if (event) {
15733
+ const isLassoDragging = app.state.activeTool.type === "lasso" && app.state.selectedElementsAreBeingDragged;
15734
+ return (app.state.activeTool.type !== "lasso" || isLassoDragging) && (app.state.objectsSnapModeEnabled && !event[KEYS34.CTRL_OR_CMD] || !app.state.objectsSnapModeEnabled && event[KEYS34.CTRL_OR_CMD] && !isGridModeEnabled(app));
15735
+ }
15736
+ if (selectedElements.length === 1 && selectedElements[0].type === "arrow") {
15737
+ return false;
15738
+ }
15739
+ return app.state.objectsSnapModeEnabled;
15740
+ };
15741
+ var areRoughlyEqual = (a, b, precision = 0.01) => {
15742
+ return Math.abs(a - b) <= precision;
15743
+ };
15744
+ var getElementsCorners = (elements, elementsMap, {
15745
+ omitCenter,
15746
+ boundingBoxCorners,
15747
+ dragOffset
15748
+ } = {
15749
+ omitCenter: false,
15750
+ boundingBoxCorners: false
15751
+ }) => {
15752
+ let result = [];
15753
+ if (elements.length === 1) {
15754
+ const element = elements[0];
15755
+ let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords5(
15756
+ element,
15757
+ elementsMap
15758
+ );
15759
+ if (dragOffset) {
15760
+ x1 += dragOffset.x;
15761
+ x2 += dragOffset.x;
15762
+ cx += dragOffset.x;
15763
+ y1 += dragOffset.y;
15764
+ y2 += dragOffset.y;
15765
+ cy += dragOffset.y;
15766
+ }
15767
+ const halfWidth = (x2 - x1) / 2;
15768
+ const halfHeight = (y2 - y1) / 2;
15769
+ if ((element.type === "diamond" || element.type === "ellipse") && !boundingBoxCorners) {
15770
+ const leftMid = pointRotateRads14(
15771
+ pointFrom18(x1, y1 + halfHeight),
15772
+ pointFrom18(cx, cy),
15773
+ element.angle
15774
+ );
15775
+ const topMid = pointRotateRads14(
15776
+ pointFrom18(x1 + halfWidth, y1),
15777
+ pointFrom18(cx, cy),
15778
+ element.angle
15779
+ );
15780
+ const rightMid = pointRotateRads14(
15781
+ pointFrom18(x2, y1 + halfHeight),
15782
+ pointFrom18(cx, cy),
15783
+ element.angle
15784
+ );
15785
+ const bottomMid = pointRotateRads14(
15786
+ pointFrom18(x1 + halfWidth, y2),
15787
+ pointFrom18(cx, cy),
15788
+ element.angle
15789
+ );
15790
+ const center = pointFrom18(cx, cy);
15791
+ result = omitCenter ? [leftMid, topMid, rightMid, bottomMid] : [leftMid, topMid, rightMid, bottomMid, center];
15792
+ } else {
15793
+ const topLeft = pointRotateRads14(
15794
+ pointFrom18(x1, y1),
15795
+ pointFrom18(cx, cy),
15796
+ element.angle
15797
+ );
15798
+ const topRight = pointRotateRads14(
15799
+ pointFrom18(x2, y1),
15800
+ pointFrom18(cx, cy),
15801
+ element.angle
15802
+ );
15803
+ const bottomLeft = pointRotateRads14(
15804
+ pointFrom18(x1, y2),
15805
+ pointFrom18(cx, cy),
15806
+ element.angle
15807
+ );
15808
+ const bottomRight = pointRotateRads14(
15809
+ pointFrom18(x2, y2),
15810
+ pointFrom18(cx, cy),
15811
+ element.angle
15812
+ );
15813
+ const center = pointFrom18(cx, cy);
15814
+ result = omitCenter ? [topLeft, topRight, bottomLeft, bottomRight] : [topLeft, topRight, bottomLeft, bottomRight, center];
15815
+ }
15816
+ } else if (elements.length > 1) {
15817
+ const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
15818
+ elements,
15819
+ dragOffset ?? { x: 0, y: 0 }
15820
+ );
15821
+ const width = maxX - minX;
15822
+ const height = maxY - minY;
15823
+ const topLeft = pointFrom18(minX, minY);
15824
+ const topRight = pointFrom18(maxX, minY);
15825
+ const bottomLeft = pointFrom18(minX, maxY);
15826
+ const bottomRight = pointFrom18(maxX, maxY);
15827
+ const center = pointFrom18(minX + width / 2, minY + height / 2);
15828
+ result = omitCenter ? [topLeft, topRight, bottomLeft, bottomRight] : [topLeft, topRight, bottomLeft, bottomRight, center];
15829
+ }
15830
+ return result.map((p) => pointFrom18(round(p[0]), round(p[1])));
15831
+ };
15832
+ var getReferenceElements = (elements, selectedElements, appState, elementsMap) => getVisibleAndNonSelectedElements(
15833
+ elements,
15834
+ selectedElements,
15835
+ appState,
15836
+ elementsMap
15837
+ );
15838
+ var getVisibleGaps = (elements, selectedElements, appState, elementsMap) => {
15839
+ const referenceElements = getReferenceElements(
15840
+ elements,
15841
+ selectedElements,
15842
+ appState,
15843
+ elementsMap
15844
+ );
15845
+ const referenceBounds = getMaximumGroups(referenceElements, elementsMap).filter(
15846
+ (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer6(elementsGroup[0]))
15847
+ ).map(
15848
+ (group) => getCommonBounds5(group).map(
15849
+ (bound) => round(bound)
15850
+ )
15851
+ );
15852
+ const horizontallySorted = referenceBounds.sort((a, b) => a[0] - b[0]);
15853
+ const horizontalGaps = [];
15854
+ let c = 0;
15855
+ horizontal:
15856
+ for (let i = 0; i < horizontallySorted.length; i++) {
15857
+ const startBounds = horizontallySorted[i];
15858
+ for (let j = i + 1; j < horizontallySorted.length; j++) {
15859
+ if (++c > VISIBLE_GAPS_LIMIT_PER_AXIS) {
15860
+ break horizontal;
15861
+ }
15862
+ const endBounds = horizontallySorted[j];
15863
+ const [, startMinY, startMaxX, startMaxY] = startBounds;
15864
+ const [endMinX, endMinY, , endMaxY] = endBounds;
15865
+ if (startMaxX < endMinX && rangesOverlap(
15866
+ rangeInclusive(startMinY, startMaxY),
15867
+ rangeInclusive(endMinY, endMaxY)
15868
+ )) {
15869
+ horizontalGaps.push({
15870
+ startBounds,
15871
+ endBounds,
15872
+ startSide: [
15873
+ pointFrom18(startMaxX, startMinY),
15874
+ pointFrom18(startMaxX, startMaxY)
15875
+ ],
15876
+ endSide: [pointFrom18(endMinX, endMinY), pointFrom18(endMinX, endMaxY)],
15877
+ length: endMinX - startMaxX,
15878
+ overlap: rangeIntersection(
15879
+ rangeInclusive(startMinY, startMaxY),
15880
+ rangeInclusive(endMinY, endMaxY)
15881
+ )
15882
+ });
15883
+ }
15884
+ }
15885
+ }
15886
+ const verticallySorted = referenceBounds.sort((a, b) => a[1] - b[1]);
15887
+ const verticalGaps = [];
15888
+ c = 0;
15889
+ vertical:
15890
+ for (let i = 0; i < verticallySorted.length; i++) {
15891
+ const startBounds = verticallySorted[i];
15892
+ for (let j = i + 1; j < verticallySorted.length; j++) {
15893
+ if (++c > VISIBLE_GAPS_LIMIT_PER_AXIS) {
15894
+ break vertical;
15895
+ }
15896
+ const endBounds = verticallySorted[j];
15897
+ const [startMinX, , startMaxX, startMaxY] = startBounds;
15898
+ const [endMinX, endMinY, endMaxX] = endBounds;
15899
+ if (startMaxY < endMinY && rangesOverlap(
15900
+ rangeInclusive(startMinX, startMaxX),
15901
+ rangeInclusive(endMinX, endMaxX)
15902
+ )) {
15903
+ verticalGaps.push({
15904
+ startBounds,
15905
+ endBounds,
15906
+ startSide: [
15907
+ pointFrom18(startMinX, startMaxY),
15908
+ pointFrom18(startMaxX, startMaxY)
15909
+ ],
15910
+ endSide: [pointFrom18(endMinX, endMinY), pointFrom18(endMaxX, endMinY)],
15911
+ length: endMinY - startMaxY,
15912
+ overlap: rangeIntersection(
15913
+ rangeInclusive(startMinX, startMaxX),
15914
+ rangeInclusive(endMinX, endMaxX)
15915
+ )
15916
+ });
15917
+ }
15918
+ }
15919
+ }
15920
+ return {
15921
+ horizontalGaps,
15922
+ verticalGaps
15923
+ };
15924
+ };
15925
+ var getGapSnaps = (selectedElements, dragOffset, app, event, nearestSnapsX, nearestSnapsY, minOffset) => {
15926
+ if (!isSnappingEnabled({ app, event, selectedElements })) {
15927
+ return [];
15928
+ }
15929
+ if (selectedElements.length === 0) {
15930
+ return [];
15931
+ }
15932
+ const visibleGaps = SnapCache.getVisibleGaps();
15933
+ if (visibleGaps) {
15934
+ const { horizontalGaps, verticalGaps } = visibleGaps;
15935
+ const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
15936
+ selectedElements,
15937
+ dragOffset
15938
+ ).map((bound) => round(bound));
15939
+ const centerX = (minX + maxX) / 2;
15940
+ const centerY = (minY + maxY) / 2;
15941
+ for (const gap of horizontalGaps) {
15942
+ if (!rangesOverlap(rangeInclusive(minY, maxY), gap.overlap)) {
15943
+ continue;
15944
+ }
15945
+ const gapMidX = gap.startSide[0][0] + gap.length / 2;
15946
+ const centerOffset = round(gapMidX - centerX);
15947
+ const gapIsLargerThanSelection = gap.length > maxX - minX;
15948
+ if (gapIsLargerThanSelection && Math.abs(centerOffset) <= minOffset.x) {
15949
+ if (Math.abs(centerOffset) < minOffset.x) {
15950
+ nearestSnapsX.length = 0;
15951
+ }
15952
+ minOffset.x = Math.abs(centerOffset);
15953
+ const snap = {
15954
+ type: "gap",
15955
+ direction: "center_horizontal",
15956
+ gap,
15957
+ offset: centerOffset
15958
+ };
15959
+ nearestSnapsX.push(snap);
15960
+ continue;
15961
+ }
15962
+ const [, , endMaxX] = gap.endBounds;
15963
+ const distanceToEndElementX = minX - endMaxX;
15964
+ const sideOffsetRight = round(gap.length - distanceToEndElementX);
15965
+ if (Math.abs(sideOffsetRight) <= minOffset.x) {
15966
+ if (Math.abs(sideOffsetRight) < minOffset.x) {
15967
+ nearestSnapsX.length = 0;
15968
+ }
15969
+ minOffset.x = Math.abs(sideOffsetRight);
15970
+ const snap = {
15971
+ type: "gap",
15972
+ direction: "side_right",
15973
+ gap,
15974
+ offset: sideOffsetRight
15975
+ };
15976
+ nearestSnapsX.push(snap);
15977
+ continue;
15978
+ }
15979
+ const [startMinX, , ,] = gap.startBounds;
15980
+ const distanceToStartElementX = startMinX - maxX;
15981
+ const sideOffsetLeft = round(distanceToStartElementX - gap.length);
15982
+ if (Math.abs(sideOffsetLeft) <= minOffset.x) {
15983
+ if (Math.abs(sideOffsetLeft) < minOffset.x) {
15984
+ nearestSnapsX.length = 0;
15985
+ }
15986
+ minOffset.x = Math.abs(sideOffsetLeft);
15987
+ const snap = {
15988
+ type: "gap",
15989
+ direction: "side_left",
15990
+ gap,
15991
+ offset: sideOffsetLeft
15992
+ };
15993
+ nearestSnapsX.push(snap);
15994
+ continue;
15995
+ }
15996
+ }
15997
+ for (const gap of verticalGaps) {
15998
+ if (!rangesOverlap(rangeInclusive(minX, maxX), gap.overlap)) {
15999
+ continue;
16000
+ }
16001
+ const gapMidY = gap.startSide[0][1] + gap.length / 2;
16002
+ const centerOffset = round(gapMidY - centerY);
16003
+ const gapIsLargerThanSelection = gap.length > maxY - minY;
16004
+ if (gapIsLargerThanSelection && Math.abs(centerOffset) <= minOffset.y) {
16005
+ if (Math.abs(centerOffset) < minOffset.y) {
16006
+ nearestSnapsY.length = 0;
16007
+ }
16008
+ minOffset.y = Math.abs(centerOffset);
16009
+ const snap = {
16010
+ type: "gap",
16011
+ direction: "center_vertical",
16012
+ gap,
16013
+ offset: centerOffset
16014
+ };
16015
+ nearestSnapsY.push(snap);
16016
+ continue;
16017
+ }
16018
+ const [, startMinY, ,] = gap.startBounds;
16019
+ const distanceToStartElementY = startMinY - maxY;
16020
+ const sideOffsetTop = round(distanceToStartElementY - gap.length);
16021
+ if (Math.abs(sideOffsetTop) <= minOffset.y) {
16022
+ if (Math.abs(sideOffsetTop) < minOffset.y) {
16023
+ nearestSnapsY.length = 0;
16024
+ }
16025
+ minOffset.y = Math.abs(sideOffsetTop);
16026
+ const snap = {
16027
+ type: "gap",
16028
+ direction: "side_top",
16029
+ gap,
16030
+ offset: sideOffsetTop
16031
+ };
16032
+ nearestSnapsY.push(snap);
16122
16033
  continue;
16123
16034
  }
16124
- if (!editingTextElement || editingTextElement.type !== "text" || element.id !== editingTextElement.id) {
16125
- elementsMap.set(element.id, element);
16035
+ const [, , , endMaxY] = gap.endBounds;
16036
+ const distanceToEndElementY = round(minY - endMaxY);
16037
+ const sideOffsetBottom = gap.length - distanceToEndElementY;
16038
+ if (Math.abs(sideOffsetBottom) <= minOffset.y) {
16039
+ if (Math.abs(sideOffsetBottom) < minOffset.y) {
16040
+ nearestSnapsY.length = 0;
16041
+ }
16042
+ minOffset.y = Math.abs(sideOffsetBottom);
16043
+ const snap = {
16044
+ type: "gap",
16045
+ direction: "side_bottom",
16046
+ gap,
16047
+ offset: sideOffsetBottom
16048
+ };
16049
+ nearestSnapsY.push(snap);
16050
+ continue;
16126
16051
  }
16127
16052
  }
16128
- return { elementsMap, newElementCanvasElement };
16129
- }
16130
- sortSelectedElementsIntoHighlightedFrame({
16131
- visibleElements,
16132
- selectedElements,
16133
- frameToHighlight
16134
- }) {
16135
- if (!selectedElements.length) {
16136
- return visibleElements;
16137
- }
16138
- const selectedElementsMap = arrayToMap24(selectedElements);
16139
- const deselectedElements = visibleElements.filter(
16140
- (element) => !selectedElementsMap.has(element.id)
16141
- );
16142
- const insertionIndex = getFrameChildrenInsertionIndex(
16143
- deselectedElements,
16144
- frameToHighlight.id
16145
- );
16146
- if (insertionIndex === null) {
16147
- return visibleElements;
16148
- }
16149
- return [
16150
- ...deselectedElements.slice(0, insertionIndex),
16151
- ...selectedElements,
16152
- ...deselectedElements.slice(insertionIndex)
16153
- ];
16154
- }
16155
- // NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
16156
- // safe to break TS contract here (for upstream cases)
16157
- destroy() {
16158
- renderStaticSceneThrottled.cancel();
16159
- this._getRenderableElements.clear();
16160
16053
  }
16161
16054
  };
16162
-
16163
- // components/ElementCanvasButtons.tsx
16164
- import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2 } from "@excalidraw/common";
16165
- import { getElementAbsoluteCoords as getElementAbsoluteCoords5 } from "@excalidraw/element";
16166
- import { jsx as jsx57 } from "react/jsx-runtime";
16167
- var CONTAINER_PADDING = 5;
16168
- var getContainerCoords2 = (element, appState, elementsMap) => {
16169
- const [x1, y1] = getElementAbsoluteCoords5(element, elementsMap);
16170
- const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords2(
16171
- { sceneX: x1 + element.width, sceneY: y1 },
16172
- appState
16055
+ var getReferenceSnapPoints = (elements, selectedElements, appState, elementsMap) => {
16056
+ const referenceElements = getReferenceElements(
16057
+ elements,
16058
+ selectedElements,
16059
+ appState,
16060
+ elementsMap
16173
16061
  );
16174
- const x = viewportX - appState.offsetLeft + 10;
16175
- const y = viewportY - appState.offsetTop;
16176
- return { x, y };
16062
+ return getMaximumGroups(referenceElements, elementsMap).filter(
16063
+ (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer6(elementsGroup[0]))
16064
+ ).flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
16177
16065
  };
16178
- var ElementCanvasButtons = ({
16179
- children,
16180
- element,
16181
- elementsMap
16182
- }) => {
16183
- const appState = useExcalidrawAppState();
16184
- if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
16185
- return null;
16066
+ var getPointSnaps = (selectedElements, selectionSnapPoints, app, event, nearestSnapsX, nearestSnapsY, minOffset) => {
16067
+ if (!isSnappingEnabled({ app, event, selectedElements }) || selectedElements.length === 0 && selectionSnapPoints.length === 0) {
16068
+ return [];
16186
16069
  }
16187
- const { x, y } = getContainerCoords2(element, appState, elementsMap);
16188
- return /* @__PURE__ */ jsx57(
16189
- "div",
16190
- {
16191
- className: "excalidraw-canvas-buttons",
16192
- style: {
16193
- top: `${y}px`,
16194
- left: `${x}px`,
16195
- // width: CONTAINER_WIDTH,
16196
- padding: CONTAINER_PADDING
16197
- },
16198
- children
16070
+ const referenceSnapPoints = SnapCache.getReferenceSnapPoints();
16071
+ if (referenceSnapPoints) {
16072
+ for (const thisSnapPoint of selectionSnapPoints) {
16073
+ for (const otherSnapPoint of referenceSnapPoints) {
16074
+ const offsetX = otherSnapPoint[0] - thisSnapPoint[0];
16075
+ const offsetY = otherSnapPoint[1] - thisSnapPoint[1];
16076
+ if (Math.abs(offsetX) <= minOffset.x) {
16077
+ if (Math.abs(offsetX) < minOffset.x) {
16078
+ nearestSnapsX.length = 0;
16079
+ }
16080
+ nearestSnapsX.push({
16081
+ type: "point",
16082
+ points: [thisSnapPoint, otherSnapPoint],
16083
+ offset: offsetX
16084
+ });
16085
+ minOffset.x = Math.abs(offsetX);
16086
+ }
16087
+ if (Math.abs(offsetY) <= minOffset.y) {
16088
+ if (Math.abs(offsetY) < minOffset.y) {
16089
+ nearestSnapsY.length = 0;
16090
+ }
16091
+ nearestSnapsY.push({
16092
+ type: "point",
16093
+ points: [thisSnapPoint, otherSnapPoint],
16094
+ offset: offsetY
16095
+ });
16096
+ minOffset.y = Math.abs(offsetY);
16097
+ }
16098
+ }
16199
16099
  }
16200
- );
16201
- };
16202
-
16203
- // laserTrails.ts
16204
- import { DEFAULT_LASER_COLOR, easeOut } from "@excalidraw/common";
16205
-
16206
- // animatedTrail.ts
16207
- import { LaserPointer } from "@excalidraw/laser-pointer";
16208
- import {
16209
- SVG_NS,
16210
- getSvgPathFromStroke as getSvgPathFromStroke2,
16211
- sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3
16212
- } from "@excalidraw/common";
16213
-
16214
- // reactUtils.ts
16215
- import { version as ReactVersion } from "react";
16216
- import { unstable_batchedUpdates } from "react-dom";
16217
- import { throttleRAF } from "@excalidraw/common";
16218
- var withBatchedUpdates = (func) => (event) => {
16219
- unstable_batchedUpdates(func, event);
16100
+ }
16220
16101
  };
16221
- var withBatchedUpdatesThrottled = (func) => {
16222
- return throttleRAF((event) => {
16223
- unstable_batchedUpdates(func, event);
16102
+ var snapDraggedElements = (elements, dragOffset, app, event, elementsMap) => {
16103
+ const appState = app.state;
16104
+ const selectedElements = getSelectedElements4(elements, appState);
16105
+ if (!isSnappingEnabled({ app, event, selectedElements }) || selectedElements.length === 0) {
16106
+ return {
16107
+ snapOffset: {
16108
+ x: 0,
16109
+ y: 0
16110
+ },
16111
+ snapLines: []
16112
+ };
16113
+ }
16114
+ dragOffset.x = round(dragOffset.x);
16115
+ dragOffset.y = round(dragOffset.y);
16116
+ const nearestSnapsX = [];
16117
+ const nearestSnapsY = [];
16118
+ const snapDistance = getSnapDistance(appState.zoom.value);
16119
+ const minOffset = {
16120
+ x: snapDistance,
16121
+ y: snapDistance
16122
+ };
16123
+ const selectionPoints = getElementsCorners(selectedElements, elementsMap, {
16124
+ dragOffset
16224
16125
  });
16126
+ getPointSnaps(
16127
+ selectedElements,
16128
+ selectionPoints,
16129
+ app,
16130
+ event,
16131
+ nearestSnapsX,
16132
+ nearestSnapsY,
16133
+ minOffset
16134
+ );
16135
+ getGapSnaps(
16136
+ selectedElements,
16137
+ dragOffset,
16138
+ app,
16139
+ event,
16140
+ nearestSnapsX,
16141
+ nearestSnapsY,
16142
+ minOffset
16143
+ );
16144
+ const snapOffset = {
16145
+ x: nearestSnapsX[0]?.offset ?? 0,
16146
+ y: nearestSnapsY[0]?.offset ?? 0
16147
+ };
16148
+ minOffset.x = 0;
16149
+ minOffset.y = 0;
16150
+ nearestSnapsX.length = 0;
16151
+ nearestSnapsY.length = 0;
16152
+ const newDragOffset = {
16153
+ x: round(dragOffset.x + snapOffset.x),
16154
+ y: round(dragOffset.y + snapOffset.y)
16155
+ };
16156
+ getPointSnaps(
16157
+ selectedElements,
16158
+ getElementsCorners(selectedElements, elementsMap, {
16159
+ dragOffset: newDragOffset
16160
+ }),
16161
+ app,
16162
+ event,
16163
+ nearestSnapsX,
16164
+ nearestSnapsY,
16165
+ minOffset
16166
+ );
16167
+ getGapSnaps(
16168
+ selectedElements,
16169
+ newDragOffset,
16170
+ app,
16171
+ event,
16172
+ nearestSnapsX,
16173
+ nearestSnapsY,
16174
+ minOffset
16175
+ );
16176
+ const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
16177
+ const gapSnapLines = createGapSnapLines(
16178
+ selectedElements,
16179
+ newDragOffset,
16180
+ [...nearestSnapsX, ...nearestSnapsY].filter(
16181
+ (snap) => snap.type === "gap"
16182
+ )
16183
+ );
16184
+ return {
16185
+ snapOffset,
16186
+ snapLines: [...pointSnapLines, ...gapSnapLines]
16187
+ };
16225
16188
  };
16226
- var isRenderThrottlingEnabled = (() => {
16227
- let IS_REACT_18_AND_UP;
16228
- try {
16229
- const version = ReactVersion.split(".");
16230
- IS_REACT_18_AND_UP = Number(version[0]) > 17;
16231
- } catch {
16232
- IS_REACT_18_AND_UP = false;
16189
+ var round = (x) => {
16190
+ const decimalPlaces = 6;
16191
+ return Math.round(x * 10 ** decimalPlaces) / 10 ** decimalPlaces;
16192
+ };
16193
+ var dedupePoints = (points) => {
16194
+ const map = /* @__PURE__ */ new Map();
16195
+ for (const point of points) {
16196
+ const key = point.join(",");
16197
+ if (!map.has(key)) {
16198
+ map.set(key, point);
16199
+ }
16233
16200
  }
16234
- let hasWarned = false;
16235
- return () => {
16236
- if (window.EXCALIDRAW_THROTTLE_RENDER === true) {
16237
- if (!IS_REACT_18_AND_UP) {
16238
- if (!hasWarned) {
16239
- hasWarned = true;
16240
- console.warn(
16241
- "Excalidraw: render throttling is disabled on React versions < 18."
16242
- );
16201
+ return Array.from(map.values());
16202
+ };
16203
+ var createPointSnapLines = (nearestSnapsX, nearestSnapsY) => {
16204
+ const snapsX = {};
16205
+ const snapsY = {};
16206
+ if (nearestSnapsX.length > 0) {
16207
+ for (const snap of nearestSnapsX) {
16208
+ if (snap.type === "point") {
16209
+ const key = round(snap.points[0][0]);
16210
+ if (!snapsX[key]) {
16211
+ snapsX[key] = [];
16243
16212
  }
16244
- return false;
16213
+ snapsX[key].push(
16214
+ ...snap.points.map(
16215
+ (p) => pointFrom18(round(p[0]), round(p[1]))
16216
+ )
16217
+ );
16245
16218
  }
16246
- return true;
16247
- }
16248
- return false;
16249
- };
16250
- })();
16251
-
16252
- // renderer/animation.ts
16253
- var _AnimationController = class _AnimationController {
16254
- static start(key, animation) {
16255
- if (_AnimationController.animations.has(key)) {
16256
- return;
16257
- }
16258
- const initialState = animation({
16259
- deltaTime: 0,
16260
- state: void 0
16261
- });
16262
- if (initialState) {
16263
- _AnimationController.animations.set(key, {
16264
- animation,
16265
- lastTime: 0,
16266
- state: initialState
16267
- });
16268
- _AnimationController.scheduleNextFrame();
16269
- }
16270
- }
16271
- static scheduleNextFrame() {
16272
- if (_AnimationController.scheduledFrame) {
16273
- return;
16274
- }
16275
- if (isRenderThrottlingEnabled()) {
16276
- _AnimationController.scheduledFrame = {
16277
- id: requestAnimationFrame(_AnimationController.tick),
16278
- type: "raf"
16279
- };
16280
- } else {
16281
- _AnimationController.scheduledFrame = {
16282
- id: setTimeout(_AnimationController.tick, 0),
16283
- type: "timeout"
16284
- };
16285
16219
  }
16286
16220
  }
16287
- static cancelScheduledFrame() {
16288
- if (!_AnimationController.scheduledFrame) {
16289
- return;
16290
- }
16291
- if (_AnimationController.scheduledFrame.type === "raf") {
16292
- cancelAnimationFrame(_AnimationController.scheduledFrame.id);
16293
- } else {
16294
- clearTimeout(_AnimationController.scheduledFrame.id);
16221
+ if (nearestSnapsY.length > 0) {
16222
+ for (const snap of nearestSnapsY) {
16223
+ if (snap.type === "point") {
16224
+ const key = round(snap.points[0][1]);
16225
+ if (!snapsY[key]) {
16226
+ snapsY[key] = [];
16227
+ }
16228
+ snapsY[key].push(
16229
+ ...snap.points.map(
16230
+ (p) => pointFrom18(round(p[0]), round(p[1]))
16231
+ )
16232
+ );
16233
+ }
16295
16234
  }
16296
- _AnimationController.scheduledFrame = null;
16297
16235
  }
16298
- static cancelScheduledFrameIfIdle() {
16299
- if (_AnimationController.animations.size > 0) {
16300
- return false;
16236
+ return Object.entries(snapsX).map(([key, points]) => {
16237
+ return {
16238
+ type: "points",
16239
+ points: dedupePoints(
16240
+ points.map((p) => {
16241
+ return pointFrom18(Number(key), p[1]);
16242
+ }).sort((a, b) => a[1] - b[1])
16243
+ )
16244
+ };
16245
+ }).concat(
16246
+ Object.entries(snapsY).map(([key, points]) => {
16247
+ return {
16248
+ type: "points",
16249
+ points: dedupePoints(
16250
+ points.map((p) => {
16251
+ return pointFrom18(p[0], Number(key));
16252
+ }).sort((a, b) => a[0] - b[0])
16253
+ )
16254
+ };
16255
+ })
16256
+ );
16257
+ };
16258
+ var dedupeGapSnapLines = (gapSnapLines) => {
16259
+ const map = /* @__PURE__ */ new Map();
16260
+ for (const gapSnapLine of gapSnapLines) {
16261
+ const key = gapSnapLine.points.flat().map((point) => [round(point)]).join(",");
16262
+ if (!map.has(key)) {
16263
+ map.set(key, gapSnapLine);
16301
16264
  }
16302
- _AnimationController.cancelScheduledFrame();
16303
- return true;
16304
16265
  }
16305
- static tick() {
16306
- _AnimationController.scheduledFrame = null;
16307
- if (_AnimationController.animations.size > 0) {
16308
- for (const [key, animation] of _AnimationController.animations) {
16309
- const now = performance.now();
16310
- const deltaTime = animation.lastTime === 0 ? 0 : now - animation.lastTime;
16311
- const state = animation.animation({
16312
- deltaTime,
16313
- state: animation.state
16314
- });
16315
- if (!state) {
16316
- _AnimationController.animations.delete(key);
16317
- if (_AnimationController.cancelScheduledFrameIfIdle()) {
16318
- return;
16319
- }
16320
- } else {
16321
- animation.lastTime = now;
16322
- animation.state = state;
16266
+ return Array.from(map.values());
16267
+ };
16268
+ var createGapSnapLines = (selectedElements, dragOffset, gapSnaps) => {
16269
+ const [minX, minY, maxX, maxY] = getDraggedElementsBounds(
16270
+ selectedElements,
16271
+ dragOffset
16272
+ );
16273
+ const gapSnapLines = [];
16274
+ for (const gapSnap of gapSnaps) {
16275
+ const [startMinX, startMinY, startMaxX, startMaxY] = gapSnap.gap.startBounds;
16276
+ const [endMinX, endMinY, endMaxX, endMaxY] = gapSnap.gap.endBounds;
16277
+ const verticalIntersection = rangeIntersection(
16278
+ rangeInclusive(minY, maxY),
16279
+ gapSnap.gap.overlap
16280
+ );
16281
+ const horizontalGapIntersection = rangeIntersection(
16282
+ rangeInclusive(minX, maxX),
16283
+ gapSnap.gap.overlap
16284
+ );
16285
+ switch (gapSnap.direction) {
16286
+ case "center_horizontal": {
16287
+ if (verticalIntersection) {
16288
+ const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
16289
+ gapSnapLines.push(
16290
+ {
16291
+ type: "gap",
16292
+ direction: "horizontal",
16293
+ points: [
16294
+ pointFrom18(gapSnap.gap.startSide[0][0], gapLineY),
16295
+ pointFrom18(minX, gapLineY)
16296
+ ]
16297
+ },
16298
+ {
16299
+ type: "gap",
16300
+ direction: "horizontal",
16301
+ points: [
16302
+ pointFrom18(maxX, gapLineY),
16303
+ pointFrom18(gapSnap.gap.endSide[0][0], gapLineY)
16304
+ ]
16305
+ }
16306
+ );
16307
+ }
16308
+ break;
16309
+ }
16310
+ case "center_vertical": {
16311
+ if (horizontalGapIntersection) {
16312
+ const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
16313
+ gapSnapLines.push(
16314
+ {
16315
+ type: "gap",
16316
+ direction: "vertical",
16317
+ points: [
16318
+ pointFrom18(gapLineX, gapSnap.gap.startSide[0][1]),
16319
+ pointFrom18(gapLineX, minY)
16320
+ ]
16321
+ },
16322
+ {
16323
+ type: "gap",
16324
+ direction: "vertical",
16325
+ points: [
16326
+ pointFrom18(gapLineX, maxY),
16327
+ pointFrom18(gapLineX, gapSnap.gap.endSide[0][1])
16328
+ ]
16329
+ }
16330
+ );
16331
+ }
16332
+ break;
16333
+ }
16334
+ case "side_right": {
16335
+ if (verticalIntersection) {
16336
+ const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
16337
+ gapSnapLines.push(
16338
+ {
16339
+ type: "gap",
16340
+ direction: "horizontal",
16341
+ points: [
16342
+ pointFrom18(startMaxX, gapLineY),
16343
+ pointFrom18(endMinX, gapLineY)
16344
+ ]
16345
+ },
16346
+ {
16347
+ type: "gap",
16348
+ direction: "horizontal",
16349
+ points: [pointFrom18(endMaxX, gapLineY), pointFrom18(minX, gapLineY)]
16350
+ }
16351
+ );
16352
+ }
16353
+ break;
16354
+ }
16355
+ case "side_left": {
16356
+ if (verticalIntersection) {
16357
+ const gapLineY = (verticalIntersection[0] + verticalIntersection[1]) / 2;
16358
+ gapSnapLines.push(
16359
+ {
16360
+ type: "gap",
16361
+ direction: "horizontal",
16362
+ points: [
16363
+ pointFrom18(maxX, gapLineY),
16364
+ pointFrom18(startMinX, gapLineY)
16365
+ ]
16366
+ },
16367
+ {
16368
+ type: "gap",
16369
+ direction: "horizontal",
16370
+ points: [
16371
+ pointFrom18(startMaxX, gapLineY),
16372
+ pointFrom18(endMinX, gapLineY)
16373
+ ]
16374
+ }
16375
+ );
16323
16376
  }
16377
+ break;
16324
16378
  }
16325
- if (_AnimationController.cancelScheduledFrameIfIdle()) {
16326
- return;
16379
+ case "side_top": {
16380
+ if (horizontalGapIntersection) {
16381
+ const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
16382
+ gapSnapLines.push(
16383
+ {
16384
+ type: "gap",
16385
+ direction: "vertical",
16386
+ points: [
16387
+ pointFrom18(gapLineX, maxY),
16388
+ pointFrom18(gapLineX, startMinY)
16389
+ ]
16390
+ },
16391
+ {
16392
+ type: "gap",
16393
+ direction: "vertical",
16394
+ points: [
16395
+ pointFrom18(gapLineX, startMaxY),
16396
+ pointFrom18(gapLineX, endMinY)
16397
+ ]
16398
+ }
16399
+ );
16400
+ }
16401
+ break;
16327
16402
  }
16328
- _AnimationController.scheduleNextFrame();
16329
- }
16330
- }
16331
- static running(key) {
16332
- return _AnimationController.animations.has(key);
16333
- }
16334
- static cancel(key) {
16335
- _AnimationController.animations.delete(key);
16336
- _AnimationController.cancelScheduledFrameIfIdle();
16337
- }
16338
- };
16339
- __publicField(_AnimationController, "scheduledFrame", null);
16340
- __publicField(_AnimationController, "animations", /* @__PURE__ */ new Map());
16341
- var AnimationController = _AnimationController;
16342
-
16343
- // animatedTrail.ts
16344
- var _AnimatedTrail = class _AnimatedTrail {
16345
- constructor(app, options) {
16346
- this.app = app;
16347
- this.options = options;
16348
- __publicField(this, "currentTrail");
16349
- __publicField(this, "pastTrails", []);
16350
- __publicField(this, "container");
16351
- __publicField(this, "trailElement");
16352
- __publicField(this, "trailAnimation");
16353
- __publicField(this, "key");
16354
- this.key = `animated-trail-${_AnimatedTrail.counter++}`;
16355
- this.trailElement = document.createElementNS(SVG_NS, "path");
16356
- if (this.options.animateTrail) {
16357
- this.trailAnimation = document.createElementNS(SVG_NS, "animate");
16358
- this.trailAnimation.setAttribute("attributeName", "stroke-dashoffset");
16359
- this.trailElement.setAttribute("stroke-dasharray", "7 7");
16360
- this.trailElement.setAttribute("stroke-dashoffset", "10");
16361
- this.trailAnimation.setAttribute("from", "0");
16362
- this.trailAnimation.setAttribute("to", `-14`);
16363
- this.trailAnimation.setAttribute("dur", "0.3s");
16364
- this.trailElement.appendChild(this.trailAnimation);
16365
- }
16366
- }
16367
- get hasCurrentTrail() {
16368
- return !!this.currentTrail;
16369
- }
16370
- hasLastPoint(x, y) {
16371
- if (this.currentTrail) {
16372
- const len = this.currentTrail.originalPoints.length;
16373
- return this.currentTrail.originalPoints[len - 1][0] === x && this.currentTrail.originalPoints[len - 1][1] === y;
16374
- }
16375
- return false;
16376
- }
16377
- cleanup() {
16378
- this.pastTrails = [];
16379
- this.currentTrail = void 0;
16380
- if (this.trailElement.parentNode === this.container) {
16381
- this.container?.removeChild(this.trailElement);
16382
- }
16383
- }
16384
- start(container) {
16385
- if (container) {
16386
- this.container = container;
16387
- }
16388
- if (this.trailElement.parentNode !== this.container && this.container) {
16389
- this.container.appendChild(this.trailElement);
16390
- }
16391
- if (!AnimationController.running(this.key)) {
16392
- AnimationController.start(this.key, () => {
16393
- const needsNext = this.onFrame();
16394
- if (needsNext) {
16395
- return { keep: true };
16403
+ case "side_bottom": {
16404
+ if (horizontalGapIntersection) {
16405
+ const gapLineX = (horizontalGapIntersection[0] + horizontalGapIntersection[1]) / 2;
16406
+ gapSnapLines.push(
16407
+ {
16408
+ type: "gap",
16409
+ direction: "vertical",
16410
+ points: [
16411
+ pointFrom18(gapLineX, startMaxY),
16412
+ pointFrom18(gapLineX, endMinY)
16413
+ ]
16414
+ },
16415
+ {
16416
+ type: "gap",
16417
+ direction: "vertical",
16418
+ points: [pointFrom18(gapLineX, endMaxY), pointFrom18(gapLineX, minY)]
16419
+ }
16420
+ );
16396
16421
  }
16397
- this.cleanup();
16398
- return null;
16399
- });
16422
+ break;
16423
+ }
16400
16424
  }
16401
16425
  }
16402
- stop() {
16403
- AnimationController.cancel(this.key);
16404
- this.cleanup();
16405
- }
16406
- startPath(x, y) {
16407
- this.currentTrail = new LaserPointer(this.options);
16408
- this.currentTrail.addPoint([x, y, performance.now()]);
16409
- this.update();
16410
- }
16411
- addPointToPath(x, y) {
16412
- if (this.currentTrail) {
16413
- this.currentTrail.addPoint([x, y, performance.now()]);
16414
- this.update();
16415
- }
16426
+ return dedupeGapSnapLines(
16427
+ gapSnapLines.map((gapSnapLine) => {
16428
+ return {
16429
+ ...gapSnapLine,
16430
+ points: gapSnapLine.points.map(
16431
+ (p) => pointFrom18(round(p[0]), round(p[1]))
16432
+ )
16433
+ };
16434
+ })
16435
+ );
16436
+ };
16437
+ var snapResizingElements = (selectedElements, selectedOriginalElements, app, event, dragOffset, transformHandle) => {
16438
+ if (!isSnappingEnabled({ event, selectedElements, app }) || selectedElements.length === 0 || selectedElements.length === 1 && !areRoughlyEqual(selectedElements[0].angle, 0)) {
16439
+ return {
16440
+ snapOffset: { x: 0, y: 0 },
16441
+ snapLines: []
16442
+ };
16416
16443
  }
16417
- endPath() {
16418
- if (this.currentTrail) {
16419
- this.currentTrail.close();
16420
- this.currentTrail.options.keepHead = false;
16421
- this.pastTrails.push(this.currentTrail);
16422
- this.currentTrail = void 0;
16423
- this.update();
16444
+ let [minX, minY, maxX, maxY] = getCommonBounds5(selectedOriginalElements);
16445
+ if (transformHandle) {
16446
+ if (transformHandle.includes("e")) {
16447
+ maxX += dragOffset.x;
16448
+ } else if (transformHandle.includes("w")) {
16449
+ minX += dragOffset.x;
16424
16450
  }
16425
- }
16426
- getCurrentTrail() {
16427
- return this.currentTrail;
16428
- }
16429
- clearTrails() {
16430
- this.pastTrails = [];
16431
- this.currentTrail = void 0;
16432
- this.update();
16433
- }
16434
- update() {
16435
- this.start();
16436
- if (this.trailAnimation) {
16437
- this.trailAnimation.setAttribute("begin", "indefinite");
16438
- this.trailAnimation.setAttribute("repeatCount", "indefinite");
16451
+ if (transformHandle.includes("n")) {
16452
+ minY += dragOffset.y;
16453
+ } else if (transformHandle.includes("s")) {
16454
+ maxY += dragOffset.y;
16439
16455
  }
16440
16456
  }
16441
- onFrame() {
16442
- const paths = [];
16443
- for (const trail of this.pastTrails) {
16444
- paths.push(this.drawTrail(trail, this.app.state));
16445
- }
16446
- if (this.currentTrail) {
16447
- const currentPath = this.drawTrail(this.currentTrail, this.app.state);
16448
- paths.push(currentPath);
16449
- }
16450
- this.pastTrails = this.pastTrails.filter(
16451
- (t2) => t2.getStrokeOutline(t2.options.size / this.app.state.zoom.value).length !== 0
16452
- );
16453
- if (paths.length === 0) {
16454
- this.trailElement.setAttribute("d", "");
16455
- return false;
16456
- }
16457
- const svgPaths = paths.join(" ").trim();
16458
- this.trailElement.setAttribute("d", svgPaths);
16459
- if (this.trailAnimation) {
16460
- this.trailElement.setAttribute(
16461
- "fill",
16462
- (this.options.fill ?? (() => "black"))(this)
16463
- );
16464
- this.trailElement.setAttribute(
16465
- "stroke",
16466
- (this.options.stroke ?? (() => "black"))(this)
16467
- );
16468
- } else {
16469
- this.trailElement.setAttribute(
16470
- "fill",
16471
- (this.options.fill ?? (() => "black"))(this)
16472
- );
16457
+ const selectionSnapPoints = [];
16458
+ if (transformHandle) {
16459
+ switch (transformHandle) {
16460
+ case "e": {
16461
+ selectionSnapPoints.push(pointFrom18(maxX, minY), pointFrom18(maxX, maxY));
16462
+ break;
16463
+ }
16464
+ case "w": {
16465
+ selectionSnapPoints.push(pointFrom18(minX, minY), pointFrom18(minX, maxY));
16466
+ break;
16467
+ }
16468
+ case "n": {
16469
+ selectionSnapPoints.push(pointFrom18(minX, minY), pointFrom18(maxX, minY));
16470
+ break;
16471
+ }
16472
+ case "s": {
16473
+ selectionSnapPoints.push(pointFrom18(minX, maxY), pointFrom18(maxX, maxY));
16474
+ break;
16475
+ }
16476
+ case "ne": {
16477
+ selectionSnapPoints.push(pointFrom18(maxX, minY));
16478
+ break;
16479
+ }
16480
+ case "nw": {
16481
+ selectionSnapPoints.push(pointFrom18(minX, minY));
16482
+ break;
16483
+ }
16484
+ case "se": {
16485
+ selectionSnapPoints.push(pointFrom18(maxX, maxY));
16486
+ break;
16487
+ }
16488
+ case "sw": {
16489
+ selectionSnapPoints.push(pointFrom18(minX, maxY));
16490
+ break;
16491
+ }
16473
16492
  }
16474
- return true;
16475
- }
16476
- drawTrail(trail, state) {
16477
- const _stroke = trail.getStrokeOutline(trail.options.size / state.zoom.value).map(([x, y]) => {
16478
- const result = sceneCoordsToViewportCoords3(
16479
- { sceneX: x, sceneY: y },
16480
- state
16481
- );
16482
- return [result.x, result.y];
16483
- });
16484
- const stroke = this.trailAnimation ? _stroke.slice(0, _stroke.length / 2) : _stroke;
16485
- return getSvgPathFromStroke2(stroke, true);
16486
16493
  }
16494
+ const snapDistance = getSnapDistance(app.state.zoom.value);
16495
+ const minOffset = {
16496
+ x: snapDistance,
16497
+ y: snapDistance
16498
+ };
16499
+ const nearestSnapsX = [];
16500
+ const nearestSnapsY = [];
16501
+ getPointSnaps(
16502
+ selectedOriginalElements,
16503
+ selectionSnapPoints,
16504
+ app,
16505
+ event,
16506
+ nearestSnapsX,
16507
+ nearestSnapsY,
16508
+ minOffset
16509
+ );
16510
+ const snapOffset = {
16511
+ x: nearestSnapsX[0]?.offset ?? 0,
16512
+ y: nearestSnapsY[0]?.offset ?? 0
16513
+ };
16514
+ minOffset.x = 0;
16515
+ minOffset.y = 0;
16516
+ nearestSnapsX.length = 0;
16517
+ nearestSnapsY.length = 0;
16518
+ const [x1, y1, x2, y2] = getCommonBounds5(selectedElements).map(
16519
+ (bound) => round(bound)
16520
+ );
16521
+ const corners = [
16522
+ pointFrom18(x1, y1),
16523
+ pointFrom18(x1, y2),
16524
+ pointFrom18(x2, y1),
16525
+ pointFrom18(x2, y2)
16526
+ ];
16527
+ getPointSnaps(
16528
+ selectedElements,
16529
+ corners,
16530
+ app,
16531
+ event,
16532
+ nearestSnapsX,
16533
+ nearestSnapsY,
16534
+ minOffset
16535
+ );
16536
+ const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
16537
+ return {
16538
+ snapOffset,
16539
+ snapLines: pointSnapLines
16540
+ };
16487
16541
  };
16488
- __publicField(_AnimatedTrail, "counter", 0);
16489
- var AnimatedTrail = _AnimatedTrail;
16490
-
16491
- // laserTrails.ts
16492
- var LaserTrails = class {
16493
- constructor(app) {
16494
- this.app = app;
16495
- __publicField(this, "localTrail");
16496
- __publicField(this, "collabTrails", /* @__PURE__ */ new Map());
16497
- __publicField(this, "container");
16498
- this.localTrail = new AnimatedTrail(app, {
16499
- ...this.getTrailOptions(),
16500
- fill: () => DEFAULT_LASER_COLOR
16501
- });
16502
- }
16503
- getTrailOptions() {
16542
+ var snapNewElement = (newElement7, app, event, origin, dragOffset, elementsMap) => {
16543
+ if (!isSnappingEnabled({ event, selectedElements: [newElement7], app })) {
16504
16544
  return {
16505
- simplify: 0,
16506
- streamline: 0.4,
16507
- sizeMapping: (c) => {
16508
- const DECAY_TIME = 1e3;
16509
- const DECAY_LENGTH = 50;
16510
- const t2 = Math.max(
16511
- 0,
16512
- 1 - (performance.now() - c.pressure) / DECAY_TIME
16513
- );
16514
- const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
16515
- return Math.min(easeOut(l), easeOut(t2));
16516
- }
16545
+ snapOffset: { x: 0, y: 0 },
16546
+ snapLines: []
16517
16547
  };
16518
16548
  }
16519
- startPath(x, y) {
16520
- this.localTrail.startPath(x, y);
16521
- }
16522
- addPointToPath(x, y) {
16523
- this.localTrail.addPointToPath(x, y);
16524
- }
16525
- endPath() {
16526
- this.localTrail.endPath();
16527
- }
16528
- start(container) {
16529
- this.container = container;
16530
- this.localTrail.start(container);
16531
- }
16532
- stop() {
16533
- this.localTrail.stop();
16534
- this.stopCollabTrails();
16535
- this.container = void 0;
16536
- }
16537
- stopCollabTrails(collaborators) {
16538
- for (const [key, trail] of this.collabTrails) {
16539
- const collaborator = collaborators?.get(key);
16540
- if (!collaborator) {
16541
- trail.stop();
16542
- this.collabTrails.delete(key);
16543
- }
16544
- }
16549
+ const selectionSnapPoints = [
16550
+ pointFrom18(origin.x + dragOffset.x, origin.y + dragOffset.y)
16551
+ ];
16552
+ const snapDistance = getSnapDistance(app.state.zoom.value);
16553
+ const minOffset = {
16554
+ x: snapDistance,
16555
+ y: snapDistance
16556
+ };
16557
+ const nearestSnapsX = [];
16558
+ const nearestSnapsY = [];
16559
+ getPointSnaps(
16560
+ [newElement7],
16561
+ selectionSnapPoints,
16562
+ app,
16563
+ event,
16564
+ nearestSnapsX,
16565
+ nearestSnapsY,
16566
+ minOffset
16567
+ );
16568
+ const snapOffset = {
16569
+ x: nearestSnapsX[0]?.offset ?? 0,
16570
+ y: nearestSnapsY[0]?.offset ?? 0
16571
+ };
16572
+ minOffset.x = 0;
16573
+ minOffset.y = 0;
16574
+ nearestSnapsX.length = 0;
16575
+ nearestSnapsY.length = 0;
16576
+ const corners = getElementsCorners([newElement7], elementsMap, {
16577
+ boundingBoxCorners: true,
16578
+ omitCenter: true
16579
+ });
16580
+ getPointSnaps(
16581
+ [newElement7],
16582
+ corners,
16583
+ app,
16584
+ event,
16585
+ nearestSnapsX,
16586
+ nearestSnapsY,
16587
+ minOffset
16588
+ );
16589
+ const pointSnapLines = createPointSnapLines(nearestSnapsX, nearestSnapsY);
16590
+ return {
16591
+ snapOffset,
16592
+ snapLines: pointSnapLines
16593
+ };
16594
+ };
16595
+ var getSnapLinesAtPointer = (elements, app, pointer, event, elementsMap) => {
16596
+ if (!isSnappingEnabled({ event, selectedElements: [], app })) {
16597
+ return {
16598
+ originOffset: { x: 0, y: 0 },
16599
+ snapLines: []
16600
+ };
16545
16601
  }
16546
- updateCollabTrails(collaborators) {
16547
- this.stopCollabTrails(collaborators);
16548
- if (!this.container || collaborators.size === 0) {
16549
- return;
16550
- }
16551
- for (const [key, collaborator] of collaborators.entries()) {
16552
- if (collaborator.isCurrentUser) {
16553
- continue;
16554
- }
16555
- let trail = this.collabTrails.get(key);
16556
- if (!trail) {
16557
- trail = new AnimatedTrail(this.app, {
16558
- ...this.getTrailOptions(),
16559
- fill: () => collaborator.pointer?.laserColor || getClientColor(key, collaborator)
16602
+ const referenceElements = getVisibleAndNonSelectedElements(
16603
+ elements,
16604
+ [],
16605
+ app.state,
16606
+ elementsMap
16607
+ );
16608
+ const snapDistance = getSnapDistance(app.state.zoom.value);
16609
+ const minOffset = {
16610
+ x: snapDistance,
16611
+ y: snapDistance
16612
+ };
16613
+ const horizontalSnapLines = [];
16614
+ const verticalSnapLines = [];
16615
+ for (const referenceElement of referenceElements) {
16616
+ const corners = getElementsCorners([referenceElement], elementsMap);
16617
+ for (const corner of corners) {
16618
+ const offsetX = corner[0] - pointer.x;
16619
+ if (Math.abs(offsetX) <= Math.abs(minOffset.x)) {
16620
+ if (Math.abs(offsetX) < Math.abs(minOffset.x)) {
16621
+ verticalSnapLines.length = 0;
16622
+ }
16623
+ verticalSnapLines.push({
16624
+ type: "pointer",
16625
+ points: [corner, pointFrom18(corner[0], pointer.y)],
16626
+ direction: "vertical"
16560
16627
  });
16561
- trail.start(this.container);
16562
- this.collabTrails.set(key, trail);
16628
+ minOffset.x = offsetX;
16563
16629
  }
16564
- if (collaborator.pointer && collaborator.pointer.tool === "laser") {
16565
- const buttonDown = collaborator.button === "down";
16566
- const buttonUp = collaborator.button === "up";
16567
- const hasTrail = trail.hasCurrentTrail;
16568
- if (buttonDown && !hasTrail) {
16569
- trail.startPath(collaborator.pointer.x, collaborator.pointer.y);
16570
- }
16571
- const lastPointOriginal = !trail.hasLastPoint(
16572
- collaborator.pointer.x,
16573
- collaborator.pointer.y
16574
- );
16575
- if (buttonDown && lastPointOriginal) {
16576
- trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
16577
- }
16578
- if (buttonUp && hasTrail) {
16579
- trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
16580
- trail.endPath();
16630
+ const offsetY = corner[1] - pointer.y;
16631
+ if (Math.abs(offsetY) <= Math.abs(minOffset.y)) {
16632
+ if (Math.abs(offsetY) < Math.abs(minOffset.y)) {
16633
+ horizontalSnapLines.length = 0;
16581
16634
  }
16635
+ horizontalSnapLines.push({
16636
+ type: "pointer",
16637
+ points: [corner, pointFrom18(pointer.x, corner[1])],
16638
+ direction: "horizontal"
16639
+ });
16640
+ minOffset.y = offsetY;
16582
16641
  }
16583
16642
  }
16584
16643
  }
16644
+ return {
16645
+ originOffset: {
16646
+ x: verticalSnapLines.length > 0 ? verticalSnapLines[0].points[0][0] - pointer.x : 0,
16647
+ y: horizontalSnapLines.length > 0 ? horizontalSnapLines[0].points[0][1] - pointer.y : 0
16648
+ },
16649
+ snapLines: [...verticalSnapLines, ...horizontalSnapLines]
16650
+ };
16651
+ };
16652
+ var isActiveToolNonLinearSnappable = (activeToolType) => {
16653
+ return activeToolType === TOOL_TYPE.rectangle || activeToolType === TOOL_TYPE.ellipse || activeToolType === TOOL_TYPE.diamond || activeToolType === TOOL_TYPE.frame || activeToolType === TOOL_TYPE.magicframe || activeToolType === TOOL_TYPE.image || activeToolType === TOOL_TYPE.text;
16585
16654
  };
16586
16655
 
16587
16656
  // textAutoResizeHandle.ts
@@ -17359,75 +17428,6 @@ var textWysiwyg = ({
17359
17428
  return handleSubmit;
17360
17429
  };
17361
17430
 
17362
- // scene/scrollbars.ts
17363
- import { getGlobalCSSVariable } from "@excalidraw/common";
17364
- import { getCommonBounds as getCommonBounds5 } from "@excalidraw/element";
17365
- var SCROLLBAR_MARGIN = 4;
17366
- var SCROLLBAR_WIDTH = 6;
17367
- var SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
17368
- var getScrollBars = (elements, viewportWidth, viewportHeight, appState) => {
17369
- if (!elements.size) {
17370
- return {
17371
- horizontal: null,
17372
- vertical: null
17373
- };
17374
- }
17375
- const [elementsMinX, elementsMinY, elementsMaxX, elementsMaxY] = getCommonBounds5(elements);
17376
- const viewportWidthWithZoom = viewportWidth / appState.zoom.value;
17377
- const viewportHeightWithZoom = viewportHeight / appState.zoom.value;
17378
- const safeArea = {
17379
- top: parseInt(getGlobalCSSVariable("sat")) || 0,
17380
- bottom: parseInt(getGlobalCSSVariable("sab")) || 0,
17381
- left: parseInt(getGlobalCSSVariable("sal")) || 0,
17382
- right: parseInt(getGlobalCSSVariable("sar")) || 0
17383
- };
17384
- const isRTL3 = getLanguage().rtl;
17385
- const viewportMinX = -appState.scrollX + safeArea.left;
17386
- const viewportMinY = -appState.scrollY + safeArea.top;
17387
- const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right;
17388
- const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom;
17389
- const sceneMinX = Math.min(elementsMinX, viewportMinX);
17390
- const sceneMinY = Math.min(elementsMinY, viewportMinY);
17391
- const sceneMaxX = Math.max(elementsMaxX, viewportMaxX);
17392
- const sceneMaxY = Math.max(elementsMaxY, viewportMaxY);
17393
- const sceneWidth = elementsMaxX - elementsMinX;
17394
- const sceneHeight = elementsMaxY - elementsMinY;
17395
- const extendedSceneWidth = sceneMaxX - sceneMinX;
17396
- const extendedSceneHeight = sceneMaxY - sceneMinY;
17397
- const scrollWidthOffset = Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right) + SCROLLBAR_WIDTH * 2;
17398
- const scrollbarWidth = viewportWidth * (viewportWidthWithZoom / extendedSceneWidth) - scrollWidthOffset;
17399
- const scrollbarHeightOffset = Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom) + SCROLLBAR_WIDTH * 2;
17400
- const scrollbarHeight = viewportHeight * (viewportHeightWithZoom / extendedSceneHeight) - scrollbarHeightOffset;
17401
- const horizontalDeltaMultiplier = extendedSceneWidth > sceneWidth ? extendedSceneWidth * appState.zoom.value / (scrollbarWidth + scrollWidthOffset) : viewportWidth / (scrollbarWidth + scrollWidthOffset);
17402
- const verticalDeltaMultiplier = extendedSceneHeight > sceneHeight ? extendedSceneHeight * appState.zoom.value / (scrollbarHeight + scrollbarHeightOffset) : viewportHeight / (scrollbarHeight + scrollbarHeightOffset);
17403
- return {
17404
- horizontal: viewportMinX === sceneMinX && viewportMaxX === sceneMaxX ? null : {
17405
- x: Math.max(safeArea.left, SCROLLBAR_MARGIN) + SCROLLBAR_WIDTH + (viewportMinX - sceneMinX) / extendedSceneWidth * viewportWidth,
17406
- y: viewportHeight - SCROLLBAR_WIDTH - Math.max(SCROLLBAR_MARGIN, safeArea.bottom),
17407
- width: scrollbarWidth,
17408
- height: SCROLLBAR_WIDTH,
17409
- deltaMultiplier: horizontalDeltaMultiplier
17410
- },
17411
- vertical: viewportMinY === sceneMinY && viewportMaxY === sceneMaxY ? null : {
17412
- x: isRTL3 ? Math.max(safeArea.left, SCROLLBAR_MARGIN) : viewportWidth - SCROLLBAR_WIDTH - Math.max(safeArea.right, SCROLLBAR_MARGIN),
17413
- y: Math.max(safeArea.top, SCROLLBAR_MARGIN) + SCROLLBAR_WIDTH + (viewportMinY - sceneMinY) / extendedSceneHeight * viewportHeight,
17414
- width: SCROLLBAR_WIDTH,
17415
- height: scrollbarHeight,
17416
- deltaMultiplier: verticalDeltaMultiplier
17417
- }
17418
- };
17419
- };
17420
- var isOverScrollBars = (scrollBars, x, y) => {
17421
- const [isOverHorizontal, isOverVertical] = [
17422
- scrollBars.horizontal,
17423
- scrollBars.vertical
17424
- ].map((scrollBar) => {
17425
- return scrollBar != null && scrollBar.x <= x && x <= scrollBar.x + scrollBar.width && scrollBar.y <= y && y <= scrollBar.y + scrollBar.height;
17426
- });
17427
- const isOverEither = isOverHorizontal || isOverVertical;
17428
- return { isOverEither, isOverHorizontal, isOverVertical };
17429
- };
17430
-
17431
17431
  // lasso/index.ts
17432
17432
  import {
17433
17433
  pointFrom as pointFrom21
@@ -19586,6 +19586,122 @@ var getConvertibleType = (element) => {
19586
19586
  };
19587
19587
  var ConvertElementTypePopup_default = ConvertElementTypePopup;
19588
19588
 
19589
+ // components/AppStateObserver.ts
19590
+ var AppStateObserver = class {
19591
+ constructor(getState) {
19592
+ this.getState = getState;
19593
+ __publicField(this, "listeners", []);
19594
+ __publicField(this, "onStateChange", (propOrOpts, callback, opts) => {
19595
+ const {
19596
+ predicate,
19597
+ getValue,
19598
+ callback: stateChangeCallback,
19599
+ once,
19600
+ matchesImmediately
19601
+ } = this.normalize(propOrOpts, callback, opts);
19602
+ if (stateChangeCallback) {
19603
+ if (matchesImmediately) {
19604
+ queueMicrotask(() => {
19605
+ const state = this.getState();
19606
+ stateChangeCallback(getValue(state), state);
19607
+ });
19608
+ if (once) {
19609
+ return () => {
19610
+ };
19611
+ }
19612
+ }
19613
+ return this.subscribe({
19614
+ predicate,
19615
+ getValue,
19616
+ callback: stateChangeCallback,
19617
+ once
19618
+ });
19619
+ }
19620
+ if (matchesImmediately) {
19621
+ return Promise.resolve(getValue(this.getState()));
19622
+ }
19623
+ return new Promise((resolve) => {
19624
+ this.subscribe({
19625
+ predicate,
19626
+ getValue,
19627
+ callback: (value) => resolve(value),
19628
+ once: true
19629
+ });
19630
+ });
19631
+ });
19632
+ }
19633
+ isStateChangePredicateOptions(propOrOpts) {
19634
+ return typeof propOrOpts === "object" && !Array.isArray(propOrOpts) && "predicate" in propOrOpts;
19635
+ }
19636
+ subscribe(listener) {
19637
+ this.listeners.push(listener);
19638
+ return () => {
19639
+ this.listeners = this.listeners.filter(
19640
+ (existingListener) => existingListener !== listener
19641
+ );
19642
+ };
19643
+ }
19644
+ normalize(propOrOpts, callback, opts) {
19645
+ let predicate;
19646
+ let getValue;
19647
+ let normalizedCallback = callback;
19648
+ let once = opts?.once ?? false;
19649
+ let matchesImmediately = false;
19650
+ if (this.isStateChangePredicateOptions(propOrOpts)) {
19651
+ const {
19652
+ predicate: predicateFn,
19653
+ callback: callbackFromOpts,
19654
+ once: onceFromOpts
19655
+ } = propOrOpts;
19656
+ predicate = predicateFn;
19657
+ getValue = (appState) => appState;
19658
+ normalizedCallback = callbackFromOpts ? (_value, appState) => callbackFromOpts(appState) : void 0;
19659
+ once = onceFromOpts ?? false;
19660
+ matchesImmediately = predicateFn(this.getState());
19661
+ } else if (typeof propOrOpts === "function") {
19662
+ const selector = propOrOpts;
19663
+ predicate = (appState, prevState) => selector(appState) !== selector(prevState);
19664
+ getValue = (appState) => selector(appState);
19665
+ } else if (Array.isArray(propOrOpts)) {
19666
+ const keys = propOrOpts;
19667
+ predicate = (appState, prevState) => keys.some((key) => appState[key] !== prevState[key]);
19668
+ getValue = (appState) => appState;
19669
+ } else {
19670
+ const key = propOrOpts;
19671
+ predicate = (appState, prevState) => appState[key] !== prevState[key];
19672
+ getValue = (appState) => appState[key];
19673
+ }
19674
+ return {
19675
+ predicate,
19676
+ getValue,
19677
+ callback: normalizedCallback,
19678
+ once,
19679
+ matchesImmediately
19680
+ };
19681
+ }
19682
+ flush(prevState) {
19683
+ if (!this.listeners.length) {
19684
+ return;
19685
+ }
19686
+ const state = this.getState();
19687
+ const listenersToKeep = [];
19688
+ for (const listener of this.listeners) {
19689
+ if (listener.predicate(state, prevState)) {
19690
+ listener.callback(listener.getValue(state), state);
19691
+ if (!listener.once) {
19692
+ listenersToKeep.push(listener);
19693
+ }
19694
+ } else {
19695
+ listenersToKeep.push(listener);
19696
+ }
19697
+ }
19698
+ this.listeners = listenersToKeep;
19699
+ }
19700
+ clear() {
19701
+ this.listeners = [];
19702
+ }
19703
+ };
19704
+
19589
19705
  // components/Trans.tsx
19590
19706
  import React17 from "react";
19591
19707
  var SPLIT_REGEX = /({{[\w-]+}})|(<[\w-]+>)|(<\/[\w-]+>)/g;
@@ -31384,122 +31500,6 @@ var NewElementCanvas = (props) => {
31384
31500
  };
31385
31501
  var NewElementCanvas_default = NewElementCanvas;
31386
31502
 
31387
- // components/AppStateObserver.ts
31388
- var AppStateObserver = class {
31389
- constructor(getState) {
31390
- this.getState = getState;
31391
- __publicField(this, "listeners", []);
31392
- __publicField(this, "onStateChange", (propOrOpts, callback, opts) => {
31393
- const {
31394
- predicate,
31395
- getValue,
31396
- callback: stateChangeCallback,
31397
- once,
31398
- matchesImmediately
31399
- } = this.normalize(propOrOpts, callback, opts);
31400
- if (stateChangeCallback) {
31401
- if (matchesImmediately) {
31402
- queueMicrotask(() => {
31403
- const state = this.getState();
31404
- stateChangeCallback(getValue(state), state);
31405
- });
31406
- if (once) {
31407
- return () => {
31408
- };
31409
- }
31410
- }
31411
- return this.subscribe({
31412
- predicate,
31413
- getValue,
31414
- callback: stateChangeCallback,
31415
- once
31416
- });
31417
- }
31418
- if (matchesImmediately) {
31419
- return Promise.resolve(getValue(this.getState()));
31420
- }
31421
- return new Promise((resolve) => {
31422
- this.subscribe({
31423
- predicate,
31424
- getValue,
31425
- callback: (value) => resolve(value),
31426
- once: true
31427
- });
31428
- });
31429
- });
31430
- }
31431
- isStateChangePredicateOptions(propOrOpts) {
31432
- return typeof propOrOpts === "object" && !Array.isArray(propOrOpts) && "predicate" in propOrOpts;
31433
- }
31434
- subscribe(listener) {
31435
- this.listeners.push(listener);
31436
- return () => {
31437
- this.listeners = this.listeners.filter(
31438
- (existingListener) => existingListener !== listener
31439
- );
31440
- };
31441
- }
31442
- normalize(propOrOpts, callback, opts) {
31443
- let predicate;
31444
- let getValue;
31445
- let normalizedCallback = callback;
31446
- let once = opts?.once ?? false;
31447
- let matchesImmediately = false;
31448
- if (this.isStateChangePredicateOptions(propOrOpts)) {
31449
- const {
31450
- predicate: predicateFn,
31451
- callback: callbackFromOpts,
31452
- once: onceFromOpts
31453
- } = propOrOpts;
31454
- predicate = predicateFn;
31455
- getValue = (appState) => appState;
31456
- normalizedCallback = callbackFromOpts ? (_value, appState) => callbackFromOpts(appState) : void 0;
31457
- once = onceFromOpts ?? false;
31458
- matchesImmediately = predicateFn(this.getState());
31459
- } else if (typeof propOrOpts === "function") {
31460
- const selector = propOrOpts;
31461
- predicate = (appState, prevState) => selector(appState) !== selector(prevState);
31462
- getValue = (appState) => selector(appState);
31463
- } else if (Array.isArray(propOrOpts)) {
31464
- const keys = propOrOpts;
31465
- predicate = (appState, prevState) => keys.some((key) => appState[key] !== prevState[key]);
31466
- getValue = (appState) => appState;
31467
- } else {
31468
- const key = propOrOpts;
31469
- predicate = (appState, prevState) => appState[key] !== prevState[key];
31470
- getValue = (appState) => appState[key];
31471
- }
31472
- return {
31473
- predicate,
31474
- getValue,
31475
- callback: normalizedCallback,
31476
- once,
31477
- matchesImmediately
31478
- };
31479
- }
31480
- flush(prevState) {
31481
- if (!this.listeners.length) {
31482
- return;
31483
- }
31484
- const state = this.getState();
31485
- const listenersToKeep = [];
31486
- for (const listener of this.listeners) {
31487
- if (listener.predicate(state, prevState)) {
31488
- listener.callback(listener.getValue(state), state);
31489
- if (!listener.once) {
31490
- listenersToKeep.push(listener);
31491
- }
31492
- } else {
31493
- listenersToKeep.push(listener);
31494
- }
31495
- }
31496
- this.listeners = listenersToKeep;
31497
- }
31498
- clear() {
31499
- this.listeners = [];
31500
- }
31501
- };
31502
-
31503
31503
  // components/UnlockPopup.tsx
31504
31504
  import {
31505
31505
  getCommonBounds as getCommonBounds10,
@@ -31709,8 +31709,8 @@ var App = class _App extends React40.Component {
31709
31709
  __publicField(this, "missingPointerEventCleanupEmitter", new Emitter2());
31710
31710
  __publicField(this, "onRemoveEventListenersEmitter", new Emitter2());
31711
31711
  __publicField(this, "api");
31712
- __publicField(this, "addImageElementsToScene", async (imageFiles, sceneX, sceneY) => {
31713
- await this.insertImages(imageFiles, sceneX, sceneY);
31712
+ __publicField(this, "addImageElementsToScene", async (imageFiles, sceneX, sceneY, options) => {
31713
+ await this.insertImages(imageFiles, sceneX, sceneY, options);
31714
31714
  });
31715
31715
  __publicField(this, "updateEditorAtom", (atom2, ...args) => {
31716
31716
  const result = editorJotaiStore.set(atom2, ...args);
@@ -36238,7 +36238,8 @@ var App = class _App extends React40.Component {
36238
36238
  );
36239
36239
  }
36240
36240
  });
36241
- __publicField(this, "insertImages", async (imageFiles, sceneX, sceneY) => {
36241
+ __publicField(this, "insertImages", async (imageFiles, sceneX, sceneY, options) => {
36242
+ const waitFor = options?.waitFor ?? "inserted";
36242
36243
  const gridPadding = 50 / this.state.zoom.value;
36243
36244
  const placeholders = positionElementsOnGrid(
36244
36245
  imageFiles.map(() => this.newImagePlaceholder({ sceneX, sceneY })),
@@ -36281,8 +36282,17 @@ var App = class _App extends React40.Component {
36281
36282
  elements: nextElements,
36282
36283
  captureUpdate: CaptureUpdateAction41.IMMEDIATELY
36283
36284
  });
36284
- this.setState({}, () => {
36285
- this.actionManager.executeAction(actionFinalize);
36285
+ await new Promise((resolve) => {
36286
+ this.setState({}, () => {
36287
+ this.actionManager.executeAction(actionFinalize);
36288
+ if (waitFor === "painted") {
36289
+ requestAnimationFrame(() => {
36290
+ requestAnimationFrame(() => resolve());
36291
+ });
36292
+ return;
36293
+ }
36294
+ resolve();
36295
+ });
36286
36296
  });
36287
36297
  });
36288
36298
  __publicField(this, "handleAppOnDrop", async (event) => {