@myoc/excalidraw 0.19.513 → 0.19.514

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
@@ -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";
94
93
  import React40, { useContext as useContext3 } from "react";
95
94
  import { flushSync as flushSync2 } from "react-dom";
96
95
  import rough3 from "roughjs/bin/rough";
96
+ import { nanoid } from "nanoid";
97
97
  import {
98
98
  clamp as clamp8,
99
- pointDistance as pointDistance9,
100
99
  pointFrom as pointFrom34,
101
- pointRotateRads as pointRotateRads23,
100
+ pointDistance as pointDistance9,
102
101
  vector as vector3,
103
- vectorDot,
102
+ pointRotateRads as pointRotateRads23,
104
103
  vectorFromPoint as vectorFromPoint10,
105
- vectorNormalize as vectorNormalize5,
106
- vectorSubtract as vectorSubtract2
104
+ vectorSubtract as vectorSubtract2,
105
+ vectorDot,
106
+ vectorNormalize as vectorNormalize5
107
107
  } from "@excalidraw/math";
108
108
  import {
109
- addEventListener as addEventListener2,
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
109
  COLOR_PALETTE as COLOR_PALETTE6,
119
- createUserAgentDescriptor,
110
+ CODES as CODES11,
111
+ shouldResizeFromCenter,
112
+ shouldMaintainAspectRatio,
113
+ shouldRotateWithDiscreteAngle as shouldRotateWithDiscreteAngle3,
114
+ isArrowKey as isArrowKey2,
115
+ KEYS as KEYS50,
116
+ APP_NAME,
120
117
  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,
125
118
  DEFAULT_TRANSFORM_HANDLE_SPACING as DEFAULT_TRANSFORM_HANDLE_SPACING3,
126
119
  DEFAULT_VERTICAL_ALIGN,
127
- deriveStylesPanelMode as deriveStylesPanelMode2,
128
- distance as distance2,
129
- DOUBLE_TAP_POSITION_THRESHOLD,
130
120
  DRAGGING_THRESHOLD as DRAGGING_THRESHOLD3,
131
- easeOut as easeOut4,
132
- easeToValuesRAF,
133
121
  ELEMENT_SHIFT_TRANSLATE_AMOUNT,
134
122
  ELEMENT_TRANSLATE_AMOUNT,
135
- Emitter as Emitter2,
136
123
  EVENT as EVENT10,
137
124
  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,
145
125
  IMAGE_MIME_TYPES as IMAGE_MIME_TYPES2,
146
126
  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,
162
127
  LINE_CONFIRM_THRESHOLD as LINE_CONFIRM_THRESHOLD2,
163
- loadDesktopUIModePreference,
164
- matchKey as matchKey3,
165
128
  MIME_TYPES as MIME_TYPES7,
166
- MINIMUM_ARROW_SIZE,
167
129
  MQ_RIGHT_SIDEBAR_MIN_WIDTH,
168
- muteFSAbortError,
169
- normalizeEOL as normalizeEOL2,
170
- normalizeLink as normalizeLink3,
171
- oneOf,
172
130
  POINTER_BUTTON as POINTER_BUTTON2,
173
- POINTER_EVENTS,
174
- randomInteger as randomInteger4,
175
131
  ROUNDNESS as ROUNDNESS7,
176
- sceneCoordsToViewportCoords as sceneCoordsToViewportCoords7,
177
132
  SCROLL_TIMEOUT,
178
- setDesktopUIMode,
179
- shouldMaintainAspectRatio,
180
- shouldResizeFromCenter,
181
- shouldRotateWithDiscreteAngle as shouldRotateWithDiscreteAngle3,
182
- supportsResizeObserver as supportsResizeObserver2,
183
133
  TAP_TWICE_TIMEOUT,
184
134
  TEXT_TO_CENTER_SNAP_THRESHOLD,
185
135
  THEME as THEME17,
186
- TOOL_TYPE as TOOL_TYPE3,
187
136
  TOUCH_CTX_MENU_TIMEOUT,
137
+ VERTICAL_ALIGN as VERTICAL_ALIGN5,
138
+ YOUTUBE_STATES,
139
+ ZOOM_STEP as ZOOM_STEP2,
140
+ POINTER_EVENTS,
141
+ 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,
188
149
  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,
189
160
  tupleToCoors,
190
- updateActiveTool as updateActiveTool8,
191
- updateObject as updateObject2,
192
- updateStable,
193
- VERTICAL_ALIGN as VERTICAL_ALIGN5,
194
161
  viewportCoordsToSceneCoords as viewportCoordsToSceneCoords3,
195
162
  wrapEvent as wrapEvent2,
196
- YOUTUBE_STATES,
197
- ZOOM_STEP as ZOOM_STEP2
163
+ updateObject as updateObject2,
164
+ updateActiveTool as updateActiveTool8,
165
+ isTransparent as isTransparent6,
166
+ easeToValuesRAF,
167
+ muteFSAbortError,
168
+ isTestEnv as isTestEnv5,
169
+ isDevEnv as isDevEnv9,
170
+ easeOut as easeOut4,
171
+ 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
198
198
  } from "@excalidraw/common";
199
199
  import {
200
- updateImageCache as _updateImageCache,
201
- addElementsToFrame as addElementsToFrame2,
202
- bindOrUnbindBindingElement as bindOrUnbindBindingElement2,
203
- bindOrUnbindBindingElements as bindOrUnbindBindingElements2,
204
- calculateFixedPointForNonElbowArrowBinding as calculateFixedPointForNonElbowArrowBinding2,
205
- CaptureUpdateAction as CaptureUpdateAction41,
206
- convertToExcalidrawElements,
207
- createSrcDoc,
208
- cropElement,
209
- deepCopyElement as deepCopyElement4,
210
- doBoundsIntersect as doBoundsIntersect4,
211
- dragNewElement,
212
- dragSelectedElements,
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,
200
+ getObservedAppState,
226
201
  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
202
  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,
203
+ bindOrUnbindBindingElements as bindOrUnbindBindingElements2,
204
+ fixBindingsAfterDeletion as fixBindingsAfterDeletion2,
245
205
  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,
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,
220
+ deepCopyElement as deepCopyElement4,
221
+ duplicateElements as duplicateElements2,
267
222
  hasBoundTextElement as hasBoundTextElement9,
268
- hitElementBoundingBox as hitElementBoundingBox2,
269
- hitElementBoundingBoxOnly,
270
- hitElementBoundText,
271
- hitElementItself as hitElementItself3,
272
223
  isArrowElement as isArrowElement14,
273
- isBindableElement as isBindableElement3,
274
224
  isBindingElement as isBindingElement4,
275
225
  isBindingElementType,
276
- isBindingEnabled as isBindingEnabled2,
277
226
  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,
288
227
  isFrameLikeElement as isFrameLikeElement15,
289
- isIframeElement as isIframeElement2,
290
- isIframeLikeElement as isIframeLikeElement2,
291
228
  isImageElement as isImageElement9,
229
+ isEmbeddableElement as isEmbeddableElement4,
292
230
  isInitializedImageElement as isInitializedImageElement3,
293
- isInvisiblySmallElement as isInvisiblySmallElement3,
294
231
  isLinearElement as isLinearElement12,
295
232
  isLinearElementType as isLinearElementType2,
296
- isLineElement as isLineElement8,
233
+ isUsingAdaptiveRadius as isUsingAdaptiveRadius4,
234
+ isIframeElement as isIframeElement2,
235
+ isIframeLikeElement as isIframeLikeElement2,
297
236
  isMagicFrameElement as isMagicFrameElement2,
298
- isMeasureTextSupported,
299
- isNonDeletedElement,
300
- isPathALoop as isPathALoop3,
301
- isPointInElement as isPointInElement3,
302
- isSelectedViaGroup as isSelectedViaGroup2,
303
- isSimpleArrow,
304
237
  isTextBindableContainer as isTextBindableContainer3,
238
+ isElbowArrow as isElbowArrow10,
239
+ isFlowchartNodeElement as isFlowchartNodeElement2,
240
+ isBindableElement as isBindableElement3,
305
241
  isTextElement as isTextElement19,
306
- isUsingAdaptiveRadius as isUsingAdaptiveRadius4,
307
- isValidTextContainer,
308
- makeNextSelectedElementIds as makeNextSelectedElementIds3,
309
- maxBindingDistance_simple as maxBindingDistance_simple3,
310
- maybeHandleArrowPointlikeDrag,
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,
311
250
  maybeParseEmbedSrc,
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,
251
+ getEmbedLink as getEmbedLink2,
252
+ getInitializedImageElements,
325
253
  normalizeSVG,
326
- normalizeText as normalizeText2,
327
- parseElementLinkFromURL,
328
- positionElementsOnGrid,
254
+ updateImageCache as _updateImageCache,
255
+ getBoundTextElement as getBoundTextElement15,
256
+ getContainerCenter,
257
+ getContainerElement as getContainerElement5,
258
+ isValidTextContainer,
329
259
  redrawTextBoundingBox as redrawTextBoundingBox8,
330
- refreshTextDimensions,
331
- removeElementsFromFrame as removeElementsFromFrame2,
260
+ hasBoundingBox as hasBoundingBox2,
261
+ getCommonFrameId as getCommonFrameId2,
262
+ getFrameChildren as getFrameChildren6,
263
+ getFrameChildrenInsertionIndex as getFrameChildrenInsertionIndex2,
264
+ isCursorInFrame,
265
+ addElementsToFrame as addElementsToFrame2,
332
266
  replaceAllElementsInFrame as replaceAllElementsInFrame4,
333
- Scene,
334
- selectGroupsForSelectedElements as selectGroupsForSelectedElements7,
267
+ 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,
335
295
  ShapeCache as ShapeCache4,
336
- Store,
337
- StoreDelta as StoreDelta2,
296
+ getRenderOpacity,
297
+ editGroupForSelectedElement,
298
+ getElementsInGroup as getElementsInGroup10,
299
+ getSelectedGroupIdForElement,
300
+ getSelectedGroupIds as getSelectedGroupIds4,
301
+ isElementInGroup as isElementInGroup2,
302
+ isSelectedViaGroup as isSelectedViaGroup2,
303
+ selectGroupsForSelectedElements as selectGroupsForSelectedElements7,
338
304
  syncInvalidIndices,
339
305
  syncMovedIndices as syncMovedIndices5,
306
+ excludeElementsInFramesFromSelection,
307
+ getSelectionStateForElements as getSelectionStateForElements2,
308
+ makeNextSelectedElementIds as makeNextSelectedElementIds3,
309
+ getResizeOffsetXY,
310
+ getResizeArrowDirection,
340
311
  transformElements,
341
- updateBoundElements as updateBoundElements4,
342
- updateFrameMembershipOfSelectedElements as updateFrameMembershipOfSelectedElements6,
343
- wrapText as wrapText5
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
344
344
  } from "@excalidraw/element";
345
345
  import { LinearElementEditor as LinearElementEditor11 } from "@excalidraw/element/linearElementEditor";
346
346
  import { findShapeByKey } from "@excalidraw/element/shapes";
@@ -15025,428 +15025,980 @@ 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
- // 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
15038
- );
15039
- const x = viewportX - appState.offsetLeft + 10;
15040
- const y = viewportY - appState.offsetTop;
15041
- return { x, y };
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;
15042
15052
  };
15043
- var ElementCanvasButtons = ({
15044
- children,
15045
- element,
15046
- elementsMap
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
15047
15079
  }) => {
15048
- const appState = useExcalidrawAppState();
15049
- if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
15050
- return null;
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));
15051
15083
  }
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
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;
15064
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
+ )
15065
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
+ };
15066
15272
  };
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);
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
+ }
15085
15402
  };
15086
- var withBatchedUpdatesThrottled = (func) => {
15087
- return throttleRAF((event) => {
15088
- unstable_batchedUpdates(func, event);
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
15089
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
+ };
15090
15536
  };
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;
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
+ }
15098
15548
  }
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."
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
+ })
15604
+ );
15605
+ };
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
+ }
15613
+ }
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
+ }
15107
15723
  );
15108
15724
  }
15109
- return false;
15725
+ break;
15110
15726
  }
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();
15134
- }
15135
- }
15136
- static scheduleNextFrame() {
15137
- if (_AnimationController.scheduledFrame) {
15138
- return;
15139
- }
15140
- if (isRenderThrottlingEnabled()) {
15141
- _AnimationController.scheduledFrame = {
15142
- id: requestAnimationFrame(_AnimationController.tick),
15143
- type: "raf"
15144
- };
15145
- } else {
15146
- _AnimationController.scheduledFrame = {
15147
- id: setTimeout(_AnimationController.tick, 0),
15148
- type: "timeout"
15149
- };
15150
- }
15151
- }
15152
- static cancelScheduledFrame() {
15153
- if (!_AnimationController.scheduledFrame) {
15154
- return;
15155
- }
15156
- if (_AnimationController.scheduledFrame.type === "raf") {
15157
- cancelAnimationFrame(_AnimationController.scheduledFrame.id);
15158
- } else {
15159
- clearTimeout(_AnimationController.scheduledFrame.id);
15160
- }
15161
- _AnimationController.scheduledFrame = null;
15162
- }
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;
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
+ );
15188
15748
  }
15749
+ break;
15189
15750
  }
15190
- if (_AnimationController.cancelScheduledFrameIfIdle()) {
15191
- return;
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
+ }
15768
+ );
15769
+ }
15770
+ break;
15192
15771
  }
15193
- _AnimationController.scheduleNextFrame();
15194
15772
  }
15195
15773
  }
15196
- static running(key) {
15197
- return _AnimationController.animations.has(key);
15198
- }
15199
- static cancel(key) {
15200
- _AnimationController.animations.delete(key);
15201
- _AnimationController.cancelScheduledFrameIfIdle();
15202
- }
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
+ )
15781
+ };
15782
+ })
15783
+ );
15203
15784
  };
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
- }
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
+ };
15281
15791
  }
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();
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;
15289
15798
  }
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");
15799
+ if (transformHandle.includes("n")) {
15800
+ minY += dragOffset.y;
15801
+ } else if (transformHandle.includes("s")) {
15802
+ maxY += dragOffset.y;
15304
15803
  }
15305
15804
  }
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
- );
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;
15835
+ }
15836
+ case "sw": {
15837
+ selectionSnapPoints.push(pointFrom18(minX, maxY));
15838
+ break;
15839
+ }
15338
15840
  }
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);
15351
15841
  }
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
+ };
15352
15889
  };
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() {
15890
+ var snapNewElement = (newElement7, app, event, origin, dragOffset, elementsMap) => {
15891
+ if (!isSnappingEnabled({ event, selectedElements: [newElement7], app })) {
15369
15892
  return {
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
- }
15893
+ snapOffset: { x: 0, y: 0 },
15894
+ snapLines: []
15382
15895
  };
15383
15896
  }
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
- }
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
+ };
15943
+ var getSnapLinesAtPointer = (elements, app, pointer, event, elementsMap) => {
15944
+ if (!isSnappingEnabled({ event, selectedElements: [], app })) {
15945
+ return {
15946
+ originOffset: { x: 0, y: 0 },
15947
+ snapLines: []
15948
+ };
15410
15949
  }
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)
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"
15425
15975
  });
15426
- trail.start(this.container);
15427
- this.collabTrails.set(key, trail);
15976
+ minOffset.x = offsetX;
15428
15977
  }
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();
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;
15446
15982
  }
15983
+ horizontalSnapLines.push({
15984
+ type: "pointer",
15985
+ points: [corner, pointFrom18(pointer.x, corner[1])],
15986
+ direction: "horizontal"
15987
+ });
15988
+ minOffset.y = offsetY;
15447
15989
  }
15448
15990
  }
15449
15991
  }
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;
15450
16002
  };
15451
16003
 
15452
16004
  // scene/Renderer.ts
@@ -15587,1070 +16139,449 @@ var Renderer = class {
15587
16139
  const deselectedElements = visibleElements.filter(
15588
16140
  (element) => !selectedElementsMap.has(element.id)
15589
16141
  );
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);
16033
- continue;
16034
- }
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;
16051
- }
16142
+ const insertionIndex = getFrameChildrenInsertionIndex(
16143
+ deselectedElements,
16144
+ frameToHighlight.id
16145
+ );
16146
+ if (insertionIndex === null) {
16147
+ return visibleElements;
16052
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();
16053
16160
  }
16054
16161
  };
16055
- var getReferenceSnapPoints = (elements, selectedElements, appState, elementsMap) => {
16056
- const referenceElements = getReferenceElements(
16057
- elements,
16058
- selectedElements,
16059
- appState,
16060
- elementsMap
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
16061
16173
  );
16062
- return getMaximumGroups(referenceElements, elementsMap).filter(
16063
- (elementsGroup) => !(elementsGroup.length === 1 && isBoundToContainer6(elementsGroup[0]))
16064
- ).flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
16174
+ const x = viewportX - appState.offsetLeft + 10;
16175
+ const y = viewportY - appState.offsetTop;
16176
+ return { x, y };
16065
16177
  };
16066
- var getPointSnaps = (selectedElements, selectionSnapPoints, app, event, nearestSnapsX, nearestSnapsY, minOffset) => {
16067
- if (!isSnappingEnabled({ app, event, selectedElements }) || selectedElements.length === 0 && selectionSnapPoints.length === 0) {
16068
- return [];
16069
- }
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
- }
16099
- }
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;
16100
16186
  }
16101
- };
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
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
16110
16197
  },
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
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
- )
16198
+ children
16199
+ }
16183
16200
  );
16184
- return {
16185
- snapOffset,
16186
- snapLines: [...pointSnapLines, ...gapSnapLines]
16187
- };
16188
16201
  };
16189
- var round = (x) => {
16190
- const decimalPlaces = 6;
16191
- return Math.round(x * 10 ** decimalPlaces) / 10 ** decimalPlaces;
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);
16192
16220
  };
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
- }
16200
- }
16201
- return Array.from(map.values());
16221
+ var withBatchedUpdatesThrottled = (func) => {
16222
+ return throttleRAF((event) => {
16223
+ unstable_batchedUpdates(func, event);
16224
+ });
16202
16225
  };
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] = [];
16212
- }
16213
- snapsX[key].push(
16214
- ...snap.points.map(
16215
- (p) => pointFrom18(round(p[0]), round(p[1]))
16216
- )
16217
- );
16218
- }
16219
- }
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;
16220
16233
  }
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] = [];
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
+ );
16227
16243
  }
16228
- snapsY[key].push(
16229
- ...snap.points.map(
16230
- (p) => pointFrom18(round(p[0]), round(p[1]))
16231
- )
16232
- );
16244
+ return false;
16233
16245
  }
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();
16234
16269
  }
16235
16270
  }
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
- )
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"
16254
16284
  };
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);
16264
16285
  }
16265
16286
  }
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
- );
16376
- }
16377
- break;
16378
- }
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
- );
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);
16295
+ }
16296
+ _AnimationController.scheduledFrame = null;
16297
+ }
16298
+ static cancelScheduledFrameIfIdle() {
16299
+ if (_AnimationController.animations.size > 0) {
16300
+ return false;
16301
+ }
16302
+ _AnimationController.cancelScheduledFrame();
16303
+ return true;
16304
+ }
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;
16400
16323
  }
16401
- break;
16402
16324
  }
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
- );
16421
- }
16422
- break;
16325
+ if (_AnimationController.cancelScheduledFrameIfIdle()) {
16326
+ return;
16423
16327
  }
16328
+ _AnimationController.scheduleNextFrame();
16424
16329
  }
16425
16330
  }
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
- );
16331
+ static running(key) {
16332
+ return _AnimationController.animations.has(key);
16333
+ }
16334
+ static cancel(key) {
16335
+ _AnimationController.animations.delete(key);
16336
+ _AnimationController.cancelScheduledFrameIfIdle();
16337
+ }
16436
16338
  };
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
- };
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
+ }
16443
16366
  }
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;
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;
16450
16374
  }
16451
- if (transformHandle.includes("n")) {
16452
- minY += dragOffset.y;
16453
- } else if (transformHandle.includes("s")) {
16454
- maxY += dragOffset.y;
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);
16455
16382
  }
16456
16383
  }
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
- }
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 };
16396
+ }
16397
+ this.cleanup();
16398
+ return null;
16399
+ });
16492
16400
  }
16493
16401
  }
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
- };
16541
- };
16542
- var snapNewElement = (newElement7, app, event, origin, dragOffset, elementsMap) => {
16543
- if (!isSnappingEnabled({ event, selectedElements: [newElement7], app })) {
16544
- return {
16545
- snapOffset: { x: 0, y: 0 },
16546
- snapLines: []
16547
- };
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
+ }
16416
+ }
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();
16424
+ }
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");
16439
+ }
16440
+ }
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
+ );
16473
+ }
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);
16548
16486
  }
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
16487
  };
16595
- var getSnapLinesAtPointer = (elements, app, pointer, event, elementsMap) => {
16596
- if (!isSnappingEnabled({ event, selectedElements: [], app })) {
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() {
16597
16504
  return {
16598
- originOffset: { x: 0, y: 0 },
16599
- snapLines: []
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
+ }
16600
16517
  };
16601
16518
  }
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"
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
+ }
16545
+ }
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)
16627
16560
  });
16628
- minOffset.x = offsetX;
16561
+ trail.start(this.container);
16562
+ this.collabTrails.set(key, trail);
16629
16563
  }
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;
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();
16634
16581
  }
16635
- horizontalSnapLines.push({
16636
- type: "pointer",
16637
- points: [corner, pointFrom18(pointer.x, corner[1])],
16638
- direction: "horizontal"
16639
- });
16640
- minOffset.y = offsetY;
16641
16582
  }
16642
16583
  }
16643
16584
  }
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;
16654
16585
  };
16655
16586
 
16656
16587
  // textAutoResizeHandle.ts
@@ -17428,6 +17359,75 @@ var textWysiwyg = ({
17428
17359
  return handleSubmit;
17429
17360
  };
17430
17361
 
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,122 +19586,6 @@ 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
-
19705
19589
  // components/Trans.tsx
19706
19590
  import React17 from "react";
19707
19591
  var SPLIT_REGEX = /({{[\w-]+}})|(<[\w-]+>)|(<\/[\w-]+>)/g;
@@ -19997,17 +19881,29 @@ var Popover6 = ({
19997
19881
  // components/ContextMenu.tsx
19998
19882
  import { jsx as jsx62, jsxs as jsxs29 } from "react/jsx-runtime";
19999
19883
  var CONTEXT_MENU_SEPARATOR = "separator";
19884
+ var isCustomContextMenuItem = (item) => "onSelect" in item;
20000
19885
  var ContextMenu = React19.memo(
20001
19886
  ({ actionManager, items, top, left, onClose }) => {
20002
19887
  const appState = useExcalidrawAppState();
20003
19888
  const elements = useExcalidrawElements();
20004
19889
  const filteredItems = items.reduce((acc, item) => {
20005
- if (item && (item === CONTEXT_MENU_SEPARATOR || !item.predicate || item.predicate(
19890
+ if (!item) {
19891
+ return acc;
19892
+ }
19893
+ if (item === CONTEXT_MENU_SEPARATOR) {
19894
+ acc.push(item);
19895
+ return acc;
19896
+ }
19897
+ if (isCustomContextMenuItem(item)) {
19898
+ acc.push(item);
19899
+ return acc;
19900
+ }
19901
+ if (!item.predicate || item.predicate(
20006
19902
  elements,
20007
19903
  appState,
20008
19904
  actionManager.app.props,
20009
19905
  actionManager.app
20010
- ))) {
19906
+ )) {
20011
19907
  acc.push(item);
20012
19908
  }
20013
19909
  return acc;
@@ -20038,6 +19934,24 @@ var ContextMenu = React19.memo(
20038
19934
  }
20039
19935
  return /* @__PURE__ */ jsx62("hr", { className: "context-menu-item-separator" }, idx);
20040
19936
  }
19937
+ if (isCustomContextMenuItem(item)) {
19938
+ return /* @__PURE__ */ jsx62(
19939
+ "li",
19940
+ {
19941
+ "data-testid": item.key,
19942
+ onClick: () => {
19943
+ onClose(() => {
19944
+ item.onSelect();
19945
+ });
19946
+ },
19947
+ children: /* @__PURE__ */ jsxs29("button", { type: "button", className: "context-menu-item", children: [
19948
+ /* @__PURE__ */ jsx62("div", { className: "context-menu-item__label", children: item.label }),
19949
+ /* @__PURE__ */ jsx62("kbd", { className: "context-menu-item__shortcut" })
19950
+ ] })
19951
+ },
19952
+ item.key
19953
+ );
19954
+ }
20041
19955
  const actionName = item.name;
20042
19956
  let label = "";
20043
19957
  if (item.label) {
@@ -31500,6 +31414,122 @@ var NewElementCanvas = (props) => {
31500
31414
  };
31501
31415
  var NewElementCanvas_default = NewElementCanvas;
31502
31416
 
31417
+ // components/AppStateObserver.ts
31418
+ var AppStateObserver = class {
31419
+ constructor(getState) {
31420
+ this.getState = getState;
31421
+ __publicField(this, "listeners", []);
31422
+ __publicField(this, "onStateChange", (propOrOpts, callback, opts) => {
31423
+ const {
31424
+ predicate,
31425
+ getValue,
31426
+ callback: stateChangeCallback,
31427
+ once,
31428
+ matchesImmediately
31429
+ } = this.normalize(propOrOpts, callback, opts);
31430
+ if (stateChangeCallback) {
31431
+ if (matchesImmediately) {
31432
+ queueMicrotask(() => {
31433
+ const state = this.getState();
31434
+ stateChangeCallback(getValue(state), state);
31435
+ });
31436
+ if (once) {
31437
+ return () => {
31438
+ };
31439
+ }
31440
+ }
31441
+ return this.subscribe({
31442
+ predicate,
31443
+ getValue,
31444
+ callback: stateChangeCallback,
31445
+ once
31446
+ });
31447
+ }
31448
+ if (matchesImmediately) {
31449
+ return Promise.resolve(getValue(this.getState()));
31450
+ }
31451
+ return new Promise((resolve) => {
31452
+ this.subscribe({
31453
+ predicate,
31454
+ getValue,
31455
+ callback: (value) => resolve(value),
31456
+ once: true
31457
+ });
31458
+ });
31459
+ });
31460
+ }
31461
+ isStateChangePredicateOptions(propOrOpts) {
31462
+ return typeof propOrOpts === "object" && !Array.isArray(propOrOpts) && "predicate" in propOrOpts;
31463
+ }
31464
+ subscribe(listener) {
31465
+ this.listeners.push(listener);
31466
+ return () => {
31467
+ this.listeners = this.listeners.filter(
31468
+ (existingListener) => existingListener !== listener
31469
+ );
31470
+ };
31471
+ }
31472
+ normalize(propOrOpts, callback, opts) {
31473
+ let predicate;
31474
+ let getValue;
31475
+ let normalizedCallback = callback;
31476
+ let once = opts?.once ?? false;
31477
+ let matchesImmediately = false;
31478
+ if (this.isStateChangePredicateOptions(propOrOpts)) {
31479
+ const {
31480
+ predicate: predicateFn,
31481
+ callback: callbackFromOpts,
31482
+ once: onceFromOpts
31483
+ } = propOrOpts;
31484
+ predicate = predicateFn;
31485
+ getValue = (appState) => appState;
31486
+ normalizedCallback = callbackFromOpts ? (_value, appState) => callbackFromOpts(appState) : void 0;
31487
+ once = onceFromOpts ?? false;
31488
+ matchesImmediately = predicateFn(this.getState());
31489
+ } else if (typeof propOrOpts === "function") {
31490
+ const selector = propOrOpts;
31491
+ predicate = (appState, prevState) => selector(appState) !== selector(prevState);
31492
+ getValue = (appState) => selector(appState);
31493
+ } else if (Array.isArray(propOrOpts)) {
31494
+ const keys = propOrOpts;
31495
+ predicate = (appState, prevState) => keys.some((key) => appState[key] !== prevState[key]);
31496
+ getValue = (appState) => appState;
31497
+ } else {
31498
+ const key = propOrOpts;
31499
+ predicate = (appState, prevState) => appState[key] !== prevState[key];
31500
+ getValue = (appState) => appState[key];
31501
+ }
31502
+ return {
31503
+ predicate,
31504
+ getValue,
31505
+ callback: normalizedCallback,
31506
+ once,
31507
+ matchesImmediately
31508
+ };
31509
+ }
31510
+ flush(prevState) {
31511
+ if (!this.listeners.length) {
31512
+ return;
31513
+ }
31514
+ const state = this.getState();
31515
+ const listenersToKeep = [];
31516
+ for (const listener of this.listeners) {
31517
+ if (listener.predicate(state, prevState)) {
31518
+ listener.callback(listener.getValue(state), state);
31519
+ if (!listener.once) {
31520
+ listenersToKeep.push(listener);
31521
+ }
31522
+ } else {
31523
+ listenersToKeep.push(listener);
31524
+ }
31525
+ }
31526
+ this.listeners = listenersToKeep;
31527
+ }
31528
+ clear() {
31529
+ this.listeners = [];
31530
+ }
31531
+ };
31532
+
31503
31533
  // components/UnlockPopup.tsx
31504
31534
  import {
31505
31535
  getCommonBounds as getCommonBounds10,
@@ -31709,8 +31739,8 @@ var App = class _App extends React40.Component {
31709
31739
  __publicField(this, "missingPointerEventCleanupEmitter", new Emitter2());
31710
31740
  __publicField(this, "onRemoveEventListenersEmitter", new Emitter2());
31711
31741
  __publicField(this, "api");
31712
- __publicField(this, "addImageElementsToScene", async (imageFiles, sceneX, sceneY, options) => {
31713
- await this.insertImages(imageFiles, sceneX, sceneY, options);
31742
+ __publicField(this, "addImageElementsToScene", async (imageFiles, sceneX, sceneY) => {
31743
+ await this.insertImages(imageFiles, sceneX, sceneY);
31714
31744
  });
31715
31745
  __publicField(this, "updateEditorAtom", (atom2, ...args) => {
31716
31746
  const result = editorJotaiStore.set(atom2, ...args);
@@ -35625,7 +35655,8 @@ var App = class _App extends React40.Component {
35625
35655
  __publicField(this, "newImagePlaceholder", ({
35626
35656
  sceneX,
35627
35657
  sceneY,
35628
- addToFrameUnderCursor = true
35658
+ addToFrameUnderCursor = true,
35659
+ customData
35629
35660
  }) => {
35630
35661
  const [gridX, gridY] = getGridPoint2(
35631
35662
  sceneX,
@@ -35652,7 +35683,8 @@ var App = class _App extends React40.Component {
35652
35683
  x: gridX - placeholderSize / 2,
35653
35684
  y: gridY - placeholderSize / 2,
35654
35685
  width: placeholderSize,
35655
- height: placeholderSize
35686
+ height: placeholderSize,
35687
+ customData
35656
35688
  });
35657
35689
  });
35658
35690
  __publicField(this, "handleLinearElementOnPointerDown", (event, elementType, pointerDownState) => {
@@ -36046,7 +36078,6 @@ var App = class _App extends React40.Component {
36046
36078
  const fileId = await (this.props.generateIdForFile?.(
36047
36079
  imageFile
36048
36080
  ) || generateIdFromFile(imageFile));
36049
- console.info("Excalidraw File ID:", fileId);
36050
36081
  if (!fileId) {
36051
36082
  console.warn(
36052
36083
  "Couldn't generate file id or the supplied `generateIdForFile` didn't resolve to one."
@@ -36128,7 +36159,13 @@ var App = class _App extends React40.Component {
36128
36159
  ),
36129
36160
  multiple: true
36130
36161
  });
36131
- this.insertImages(imageFiles, x, y);
36162
+ this.insertImages(
36163
+ imageFiles.map((f) => ({
36164
+ file: f
36165
+ })),
36166
+ x,
36167
+ y
36168
+ );
36132
36169
  } catch (error) {
36133
36170
  if (error.name !== "AbortError") {
36134
36171
  console.error(error);
@@ -36238,11 +36275,12 @@ var App = class _App extends React40.Component {
36238
36275
  );
36239
36276
  }
36240
36277
  });
36241
- __publicField(this, "insertImages", async (imageFiles, sceneX, sceneY, options) => {
36242
- const waitFor = options?.waitFor ?? "inserted";
36278
+ __publicField(this, "insertImages", async (imageFiles, sceneX, sceneY) => {
36243
36279
  const gridPadding = 50 / this.state.zoom.value;
36244
36280
  const placeholders = positionElementsOnGrid(
36245
- imageFiles.map(() => this.newImagePlaceholder({ sceneX, sceneY })),
36281
+ imageFiles.map(
36282
+ ({ customData }) => this.newImagePlaceholder({ sceneX, sceneY, customData })
36283
+ ),
36246
36284
  sceneX,
36247
36285
  sceneY,
36248
36286
  gridPadding
@@ -36253,7 +36291,7 @@ var App = class _App extends React40.Component {
36253
36291
  try {
36254
36292
  return await this.initializeImage(
36255
36293
  placeholder,
36256
- await normalizeFile(imageFiles[i])
36294
+ await normalizeFile(imageFiles[i].file)
36257
36295
  );
36258
36296
  } catch (error) {
36259
36297
  this.setState({
@@ -36282,17 +36320,8 @@ var App = class _App extends React40.Component {
36282
36320
  elements: nextElements,
36283
36321
  captureUpdate: CaptureUpdateAction41.IMMEDIATELY
36284
36322
  });
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
- });
36323
+ this.setState({}, () => {
36324
+ this.actionManager.executeAction(actionFinalize);
36296
36325
  });
36297
36326
  });
36298
36327
  __publicField(this, "handleAppOnDrop", async (event) => {
@@ -36331,7 +36360,13 @@ var App = class _App extends React40.Component {
36331
36360
  }
36332
36361
  const imageFiles = fileItems.map((data) => data.file).filter((file2) => isSupportedImageFile(file2));
36333
36362
  if (imageFiles.length > 0 && this.isToolSupported("image")) {
36334
- return this.insertImages(imageFiles, sceneX, sceneY);
36363
+ return this.insertImages(
36364
+ imageFiles.map((file2) => ({
36365
+ file: file2
36366
+ })),
36367
+ sceneX,
36368
+ sceneY
36369
+ );
36335
36370
  }
36336
36371
  if (fileItems.length > 0) {
36337
36372
  const { file: file2, fileHandle } = fileItems[0];
@@ -36691,6 +36726,8 @@ var App = class _App extends React40.Component {
36691
36726
  });
36692
36727
  __publicField(this, "getContextMenuItems", (type) => {
36693
36728
  const options = [];
36729
+ const imageContextMenuItems = this.getImageContextMenuItems();
36730
+ const imageContextMenuSection = imageContextMenuItems.length > 0 ? [CONTEXT_MENU_SEPARATOR, ...imageContextMenuItems] : [];
36694
36731
  if (type === "canvas") {
36695
36732
  if (this.state.viewModeEnabled) {
36696
36733
  return [actionToggleGridMode, actionToggleViewMode, actionToggleStats];
@@ -36713,7 +36750,9 @@ var App = class _App extends React40.Component {
36713
36750
  }
36714
36751
  options.push(copyText);
36715
36752
  if (this.state.viewModeEnabled) {
36716
- return [actionCopy, ...options];
36753
+ const viewModeItems = [actionCopy];
36754
+ viewModeItems.push(...imageContextMenuSection, ...options);
36755
+ return viewModeItems;
36717
36756
  }
36718
36757
  const zIndexActions = this.editorInterface.formFactor === "desktop" ? [
36719
36758
  CONTEXT_MENU_SEPARATOR,
@@ -36722,11 +36761,17 @@ var App = class _App extends React40.Component {
36722
36761
  actionSendToBack,
36723
36762
  actionBringToFront
36724
36763
  ] : [];
36725
- return [
36764
+ const elementItems = [
36726
36765
  CONTEXT_MENU_SEPARATOR,
36727
36766
  actionCut,
36728
36767
  actionCopy,
36729
- actionPaste,
36768
+ actionPaste
36769
+ ];
36770
+ elementItems.push(...imageContextMenuSection);
36771
+ if (imageContextMenuItems.length > 0) {
36772
+ elementItems.push(CONTEXT_MENU_SEPARATOR);
36773
+ }
36774
+ elementItems.push(
36730
36775
  CONTEXT_MENU_SEPARATOR,
36731
36776
  actionSelectAllElementsInFrame,
36732
36777
  actionRemoveAllElementsFromFrame,
@@ -36759,6 +36804,21 @@ var App = class _App extends React40.Component {
36759
36804
  actionToggleElementLock,
36760
36805
  CONTEXT_MENU_SEPARATOR,
36761
36806
  actionDeleteSelected
36807
+ );
36808
+ return elementItems;
36809
+ });
36810
+ __publicField(this, "getImageContextMenuItems", () => {
36811
+ if (!this.props.imageContextMenuItems) {
36812
+ return [];
36813
+ }
36814
+ const selectedElements = this.scene.getSelectedElements(this.state);
36815
+ if (selectedElements.length === 0 || !selectedElements.every((element) => isImageElement9(element))) {
36816
+ return [];
36817
+ }
36818
+ return [
36819
+ ...this.props.imageContextMenuItems(
36820
+ selectedElements.map((element) => element.id)
36821
+ )
36762
36822
  ];
36763
36823
  });
36764
36824
  __publicField(this, "handleWheel", withBatchedUpdates(
@@ -38382,7 +38442,13 @@ var App = class _App extends React40.Component {
38382
38442
  }
38383
38443
  if (imageFiles.length > 0) {
38384
38444
  if (this.isToolSupported("image")) {
38385
- await this.insertImages(imageFiles, sceneX, sceneY);
38445
+ await this.insertImages(
38446
+ imageFiles.map((file2) => ({
38447
+ file: file2
38448
+ })),
38449
+ sceneX,
38450
+ sceneY
38451
+ );
38386
38452
  } else {
38387
38453
  this.setState({ errorMessage: t("errors.imageToolNotSupported") });
38388
38454
  }
@@ -38458,7 +38524,13 @@ var App = class _App extends React40.Component {
38458
38524
  })
38459
38525
  );
38460
38526
  const imageFiles = responses.filter((response) => !!response.file).map((response) => response.file);
38461
- await this.insertImages(imageFiles, sceneX, sceneY);
38527
+ await this.insertImages(
38528
+ imageFiles.map((file2) => ({
38529
+ file: file2
38530
+ })),
38531
+ sceneX,
38532
+ sceneY
38533
+ );
38462
38534
  const error = responses.find((response) => !!response.errorMessage);
38463
38535
  if (error && error.errorMessage) {
38464
38536
  this.setState({ errorMessage: error.errorMessage });
@@ -41206,6 +41278,7 @@ var ExcalidrawBase = (props) => {
41206
41278
  onPointerUp,
41207
41279
  onScrollChange,
41208
41280
  onDuplicate,
41281
+ imageContextMenuItems,
41209
41282
  children,
41210
41283
  validateEmbeddable,
41211
41284
  renderEmbeddable,
@@ -41301,6 +41374,7 @@ var ExcalidrawBase = (props) => {
41301
41374
  onPointerUp,
41302
41375
  onScrollChange,
41303
41376
  onDuplicate,
41377
+ imageContextMenuItems,
41304
41378
  validateEmbeddable,
41305
41379
  renderEmbeddable,
41306
41380
  showDeprecatedFonts,