@tldraw/editor 4.6.0-internal.e29318c66fb0 → 4.6.0-next.0eb36d65eec3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist-cjs/index.d.ts +124 -6
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  8. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
  9. package/dist-cjs/lib/config/createTLStore.js +1 -1
  10. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  11. package/dist-cjs/lib/config/defaultShapes.js.map +2 -2
  12. package/dist-cjs/lib/editor/Editor.js +4 -4
  13. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  14. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +1 -1
  15. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  16. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +1 -1
  17. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -4
  20. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +102 -30
  22. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +1 -1
  25. package/dist-cjs/lib/exports/StyleEmbedder.js +1 -1
  26. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  27. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  28. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  30. package/dist-cjs/lib/options.js +3 -0
  31. package/dist-cjs/lib/options.js.map +2 -2
  32. package/dist-cjs/lib/primitives/Box.js +1 -1
  33. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  34. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  35. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  36. package/dist-cjs/lib/primitives/geometry/Circle2d.js +2 -2
  37. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +2 -2
  39. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  40. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +4 -3
  41. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  42. package/dist-cjs/lib/primitives/geometry/Polyline2d.js +1 -1
  43. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  44. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +1 -1
  45. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  46. package/dist-cjs/lib/utils/getSvgPathFromPoints.js.map +2 -2
  47. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +1 -1
  48. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  49. package/dist-cjs/version.js +3 -3
  50. package/dist-cjs/version.js.map +1 -1
  51. package/dist-esm/index.d.mts +124 -6
  52. package/dist-esm/index.mjs +4 -2
  53. package/dist-esm/index.mjs.map +2 -2
  54. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  55. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
  56. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  57. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  58. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
  59. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  60. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  61. package/dist-esm/lib/config/defaultShapes.mjs.map +2 -2
  62. package/dist-esm/lib/editor/Editor.mjs +6 -6
  63. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  64. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +1 -1
  65. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  66. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +1 -1
  67. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  68. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  69. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -4
  70. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
  71. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +102 -30
  72. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  73. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  74. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +1 -1
  75. package/dist-esm/lib/exports/StyleEmbedder.mjs +1 -1
  76. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  77. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  78. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  79. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  80. package/dist-esm/lib/options.mjs +3 -0
  81. package/dist-esm/lib/options.mjs.map +2 -2
  82. package/dist-esm/lib/primitives/Box.mjs +1 -1
  83. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  84. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  85. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  86. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
  87. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  88. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
  89. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  90. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +4 -3
  91. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  92. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +1 -1
  93. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  94. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +1 -1
  95. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  96. package/dist-esm/lib/utils/getSvgPathFromPoints.mjs.map +2 -2
  97. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +1 -1
  98. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  99. package/dist-esm/version.mjs +3 -3
  100. package/dist-esm/version.mjs.map +1 -1
  101. package/package.json +7 -7
  102. package/src/index.ts +8 -1
  103. package/src/lib/TldrawEditor.tsx +1 -1
  104. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
  105. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  106. package/src/lib/config/TLEditorSnapshot.test.ts +1 -1
  107. package/src/lib/config/createTLStore.ts +1 -1
  108. package/src/lib/config/defaultShapes.ts +1 -1
  109. package/src/lib/editor/Editor.ts +9 -8
  110. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +1 -1
  111. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +1 -1
  112. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +1 -1
  113. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +5 -5
  114. package/src/lib/editor/managers/TextManager/TextManager.test.ts +18 -4
  115. package/src/lib/editor/managers/TextManager/TextManager.ts +140 -34
  116. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +1 -1
  117. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +1 -1
  118. package/src/lib/editor/shapes/ShapeUtil.ts +2 -2
  119. package/src/lib/exports/StyleEmbedder.ts +1 -1
  120. package/src/lib/exports/getSvgJsx.tsx +1 -1
  121. package/src/lib/hooks/useEditorComponents.tsx +1 -1
  122. package/src/lib/hooks/useLocalStore.ts +1 -1
  123. package/src/lib/options.ts +108 -0
  124. package/src/lib/primitives/Box.ts +1 -1
  125. package/src/lib/primitives/geometry/Arc2d.ts +2 -2
  126. package/src/lib/primitives/geometry/Circle2d.ts +2 -2
  127. package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
  128. package/src/lib/primitives/geometry/Geometry2d.ts +4 -3
  129. package/src/lib/primitives/geometry/Polyline2d.ts +1 -1
  130. package/src/lib/primitives/geometry/Stadium2d.ts +1 -1
  131. package/src/lib/utils/getSvgPathFromPoints.ts +1 -1
  132. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  133. package/src/lib/utils/sync/TLLocalSyncClient.ts +1 -1
  134. package/src/version.ts +3 -3
@@ -1,5 +1,5 @@
1
- import { Vec } from "../../../primitives/Vec.mjs";
2
1
  import { EASINGS } from "../../../primitives/easings.mjs";
2
+ import { Vec } from "../../../primitives/Vec.mjs";
3
3
  class EdgeScrollManager {
4
4
  constructor(editor) {
5
5
  this.editor = editor;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts"],
4
- "sourcesContent": ["import { Vec } from '../../../primitives/Vec'\nimport { EASINGS } from '../../../primitives/easings'\nimport type { Editor } from '../../Editor'\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge({\n\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX = screenBounds.w < 1000 ? 0.612 : 1\n\t\tconst screenSizeFactorY = screenBounds.h < 1000 ? 0.612 : 1\n\n\t\t// Determines the base speed of the scroll\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed\n\t\tconst scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel\n\t\tconst scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel\n\n\t\t// update the camera\n\t\tconst { x, y, z } = editor.getCamera()\n\t\teditor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EASINGS } from '../../../primitives/easings'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge({\n\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX = screenBounds.w < 1000 ? 0.612 : 1\n\t\tconst screenSizeFactorY = screenBounds.h < 1000 ? 0.612 : 1\n\n\t\t// Determines the base speed of the scroll\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed\n\t\tconst scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel\n\t\tconst scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel\n\n\t\t// update the camera\n\t\tconst { x, y, z } = editor.getCamera()\n\t\teditor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -17,8 +17,8 @@ import {
17
17
  isSelectionCorner
18
18
  } from "../../../primitives/Box.mjs";
19
19
  import { Mat } from "../../../primitives/Mat.mjs";
20
- import { Vec } from "../../../primitives/Vec.mjs";
21
20
  import { rangeIntersection, rangesOverlap } from "../../../primitives/utils.mjs";
21
+ import { Vec } from "../../../primitives/Vec.mjs";
22
22
  const round = (x) => {
23
23
  const decimalPlacesTolerance = 8;
24
24
  return Math.round(x * 10 ** decimalPlacesTolerance) / 10 ** decimalPlacesTolerance;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/SnapManager/BoundsSnaps.ts"],
4
- "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { TLShape, TLShapeId, VecModel } from '@tldraw/tlschema'\nimport { assertExists, dedupe, uniqueId } from '@tldraw/utils'\nimport {\n\tBox,\n\tSelectionCorner,\n\tSelectionEdge,\n\tflipSelectionHandleX,\n\tflipSelectionHandleY,\n\tisSelectionCorner,\n} from '../../../primitives/Box'\nimport { Mat } from '../../../primitives/Mat'\nimport { Vec } from '../../../primitives/Vec'\nimport { rangeIntersection, rangesOverlap } from '../../../primitives/utils'\nimport type { Editor } from '../../Editor'\nimport type {\n\tGapsSnapIndicator,\n\tPointsSnapIndicator,\n\tSnapData,\n\tSnapIndicator,\n\tSnapManager,\n} from './SnapManager'\n\n/**\n * When moving or resizing shapes, the bounds of the shape can snap to key geometry on other nearby\n * shapes. Customize how a shape snaps to others with {@link ShapeUtil.getBoundsSnapGeometry}.\n *\n * @public\n */\nexport interface BoundsSnapGeometry {\n\t/**\n\t * Points that this shape will snap to. By default, this will be the corners and center of the\n\t * shapes bounding box. To disable snapping to a specific point, use an empty array.\n\t */\n\tpoints?: VecModel[]\n}\n\n/** @public */\nexport interface BoundsSnapPoint {\n\tid: string\n\tx: number\n\ty: number\n\thandle?: SelectionCorner\n}\n\ninterface SnapPair {\n\tthisPoint: BoundsSnapPoint\n\totherPoint: BoundsSnapPoint\n}\n\ninterface NearestPointsSnap {\n\t// selection snaps to a nearby snap point\n\ttype: 'points'\n\tpoints: SnapPair\n\tnudge: number\n}\n\ntype NearestSnap =\n\t| NearestPointsSnap\n\t| {\n\t\t\t// selection snaps to the center of a gap\n\t\t\ttype: 'gap_center'\n\t\t\tgap: Gap\n\t\t\tnudge: number\n\t }\n\t| {\n\t\t\t// selection snaps to create a new gap of equal size to another gap\n\t\t\t// on the opposite side of some shape\n\t\t\ttype: 'gap_duplicate'\n\t\t\tgap: Gap\n\t\t\tprotrusionDirection: 'left' | 'right' | 'top' | 'bottom'\n\t\t\tnudge: number\n\t }\n\ninterface GapNode {\n\tid: TLShapeId\n\tpageBounds: Box\n}\n\ninterface Gap {\n\t// e.g.\n\t// start\n\t// edge \u2502 breadth\n\t// \u2502 intersection\n\t// \u25BC [40,100] end\n\t// \u2502 \u2502 edge\n\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 100,0 \u2502 \u2502\n\t// \u2502 \u2502 \u2502 \u25BC \u25BC\n\t// \u2502 \u2502 \u2502\n\t// \u2502 start \u2502 \u2502 \u2502 200,40 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t// \u2502 node \u2502 \u2502 \u2502 \u2502 \u2502 \u2502\n\t// \u2502 \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 \u2502 end \u2502\n\t// \u2502 \u2502 \u2502 \u2502 \u2502 \u2502 node \u2502\n\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 100,100 \u2502 \u2502 \u2502 \u2502\n\t// \u2502 \u2502 \u2502\n\t// 200,120 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t//\n\t// length 100\n\t// \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA\n\tstartNode: GapNode\n\tendNode: GapNode\n\tstartEdge: [Vec, Vec]\n\tendEdge: [Vec, Vec]\n\tlength: number\n\tbreadthIntersection: [number, number]\n}\n\nconst round = (x: number) => {\n\t// round numbers to avoid glitches for floating point rounding errors\n\tconst decimalPlacesTolerance = 8\n\treturn Math.round(x * 10 ** decimalPlacesTolerance) / 10 ** decimalPlacesTolerance\n}\n\nfunction findAdjacentGaps(\n\tgaps: Gap[],\n\tshapeId: TLShapeId,\n\tgapLength: number,\n\tdirection: 'forward' | 'backward',\n\tintersection: [number, number]\n): Gap[] {\n\t// TODO: take advantage of the fact that gaps is sorted by starting position?\n\tconst matches = gaps.filter(\n\t\t(gap) =>\n\t\t\t(direction === 'forward' ? gap.startNode.id === shapeId : gap.endNode.id === shapeId) &&\n\t\t\tround(gap.length) === round(gapLength) &&\n\t\t\trangeIntersection(\n\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\tintersection[0],\n\t\t\t\tintersection[1]\n\t\t\t)\n\t)\n\n\tif (matches.length === 0) return []\n\n\tconst nextNodes = new Set<TLShapeId>()\n\n\tmatches.forEach((match) => {\n\t\tconst node = direction === 'forward' ? match.endNode.id : match.startNode.id\n\t\tif (!nextNodes.has(node)) {\n\t\t\tnextNodes.add(node)\n\t\t\tconst foundGaps = findAdjacentGaps(\n\t\t\t\tgaps,\n\t\t\t\tnode,\n\t\t\t\tgapLength,\n\t\t\t\tdirection,\n\t\t\t\trangeIntersection(\n\t\t\t\t\tmatch.breadthIntersection[0],\n\t\t\t\t\tmatch.breadthIntersection[1],\n\t\t\t\t\tintersection[0],\n\t\t\t\t\tintersection[1]\n\t\t\t\t)!\n\t\t\t)\n\n\t\t\tmatches.push(...foundGaps)\n\t\t}\n\t})\n\n\treturn matches\n}\n\nfunction dedupeGapSnaps(snaps: Array<Extract<SnapIndicator, { type: 'gaps' }>>) {\n\t// sort by descending order of number of gaps\n\tsnaps.sort((a, b) => b.gaps.length - a.gaps.length)\n\t// pop off any that are included already\n\tfor (let i = snaps.length - 1; i > 0; i--) {\n\t\tconst snap = snaps[i]\n\t\tfor (let j = i - 1; j >= 0; j--) {\n\t\t\tconst otherSnap = snaps[j]\n\t\t\t// if every edge in this snap is included in the other snap somewhere, then it's redundant\n\t\t\tif (\n\t\t\t\totherSnap.direction === snap.direction &&\n\t\t\t\tsnap.gaps.every(\n\t\t\t\t\t(gap) =>\n\t\t\t\t\t\totherSnap.gaps.some(\n\t\t\t\t\t\t\t(otherGap) =>\n\t\t\t\t\t\t\t\tround(gap.startEdge[0].x) === round(otherGap.startEdge[0].x) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[0].y) === round(otherGap.startEdge[0].y) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[1].x) === round(otherGap.startEdge[1].x) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[1].y) === round(otherGap.startEdge[1].y)\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\totherSnap.gaps.some(\n\t\t\t\t\t\t\t(otherGap) =>\n\t\t\t\t\t\t\t\tround(gap.endEdge[0].x) === round(otherGap.endEdge[0].x) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[0].y) === round(otherGap.endEdge[0].y) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[1].x) === round(otherGap.endEdge[1].x) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[1].y) === round(otherGap.endEdge[1].y)\n\t\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tsnaps.splice(i, 1)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** @public */\nexport class BoundsSnaps {\n\treadonly editor: Editor\n\tconstructor(readonly manager: SnapManager) {\n\t\tthis.editor = manager.editor\n\t}\n\n\t@computed private getSnapPointsCache() {\n\t\tconst { editor } = this\n\t\treturn editor.store.createComputedCache<BoundsSnapPoint[], TLShape>('snapPoints', (shape) => {\n\t\t\tconst pageTransform = editor.getShapePageTransform(shape.id)\n\t\t\tif (!pageTransform) return undefined\n\t\t\tconst boundsSnapGeometry = editor.getShapeUtil(shape).getBoundsSnapGeometry(shape)\n\t\t\tconst snapPoints =\n\t\t\t\tboundsSnapGeometry.points ?? editor.getShapeGeometry(shape).bounds.cornersAndCenter\n\n\t\t\tif (!pageTransform || !snapPoints) return undefined\n\t\t\treturn snapPoints.map((point, i) => {\n\t\t\t\tconst { x, y } = Mat.applyToPoint(pageTransform, point)\n\t\t\t\treturn { x, y, id: `${shape.id}:${i}` }\n\t\t\t})\n\t\t})\n\t}\n\n\tgetSnapPoints(shapeId: TLShapeId) {\n\t\treturn this.getSnapPointsCache().get(shapeId) ?? []\n\t}\n\n\t// Points which belong to snappable shapes\n\t@computed private getSnappablePoints() {\n\t\tconst snapPointsCache = this.getSnapPointsCache()\n\t\tconst snappableShapes = this.manager.getSnappableShapes()\n\t\tconst result: BoundsSnapPoint[] = []\n\n\t\tfor (const shapeId of snappableShapes) {\n\t\t\tconst snapPoints = snapPointsCache.get(shapeId)\n\t\t\tif (snapPoints) {\n\t\t\t\tresult.push(...snapPoints)\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t@computed private getSnappableGapNodes(): Array<GapNode> {\n\t\treturn Array.from(this.manager.getSnappableShapes(), (shapeId) => ({\n\t\t\tid: shapeId,\n\t\t\tpageBounds: assertExists(this.editor.getShapePageBounds(shapeId)),\n\t\t}))\n\t}\n\n\t@computed private getVisibleGaps(): { horizontal: Gap[]; vertical: Gap[] } {\n\t\tconst horizontal: Gap[] = []\n\t\tconst vertical: Gap[] = []\n\n\t\tlet startNode: GapNode, endNode: GapNode\n\n\t\tconst sortedShapesOnCurrentPageHorizontal = this.getSnappableGapNodes().sort((a, b) => {\n\t\t\treturn a.pageBounds.minX - b.pageBounds.minX\n\t\t})\n\n\t\t// Collect horizontal gaps\n\t\tfor (let i = 0; i < sortedShapesOnCurrentPageHorizontal.length; i++) {\n\t\t\tstartNode = sortedShapesOnCurrentPageHorizontal[i]\n\t\t\tfor (let j = i + 1; j < sortedShapesOnCurrentPageHorizontal.length; j++) {\n\t\t\t\tendNode = sortedShapesOnCurrentPageHorizontal[j]\n\n\t\t\t\tif (\n\t\t\t\t\t// is there space between the boxes\n\t\t\t\t\tstartNode.pageBounds.maxX < endNode.pageBounds.minX &&\n\t\t\t\t\t// and they overlap in the y axis\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\tstartNode.pageBounds.minY,\n\t\t\t\t\t\tstartNode.pageBounds.maxY,\n\t\t\t\t\t\tendNode.pageBounds.minY,\n\t\t\t\t\t\tendNode.pageBounds.maxY\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\thorizontal.push({\n\t\t\t\t\t\tstartNode,\n\t\t\t\t\t\tendNode,\n\t\t\t\t\t\tstartEdge: [\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tendEdge: [\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tlength: endNode.pageBounds.minX - startNode.pageBounds.maxX,\n\t\t\t\t\t\tbreadthIntersection: rangeIntersection(\n\t\t\t\t\t\t\tstartNode.pageBounds.minY,\n\t\t\t\t\t\t\tstartNode.pageBounds.maxY,\n\t\t\t\t\t\t\tendNode.pageBounds.minY,\n\t\t\t\t\t\t\tendNode.pageBounds.maxY\n\t\t\t\t\t\t)!,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Collect vertical gaps\n\t\tconst sortedShapesOnCurrentPageVertical = sortedShapesOnCurrentPageHorizontal.sort((a, b) => {\n\t\t\treturn a.pageBounds.minY - b.pageBounds.minY\n\t\t})\n\n\t\tfor (let i = 0; i < sortedShapesOnCurrentPageVertical.length; i++) {\n\t\t\tstartNode = sortedShapesOnCurrentPageVertical[i]\n\t\t\tfor (let j = i + 1; j < sortedShapesOnCurrentPageVertical.length; j++) {\n\t\t\t\tendNode = sortedShapesOnCurrentPageVertical[j]\n\n\t\t\t\tif (\n\t\t\t\t\t// is there space between the boxes\n\t\t\t\t\tstartNode.pageBounds.maxY < endNode.pageBounds.minY &&\n\t\t\t\t\t// do they overlap in the x axis\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\tstartNode.pageBounds.minX,\n\t\t\t\t\t\tstartNode.pageBounds.maxX,\n\t\t\t\t\t\tendNode.pageBounds.minX,\n\t\t\t\t\t\tendNode.pageBounds.maxX\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tvertical.push({\n\t\t\t\t\t\tstartNode,\n\t\t\t\t\t\tendNode,\n\t\t\t\t\t\tstartEdge: [\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.minX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tendEdge: [\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.maxX, endNode.pageBounds.minY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tlength: endNode.pageBounds.minY - startNode.pageBounds.maxY,\n\t\t\t\t\t\tbreadthIntersection: rangeIntersection(\n\t\t\t\t\t\t\tstartNode.pageBounds.minX,\n\t\t\t\t\t\t\tstartNode.pageBounds.maxX,\n\t\t\t\t\t\t\tendNode.pageBounds.minX,\n\t\t\t\t\t\t\tendNode.pageBounds.maxX\n\t\t\t\t\t\t)!,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { horizontal, vertical }\n\t}\n\n\tsnapTranslateShapes({\n\t\tlockedAxis,\n\t\tinitialSelectionPageBounds,\n\t\tinitialSelectionSnapPoints,\n\t\tdragDelta,\n\t}: {\n\t\tlockedAxis: 'x' | 'y' | null\n\t\tinitialSelectionSnapPoints: BoundsSnapPoint[]\n\t\tinitialSelectionPageBounds: Box\n\t\tdragDelta: Vec\n\t}): SnapData {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\t\tconst visibleSnapPointsNotInSelection = this.getSnappablePoints()\n\n\t\tconst selectionPageBounds = initialSelectionPageBounds.clone().translate(dragDelta)\n\n\t\tconst selectionSnapPoints: BoundsSnapPoint[] = initialSelectionSnapPoints.map(\n\t\t\t({ x, y }, i) => ({\n\t\t\t\tid: 'selection:' + i,\n\t\t\t\tx: x + dragDelta.x,\n\t\t\t\ty: y + dragDelta.y,\n\t\t\t})\n\t\t)\n\n\t\tconst otherNodeSnapPoints = visibleSnapPointsNotInSelection\n\n\t\tconst nearestSnapsX: NearestSnap[] = []\n\t\tconst nearestSnapsY: NearestSnap[] = []\n\t\tconst minOffset = new Vec(snapThreshold, snapThreshold)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\tthis.collectGapSnaps({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\tminOffset,\n\t\t})\n\n\t\t// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)\n\t\tconst nudge = new Vec(\n\t\t\tlockedAxis === 'x' ? 0 : (nearestSnapsX[0]?.nudge ?? 0),\n\t\t\tlockedAxis === 'y' ? 0 : (nearestSnapsY[0]?.nudge ?? 0)\n\t\t)\n\n\t\t// ok we've figured out how much the box should be nudged, now let's find all the snap points\n\t\t// that are exact after making that translation, so we can render all of them.\n\t\t// first reset everything and adjust the original shapes to conform to the nudge\n\t\tminOffset.x = 0\n\t\tminOffset.y = 0\n\t\tnearestSnapsX.length = 0\n\t\tnearestSnapsY.length = 0\n\t\tselectionSnapPoints.forEach((s) => {\n\t\t\ts.x += nudge.x\n\t\t\ts.y += nudge.y\n\t\t})\n\t\tselectionPageBounds.translate(nudge)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\tthis.collectGapSnaps({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\tminOffset,\n\t\t})\n\n\t\tconst pointSnapsLines = this.getPointSnapLines({\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tconst gapSnapLines = this.getGapSnapLines({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tthis.manager.setIndicators([...gapSnapLines, ...pointSnapsLines])\n\n\t\treturn { nudge }\n\t}\n\n\tsnapResizeShapes({\n\t\tinitialSelectionPageBounds,\n\t\tdragDelta,\n\t\thandle: originalHandle,\n\t\tisAspectRatioLocked,\n\t\tisResizingFromCenter,\n\t}: {\n\t\t// the page bounds when the pointer went down, before any dragging\n\t\tinitialSelectionPageBounds: Box\n\t\t// how far the pointer has been dragged\n\t\tdragDelta: Vec\n\n\t\thandle: SelectionCorner | SelectionEdge\n\t\tisAspectRatioLocked: boolean\n\t\tisResizingFromCenter: boolean\n\t}): SnapData {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\n\t\t// first figure out the new bounds of the selection\n\t\tconst {\n\t\t\tbox: unsnappedResizedPageBounds,\n\t\t\tscaleX,\n\t\t\tscaleY,\n\t\t} = Box.Resize(\n\t\t\tinitialSelectionPageBounds,\n\t\t\toriginalHandle,\n\t\t\tisResizingFromCenter ? dragDelta.x * 2 : dragDelta.x,\n\t\t\tisResizingFromCenter ? dragDelta.y * 2 : dragDelta.y,\n\t\t\tisAspectRatioLocked\n\t\t)\n\n\t\tlet handle = originalHandle\n\n\t\tif (scaleX < 0) {\n\t\t\thandle = flipSelectionHandleX(handle)\n\t\t}\n\t\tif (scaleY < 0) {\n\t\t\thandle = flipSelectionHandleY(handle)\n\t\t}\n\n\t\tif (isResizingFromCenter) {\n\t\t\t// reposition if resizing from center\n\t\t\tunsnappedResizedPageBounds.center = initialSelectionPageBounds.center\n\t\t}\n\n\t\tconst isXLocked = handle === 'top' || handle === 'bottom'\n\t\tconst isYLocked = handle === 'left' || handle === 'right'\n\n\t\tconst selectionSnapPoints = getResizeSnapPointsForHandle(handle, unsnappedResizedPageBounds)\n\n\t\tconst otherNodeSnapPoints = this.getSnappablePoints()\n\n\t\tconst nearestSnapsX: NearestPointsSnap[] = []\n\t\tconst nearestSnapsY: NearestPointsSnap[] = []\n\t\tconst minOffset = new Vec(snapThreshold, snapThreshold)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\t// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)\n\t\tconst nudge = new Vec(\n\t\t\tisXLocked ? 0 : (nearestSnapsX[0]?.nudge ?? 0),\n\t\t\tisYLocked ? 0 : (nearestSnapsY[0]?.nudge ?? 0)\n\t\t)\n\n\t\tif (isAspectRatioLocked && isSelectionCorner(handle) && nudge.len() !== 0) {\n\t\t\t// if the aspect ratio is locked we need to make the nudge diagonal rather than independent in each axis\n\t\t\t// so we use the aspect ratio along with one axis value to set the other axis value, but which axis we use\n\t\t\t// as a source of truth depends what we have snapped to and how far.\n\n\t\t\t// if we found a snap in both axes, pick the closest one and discard the other\n\t\t\tconst primaryNudgeAxis: 'x' | 'y' =\n\t\t\t\tnearestSnapsX.length && nearestSnapsY.length\n\t\t\t\t\t? Math.abs(nudge.x) < Math.abs(nudge.y)\n\t\t\t\t\t\t? 'x'\n\t\t\t\t\t\t: 'y'\n\t\t\t\t\t: nearestSnapsX.length\n\t\t\t\t\t\t? 'x'\n\t\t\t\t\t\t: 'y'\n\n\t\t\tconst ratio = initialSelectionPageBounds.aspectRatio\n\n\t\t\tif (primaryNudgeAxis === 'x') {\n\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\tnudge.y = nudge.x / ratio\n\t\t\t\tif (handle === 'bottom_left' || handle === 'top_right') {\n\t\t\t\t\tnudge.y = -nudge.y\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\tnudge.x = nudge.y * ratio\n\t\t\t\tif (handle === 'bottom_left' || handle === 'top_right') {\n\t\t\t\t\tnudge.x = -nudge.x\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// now resize the box after nudging, calculate the snaps again, and return the snap lines to match\n\t\t// the fully resized box\n\t\tconst snappedDelta = Vec.Add(dragDelta, nudge)\n\n\t\t// first figure out the new bounds of the selection\n\t\tconst { box: snappedResizedPageBounds } = Box.Resize(\n\t\t\tinitialSelectionPageBounds,\n\t\t\toriginalHandle,\n\t\t\tisResizingFromCenter ? snappedDelta.x * 2 : snappedDelta.x,\n\t\t\tisResizingFromCenter ? snappedDelta.y * 2 : snappedDelta.y,\n\t\t\tisAspectRatioLocked\n\t\t)\n\n\t\tif (isResizingFromCenter) {\n\t\t\t// reposition if resizing from center\n\t\t\tsnappedResizedPageBounds.center = initialSelectionPageBounds.center\n\t\t}\n\n\t\tconst snappedSelectionPoints = getResizeSnapPointsForHandle('any', snappedResizedPageBounds)\n\t\t// calculate snaps again using all points\n\t\tnearestSnapsX.length = 0\n\t\tnearestSnapsY.length = 0\n\t\tminOffset.x = 0\n\t\tminOffset.y = 0\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints: snappedSelectionPoints,\n\t\t})\n\t\tconst pointSnaps = this.getPointSnapLines({\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tthis.manager.setIndicators([...pointSnaps])\n\n\t\treturn { nudge }\n\t}\n\n\tprivate collectPointSnaps({\n\t\tselectionSnapPoints,\n\t\totherNodeSnapPoints,\n\t\tminOffset,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionSnapPoints: BoundsSnapPoint[]\n\t\totherNodeSnapPoints: BoundsSnapPoint[]\n\t\tminOffset: Vec\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}) {\n\t\t// for each snap point on the bounding box of the selection, find the set of points\n\t\t// which are closest to it in each axis\n\t\tfor (const thisSnapPoint of selectionSnapPoints) {\n\t\t\tfor (const otherSnapPoint of otherNodeSnapPoints) {\n\t\t\t\tconst offset = Vec.Sub(thisSnapPoint, otherSnapPoint)\n\t\t\t\tconst offsetX = Math.abs(offset.x)\n\t\t\t\tconst offsetY = Math.abs(offset.y)\n\n\t\t\t\tif (round(offsetX) <= round(minOffset.x)) {\n\t\t\t\t\tif (round(offsetX) < round(minOffset.x)) {\n\t\t\t\t\t\t// we found a point that is significantly closer than all previous points\n\t\t\t\t\t\t// so wipe the slate clean and start over\n\t\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t\t}\n\n\t\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\t\ttype: 'points',\n\t\t\t\t\t\tpoints: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },\n\t\t\t\t\t\tnudge: otherSnapPoint.x - thisSnapPoint.x,\n\t\t\t\t\t})\n\t\t\t\t\tminOffset.x = offsetX\n\t\t\t\t}\n\n\t\t\t\tif (round(offsetY) <= round(minOffset.y)) {\n\t\t\t\t\tif (round(offsetY) < round(minOffset.y)) {\n\t\t\t\t\t\t// we found a point that is significantly closer than all previous points\n\t\t\t\t\t\t// so wipe the slate clean and start over\n\t\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t\t}\n\t\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\t\ttype: 'points',\n\t\t\t\t\t\tpoints: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },\n\t\t\t\t\t\tnudge: otherSnapPoint.y - thisSnapPoint.y,\n\t\t\t\t\t})\n\t\t\t\t\tminOffset.y = offsetY\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate collectGapSnaps({\n\t\tselectionPageBounds,\n\t\tminOffset,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionPageBounds: Box\n\t\tminOffset: Vec\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}) {\n\t\tconst { horizontal, vertical } = this.getVisibleGaps()\n\n\t\tfor (const gap of horizontal) {\n\t\t\t// ignore this gap if the selection doesn't overlap with it in the y axis\n\t\t\tif (\n\t\t\t\t!rangesOverlap(\n\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for center match\n\t\t\tconst gapMidX = gap.startEdge[0].x + gap.length / 2\n\t\t\tconst centerNudge = gapMidX - selectionPageBounds.center.x\n\t\t\tconst gapIsLargerThanSelection = gap.length > selectionPageBounds.width\n\n\t\t\tif (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(centerNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(centerNudge)\n\n\t\t\t\tconst snap: NearestSnap = {\n\t\t\t\t\ttype: 'gap_center',\n\t\t\t\t\tgap,\n\t\t\t\t\tnudge: centerNudge,\n\t\t\t\t}\n\n\t\t\t\t// we need to avoid creating visual noise with too many center snaps in situations\n\t\t\t\t// where there are lots of adjacent items with even spacing\n\t\t\t\t// so let's only show other center snaps where the gap's breadth does not overlap with this one\n\t\t\t\t// i.e.\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502\n\t\t\t\t// \u2502 \u2502 \u253C\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502 don't show these\n\t\t\t\t// show these \u2502 \u2502 \u253C larger gaps since\n\t\t\t\t// smaller \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 the smaller ones\n\t\t\t\t// gaps \u253C \u2502 cover the same\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u250C\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510 information\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t//\n\t\t\t\t// but we want to show all of these ones since the gap breadths don't overlap\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2518 \u253C\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2510 \u253C\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u250C\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\t\t\t\tconst otherCenterSnap = nearestSnapsX.find(({ type }) => type === 'gap_center') as\n\t\t\t\t\t| Extract<NearestSnap, { type: 'gap_center' }>\n\t\t\t\t\t| undefined\n\n\t\t\t\tconst gapBreadthsOverlap =\n\t\t\t\t\totherCenterSnap &&\n\t\t\t\t\trangeIntersection(\n\t\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[0],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[1]\n\t\t\t\t\t)\n\n\t\t\t\t// if there is another center snap and it's bigger than this one, and it overlaps with this one, replace it\n\t\t\t\tif (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsX[nearestSnapsX.indexOf(otherCenterSnap)] = snap\n\t\t\t\t} else if (!otherCenterSnap || !gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsX.push(snap)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// check for duplication left match\n\t\t\tconst duplicationLeftX = gap.startNode.pageBounds.minX - gap.length\n\t\t\tconst selectionRightX = selectionPageBounds.maxX\n\n\t\t\tconst duplicationLeftNudge = duplicationLeftX - selectionRightX\n\t\t\tif (round(Math.abs(duplicationLeftNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(duplicationLeftNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(duplicationLeftNudge)\n\n\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'left',\n\t\t\t\t\tnudge: duplicationLeftNudge,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// check for duplication right match\n\t\t\tconst duplicationRightX = gap.endNode.pageBounds.maxX + gap.length\n\t\t\tconst selectionLeftX = selectionPageBounds.minX\n\n\t\t\tconst duplicationRightNudge = duplicationRightX - selectionLeftX\n\t\t\tif (round(Math.abs(duplicationRightNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(duplicationRightNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(duplicationRightNudge)\n\n\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'right',\n\t\t\t\t\tnudge: duplicationRightNudge,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tfor (const gap of vertical) {\n\t\t\t// ignore this gap if the selection doesn't overlap with it in the y axis\n\t\t\tif (\n\t\t\t\t!rangesOverlap(\n\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for center match\n\t\t\tconst gapMidY = gap.startEdge[0].y + gap.length / 2\n\t\t\tconst centerNudge = gapMidY - selectionPageBounds.center.y\n\n\t\t\tconst gapIsLargerThanSelection = gap.length > selectionPageBounds.height\n\n\t\t\tif (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(centerNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(centerNudge)\n\n\t\t\t\tconst snap: NearestSnap = {\n\t\t\t\t\ttype: 'gap_center',\n\t\t\t\t\tgap,\n\t\t\t\t\tnudge: centerNudge,\n\t\t\t\t}\n\n\t\t\t\t// we need to avoid creating visual noise with too many center snaps in situations\n\t\t\t\t// where there are lots of adjacent items with even spacing\n\t\t\t\t// so let's only show other center snaps where the gap's breadth does not overlap with this one\n\t\t\t\t// i.e.\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502\n\t\t\t\t// \u2502 \u2502 \u253C\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502 don't show these\n\t\t\t\t// show these \u2502 \u2502 \u253C larger gaps since\n\t\t\t\t// smaller \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 the smaller ones\n\t\t\t\t// gaps \u253C \u2502 cover the same\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u250C\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510 information\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t//\n\t\t\t\t// but we want to show all of these ones since the gap breadths don't overlap\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2518 \u253C\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2510 \u253C\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u250C\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\t\t\t\tconst otherCenterSnap = nearestSnapsY.find(({ type }) => type === 'gap_center') as\n\t\t\t\t\t| Extract<NearestSnap, { type: 'gap_center' }>\n\t\t\t\t\t| undefined\n\n\t\t\t\tconst gapBreadthsOverlap =\n\t\t\t\t\totherCenterSnap &&\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[0],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[1],\n\t\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\t\tgap.breadthIntersection[1]\n\t\t\t\t\t)\n\n\t\t\t\t// if there is another center snap and it's bigger than this one, and it overlaps with this one, replace it\n\t\t\t\tif (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsY[nearestSnapsY.indexOf(otherCenterSnap)] = snap\n\t\t\t\t} else if (!otherCenterSnap || !gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsY.push(snap)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for duplication top match\n\t\t\tconst duplicationTopY = gap.startNode.pageBounds.minY - gap.length\n\t\t\tconst selectionBottomY = selectionPageBounds.maxY\n\n\t\t\tconst duplicationTopNudge = duplicationTopY - selectionBottomY\n\t\t\tif (round(Math.abs(duplicationTopNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(duplicationTopNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(duplicationTopNudge)\n\n\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'top',\n\t\t\t\t\tnudge: duplicationTopNudge,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// check for duplication bottom match\n\t\t\tconst duplicationBottomY = gap.endNode.pageBounds.maxY + gap.length\n\t\t\tconst selectionTopY = selectionPageBounds.minY\n\n\t\t\tconst duplicationBottomNudge = duplicationBottomY - selectionTopY\n\t\t\tif (round(Math.abs(duplicationBottomNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(duplicationBottomNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(duplicationBottomNudge)\n\n\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'bottom',\n\t\t\t\t\tnudge: duplicationBottomNudge,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getPointSnapLines({\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}): PointsSnapIndicator[] {\n\t\t// point snaps may align on multiple parallel lines so we need to split the pairs\n\t\t// into groups based on where they are in their their snap axes\n\t\tconst snapGroupsX = {} as { [key: string]: SnapPair[] }\n\t\tconst snapGroupsY = {} as { [key: string]: SnapPair[] }\n\n\t\tif (nearestSnapsX.length > 0) {\n\t\t\tfor (const snap of nearestSnapsX) {\n\t\t\t\tif (snap.type === 'points') {\n\t\t\t\t\tconst key = round(snap.points.otherPoint.x)\n\t\t\t\t\tif (!snapGroupsX[key]) {\n\t\t\t\t\t\tsnapGroupsX[key] = []\n\t\t\t\t\t}\n\t\t\t\t\tsnapGroupsX[key].push(snap.points)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (nearestSnapsY.length > 0) {\n\t\t\tfor (const snap of nearestSnapsY) {\n\t\t\t\tif (snap.type === 'points') {\n\t\t\t\t\tconst key = round(snap.points.otherPoint.y)\n\t\t\t\t\tif (!snapGroupsY[key]) {\n\t\t\t\t\t\tsnapGroupsY[key] = []\n\t\t\t\t\t}\n\t\t\t\t\tsnapGroupsY[key].push(snap.points)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// and finally create all the snap lines for the UI to render\n\t\treturn Object.values(snapGroupsX)\n\t\t\t.concat(Object.values(snapGroupsY))\n\t\t\t.map((snapGroup) => ({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: dedupe(\n\t\t\t\t\tsnapGroup\n\t\t\t\t\t\t.map((snap) => Vec.From(snap.otherPoint))\n\t\t\t\t\t\t// be sure to nudge over the selection snap points\n\t\t\t\t\t\t.concat(snapGroup.map((snap) => Vec.From(snap.thisPoint))),\n\t\t\t\t\t(a: Vec, b: Vec) => a.equals(b)\n\t\t\t\t),\n\t\t\t}))\n\t}\n\n\tprivate getGapSnapLines({\n\t\tselectionPageBounds,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionPageBounds: Box\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}): GapsSnapIndicator[] {\n\t\tconst { vertical, horizontal } = this.getVisibleGaps()\n\n\t\tconst selectionSides: Record<SelectionEdge, [Vec, Vec]> = {\n\t\t\ttop: selectionPageBounds.sides[0],\n\t\t\tright: selectionPageBounds.sides[1],\n\t\t\t// need bottom and left to be sorted asc, which .sides is not.\n\t\t\tbottom: [selectionPageBounds.corners[3], selectionPageBounds.corners[2]],\n\t\t\tleft: [selectionPageBounds.corners[0], selectionPageBounds.corners[3]],\n\t\t}\n\n\t\tconst result: GapsSnapIndicator[] = []\n\n\t\tif (nearestSnapsX.length > 0) {\n\t\t\tfor (const snap of nearestSnapsX) {\n\t\t\t\tif (snap.type === 'points') continue\n\n\t\t\t\tconst {\n\t\t\t\t\tgap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge },\n\t\t\t\t} = snap\n\n\t\t\t\tswitch (snap.type) {\n\t\t\t\t\tcase 'gap_center': {\n\t\t\t\t\t\t// create\n\t\t\t\t\t\tconst newGapsLength = (length - selectionPageBounds.width) / 2\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t\t\t)!\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'horizontal',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps: [\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge,\n\t\t\t\t\t\t\t\t\tendEdge: selectionSides.left,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.right,\n\t\t\t\t\t\t\t\t\tendEdge,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'gap_duplicate': {\n\t\t\t\t\t\t// create\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t\t\t)!\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'horizontal',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps:\n\t\t\t\t\t\t\t\tsnap.protrusionDirection === 'left'\n\t\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.right,\n\t\t\t\t\t\t\t\t\t\t\t\tendEdge: startEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(-startNode.pageBounds.width, 0)\n\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: endEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(snap.gap.endNode.pageBounds.width, 0)\n\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\tendEdge: selectionSides.left,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (nearestSnapsY.length > 0) {\n\t\t\tfor (const snap of nearestSnapsY) {\n\t\t\t\tif (snap.type === 'points') continue\n\n\t\t\t\tconst {\n\t\t\t\t\tgap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge },\n\t\t\t\t} = snap\n\n\t\t\t\tswitch (snap.type) {\n\t\t\t\t\tcase 'gap_center': {\n\t\t\t\t\t\tconst newGapsLength = (length - selectionPageBounds.height) / 2\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t\t\t)!\n\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'vertical',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps: [\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge,\n\t\t\t\t\t\t\t\t\tendEdge: selectionSides.top,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.bottom,\n\t\t\t\t\t\t\t\t\tendEdge,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\tsnap.gap.endNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'gap_duplicate':\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t\t\t\t)!\n\n\t\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\t\tdirection: 'vertical',\n\t\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\t\tgaps:\n\t\t\t\t\t\t\t\t\tsnap.protrusionDirection === 'top'\n\t\t\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.bottom,\n\t\t\t\t\t\t\t\t\t\t\t\t\tendEdge: startEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(0, -startNode.pageBounds.height)\n\t\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: endEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(0, endNode.pageBounds.height)\n\t\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\t\tendEdge: selectionSides.top,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdedupeGapSnaps(result)\n\t\treturn result\n\t}\n}\n\nfunction getResizeSnapPointsForHandle(\n\thandle: SelectionCorner | SelectionEdge | 'any',\n\tselectionPageBounds: Box\n): BoundsSnapPoint[] {\n\tconst { minX, maxX, minY, maxY } = selectionPageBounds\n\tconst result: BoundsSnapPoint[] = []\n\n\t// top left corner\n\tswitch (handle) {\n\t\tcase 'top':\n\t\tcase 'left':\n\t\tcase 'top_left':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'top_left',\n\t\t\t\thandle: 'top_left',\n\t\t\t\tx: minX,\n\t\t\t\ty: minY,\n\t\t\t})\n\t}\n\n\t// top right corner\n\tswitch (handle) {\n\t\tcase 'top':\n\t\tcase 'right':\n\t\tcase 'top_right':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'top_right',\n\t\t\t\thandle: 'top_right',\n\t\t\t\tx: maxX,\n\t\t\t\ty: minY,\n\t\t\t})\n\t}\n\n\t// bottom right corner\n\tswitch (handle) {\n\t\tcase 'bottom':\n\t\tcase 'right':\n\t\tcase 'bottom_right':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'bottom_right',\n\t\t\t\thandle: 'bottom_right',\n\t\t\t\tx: maxX,\n\t\t\t\ty: maxY,\n\t\t\t})\n\t}\n\n\t// bottom left corner\n\tswitch (handle) {\n\t\tcase 'bottom':\n\t\tcase 'left':\n\t\tcase 'bottom_left':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'bottom_left',\n\t\t\t\thandle: 'bottom_left',\n\t\t\t\tx: minX,\n\t\t\t\ty: maxY,\n\t\t\t})\n\t}\n\n\treturn result\n}\n"],
5
- "mappings": ";;;;;;;;;;AAAA,SAAS,gBAAgB;AAEzB,SAAS,cAAc,QAAQ,gBAAgB;AAC/C;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,WAAW;AACpB,SAAS,WAAW;AACpB,SAAS,mBAAmB,qBAAqB;AA8FjD,MAAM,QAAQ,CAAC,MAAc;AAE5B,QAAM,yBAAyB;AAC/B,SAAO,KAAK,MAAM,IAAI,MAAM,sBAAsB,IAAI,MAAM;AAC7D;AAEA,SAAS,iBACR,MACA,SACA,WACA,WACA,cACQ;AAER,QAAM,UAAU,KAAK;AAAA,IACpB,CAAC,SACC,cAAc,YAAY,IAAI,UAAU,OAAO,UAAU,IAAI,QAAQ,OAAO,YAC7E,MAAM,IAAI,MAAM,MAAM,MAAM,SAAS,KACrC;AAAA,MACC,IAAI,oBAAoB,CAAC;AAAA,MACzB,IAAI,oBAAoB,CAAC;AAAA,MACzB,aAAa,CAAC;AAAA,MACd,aAAa,CAAC;AAAA,IACf;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,QAAM,YAAY,oBAAI,IAAe;AAErC,UAAQ,QAAQ,CAAC,UAAU;AAC1B,UAAM,OAAO,cAAc,YAAY,MAAM,QAAQ,KAAK,MAAM,UAAU;AAC1E,QAAI,CAAC,UAAU,IAAI,IAAI,GAAG;AACzB,gBAAU,IAAI,IAAI;AAClB,YAAM,YAAY;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACC,MAAM,oBAAoB,CAAC;AAAA,UAC3B,MAAM,oBAAoB,CAAC;AAAA,UAC3B,aAAa,CAAC;AAAA,UACd,aAAa,CAAC;AAAA,QACf;AAAA,MACD;AAEA,cAAQ,KAAK,GAAG,SAAS;AAAA,IAC1B;AAAA,EACD,CAAC;AAED,SAAO;AACR;AAEA,SAAS,eAAe,OAAwD;AAE/E,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AAElD,WAAS,IAAI,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK;AAC1C,UAAM,OAAO,MAAM,CAAC;AACpB,aAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAChC,YAAM,YAAY,MAAM,CAAC;AAEzB,UACC,UAAU,cAAc,KAAK,aAC7B,KAAK,KAAK;AAAA,QACT,CAAC,QACA,UAAU,KAAK;AAAA,UACd,CAAC,aACA,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC;AAAA,QAC7D,KACA,UAAU,KAAK;AAAA,UACd,CAAC,aACA,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC;AAAA,QACzD;AAAA,MACF,GACC;AACD,cAAM,OAAO,GAAG,CAAC;AACjB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;AAGO,MAAM,YAAY;AAAA,EAExB,YAAqB,SAAsB;AAAtB;AACpB,SAAK,SAAS,QAAQ;AAAA,EACvB;AAAA,EAHS;AAAA,EAKS,qBAAqB;AACtC,UAAM,EAAE,OAAO,IAAI;AACnB,WAAO,OAAO,MAAM,oBAAgD,cAAc,CAAC,UAAU;AAC5F,YAAM,gBAAgB,OAAO,sBAAsB,MAAM,EAAE;AAC3D,UAAI,CAAC,cAAe,QAAO;AAC3B,YAAM,qBAAqB,OAAO,aAAa,KAAK,EAAE,sBAAsB,KAAK;AACjF,YAAM,aACL,mBAAmB,UAAU,OAAO,iBAAiB,KAAK,EAAE,OAAO;AAEpE,UAAI,CAAC,iBAAiB,CAAC,WAAY,QAAO;AAC1C,aAAO,WAAW,IAAI,CAAC,OAAO,MAAM;AACnC,cAAM,EAAE,GAAG,EAAE,IAAI,IAAI,aAAa,eAAe,KAAK;AACtD,eAAO,EAAE,GAAG,GAAG,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC,GAAG;AAAA,MACvC,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAAA,EAEA,cAAc,SAAoB;AACjC,WAAO,KAAK,mBAAmB,EAAE,IAAI,OAAO,KAAK,CAAC;AAAA,EACnD;AAAA,EAGkB,qBAAqB;AACtC,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,kBAAkB,KAAK,QAAQ,mBAAmB;AACxD,UAAM,SAA4B,CAAC;AAEnC,eAAW,WAAW,iBAAiB;AACtC,YAAM,aAAa,gBAAgB,IAAI,OAAO;AAC9C,UAAI,YAAY;AACf,eAAO,KAAK,GAAG,UAAU;AAAA,MAC1B;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA,EAEkB,uBAAuC;AACxD,WAAO,MAAM,KAAK,KAAK,QAAQ,mBAAmB,GAAG,CAAC,aAAa;AAAA,MAClE,IAAI;AAAA,MACJ,YAAY,aAAa,KAAK,OAAO,mBAAmB,OAAO,CAAC;AAAA,IACjE,EAAE;AAAA,EACH;AAAA,EAEkB,iBAAyD;AAC1E,UAAM,aAAoB,CAAC;AAC3B,UAAM,WAAkB,CAAC;AAEzB,QAAI,WAAoB;AAExB,UAAM,sCAAsC,KAAK,qBAAqB,EAAE,KAAK,CAAC,GAAG,MAAM;AACtF,aAAO,EAAE,WAAW,OAAO,EAAE,WAAW;AAAA,IACzC,CAAC;AAGD,aAAS,IAAI,GAAG,IAAI,oCAAoC,QAAQ,KAAK;AACpE,kBAAY,oCAAoC,CAAC;AACjD,eAAS,IAAI,IAAI,GAAG,IAAI,oCAAoC,QAAQ,KAAK;AACxE,kBAAU,oCAAoC,CAAC;AAE/C;AAAA;AAAA,UAEC,UAAU,WAAW,OAAO,QAAQ,WAAW;AAAA,UAE/C;AAAA,YACC,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,YACrB,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,UACpB;AAAA,UACC;AACD,qBAAW,KAAK;AAAA,YACf;AAAA,YACA;AAAA,YACA,WAAW;AAAA,cACV,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,cAC5D,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,YAC7D;AAAA,YACA,SAAS;AAAA,cACR,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,cACxD,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,YACzD;AAAA,YACA,QAAQ,QAAQ,WAAW,OAAO,UAAU,WAAW;AAAA,YACvD,qBAAqB;AAAA,cACpB,UAAU,WAAW;AAAA,cACrB,UAAU,WAAW;AAAA,cACrB,QAAQ,WAAW;AAAA,cACnB,QAAQ,WAAW;AAAA,YACpB;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAGA,UAAM,oCAAoC,oCAAoC,KAAK,CAAC,GAAG,MAAM;AAC5F,aAAO,EAAE,WAAW,OAAO,EAAE,WAAW;AAAA,IACzC,CAAC;AAED,aAAS,IAAI,GAAG,IAAI,kCAAkC,QAAQ,KAAK;AAClE,kBAAY,kCAAkC,CAAC;AAC/C,eAAS,IAAI,IAAI,GAAG,IAAI,kCAAkC,QAAQ,KAAK;AACtE,kBAAU,kCAAkC,CAAC;AAE7C;AAAA;AAAA,UAEC,UAAU,WAAW,OAAO,QAAQ,WAAW;AAAA,UAE/C;AAAA,YACC,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,YACrB,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,UACpB;AAAA,UACC;AACD,mBAAS,KAAK;AAAA,YACb;AAAA,YACA;AAAA,YACA,WAAW;AAAA,cACV,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,cAC5D,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,YAC7D;AAAA,YACA,SAAS;AAAA,cACR,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,cACxD,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,YACzD;AAAA,YACA,QAAQ,QAAQ,WAAW,OAAO,UAAU,WAAW;AAAA,YACvD,qBAAqB;AAAA,cACpB,UAAU,WAAW;AAAA,cACrB,UAAU,WAAW;AAAA,cACrB,QAAQ,WAAW;AAAA,cACnB,QAAQ,WAAW;AAAA,YACpB;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAEA,WAAO,EAAE,YAAY,SAAS;AAAA,EAC/B;AAAA,EAEA,oBAAoB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKa;AACZ,UAAM,gBAAgB,KAAK,QAAQ,iBAAiB;AACpD,UAAM,kCAAkC,KAAK,mBAAmB;AAEhE,UAAM,sBAAsB,2BAA2B,MAAM,EAAE,UAAU,SAAS;AAElF,UAAM,sBAAyC,2BAA2B;AAAA,MACzE,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO;AAAA,QACjB,IAAI,eAAe;AAAA,QACnB,GAAG,IAAI,UAAU;AAAA,QACjB,GAAG,IAAI,UAAU;AAAA,MAClB;AAAA,IACD;AAEA,UAAM,sBAAsB;AAE5B,UAAM,gBAA+B,CAAC;AACtC,UAAM,gBAA+B,CAAC;AACtC,UAAM,YAAY,IAAI,IAAI,eAAe,aAAa;AAEtD,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAGD,UAAM,QAAQ,IAAI;AAAA,MACjB,eAAe,MAAM,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,MACrD,eAAe,MAAM,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,IACtD;AAKA,cAAU,IAAI;AACd,cAAU,IAAI;AACd,kBAAc,SAAS;AACvB,kBAAc,SAAS;AACvB,wBAAoB,QAAQ,CAAC,MAAM;AAClC,QAAE,KAAK,MAAM;AACb,QAAE,KAAK,MAAM;AAAA,IACd,CAAC;AACD,wBAAoB,UAAU,KAAK;AAEnC,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,UAAM,kBAAkB,KAAK,kBAAkB;AAAA,MAC9C;AAAA,MACA;AAAA,IACD,CAAC;AAED,UAAM,eAAe,KAAK,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,QAAQ,cAAc,CAAC,GAAG,cAAc,GAAG,eAAe,CAAC;AAEhE,WAAO,EAAE,MAAM;AAAA,EAChB;AAAA,EAEA,iBAAiB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACD,GASa;AACZ,UAAM,gBAAgB,KAAK,QAAQ,iBAAiB;AAGpD,UAAM;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACD,IAAI,IAAI;AAAA,MACP;AAAA,MACA;AAAA,MACA,uBAAuB,UAAU,IAAI,IAAI,UAAU;AAAA,MACnD,uBAAuB,UAAU,IAAI,IAAI,UAAU;AAAA,MACnD;AAAA,IACD;AAEA,QAAI,SAAS;AAEb,QAAI,SAAS,GAAG;AACf,eAAS,qBAAqB,MAAM;AAAA,IACrC;AACA,QAAI,SAAS,GAAG;AACf,eAAS,qBAAqB,MAAM;AAAA,IACrC;AAEA,QAAI,sBAAsB;AAEzB,iCAA2B,SAAS,2BAA2B;AAAA,IAChE;AAEA,UAAM,YAAY,WAAW,SAAS,WAAW;AACjD,UAAM,YAAY,WAAW,UAAU,WAAW;AAElD,UAAM,sBAAsB,6BAA6B,QAAQ,0BAA0B;AAE3F,UAAM,sBAAsB,KAAK,mBAAmB;AAEpD,UAAM,gBAAqC,CAAC;AAC5C,UAAM,gBAAqC,CAAC;AAC5C,UAAM,YAAY,IAAI,IAAI,eAAe,aAAa;AAEtD,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAGD,UAAM,QAAQ,IAAI;AAAA,MACjB,YAAY,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,MAC5C,YAAY,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,IAC7C;AAEA,QAAI,uBAAuB,kBAAkB,MAAM,KAAK,MAAM,IAAI,MAAM,GAAG;AAM1E,YAAM,mBACL,cAAc,UAAU,cAAc,SACnC,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,IACnC,MACA,MACD,cAAc,SACb,MACA;AAEL,YAAM,QAAQ,2BAA2B;AAEzC,UAAI,qBAAqB,KAAK;AAC7B,sBAAc,SAAS;AACvB,cAAM,IAAI,MAAM,IAAI;AACpB,YAAI,WAAW,iBAAiB,WAAW,aAAa;AACvD,gBAAM,IAAI,CAAC,MAAM;AAAA,QAClB;AAAA,MACD,OAAO;AACN,sBAAc,SAAS;AACvB,cAAM,IAAI,MAAM,IAAI;AACpB,YAAI,WAAW,iBAAiB,WAAW,aAAa;AACvD,gBAAM,IAAI,CAAC,MAAM;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAIA,UAAM,eAAe,IAAI,IAAI,WAAW,KAAK;AAG7C,UAAM,EAAE,KAAK,yBAAyB,IAAI,IAAI;AAAA,MAC7C;AAAA,MACA;AAAA,MACA,uBAAuB,aAAa,IAAI,IAAI,aAAa;AAAA,MACzD,uBAAuB,aAAa,IAAI,IAAI,aAAa;AAAA,MACzD;AAAA,IACD;AAEA,QAAI,sBAAsB;AAEzB,+BAAyB,SAAS,2BAA2B;AAAA,IAC9D;AAEA,UAAM,yBAAyB,6BAA6B,OAAO,wBAAwB;AAE3F,kBAAc,SAAS;AACvB,kBAAc,SAAS;AACvB,cAAU,IAAI;AACd,cAAU,IAAI;AAEd,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,qBAAqB;AAAA,IACtB,CAAC;AACD,UAAM,aAAa,KAAK,kBAAkB;AAAA,MACzC;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,QAAQ,cAAc,CAAC,GAAG,UAAU,CAAC;AAE1C,WAAO,EAAE,MAAM;AAAA,EAChB;AAAA,EAEQ,kBAAkB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAMG;AAGF,eAAW,iBAAiB,qBAAqB;AAChD,iBAAW,kBAAkB,qBAAqB;AACjD,cAAM,SAAS,IAAI,IAAI,eAAe,cAAc;AACpD,cAAM,UAAU,KAAK,IAAI,OAAO,CAAC;AACjC,cAAM,UAAU,KAAK,IAAI,OAAO,CAAC;AAEjC,YAAI,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,GAAG;AACzC,cAAI,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;AAGxC,0BAAc,SAAS;AAAA,UACxB;AAEA,wBAAc,KAAK;AAAA,YAClB,MAAM;AAAA,YACN,QAAQ,EAAE,WAAW,eAAe,YAAY,eAAe;AAAA,YAC/D,OAAO,eAAe,IAAI,cAAc;AAAA,UACzC,CAAC;AACD,oBAAU,IAAI;AAAA,QACf;AAEA,YAAI,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,GAAG;AACzC,cAAI,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;AAGxC,0BAAc,SAAS;AAAA,UACxB;AACA,wBAAc,KAAK;AAAA,YAClB,MAAM;AAAA,YACN,QAAQ,EAAE,WAAW,eAAe,YAAY,eAAe;AAAA,YAC/D,OAAO,eAAe,IAAI,cAAc;AAAA,UACzC,CAAC;AACD,oBAAU,IAAI;AAAA,QACf;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,gBAAgB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKG;AACF,UAAM,EAAE,YAAY,SAAS,IAAI,KAAK,eAAe;AAErD,eAAW,OAAO,YAAY;AAE7B,UACC,CAAC;AAAA,QACA,IAAI,oBAAoB,CAAC;AAAA,QACzB,IAAI,oBAAoB,CAAC;AAAA,QACzB,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,MACrB,GACC;AACD;AAAA,MACD;AAGA,YAAM,UAAU,IAAI,UAAU,CAAC,EAAE,IAAI,IAAI,SAAS;AAClD,YAAM,cAAc,UAAU,oBAAoB,OAAO;AACzD,YAAM,2BAA2B,IAAI,SAAS,oBAAoB;AAElE,UAAI,4BAA4B,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACnF,YAAI,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEtD,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,WAAW;AAElC,cAAM,OAAoB;AAAA,UACzB,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,QACR;AA2CA,cAAM,kBAAkB,cAAc,KAAK,CAAC,EAAE,KAAK,MAAM,SAAS,YAAY;AAI9E,cAAM,qBACL,mBACA;AAAA,UACC,IAAI,oBAAoB,CAAC;AAAA,UACzB,IAAI,oBAAoB,CAAC;AAAA,UACzB,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,QAC1C;AAGD,YAAI,mBAAmB,gBAAgB,IAAI,SAAS,IAAI,UAAU,oBAAoB;AACrF,wBAAc,cAAc,QAAQ,eAAe,CAAC,IAAI;AAAA,QACzD,WAAW,CAAC,mBAAmB,CAAC,oBAAoB;AACnD,wBAAc,KAAK,IAAI;AAAA,QACxB;AAAA,MACD;AAGA,YAAM,mBAAmB,IAAI,UAAU,WAAW,OAAO,IAAI;AAC7D,YAAM,kBAAkB,oBAAoB;AAE5C,YAAM,uBAAuB,mBAAmB;AAChD,UAAI,MAAM,KAAK,IAAI,oBAAoB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAChE,YAAI,MAAM,KAAK,IAAI,oBAAoB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAE/D,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,oBAAoB;AAE3C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAGA,YAAM,oBAAoB,IAAI,QAAQ,WAAW,OAAO,IAAI;AAC5D,YAAM,iBAAiB,oBAAoB;AAE3C,YAAM,wBAAwB,oBAAoB;AAClD,UAAI,MAAM,KAAK,IAAI,qBAAqB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACjE,YAAI,MAAM,KAAK,IAAI,qBAAqB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEhE,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,qBAAqB;AAE5C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,IACD;AAEA,eAAW,OAAO,UAAU;AAE3B,UACC,CAAC;AAAA,QACA,IAAI,oBAAoB,CAAC;AAAA,QACzB,IAAI,oBAAoB,CAAC;AAAA,QACzB,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,MACrB,GACC;AACD;AAAA,MACD;AAGA,YAAM,UAAU,IAAI,UAAU,CAAC,EAAE,IAAI,IAAI,SAAS;AAClD,YAAM,cAAc,UAAU,oBAAoB,OAAO;AAEzD,YAAM,2BAA2B,IAAI,SAAS,oBAAoB;AAElE,UAAI,4BAA4B,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACnF,YAAI,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEtD,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,WAAW;AAElC,cAAM,OAAoB;AAAA,UACzB,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,QACR;AA2CA,cAAM,kBAAkB,cAAc,KAAK,CAAC,EAAE,KAAK,MAAM,SAAS,YAAY;AAI9E,cAAM,qBACL,mBACA;AAAA,UACC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,IAAI,oBAAoB,CAAC;AAAA,UACzB,IAAI,oBAAoB,CAAC;AAAA,QAC1B;AAGD,YAAI,mBAAmB,gBAAgB,IAAI,SAAS,IAAI,UAAU,oBAAoB;AACrF,wBAAc,cAAc,QAAQ,eAAe,CAAC,IAAI;AAAA,QACzD,WAAW,CAAC,mBAAmB,CAAC,oBAAoB;AACnD,wBAAc,KAAK,IAAI;AAAA,QACxB;AACA;AAAA,MACD;AAGA,YAAM,kBAAkB,IAAI,UAAU,WAAW,OAAO,IAAI;AAC5D,YAAM,mBAAmB,oBAAoB;AAE7C,YAAM,sBAAsB,kBAAkB;AAC9C,UAAI,MAAM,KAAK,IAAI,mBAAmB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAC/D,YAAI,MAAM,KAAK,IAAI,mBAAmB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAE9D,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,mBAAmB;AAE1C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAGA,YAAM,qBAAqB,IAAI,QAAQ,WAAW,OAAO,IAAI;AAC7D,YAAM,gBAAgB,oBAAoB;AAE1C,YAAM,yBAAyB,qBAAqB;AACpD,UAAI,MAAM,KAAK,IAAI,sBAAsB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAClE,YAAI,MAAM,KAAK,IAAI,sBAAsB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEjE,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,sBAAsB;AAE7C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,kBAAkB;AAAA,IACzB;AAAA,IACA;AAAA,EACD,GAG0B;AAGzB,UAAM,cAAc,CAAC;AACrB,UAAM,cAAc,CAAC;AAErB,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,UAAU;AAC3B,gBAAM,MAAM,MAAM,KAAK,OAAO,WAAW,CAAC;AAC1C,cAAI,CAAC,YAAY,GAAG,GAAG;AACtB,wBAAY,GAAG,IAAI,CAAC;AAAA,UACrB;AACA,sBAAY,GAAG,EAAE,KAAK,KAAK,MAAM;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AAEA,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,UAAU;AAC3B,gBAAM,MAAM,MAAM,KAAK,OAAO,WAAW,CAAC;AAC1C,cAAI,CAAC,YAAY,GAAG,GAAG;AACtB,wBAAY,GAAG,IAAI,CAAC;AAAA,UACrB;AACA,sBAAY,GAAG,EAAE,KAAK,KAAK,MAAM;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AAGA,WAAO,OAAO,OAAO,WAAW,EAC9B,OAAO,OAAO,OAAO,WAAW,CAAC,EACjC,IAAI,CAAC,eAAe;AAAA,MACpB,IAAI,SAAS;AAAA,MACb,MAAM;AAAA,MACN,QAAQ;AAAA,QACP,UACE,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,UAAU,CAAC,EAEvC,OAAO,UAAU,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC;AAAA,QAC1D,CAAC,GAAQ,MAAW,EAAE,OAAO,CAAC;AAAA,MAC/B;AAAA,IACD,EAAE;AAAA,EACJ;AAAA,EAEQ,gBAAgB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAIwB;AACvB,UAAM,EAAE,UAAU,WAAW,IAAI,KAAK,eAAe;AAErD,UAAM,iBAAoD;AAAA,MACzD,KAAK,oBAAoB,MAAM,CAAC;AAAA,MAChC,OAAO,oBAAoB,MAAM,CAAC;AAAA;AAAA,MAElC,QAAQ,CAAC,oBAAoB,QAAQ,CAAC,GAAG,oBAAoB,QAAQ,CAAC,CAAC;AAAA,MACvE,MAAM,CAAC,oBAAoB,QAAQ,CAAC,GAAG,oBAAoB,QAAQ,CAAC,CAAC;AAAA,IACtE;AAEA,UAAM,SAA8B,CAAC;AAErC,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,SAAU;AAE5B,cAAM;AAAA,UACL,KAAK,EAAE,qBAAqB,WAAW,WAAW,SAAS,QAAQ,QAAQ;AAAA,QAC5E,IAAI;AAEJ,gBAAQ,KAAK,MAAM;AAAA,UAClB,KAAK,cAAc;AAElB,kBAAM,iBAAiB,SAAS,oBAAoB,SAAS;AAC7D,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AACA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MAAM;AAAA,gBACL,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA;AAAA,kBACC;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B;AAAA,gBACD;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,QAAQ;AAAA,kBACR;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YACD,CAAC;AACD;AAAA,UACD;AAAA,UACA,KAAK,iBAAiB;AAErB,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AACA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MACC,KAAK,wBAAwB,SAC1B;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B,SAAS,UAAU;AAAA,oBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,CAAC,UAAU,WAAW,OAAO,CAAC;AAAA,kBAC/C;AAAA,gBACD;AAAA,gBACA,EAAE,WAAW,QAAQ;AAAA,gBACrB,GAAG;AAAA,kBACF;AAAA,kBACA,QAAQ;AAAA,kBACR;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD,IACC;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA,EAAE,WAAW,QAAQ;AAAA,gBACrB;AAAA,kBACC,WAAW,QAAQ;AAAA,oBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,QAAQ,WAAW,OAAO,CAAC;AAAA,kBACrD;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,cACD;AAAA,YACJ,CAAC;AAED;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,SAAU;AAE5B,cAAM;AAAA,UACL,KAAK,EAAE,qBAAqB,WAAW,WAAW,SAAS,QAAQ,QAAQ;AAAA,QAC5E,IAAI;AAEJ,gBAAQ,KAAK,MAAM;AAAA,UAClB,KAAK,cAAc;AAClB,kBAAM,iBAAiB,SAAS,oBAAoB,UAAU;AAC9D,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AAEA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MAAM;AAAA,gBACL,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA;AAAA,kBACC;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B;AAAA,gBACD;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,KAAK,IAAI,QAAQ;AAAA,kBACjB;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YACD,CAAC;AACD;AAAA,UACD;AAAA,UACA,KAAK;AACJ;AACC,oBAAM,yBAAyB;AAAA,gBAC9B,oBAAoB,CAAC;AAAA,gBACrB,oBAAoB,CAAC;AAAA,gBACrB,oBAAoB;AAAA,gBACpB,oBAAoB;AAAA,cACrB;AAEA,qBAAO,KAAK;AAAA,gBACX,MAAM;AAAA,gBACN,WAAW;AAAA,gBACX,IAAI,SAAS;AAAA,gBACb,MACC,KAAK,wBAAwB,QAC1B;AAAA,kBACA;AAAA,oBACC,WAAW,eAAe;AAAA,oBAC1B,SAAS,UAAU;AAAA,sBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,UAAU,WAAW,MAAM;AAAA,oBAChD;AAAA,kBACD;AAAA,kBACA,EAAE,WAAW,QAAQ;AAAA,kBACrB,GAAG;AAAA,oBACF;AAAA,oBACA,QAAQ;AAAA,oBACR;AAAA,oBACA;AAAA,oBACA;AAAA,kBACD;AAAA,gBACD,IACC;AAAA,kBACA,GAAG;AAAA,oBACF;AAAA,oBACA,UAAU;AAAA,oBACV;AAAA,oBACA;AAAA,oBACA;AAAA,kBACD;AAAA,kBACA,EAAE,WAAW,QAAQ;AAAA,kBACrB;AAAA,oBACC,WAAW,QAAQ;AAAA,sBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,WAAW,MAAM;AAAA,oBAC7C;AAAA,oBACA,SAAS,eAAe;AAAA,kBACzB;AAAA,gBACD;AAAA,cACJ,CAAC;AAAA,YACF;AACA;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAEA,mBAAe,MAAM;AACrB,WAAO;AAAA,EACR;AACD;AAv+BmB;AAAA,EAAjB;AAAA,GANW,YAMM;AAsBA;AAAA,EAAjB;AAAA,GA5BW,YA4BM;AAeA;AAAA,EAAjB;AAAA,GA3CW,YA2CM;AAOA;AAAA,EAAjB;AAAA,GAlDW,YAkDM;AA67BnB,SAAS,6BACR,QACA,qBACoB;AACpB,QAAM,EAAE,MAAM,MAAM,MAAM,KAAK,IAAI;AACnC,QAAM,SAA4B,CAAC;AAGnC,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAEA,SAAO;AACR;",
4
+ "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { TLShape, TLShapeId, VecModel } from '@tldraw/tlschema'\nimport { assertExists, dedupe, uniqueId } from '@tldraw/utils'\nimport {\n\tBox,\n\tSelectionCorner,\n\tSelectionEdge,\n\tflipSelectionHandleX,\n\tflipSelectionHandleY,\n\tisSelectionCorner,\n} from '../../../primitives/Box'\nimport { Mat } from '../../../primitives/Mat'\nimport { rangeIntersection, rangesOverlap } from '../../../primitives/utils'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\nimport type {\n\tGapsSnapIndicator,\n\tPointsSnapIndicator,\n\tSnapData,\n\tSnapIndicator,\n\tSnapManager,\n} from './SnapManager'\n\n/**\n * When moving or resizing shapes, the bounds of the shape can snap to key geometry on other nearby\n * shapes. Customize how a shape snaps to others with {@link ShapeUtil.getBoundsSnapGeometry}.\n *\n * @public\n */\nexport interface BoundsSnapGeometry {\n\t/**\n\t * Points that this shape will snap to. By default, this will be the corners and center of the\n\t * shapes bounding box. To disable snapping to a specific point, use an empty array.\n\t */\n\tpoints?: VecModel[]\n}\n\n/** @public */\nexport interface BoundsSnapPoint {\n\tid: string\n\tx: number\n\ty: number\n\thandle?: SelectionCorner\n}\n\ninterface SnapPair {\n\tthisPoint: BoundsSnapPoint\n\totherPoint: BoundsSnapPoint\n}\n\ninterface NearestPointsSnap {\n\t// selection snaps to a nearby snap point\n\ttype: 'points'\n\tpoints: SnapPair\n\tnudge: number\n}\n\ntype NearestSnap =\n\t| NearestPointsSnap\n\t| {\n\t\t\t// selection snaps to the center of a gap\n\t\t\ttype: 'gap_center'\n\t\t\tgap: Gap\n\t\t\tnudge: number\n\t }\n\t| {\n\t\t\t// selection snaps to create a new gap of equal size to another gap\n\t\t\t// on the opposite side of some shape\n\t\t\ttype: 'gap_duplicate'\n\t\t\tgap: Gap\n\t\t\tprotrusionDirection: 'left' | 'right' | 'top' | 'bottom'\n\t\t\tnudge: number\n\t }\n\ninterface GapNode {\n\tid: TLShapeId\n\tpageBounds: Box\n}\n\ninterface Gap {\n\t// e.g.\n\t// start\n\t// edge \u2502 breadth\n\t// \u2502 intersection\n\t// \u25BC [40,100] end\n\t// \u2502 \u2502 edge\n\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 100,0 \u2502 \u2502\n\t// \u2502 \u2502 \u2502 \u25BC \u25BC\n\t// \u2502 \u2502 \u2502\n\t// \u2502 start \u2502 \u2502 \u2502 200,40 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t// \u2502 node \u2502 \u2502 \u2502 \u2502 \u2502 \u2502\n\t// \u2502 \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 \u2502 end \u2502\n\t// \u2502 \u2502 \u2502 \u2502 \u2502 \u2502 node \u2502\n\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 100,100 \u2502 \u2502 \u2502 \u2502\n\t// \u2502 \u2502 \u2502\n\t// 200,120 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t//\n\t// length 100\n\t// \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA\n\tstartNode: GapNode\n\tendNode: GapNode\n\tstartEdge: [Vec, Vec]\n\tendEdge: [Vec, Vec]\n\tlength: number\n\tbreadthIntersection: [number, number]\n}\n\nconst round = (x: number) => {\n\t// round numbers to avoid glitches for floating point rounding errors\n\tconst decimalPlacesTolerance = 8\n\treturn Math.round(x * 10 ** decimalPlacesTolerance) / 10 ** decimalPlacesTolerance\n}\n\nfunction findAdjacentGaps(\n\tgaps: Gap[],\n\tshapeId: TLShapeId,\n\tgapLength: number,\n\tdirection: 'forward' | 'backward',\n\tintersection: [number, number]\n): Gap[] {\n\t// TODO: take advantage of the fact that gaps is sorted by starting position?\n\tconst matches = gaps.filter(\n\t\t(gap) =>\n\t\t\t(direction === 'forward' ? gap.startNode.id === shapeId : gap.endNode.id === shapeId) &&\n\t\t\tround(gap.length) === round(gapLength) &&\n\t\t\trangeIntersection(\n\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\tintersection[0],\n\t\t\t\tintersection[1]\n\t\t\t)\n\t)\n\n\tif (matches.length === 0) return []\n\n\tconst nextNodes = new Set<TLShapeId>()\n\n\tmatches.forEach((match) => {\n\t\tconst node = direction === 'forward' ? match.endNode.id : match.startNode.id\n\t\tif (!nextNodes.has(node)) {\n\t\t\tnextNodes.add(node)\n\t\t\tconst foundGaps = findAdjacentGaps(\n\t\t\t\tgaps,\n\t\t\t\tnode,\n\t\t\t\tgapLength,\n\t\t\t\tdirection,\n\t\t\t\trangeIntersection(\n\t\t\t\t\tmatch.breadthIntersection[0],\n\t\t\t\t\tmatch.breadthIntersection[1],\n\t\t\t\t\tintersection[0],\n\t\t\t\t\tintersection[1]\n\t\t\t\t)!\n\t\t\t)\n\n\t\t\tmatches.push(...foundGaps)\n\t\t}\n\t})\n\n\treturn matches\n}\n\nfunction dedupeGapSnaps(snaps: Array<Extract<SnapIndicator, { type: 'gaps' }>>) {\n\t// sort by descending order of number of gaps\n\tsnaps.sort((a, b) => b.gaps.length - a.gaps.length)\n\t// pop off any that are included already\n\tfor (let i = snaps.length - 1; i > 0; i--) {\n\t\tconst snap = snaps[i]\n\t\tfor (let j = i - 1; j >= 0; j--) {\n\t\t\tconst otherSnap = snaps[j]\n\t\t\t// if every edge in this snap is included in the other snap somewhere, then it's redundant\n\t\t\tif (\n\t\t\t\totherSnap.direction === snap.direction &&\n\t\t\t\tsnap.gaps.every(\n\t\t\t\t\t(gap) =>\n\t\t\t\t\t\totherSnap.gaps.some(\n\t\t\t\t\t\t\t(otherGap) =>\n\t\t\t\t\t\t\t\tround(gap.startEdge[0].x) === round(otherGap.startEdge[0].x) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[0].y) === round(otherGap.startEdge[0].y) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[1].x) === round(otherGap.startEdge[1].x) &&\n\t\t\t\t\t\t\t\tround(gap.startEdge[1].y) === round(otherGap.startEdge[1].y)\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\totherSnap.gaps.some(\n\t\t\t\t\t\t\t(otherGap) =>\n\t\t\t\t\t\t\t\tround(gap.endEdge[0].x) === round(otherGap.endEdge[0].x) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[0].y) === round(otherGap.endEdge[0].y) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[1].x) === round(otherGap.endEdge[1].x) &&\n\t\t\t\t\t\t\t\tround(gap.endEdge[1].y) === round(otherGap.endEdge[1].y)\n\t\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tsnaps.splice(i, 1)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** @public */\nexport class BoundsSnaps {\n\treadonly editor: Editor\n\tconstructor(readonly manager: SnapManager) {\n\t\tthis.editor = manager.editor\n\t}\n\n\t@computed private getSnapPointsCache() {\n\t\tconst { editor } = this\n\t\treturn editor.store.createComputedCache<BoundsSnapPoint[], TLShape>('snapPoints', (shape) => {\n\t\t\tconst pageTransform = editor.getShapePageTransform(shape.id)\n\t\t\tif (!pageTransform) return undefined\n\t\t\tconst boundsSnapGeometry = editor.getShapeUtil(shape).getBoundsSnapGeometry(shape)\n\t\t\tconst snapPoints =\n\t\t\t\tboundsSnapGeometry.points ?? editor.getShapeGeometry(shape).bounds.cornersAndCenter\n\n\t\t\tif (!pageTransform || !snapPoints) return undefined\n\t\t\treturn snapPoints.map((point, i) => {\n\t\t\t\tconst { x, y } = Mat.applyToPoint(pageTransform, point)\n\t\t\t\treturn { x, y, id: `${shape.id}:${i}` }\n\t\t\t})\n\t\t})\n\t}\n\n\tgetSnapPoints(shapeId: TLShapeId) {\n\t\treturn this.getSnapPointsCache().get(shapeId) ?? []\n\t}\n\n\t// Points which belong to snappable shapes\n\t@computed private getSnappablePoints() {\n\t\tconst snapPointsCache = this.getSnapPointsCache()\n\t\tconst snappableShapes = this.manager.getSnappableShapes()\n\t\tconst result: BoundsSnapPoint[] = []\n\n\t\tfor (const shapeId of snappableShapes) {\n\t\t\tconst snapPoints = snapPointsCache.get(shapeId)\n\t\t\tif (snapPoints) {\n\t\t\t\tresult.push(...snapPoints)\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\t@computed private getSnappableGapNodes(): Array<GapNode> {\n\t\treturn Array.from(this.manager.getSnappableShapes(), (shapeId) => ({\n\t\t\tid: shapeId,\n\t\t\tpageBounds: assertExists(this.editor.getShapePageBounds(shapeId)),\n\t\t}))\n\t}\n\n\t@computed private getVisibleGaps(): { horizontal: Gap[]; vertical: Gap[] } {\n\t\tconst horizontal: Gap[] = []\n\t\tconst vertical: Gap[] = []\n\n\t\tlet startNode: GapNode, endNode: GapNode\n\n\t\tconst sortedShapesOnCurrentPageHorizontal = this.getSnappableGapNodes().sort((a, b) => {\n\t\t\treturn a.pageBounds.minX - b.pageBounds.minX\n\t\t})\n\n\t\t// Collect horizontal gaps\n\t\tfor (let i = 0; i < sortedShapesOnCurrentPageHorizontal.length; i++) {\n\t\t\tstartNode = sortedShapesOnCurrentPageHorizontal[i]\n\t\t\tfor (let j = i + 1; j < sortedShapesOnCurrentPageHorizontal.length; j++) {\n\t\t\t\tendNode = sortedShapesOnCurrentPageHorizontal[j]\n\n\t\t\t\tif (\n\t\t\t\t\t// is there space between the boxes\n\t\t\t\t\tstartNode.pageBounds.maxX < endNode.pageBounds.minX &&\n\t\t\t\t\t// and they overlap in the y axis\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\tstartNode.pageBounds.minY,\n\t\t\t\t\t\tstartNode.pageBounds.maxY,\n\t\t\t\t\t\tendNode.pageBounds.minY,\n\t\t\t\t\t\tendNode.pageBounds.maxY\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\thorizontal.push({\n\t\t\t\t\t\tstartNode,\n\t\t\t\t\t\tendNode,\n\t\t\t\t\t\tstartEdge: [\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tendEdge: [\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tlength: endNode.pageBounds.minX - startNode.pageBounds.maxX,\n\t\t\t\t\t\tbreadthIntersection: rangeIntersection(\n\t\t\t\t\t\t\tstartNode.pageBounds.minY,\n\t\t\t\t\t\t\tstartNode.pageBounds.maxY,\n\t\t\t\t\t\t\tendNode.pageBounds.minY,\n\t\t\t\t\t\t\tendNode.pageBounds.maxY\n\t\t\t\t\t\t)!,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Collect vertical gaps\n\t\tconst sortedShapesOnCurrentPageVertical = sortedShapesOnCurrentPageHorizontal.sort((a, b) => {\n\t\t\treturn a.pageBounds.minY - b.pageBounds.minY\n\t\t})\n\n\t\tfor (let i = 0; i < sortedShapesOnCurrentPageVertical.length; i++) {\n\t\t\tstartNode = sortedShapesOnCurrentPageVertical[i]\n\t\t\tfor (let j = i + 1; j < sortedShapesOnCurrentPageVertical.length; j++) {\n\t\t\t\tendNode = sortedShapesOnCurrentPageVertical[j]\n\n\t\t\t\tif (\n\t\t\t\t\t// is there space between the boxes\n\t\t\t\t\tstartNode.pageBounds.maxY < endNode.pageBounds.minY &&\n\t\t\t\t\t// do they overlap in the x axis\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\tstartNode.pageBounds.minX,\n\t\t\t\t\t\tstartNode.pageBounds.maxX,\n\t\t\t\t\t\tendNode.pageBounds.minX,\n\t\t\t\t\t\tendNode.pageBounds.maxX\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tvertical.push({\n\t\t\t\t\t\tstartNode,\n\t\t\t\t\t\tendNode,\n\t\t\t\t\t\tstartEdge: [\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.minX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t\tnew Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tendEdge: [\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),\n\t\t\t\t\t\t\tnew Vec(endNode.pageBounds.maxX, endNode.pageBounds.minY),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tlength: endNode.pageBounds.minY - startNode.pageBounds.maxY,\n\t\t\t\t\t\tbreadthIntersection: rangeIntersection(\n\t\t\t\t\t\t\tstartNode.pageBounds.minX,\n\t\t\t\t\t\t\tstartNode.pageBounds.maxX,\n\t\t\t\t\t\t\tendNode.pageBounds.minX,\n\t\t\t\t\t\t\tendNode.pageBounds.maxX\n\t\t\t\t\t\t)!,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { horizontal, vertical }\n\t}\n\n\tsnapTranslateShapes({\n\t\tlockedAxis,\n\t\tinitialSelectionPageBounds,\n\t\tinitialSelectionSnapPoints,\n\t\tdragDelta,\n\t}: {\n\t\tlockedAxis: 'x' | 'y' | null\n\t\tinitialSelectionSnapPoints: BoundsSnapPoint[]\n\t\tinitialSelectionPageBounds: Box\n\t\tdragDelta: Vec\n\t}): SnapData {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\t\tconst visibleSnapPointsNotInSelection = this.getSnappablePoints()\n\n\t\tconst selectionPageBounds = initialSelectionPageBounds.clone().translate(dragDelta)\n\n\t\tconst selectionSnapPoints: BoundsSnapPoint[] = initialSelectionSnapPoints.map(\n\t\t\t({ x, y }, i) => ({\n\t\t\t\tid: 'selection:' + i,\n\t\t\t\tx: x + dragDelta.x,\n\t\t\t\ty: y + dragDelta.y,\n\t\t\t})\n\t\t)\n\n\t\tconst otherNodeSnapPoints = visibleSnapPointsNotInSelection\n\n\t\tconst nearestSnapsX: NearestSnap[] = []\n\t\tconst nearestSnapsY: NearestSnap[] = []\n\t\tconst minOffset = new Vec(snapThreshold, snapThreshold)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\tthis.collectGapSnaps({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\tminOffset,\n\t\t})\n\n\t\t// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)\n\t\tconst nudge = new Vec(\n\t\t\tlockedAxis === 'x' ? 0 : (nearestSnapsX[0]?.nudge ?? 0),\n\t\t\tlockedAxis === 'y' ? 0 : (nearestSnapsY[0]?.nudge ?? 0)\n\t\t)\n\n\t\t// ok we've figured out how much the box should be nudged, now let's find all the snap points\n\t\t// that are exact after making that translation, so we can render all of them.\n\t\t// first reset everything and adjust the original shapes to conform to the nudge\n\t\tminOffset.x = 0\n\t\tminOffset.y = 0\n\t\tnearestSnapsX.length = 0\n\t\tnearestSnapsY.length = 0\n\t\tselectionSnapPoints.forEach((s) => {\n\t\t\ts.x += nudge.x\n\t\t\ts.y += nudge.y\n\t\t})\n\t\tselectionPageBounds.translate(nudge)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\tthis.collectGapSnaps({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\tminOffset,\n\t\t})\n\n\t\tconst pointSnapsLines = this.getPointSnapLines({\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tconst gapSnapLines = this.getGapSnapLines({\n\t\t\tselectionPageBounds,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tthis.manager.setIndicators([...gapSnapLines, ...pointSnapsLines])\n\n\t\treturn { nudge }\n\t}\n\n\tsnapResizeShapes({\n\t\tinitialSelectionPageBounds,\n\t\tdragDelta,\n\t\thandle: originalHandle,\n\t\tisAspectRatioLocked,\n\t\tisResizingFromCenter,\n\t}: {\n\t\t// the page bounds when the pointer went down, before any dragging\n\t\tinitialSelectionPageBounds: Box\n\t\t// how far the pointer has been dragged\n\t\tdragDelta: Vec\n\n\t\thandle: SelectionCorner | SelectionEdge\n\t\tisAspectRatioLocked: boolean\n\t\tisResizingFromCenter: boolean\n\t}): SnapData {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\n\t\t// first figure out the new bounds of the selection\n\t\tconst {\n\t\t\tbox: unsnappedResizedPageBounds,\n\t\t\tscaleX,\n\t\t\tscaleY,\n\t\t} = Box.Resize(\n\t\t\tinitialSelectionPageBounds,\n\t\t\toriginalHandle,\n\t\t\tisResizingFromCenter ? dragDelta.x * 2 : dragDelta.x,\n\t\t\tisResizingFromCenter ? dragDelta.y * 2 : dragDelta.y,\n\t\t\tisAspectRatioLocked\n\t\t)\n\n\t\tlet handle = originalHandle\n\n\t\tif (scaleX < 0) {\n\t\t\thandle = flipSelectionHandleX(handle)\n\t\t}\n\t\tif (scaleY < 0) {\n\t\t\thandle = flipSelectionHandleY(handle)\n\t\t}\n\n\t\tif (isResizingFromCenter) {\n\t\t\t// reposition if resizing from center\n\t\t\tunsnappedResizedPageBounds.center = initialSelectionPageBounds.center\n\t\t}\n\n\t\tconst isXLocked = handle === 'top' || handle === 'bottom'\n\t\tconst isYLocked = handle === 'left' || handle === 'right'\n\n\t\tconst selectionSnapPoints = getResizeSnapPointsForHandle(handle, unsnappedResizedPageBounds)\n\n\t\tconst otherNodeSnapPoints = this.getSnappablePoints()\n\n\t\tconst nearestSnapsX: NearestPointsSnap[] = []\n\t\tconst nearestSnapsY: NearestPointsSnap[] = []\n\t\tconst minOffset = new Vec(snapThreshold, snapThreshold)\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints,\n\t\t})\n\n\t\t// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)\n\t\tconst nudge = new Vec(\n\t\t\tisXLocked ? 0 : (nearestSnapsX[0]?.nudge ?? 0),\n\t\t\tisYLocked ? 0 : (nearestSnapsY[0]?.nudge ?? 0)\n\t\t)\n\n\t\tif (isAspectRatioLocked && isSelectionCorner(handle) && nudge.len() !== 0) {\n\t\t\t// if the aspect ratio is locked we need to make the nudge diagonal rather than independent in each axis\n\t\t\t// so we use the aspect ratio along with one axis value to set the other axis value, but which axis we use\n\t\t\t// as a source of truth depends what we have snapped to and how far.\n\n\t\t\t// if we found a snap in both axes, pick the closest one and discard the other\n\t\t\tconst primaryNudgeAxis: 'x' | 'y' =\n\t\t\t\tnearestSnapsX.length && nearestSnapsY.length\n\t\t\t\t\t? Math.abs(nudge.x) < Math.abs(nudge.y)\n\t\t\t\t\t\t? 'x'\n\t\t\t\t\t\t: 'y'\n\t\t\t\t\t: nearestSnapsX.length\n\t\t\t\t\t\t? 'x'\n\t\t\t\t\t\t: 'y'\n\n\t\t\tconst ratio = initialSelectionPageBounds.aspectRatio\n\n\t\t\tif (primaryNudgeAxis === 'x') {\n\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\tnudge.y = nudge.x / ratio\n\t\t\t\tif (handle === 'bottom_left' || handle === 'top_right') {\n\t\t\t\t\tnudge.y = -nudge.y\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\tnudge.x = nudge.y * ratio\n\t\t\t\tif (handle === 'bottom_left' || handle === 'top_right') {\n\t\t\t\t\tnudge.x = -nudge.x\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// now resize the box after nudging, calculate the snaps again, and return the snap lines to match\n\t\t// the fully resized box\n\t\tconst snappedDelta = Vec.Add(dragDelta, nudge)\n\n\t\t// first figure out the new bounds of the selection\n\t\tconst { box: snappedResizedPageBounds } = Box.Resize(\n\t\t\tinitialSelectionPageBounds,\n\t\t\toriginalHandle,\n\t\t\tisResizingFromCenter ? snappedDelta.x * 2 : snappedDelta.x,\n\t\t\tisResizingFromCenter ? snappedDelta.y * 2 : snappedDelta.y,\n\t\t\tisAspectRatioLocked\n\t\t)\n\n\t\tif (isResizingFromCenter) {\n\t\t\t// reposition if resizing from center\n\t\t\tsnappedResizedPageBounds.center = initialSelectionPageBounds.center\n\t\t}\n\n\t\tconst snappedSelectionPoints = getResizeSnapPointsForHandle('any', snappedResizedPageBounds)\n\t\t// calculate snaps again using all points\n\t\tnearestSnapsX.length = 0\n\t\tnearestSnapsY.length = 0\n\t\tminOffset.x = 0\n\t\tminOffset.y = 0\n\n\t\tthis.collectPointSnaps({\n\t\t\tminOffset,\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t\totherNodeSnapPoints,\n\t\t\tselectionSnapPoints: snappedSelectionPoints,\n\t\t})\n\t\tconst pointSnaps = this.getPointSnapLines({\n\t\t\tnearestSnapsX,\n\t\t\tnearestSnapsY,\n\t\t})\n\n\t\tthis.manager.setIndicators([...pointSnaps])\n\n\t\treturn { nudge }\n\t}\n\n\tprivate collectPointSnaps({\n\t\tselectionSnapPoints,\n\t\totherNodeSnapPoints,\n\t\tminOffset,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionSnapPoints: BoundsSnapPoint[]\n\t\totherNodeSnapPoints: BoundsSnapPoint[]\n\t\tminOffset: Vec\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}) {\n\t\t// for each snap point on the bounding box of the selection, find the set of points\n\t\t// which are closest to it in each axis\n\t\tfor (const thisSnapPoint of selectionSnapPoints) {\n\t\t\tfor (const otherSnapPoint of otherNodeSnapPoints) {\n\t\t\t\tconst offset = Vec.Sub(thisSnapPoint, otherSnapPoint)\n\t\t\t\tconst offsetX = Math.abs(offset.x)\n\t\t\t\tconst offsetY = Math.abs(offset.y)\n\n\t\t\t\tif (round(offsetX) <= round(minOffset.x)) {\n\t\t\t\t\tif (round(offsetX) < round(minOffset.x)) {\n\t\t\t\t\t\t// we found a point that is significantly closer than all previous points\n\t\t\t\t\t\t// so wipe the slate clean and start over\n\t\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t\t}\n\n\t\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\t\ttype: 'points',\n\t\t\t\t\t\tpoints: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },\n\t\t\t\t\t\tnudge: otherSnapPoint.x - thisSnapPoint.x,\n\t\t\t\t\t})\n\t\t\t\t\tminOffset.x = offsetX\n\t\t\t\t}\n\n\t\t\t\tif (round(offsetY) <= round(minOffset.y)) {\n\t\t\t\t\tif (round(offsetY) < round(minOffset.y)) {\n\t\t\t\t\t\t// we found a point that is significantly closer than all previous points\n\t\t\t\t\t\t// so wipe the slate clean and start over\n\t\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t\t}\n\t\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\t\ttype: 'points',\n\t\t\t\t\t\tpoints: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },\n\t\t\t\t\t\tnudge: otherSnapPoint.y - thisSnapPoint.y,\n\t\t\t\t\t})\n\t\t\t\t\tminOffset.y = offsetY\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate collectGapSnaps({\n\t\tselectionPageBounds,\n\t\tminOffset,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionPageBounds: Box\n\t\tminOffset: Vec\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}) {\n\t\tconst { horizontal, vertical } = this.getVisibleGaps()\n\n\t\tfor (const gap of horizontal) {\n\t\t\t// ignore this gap if the selection doesn't overlap with it in the y axis\n\t\t\tif (\n\t\t\t\t!rangesOverlap(\n\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for center match\n\t\t\tconst gapMidX = gap.startEdge[0].x + gap.length / 2\n\t\t\tconst centerNudge = gapMidX - selectionPageBounds.center.x\n\t\t\tconst gapIsLargerThanSelection = gap.length > selectionPageBounds.width\n\n\t\t\tif (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(centerNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(centerNudge)\n\n\t\t\t\tconst snap: NearestSnap = {\n\t\t\t\t\ttype: 'gap_center',\n\t\t\t\t\tgap,\n\t\t\t\t\tnudge: centerNudge,\n\t\t\t\t}\n\n\t\t\t\t// we need to avoid creating visual noise with too many center snaps in situations\n\t\t\t\t// where there are lots of adjacent items with even spacing\n\t\t\t\t// so let's only show other center snaps where the gap's breadth does not overlap with this one\n\t\t\t\t// i.e.\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502\n\t\t\t\t// \u2502 \u2502 \u253C\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502 don't show these\n\t\t\t\t// show these \u2502 \u2502 \u253C larger gaps since\n\t\t\t\t// smaller \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 the smaller ones\n\t\t\t\t// gaps \u253C \u2502 cover the same\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u250C\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510 information\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t//\n\t\t\t\t// but we want to show all of these ones since the gap breadths don't overlap\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2518 \u253C\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2510 \u253C\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u250C\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\t\t\t\tconst otherCenterSnap = nearestSnapsX.find(({ type }) => type === 'gap_center') as\n\t\t\t\t\t| Extract<NearestSnap, { type: 'gap_center' }>\n\t\t\t\t\t| undefined\n\n\t\t\t\tconst gapBreadthsOverlap =\n\t\t\t\t\totherCenterSnap &&\n\t\t\t\t\trangeIntersection(\n\t\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[0],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[1]\n\t\t\t\t\t)\n\n\t\t\t\t// if there is another center snap and it's bigger than this one, and it overlaps with this one, replace it\n\t\t\t\tif (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsX[nearestSnapsX.indexOf(otherCenterSnap)] = snap\n\t\t\t\t} else if (!otherCenterSnap || !gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsX.push(snap)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// check for duplication left match\n\t\t\tconst duplicationLeftX = gap.startNode.pageBounds.minX - gap.length\n\t\t\tconst selectionRightX = selectionPageBounds.maxX\n\n\t\t\tconst duplicationLeftNudge = duplicationLeftX - selectionRightX\n\t\t\tif (round(Math.abs(duplicationLeftNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(duplicationLeftNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(duplicationLeftNudge)\n\n\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'left',\n\t\t\t\t\tnudge: duplicationLeftNudge,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// check for duplication right match\n\t\t\tconst duplicationRightX = gap.endNode.pageBounds.maxX + gap.length\n\t\t\tconst selectionLeftX = selectionPageBounds.minX\n\n\t\t\tconst duplicationRightNudge = duplicationRightX - selectionLeftX\n\t\t\tif (round(Math.abs(duplicationRightNudge)) <= round(minOffset.x)) {\n\t\t\t\tif (round(Math.abs(duplicationRightNudge)) < round(minOffset.x)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsX.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.x = Math.abs(duplicationRightNudge)\n\n\t\t\t\tnearestSnapsX.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'right',\n\t\t\t\t\tnudge: duplicationRightNudge,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tfor (const gap of vertical) {\n\t\t\t// ignore this gap if the selection doesn't overlap with it in the y axis\n\t\t\tif (\n\t\t\t\t!rangesOverlap(\n\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\tgap.breadthIntersection[1],\n\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for center match\n\t\t\tconst gapMidY = gap.startEdge[0].y + gap.length / 2\n\t\t\tconst centerNudge = gapMidY - selectionPageBounds.center.y\n\n\t\t\tconst gapIsLargerThanSelection = gap.length > selectionPageBounds.height\n\n\t\t\tif (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(centerNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(centerNudge)\n\n\t\t\t\tconst snap: NearestSnap = {\n\t\t\t\t\ttype: 'gap_center',\n\t\t\t\t\tgap,\n\t\t\t\t\tnudge: centerNudge,\n\t\t\t\t}\n\n\t\t\t\t// we need to avoid creating visual noise with too many center snaps in situations\n\t\t\t\t// where there are lots of adjacent items with even spacing\n\t\t\t\t// so let's only show other center snaps where the gap's breadth does not overlap with this one\n\t\t\t\t// i.e.\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502\n\t\t\t\t// \u2502 \u2502 \u253C\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2510 \u2502 don't show these\n\t\t\t\t// show these \u2502 \u2502 \u253C larger gaps since\n\t\t\t\t// smaller \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2518 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 the smaller ones\n\t\t\t\t// gaps \u253C \u2502 cover the same\n\t\t\t\t// \u2500\u2500\u2500\u2500\u2500\u25BA \u250C\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510 information\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t//\n\t\t\t\t// but we want to show all of these ones since the gap breadths don't overlap\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2518 \u253C\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2510\n\t\t\t\t// \u2502 \u2502 \u25C4\u2500\u2500\u2500\u2500\u2500 i'm dragging this one\n\t\t\t\t// \u2514\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2518\n\t\t\t\t// \u253C \u2502\n\t\t\t\t// \u250C\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2510 \u253C\n\t\t\t\t// \u2502 \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u250C\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\t\t\t\t// \u2502 \u2502\n\t\t\t\t// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\t\t\t\tconst otherCenterSnap = nearestSnapsY.find(({ type }) => type === 'gap_center') as\n\t\t\t\t\t| Extract<NearestSnap, { type: 'gap_center' }>\n\t\t\t\t\t| undefined\n\n\t\t\t\tconst gapBreadthsOverlap =\n\t\t\t\t\totherCenterSnap &&\n\t\t\t\t\trangesOverlap(\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[0],\n\t\t\t\t\t\totherCenterSnap.gap.breadthIntersection[1],\n\t\t\t\t\t\tgap.breadthIntersection[0],\n\t\t\t\t\t\tgap.breadthIntersection[1]\n\t\t\t\t\t)\n\n\t\t\t\t// if there is another center snap and it's bigger than this one, and it overlaps with this one, replace it\n\t\t\t\tif (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsY[nearestSnapsY.indexOf(otherCenterSnap)] = snap\n\t\t\t\t} else if (!otherCenterSnap || !gapBreadthsOverlap) {\n\t\t\t\t\tnearestSnapsY.push(snap)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// check for duplication top match\n\t\t\tconst duplicationTopY = gap.startNode.pageBounds.minY - gap.length\n\t\t\tconst selectionBottomY = selectionPageBounds.maxY\n\n\t\t\tconst duplicationTopNudge = duplicationTopY - selectionBottomY\n\t\t\tif (round(Math.abs(duplicationTopNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(duplicationTopNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(duplicationTopNudge)\n\n\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'top',\n\t\t\t\t\tnudge: duplicationTopNudge,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// check for duplication bottom match\n\t\t\tconst duplicationBottomY = gap.endNode.pageBounds.maxY + gap.length\n\t\t\tconst selectionTopY = selectionPageBounds.minY\n\n\t\t\tconst duplicationBottomNudge = duplicationBottomY - selectionTopY\n\t\t\tif (round(Math.abs(duplicationBottomNudge)) <= round(minOffset.y)) {\n\t\t\t\tif (round(Math.abs(duplicationBottomNudge)) < round(minOffset.y)) {\n\t\t\t\t\t// reset if we found a closer snap\n\t\t\t\t\tnearestSnapsY.length = 0\n\t\t\t\t}\n\t\t\t\tminOffset.y = Math.abs(duplicationBottomNudge)\n\n\t\t\t\tnearestSnapsY.push({\n\t\t\t\t\ttype: 'gap_duplicate',\n\t\t\t\t\tgap,\n\t\t\t\t\tprotrusionDirection: 'bottom',\n\t\t\t\t\tnudge: duplicationBottomNudge,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getPointSnapLines({\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}): PointsSnapIndicator[] {\n\t\t// point snaps may align on multiple parallel lines so we need to split the pairs\n\t\t// into groups based on where they are in their their snap axes\n\t\tconst snapGroupsX = {} as { [key: string]: SnapPair[] }\n\t\tconst snapGroupsY = {} as { [key: string]: SnapPair[] }\n\n\t\tif (nearestSnapsX.length > 0) {\n\t\t\tfor (const snap of nearestSnapsX) {\n\t\t\t\tif (snap.type === 'points') {\n\t\t\t\t\tconst key = round(snap.points.otherPoint.x)\n\t\t\t\t\tif (!snapGroupsX[key]) {\n\t\t\t\t\t\tsnapGroupsX[key] = []\n\t\t\t\t\t}\n\t\t\t\t\tsnapGroupsX[key].push(snap.points)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (nearestSnapsY.length > 0) {\n\t\t\tfor (const snap of nearestSnapsY) {\n\t\t\t\tif (snap.type === 'points') {\n\t\t\t\t\tconst key = round(snap.points.otherPoint.y)\n\t\t\t\t\tif (!snapGroupsY[key]) {\n\t\t\t\t\t\tsnapGroupsY[key] = []\n\t\t\t\t\t}\n\t\t\t\t\tsnapGroupsY[key].push(snap.points)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// and finally create all the snap lines for the UI to render\n\t\treturn Object.values(snapGroupsX)\n\t\t\t.concat(Object.values(snapGroupsY))\n\t\t\t.map((snapGroup) => ({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: dedupe(\n\t\t\t\t\tsnapGroup\n\t\t\t\t\t\t.map((snap) => Vec.From(snap.otherPoint))\n\t\t\t\t\t\t// be sure to nudge over the selection snap points\n\t\t\t\t\t\t.concat(snapGroup.map((snap) => Vec.From(snap.thisPoint))),\n\t\t\t\t\t(a: Vec, b: Vec) => a.equals(b)\n\t\t\t\t),\n\t\t\t}))\n\t}\n\n\tprivate getGapSnapLines({\n\t\tselectionPageBounds,\n\t\tnearestSnapsX,\n\t\tnearestSnapsY,\n\t}: {\n\t\tselectionPageBounds: Box\n\t\tnearestSnapsX: NearestSnap[]\n\t\tnearestSnapsY: NearestSnap[]\n\t}): GapsSnapIndicator[] {\n\t\tconst { vertical, horizontal } = this.getVisibleGaps()\n\n\t\tconst selectionSides: Record<SelectionEdge, [Vec, Vec]> = {\n\t\t\ttop: selectionPageBounds.sides[0],\n\t\t\tright: selectionPageBounds.sides[1],\n\t\t\t// need bottom and left to be sorted asc, which .sides is not.\n\t\t\tbottom: [selectionPageBounds.corners[3], selectionPageBounds.corners[2]],\n\t\t\tleft: [selectionPageBounds.corners[0], selectionPageBounds.corners[3]],\n\t\t}\n\n\t\tconst result: GapsSnapIndicator[] = []\n\n\t\tif (nearestSnapsX.length > 0) {\n\t\t\tfor (const snap of nearestSnapsX) {\n\t\t\t\tif (snap.type === 'points') continue\n\n\t\t\t\tconst {\n\t\t\t\t\tgap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge },\n\t\t\t\t} = snap\n\n\t\t\t\tswitch (snap.type) {\n\t\t\t\t\tcase 'gap_center': {\n\t\t\t\t\t\t// create\n\t\t\t\t\t\tconst newGapsLength = (length - selectionPageBounds.width) / 2\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t\t\t)!\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'horizontal',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps: [\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge,\n\t\t\t\t\t\t\t\t\tendEdge: selectionSides.left,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.right,\n\t\t\t\t\t\t\t\t\tendEdge,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'gap_duplicate': {\n\t\t\t\t\t\t// create\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minY,\n\t\t\t\t\t\t\tselectionPageBounds.maxY\n\t\t\t\t\t\t)!\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'horizontal',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps:\n\t\t\t\t\t\t\t\tsnap.protrusionDirection === 'left'\n\t\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.right,\n\t\t\t\t\t\t\t\t\t\t\t\tendEdge: startEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(-startNode.pageBounds.width, 0)\n\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\thorizontal,\n\t\t\t\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: endEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(snap.gap.endNode.pageBounds.width, 0)\n\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\tendEdge: selectionSides.left,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (nearestSnapsY.length > 0) {\n\t\t\tfor (const snap of nearestSnapsY) {\n\t\t\t\tif (snap.type === 'points') continue\n\n\t\t\t\tconst {\n\t\t\t\t\tgap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge },\n\t\t\t\t} = snap\n\n\t\t\t\tswitch (snap.type) {\n\t\t\t\t\tcase 'gap_center': {\n\t\t\t\t\t\tconst newGapsLength = (length - selectionPageBounds.height) / 2\n\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t\t\t)!\n\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\tdirection: 'vertical',\n\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\tgaps: [\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge,\n\t\t\t\t\t\t\t\t\tendEdge: selectionSides.top,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.bottom,\n\t\t\t\t\t\t\t\t\tendEdge,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\tsnap.gap.endNode.id,\n\t\t\t\t\t\t\t\t\tnewGapsLength,\n\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'gap_duplicate':\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst gapBreadthIntersection = rangeIntersection(\n\t\t\t\t\t\t\t\tbreadthIntersection[0],\n\t\t\t\t\t\t\t\tbreadthIntersection[1],\n\t\t\t\t\t\t\t\tselectionPageBounds.minX,\n\t\t\t\t\t\t\t\tselectionPageBounds.maxX\n\t\t\t\t\t\t\t)!\n\n\t\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\t\ttype: 'gaps',\n\t\t\t\t\t\t\t\tdirection: 'vertical',\n\t\t\t\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\t\t\t\tgaps:\n\t\t\t\t\t\t\t\t\tsnap.protrusionDirection === 'top'\n\t\t\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: selectionSides.bottom,\n\t\t\t\t\t\t\t\t\t\t\t\t\tendEdge: startEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(0, -startNode.pageBounds.height)\n\t\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\t\t\t\t\tendNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t\t'forward',\n\t\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t\t\t\t...findAdjacentGaps(\n\t\t\t\t\t\t\t\t\t\t\t\t\tvertical,\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartNode.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlength,\n\t\t\t\t\t\t\t\t\t\t\t\t\t'backward',\n\t\t\t\t\t\t\t\t\t\t\t\t\tgapBreadthIntersection\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t{ startEdge, endEdge },\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tstartEdge: endEdge.map((v) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tv.clone().addXY(0, endNode.pageBounds.height)\n\t\t\t\t\t\t\t\t\t\t\t\t\t) as [Vec, Vec],\n\t\t\t\t\t\t\t\t\t\t\t\t\tendEdge: selectionSides.top,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdedupeGapSnaps(result)\n\t\treturn result\n\t}\n}\n\nfunction getResizeSnapPointsForHandle(\n\thandle: SelectionCorner | SelectionEdge | 'any',\n\tselectionPageBounds: Box\n): BoundsSnapPoint[] {\n\tconst { minX, maxX, minY, maxY } = selectionPageBounds\n\tconst result: BoundsSnapPoint[] = []\n\n\t// top left corner\n\tswitch (handle) {\n\t\tcase 'top':\n\t\tcase 'left':\n\t\tcase 'top_left':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'top_left',\n\t\t\t\thandle: 'top_left',\n\t\t\t\tx: minX,\n\t\t\t\ty: minY,\n\t\t\t})\n\t}\n\n\t// top right corner\n\tswitch (handle) {\n\t\tcase 'top':\n\t\tcase 'right':\n\t\tcase 'top_right':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'top_right',\n\t\t\t\thandle: 'top_right',\n\t\t\t\tx: maxX,\n\t\t\t\ty: minY,\n\t\t\t})\n\t}\n\n\t// bottom right corner\n\tswitch (handle) {\n\t\tcase 'bottom':\n\t\tcase 'right':\n\t\tcase 'bottom_right':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'bottom_right',\n\t\t\t\thandle: 'bottom_right',\n\t\t\t\tx: maxX,\n\t\t\t\ty: maxY,\n\t\t\t})\n\t}\n\n\t// bottom left corner\n\tswitch (handle) {\n\t\tcase 'bottom':\n\t\tcase 'left':\n\t\tcase 'bottom_left':\n\t\tcase 'any':\n\t\t\tresult.push({\n\t\t\t\tid: 'bottom_left',\n\t\t\t\thandle: 'bottom_left',\n\t\t\t\tx: minX,\n\t\t\t\ty: maxY,\n\t\t\t})\n\t}\n\n\treturn result\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAS,gBAAgB;AAEzB,SAAS,cAAc,QAAQ,gBAAgB;AAC/C;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,WAAW;AACpB,SAAS,mBAAmB,qBAAqB;AACjD,SAAS,WAAW;AA8FpB,MAAM,QAAQ,CAAC,MAAc;AAE5B,QAAM,yBAAyB;AAC/B,SAAO,KAAK,MAAM,IAAI,MAAM,sBAAsB,IAAI,MAAM;AAC7D;AAEA,SAAS,iBACR,MACA,SACA,WACA,WACA,cACQ;AAER,QAAM,UAAU,KAAK;AAAA,IACpB,CAAC,SACC,cAAc,YAAY,IAAI,UAAU,OAAO,UAAU,IAAI,QAAQ,OAAO,YAC7E,MAAM,IAAI,MAAM,MAAM,MAAM,SAAS,KACrC;AAAA,MACC,IAAI,oBAAoB,CAAC;AAAA,MACzB,IAAI,oBAAoB,CAAC;AAAA,MACzB,aAAa,CAAC;AAAA,MACd,aAAa,CAAC;AAAA,IACf;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,QAAM,YAAY,oBAAI,IAAe;AAErC,UAAQ,QAAQ,CAAC,UAAU;AAC1B,UAAM,OAAO,cAAc,YAAY,MAAM,QAAQ,KAAK,MAAM,UAAU;AAC1E,QAAI,CAAC,UAAU,IAAI,IAAI,GAAG;AACzB,gBAAU,IAAI,IAAI;AAClB,YAAM,YAAY;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACC,MAAM,oBAAoB,CAAC;AAAA,UAC3B,MAAM,oBAAoB,CAAC;AAAA,UAC3B,aAAa,CAAC;AAAA,UACd,aAAa,CAAC;AAAA,QACf;AAAA,MACD;AAEA,cAAQ,KAAK,GAAG,SAAS;AAAA,IAC1B;AAAA,EACD,CAAC;AAED,SAAO;AACR;AAEA,SAAS,eAAe,OAAwD;AAE/E,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AAElD,WAAS,IAAI,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK;AAC1C,UAAM,OAAO,MAAM,CAAC;AACpB,aAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAChC,YAAM,YAAY,MAAM,CAAC;AAEzB,UACC,UAAU,cAAc,KAAK,aAC7B,KAAK,KAAK;AAAA,QACT,CAAC,QACA,UAAU,KAAK;AAAA,UACd,CAAC,aACA,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC,KAC3D,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC;AAAA,QAC7D,KACA,UAAU,KAAK;AAAA,UACd,CAAC,aACA,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC,KACvD,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,MAAM,SAAS,QAAQ,CAAC,EAAE,CAAC;AAAA,QACzD;AAAA,MACF,GACC;AACD,cAAM,OAAO,GAAG,CAAC;AACjB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;AAGO,MAAM,YAAY;AAAA,EAExB,YAAqB,SAAsB;AAAtB;AACpB,SAAK,SAAS,QAAQ;AAAA,EACvB;AAAA,EAHS;AAAA,EAKS,qBAAqB;AACtC,UAAM,EAAE,OAAO,IAAI;AACnB,WAAO,OAAO,MAAM,oBAAgD,cAAc,CAAC,UAAU;AAC5F,YAAM,gBAAgB,OAAO,sBAAsB,MAAM,EAAE;AAC3D,UAAI,CAAC,cAAe,QAAO;AAC3B,YAAM,qBAAqB,OAAO,aAAa,KAAK,EAAE,sBAAsB,KAAK;AACjF,YAAM,aACL,mBAAmB,UAAU,OAAO,iBAAiB,KAAK,EAAE,OAAO;AAEpE,UAAI,CAAC,iBAAiB,CAAC,WAAY,QAAO;AAC1C,aAAO,WAAW,IAAI,CAAC,OAAO,MAAM;AACnC,cAAM,EAAE,GAAG,EAAE,IAAI,IAAI,aAAa,eAAe,KAAK;AACtD,eAAO,EAAE,GAAG,GAAG,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC,GAAG;AAAA,MACvC,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAAA,EAEA,cAAc,SAAoB;AACjC,WAAO,KAAK,mBAAmB,EAAE,IAAI,OAAO,KAAK,CAAC;AAAA,EACnD;AAAA,EAGkB,qBAAqB;AACtC,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,kBAAkB,KAAK,QAAQ,mBAAmB;AACxD,UAAM,SAA4B,CAAC;AAEnC,eAAW,WAAW,iBAAiB;AACtC,YAAM,aAAa,gBAAgB,IAAI,OAAO;AAC9C,UAAI,YAAY;AACf,eAAO,KAAK,GAAG,UAAU;AAAA,MAC1B;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA,EAEkB,uBAAuC;AACxD,WAAO,MAAM,KAAK,KAAK,QAAQ,mBAAmB,GAAG,CAAC,aAAa;AAAA,MAClE,IAAI;AAAA,MACJ,YAAY,aAAa,KAAK,OAAO,mBAAmB,OAAO,CAAC;AAAA,IACjE,EAAE;AAAA,EACH;AAAA,EAEkB,iBAAyD;AAC1E,UAAM,aAAoB,CAAC;AAC3B,UAAM,WAAkB,CAAC;AAEzB,QAAI,WAAoB;AAExB,UAAM,sCAAsC,KAAK,qBAAqB,EAAE,KAAK,CAAC,GAAG,MAAM;AACtF,aAAO,EAAE,WAAW,OAAO,EAAE,WAAW;AAAA,IACzC,CAAC;AAGD,aAAS,IAAI,GAAG,IAAI,oCAAoC,QAAQ,KAAK;AACpE,kBAAY,oCAAoC,CAAC;AACjD,eAAS,IAAI,IAAI,GAAG,IAAI,oCAAoC,QAAQ,KAAK;AACxE,kBAAU,oCAAoC,CAAC;AAE/C;AAAA;AAAA,UAEC,UAAU,WAAW,OAAO,QAAQ,WAAW;AAAA,UAE/C;AAAA,YACC,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,YACrB,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,UACpB;AAAA,UACC;AACD,qBAAW,KAAK;AAAA,YACf;AAAA,YACA;AAAA,YACA,WAAW;AAAA,cACV,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,cAC5D,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,YAC7D;AAAA,YACA,SAAS;AAAA,cACR,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,cACxD,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,YACzD;AAAA,YACA,QAAQ,QAAQ,WAAW,OAAO,UAAU,WAAW;AAAA,YACvD,qBAAqB;AAAA,cACpB,UAAU,WAAW;AAAA,cACrB,UAAU,WAAW;AAAA,cACrB,QAAQ,WAAW;AAAA,cACnB,QAAQ,WAAW;AAAA,YACpB;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAGA,UAAM,oCAAoC,oCAAoC,KAAK,CAAC,GAAG,MAAM;AAC5F,aAAO,EAAE,WAAW,OAAO,EAAE,WAAW;AAAA,IACzC,CAAC;AAED,aAAS,IAAI,GAAG,IAAI,kCAAkC,QAAQ,KAAK;AAClE,kBAAY,kCAAkC,CAAC;AAC/C,eAAS,IAAI,IAAI,GAAG,IAAI,kCAAkC,QAAQ,KAAK;AACtE,kBAAU,kCAAkC,CAAC;AAE7C;AAAA;AAAA,UAEC,UAAU,WAAW,OAAO,QAAQ,WAAW;AAAA,UAE/C;AAAA,YACC,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,YACrB,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,UACpB;AAAA,UACC;AACD,mBAAS,KAAK;AAAA,YACb;AAAA,YACA;AAAA,YACA,WAAW;AAAA,cACV,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,cAC5D,IAAI,IAAI,UAAU,WAAW,MAAM,UAAU,WAAW,IAAI;AAAA,YAC7D;AAAA,YACA,SAAS;AAAA,cACR,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,cACxD,IAAI,IAAI,QAAQ,WAAW,MAAM,QAAQ,WAAW,IAAI;AAAA,YACzD;AAAA,YACA,QAAQ,QAAQ,WAAW,OAAO,UAAU,WAAW;AAAA,YACvD,qBAAqB;AAAA,cACpB,UAAU,WAAW;AAAA,cACrB,UAAU,WAAW;AAAA,cACrB,QAAQ,WAAW;AAAA,cACnB,QAAQ,WAAW;AAAA,YACpB;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAEA,WAAO,EAAE,YAAY,SAAS;AAAA,EAC/B;AAAA,EAEA,oBAAoB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKa;AACZ,UAAM,gBAAgB,KAAK,QAAQ,iBAAiB;AACpD,UAAM,kCAAkC,KAAK,mBAAmB;AAEhE,UAAM,sBAAsB,2BAA2B,MAAM,EAAE,UAAU,SAAS;AAElF,UAAM,sBAAyC,2BAA2B;AAAA,MACzE,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO;AAAA,QACjB,IAAI,eAAe;AAAA,QACnB,GAAG,IAAI,UAAU;AAAA,QACjB,GAAG,IAAI,UAAU;AAAA,MAClB;AAAA,IACD;AAEA,UAAM,sBAAsB;AAE5B,UAAM,gBAA+B,CAAC;AACtC,UAAM,gBAA+B,CAAC;AACtC,UAAM,YAAY,IAAI,IAAI,eAAe,aAAa;AAEtD,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAGD,UAAM,QAAQ,IAAI;AAAA,MACjB,eAAe,MAAM,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,MACrD,eAAe,MAAM,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,IACtD;AAKA,cAAU,IAAI;AACd,cAAU,IAAI;AACd,kBAAc,SAAS;AACvB,kBAAc,SAAS;AACvB,wBAAoB,QAAQ,CAAC,MAAM;AAClC,QAAE,KAAK,MAAM;AACb,QAAE,KAAK,MAAM;AAAA,IACd,CAAC;AACD,wBAAoB,UAAU,KAAK;AAEnC,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,UAAM,kBAAkB,KAAK,kBAAkB;AAAA,MAC9C;AAAA,MACA;AAAA,IACD,CAAC;AAED,UAAM,eAAe,KAAK,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,QAAQ,cAAc,CAAC,GAAG,cAAc,GAAG,eAAe,CAAC;AAEhE,WAAO,EAAE,MAAM;AAAA,EAChB;AAAA,EAEA,iBAAiB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACD,GASa;AACZ,UAAM,gBAAgB,KAAK,QAAQ,iBAAiB;AAGpD,UAAM;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACD,IAAI,IAAI;AAAA,MACP;AAAA,MACA;AAAA,MACA,uBAAuB,UAAU,IAAI,IAAI,UAAU;AAAA,MACnD,uBAAuB,UAAU,IAAI,IAAI,UAAU;AAAA,MACnD;AAAA,IACD;AAEA,QAAI,SAAS;AAEb,QAAI,SAAS,GAAG;AACf,eAAS,qBAAqB,MAAM;AAAA,IACrC;AACA,QAAI,SAAS,GAAG;AACf,eAAS,qBAAqB,MAAM;AAAA,IACrC;AAEA,QAAI,sBAAsB;AAEzB,iCAA2B,SAAS,2BAA2B;AAAA,IAChE;AAEA,UAAM,YAAY,WAAW,SAAS,WAAW;AACjD,UAAM,YAAY,WAAW,UAAU,WAAW;AAElD,UAAM,sBAAsB,6BAA6B,QAAQ,0BAA0B;AAE3F,UAAM,sBAAsB,KAAK,mBAAmB;AAEpD,UAAM,gBAAqC,CAAC;AAC5C,UAAM,gBAAqC,CAAC;AAC5C,UAAM,YAAY,IAAI,IAAI,eAAe,aAAa;AAEtD,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC;AAGD,UAAM,QAAQ,IAAI;AAAA,MACjB,YAAY,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,MAC5C,YAAY,IAAK,cAAc,CAAC,GAAG,SAAS;AAAA,IAC7C;AAEA,QAAI,uBAAuB,kBAAkB,MAAM,KAAK,MAAM,IAAI,MAAM,GAAG;AAM1E,YAAM,mBACL,cAAc,UAAU,cAAc,SACnC,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,IACnC,MACA,MACD,cAAc,SACb,MACA;AAEL,YAAM,QAAQ,2BAA2B;AAEzC,UAAI,qBAAqB,KAAK;AAC7B,sBAAc,SAAS;AACvB,cAAM,IAAI,MAAM,IAAI;AACpB,YAAI,WAAW,iBAAiB,WAAW,aAAa;AACvD,gBAAM,IAAI,CAAC,MAAM;AAAA,QAClB;AAAA,MACD,OAAO;AACN,sBAAc,SAAS;AACvB,cAAM,IAAI,MAAM,IAAI;AACpB,YAAI,WAAW,iBAAiB,WAAW,aAAa;AACvD,gBAAM,IAAI,CAAC,MAAM;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAIA,UAAM,eAAe,IAAI,IAAI,WAAW,KAAK;AAG7C,UAAM,EAAE,KAAK,yBAAyB,IAAI,IAAI;AAAA,MAC7C;AAAA,MACA;AAAA,MACA,uBAAuB,aAAa,IAAI,IAAI,aAAa;AAAA,MACzD,uBAAuB,aAAa,IAAI,IAAI,aAAa;AAAA,MACzD;AAAA,IACD;AAEA,QAAI,sBAAsB;AAEzB,+BAAyB,SAAS,2BAA2B;AAAA,IAC9D;AAEA,UAAM,yBAAyB,6BAA6B,OAAO,wBAAwB;AAE3F,kBAAc,SAAS;AACvB,kBAAc,SAAS;AACvB,cAAU,IAAI;AACd,cAAU,IAAI;AAEd,SAAK,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,qBAAqB;AAAA,IACtB,CAAC;AACD,UAAM,aAAa,KAAK,kBAAkB;AAAA,MACzC;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,QAAQ,cAAc,CAAC,GAAG,UAAU,CAAC;AAE1C,WAAO,EAAE,MAAM;AAAA,EAChB;AAAA,EAEQ,kBAAkB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAMG;AAGF,eAAW,iBAAiB,qBAAqB;AAChD,iBAAW,kBAAkB,qBAAqB;AACjD,cAAM,SAAS,IAAI,IAAI,eAAe,cAAc;AACpD,cAAM,UAAU,KAAK,IAAI,OAAO,CAAC;AACjC,cAAM,UAAU,KAAK,IAAI,OAAO,CAAC;AAEjC,YAAI,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,GAAG;AACzC,cAAI,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;AAGxC,0BAAc,SAAS;AAAA,UACxB;AAEA,wBAAc,KAAK;AAAA,YAClB,MAAM;AAAA,YACN,QAAQ,EAAE,WAAW,eAAe,YAAY,eAAe;AAAA,YAC/D,OAAO,eAAe,IAAI,cAAc;AAAA,UACzC,CAAC;AACD,oBAAU,IAAI;AAAA,QACf;AAEA,YAAI,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,GAAG;AACzC,cAAI,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;AAGxC,0BAAc,SAAS;AAAA,UACxB;AACA,wBAAc,KAAK;AAAA,YAClB,MAAM;AAAA,YACN,QAAQ,EAAE,WAAW,eAAe,YAAY,eAAe;AAAA,YAC/D,OAAO,eAAe,IAAI,cAAc;AAAA,UACzC,CAAC;AACD,oBAAU,IAAI;AAAA,QACf;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,gBAAgB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKG;AACF,UAAM,EAAE,YAAY,SAAS,IAAI,KAAK,eAAe;AAErD,eAAW,OAAO,YAAY;AAE7B,UACC,CAAC;AAAA,QACA,IAAI,oBAAoB,CAAC;AAAA,QACzB,IAAI,oBAAoB,CAAC;AAAA,QACzB,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,MACrB,GACC;AACD;AAAA,MACD;AAGA,YAAM,UAAU,IAAI,UAAU,CAAC,EAAE,IAAI,IAAI,SAAS;AAClD,YAAM,cAAc,UAAU,oBAAoB,OAAO;AACzD,YAAM,2BAA2B,IAAI,SAAS,oBAAoB;AAElE,UAAI,4BAA4B,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACnF,YAAI,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEtD,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,WAAW;AAElC,cAAM,OAAoB;AAAA,UACzB,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,QACR;AA2CA,cAAM,kBAAkB,cAAc,KAAK,CAAC,EAAE,KAAK,MAAM,SAAS,YAAY;AAI9E,cAAM,qBACL,mBACA;AAAA,UACC,IAAI,oBAAoB,CAAC;AAAA,UACzB,IAAI,oBAAoB,CAAC;AAAA,UACzB,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,QAC1C;AAGD,YAAI,mBAAmB,gBAAgB,IAAI,SAAS,IAAI,UAAU,oBAAoB;AACrF,wBAAc,cAAc,QAAQ,eAAe,CAAC,IAAI;AAAA,QACzD,WAAW,CAAC,mBAAmB,CAAC,oBAAoB;AACnD,wBAAc,KAAK,IAAI;AAAA,QACxB;AAAA,MACD;AAGA,YAAM,mBAAmB,IAAI,UAAU,WAAW,OAAO,IAAI;AAC7D,YAAM,kBAAkB,oBAAoB;AAE5C,YAAM,uBAAuB,mBAAmB;AAChD,UAAI,MAAM,KAAK,IAAI,oBAAoB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAChE,YAAI,MAAM,KAAK,IAAI,oBAAoB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAE/D,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,oBAAoB;AAE3C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAGA,YAAM,oBAAoB,IAAI,QAAQ,WAAW,OAAO,IAAI;AAC5D,YAAM,iBAAiB,oBAAoB;AAE3C,YAAM,wBAAwB,oBAAoB;AAClD,UAAI,MAAM,KAAK,IAAI,qBAAqB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACjE,YAAI,MAAM,KAAK,IAAI,qBAAqB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEhE,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,qBAAqB;AAE5C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,IACD;AAEA,eAAW,OAAO,UAAU;AAE3B,UACC,CAAC;AAAA,QACA,IAAI,oBAAoB,CAAC;AAAA,QACzB,IAAI,oBAAoB,CAAC;AAAA,QACzB,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,MACrB,GACC;AACD;AAAA,MACD;AAGA,YAAM,UAAU,IAAI,UAAU,CAAC,EAAE,IAAI,IAAI,SAAS;AAClD,YAAM,cAAc,UAAU,oBAAoB,OAAO;AAEzD,YAAM,2BAA2B,IAAI,SAAS,oBAAoB;AAElE,UAAI,4BAA4B,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AACnF,YAAI,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEtD,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,WAAW;AAElC,cAAM,OAAoB;AAAA,UACzB,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,QACR;AA2CA,cAAM,kBAAkB,cAAc,KAAK,CAAC,EAAE,KAAK,MAAM,SAAS,YAAY;AAI9E,cAAM,qBACL,mBACA;AAAA,UACC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,gBAAgB,IAAI,oBAAoB,CAAC;AAAA,UACzC,IAAI,oBAAoB,CAAC;AAAA,UACzB,IAAI,oBAAoB,CAAC;AAAA,QAC1B;AAGD,YAAI,mBAAmB,gBAAgB,IAAI,SAAS,IAAI,UAAU,oBAAoB;AACrF,wBAAc,cAAc,QAAQ,eAAe,CAAC,IAAI;AAAA,QACzD,WAAW,CAAC,mBAAmB,CAAC,oBAAoB;AACnD,wBAAc,KAAK,IAAI;AAAA,QACxB;AACA;AAAA,MACD;AAGA,YAAM,kBAAkB,IAAI,UAAU,WAAW,OAAO,IAAI;AAC5D,YAAM,mBAAmB,oBAAoB;AAE7C,YAAM,sBAAsB,kBAAkB;AAC9C,UAAI,MAAM,KAAK,IAAI,mBAAmB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAC/D,YAAI,MAAM,KAAK,IAAI,mBAAmB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAE9D,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,mBAAmB;AAE1C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAGA,YAAM,qBAAqB,IAAI,QAAQ,WAAW,OAAO,IAAI;AAC7D,YAAM,gBAAgB,oBAAoB;AAE1C,YAAM,yBAAyB,qBAAqB;AACpD,UAAI,MAAM,KAAK,IAAI,sBAAsB,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG;AAClE,YAAI,MAAM,KAAK,IAAI,sBAAsB,CAAC,IAAI,MAAM,UAAU,CAAC,GAAG;AAEjE,wBAAc,SAAS;AAAA,QACxB;AACA,kBAAU,IAAI,KAAK,IAAI,sBAAsB;AAE7C,sBAAc,KAAK;AAAA,UAClB,MAAM;AAAA,UACN;AAAA,UACA,qBAAqB;AAAA,UACrB,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,kBAAkB;AAAA,IACzB;AAAA,IACA;AAAA,EACD,GAG0B;AAGzB,UAAM,cAAc,CAAC;AACrB,UAAM,cAAc,CAAC;AAErB,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,UAAU;AAC3B,gBAAM,MAAM,MAAM,KAAK,OAAO,WAAW,CAAC;AAC1C,cAAI,CAAC,YAAY,GAAG,GAAG;AACtB,wBAAY,GAAG,IAAI,CAAC;AAAA,UACrB;AACA,sBAAY,GAAG,EAAE,KAAK,KAAK,MAAM;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AAEA,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,UAAU;AAC3B,gBAAM,MAAM,MAAM,KAAK,OAAO,WAAW,CAAC;AAC1C,cAAI,CAAC,YAAY,GAAG,GAAG;AACtB,wBAAY,GAAG,IAAI,CAAC;AAAA,UACrB;AACA,sBAAY,GAAG,EAAE,KAAK,KAAK,MAAM;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AAGA,WAAO,OAAO,OAAO,WAAW,EAC9B,OAAO,OAAO,OAAO,WAAW,CAAC,EACjC,IAAI,CAAC,eAAe;AAAA,MACpB,IAAI,SAAS;AAAA,MACb,MAAM;AAAA,MACN,QAAQ;AAAA,QACP,UACE,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,UAAU,CAAC,EAEvC,OAAO,UAAU,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC;AAAA,QAC1D,CAAC,GAAQ,MAAW,EAAE,OAAO,CAAC;AAAA,MAC/B;AAAA,IACD,EAAE;AAAA,EACJ;AAAA,EAEQ,gBAAgB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAIwB;AACvB,UAAM,EAAE,UAAU,WAAW,IAAI,KAAK,eAAe;AAErD,UAAM,iBAAoD;AAAA,MACzD,KAAK,oBAAoB,MAAM,CAAC;AAAA,MAChC,OAAO,oBAAoB,MAAM,CAAC;AAAA;AAAA,MAElC,QAAQ,CAAC,oBAAoB,QAAQ,CAAC,GAAG,oBAAoB,QAAQ,CAAC,CAAC;AAAA,MACvE,MAAM,CAAC,oBAAoB,QAAQ,CAAC,GAAG,oBAAoB,QAAQ,CAAC,CAAC;AAAA,IACtE;AAEA,UAAM,SAA8B,CAAC;AAErC,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,SAAU;AAE5B,cAAM;AAAA,UACL,KAAK,EAAE,qBAAqB,WAAW,WAAW,SAAS,QAAQ,QAAQ;AAAA,QAC5E,IAAI;AAEJ,gBAAQ,KAAK,MAAM;AAAA,UAClB,KAAK,cAAc;AAElB,kBAAM,iBAAiB,SAAS,oBAAoB,SAAS;AAC7D,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AACA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MAAM;AAAA,gBACL,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA;AAAA,kBACC;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B;AAAA,gBACD;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,QAAQ;AAAA,kBACR;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YACD,CAAC;AACD;AAAA,UACD;AAAA,UACA,KAAK,iBAAiB;AAErB,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AACA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MACC,KAAK,wBAAwB,SAC1B;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B,SAAS,UAAU;AAAA,oBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,CAAC,UAAU,WAAW,OAAO,CAAC;AAAA,kBAC/C;AAAA,gBACD;AAAA,gBACA,EAAE,WAAW,QAAQ;AAAA,gBACrB,GAAG;AAAA,kBACF;AAAA,kBACA,QAAQ;AAAA,kBACR;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD,IACC;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA,EAAE,WAAW,QAAQ;AAAA,gBACrB;AAAA,kBACC,WAAW,QAAQ;AAAA,oBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,QAAQ,WAAW,OAAO,CAAC;AAAA,kBACrD;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,cACD;AAAA,YACJ,CAAC;AAED;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,QAAI,cAAc,SAAS,GAAG;AAC7B,iBAAW,QAAQ,eAAe;AACjC,YAAI,KAAK,SAAS,SAAU;AAE5B,cAAM;AAAA,UACL,KAAK,EAAE,qBAAqB,WAAW,WAAW,SAAS,QAAQ,QAAQ;AAAA,QAC5E,IAAI;AAEJ,gBAAQ,KAAK,MAAM;AAAA,UAClB,KAAK,cAAc;AAClB,kBAAM,iBAAiB,SAAS,oBAAoB,UAAU;AAC9D,kBAAM,yBAAyB;AAAA,cAC9B,oBAAoB,CAAC;AAAA,cACrB,oBAAoB,CAAC;AAAA,cACrB,oBAAoB;AAAA,cACpB,oBAAoB;AAAA,YACrB;AAEA,mBAAO,KAAK;AAAA,cACX,MAAM;AAAA,cACN,WAAW;AAAA,cACX,IAAI,SAAS;AAAA,cACb,MAAM;AAAA,gBACL,GAAG;AAAA,kBACF;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,gBACA;AAAA,kBACC;AAAA,kBACA,SAAS,eAAe;AAAA,gBACzB;AAAA,gBACA;AAAA,kBACC,WAAW,eAAe;AAAA,kBAC1B;AAAA,gBACD;AAAA,gBACA,GAAG;AAAA,kBACF;AAAA,kBACA,KAAK,IAAI,QAAQ;AAAA,kBACjB;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YACD,CAAC;AACD;AAAA,UACD;AAAA,UACA,KAAK;AACJ;AACC,oBAAM,yBAAyB;AAAA,gBAC9B,oBAAoB,CAAC;AAAA,gBACrB,oBAAoB,CAAC;AAAA,gBACrB,oBAAoB;AAAA,gBACpB,oBAAoB;AAAA,cACrB;AAEA,qBAAO,KAAK;AAAA,gBACX,MAAM;AAAA,gBACN,WAAW;AAAA,gBACX,IAAI,SAAS;AAAA,gBACb,MACC,KAAK,wBAAwB,QAC1B;AAAA,kBACA;AAAA,oBACC,WAAW,eAAe;AAAA,oBAC1B,SAAS,UAAU;AAAA,sBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,UAAU,WAAW,MAAM;AAAA,oBAChD;AAAA,kBACD;AAAA,kBACA,EAAE,WAAW,QAAQ;AAAA,kBACrB,GAAG;AAAA,oBACF;AAAA,oBACA,QAAQ;AAAA,oBACR;AAAA,oBACA;AAAA,oBACA;AAAA,kBACD;AAAA,gBACD,IACC;AAAA,kBACA,GAAG;AAAA,oBACF;AAAA,oBACA,UAAU;AAAA,oBACV;AAAA,oBACA;AAAA,oBACA;AAAA,kBACD;AAAA,kBACA,EAAE,WAAW,QAAQ;AAAA,kBACrB;AAAA,oBACC,WAAW,QAAQ;AAAA,sBAAI,CAAC,MACvB,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,WAAW,MAAM;AAAA,oBAC7C;AAAA,oBACA,SAAS,eAAe;AAAA,kBACzB;AAAA,gBACD;AAAA,cACJ,CAAC;AAAA,YACF;AACA;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAEA,mBAAe,MAAM;AACrB,WAAO;AAAA,EACR;AACD;AAv+BmB;AAAA,EAAjB;AAAA,GANW,YAMM;AAsBA;AAAA,EAAjB;AAAA,GA5BW,YA4BM;AAeA;AAAA,EAAjB;AAAA,GA3CW,YA2CM;AAOA;AAAA,EAAjB;AAAA,GAlDW,YAkDM;AA67BnB,SAAS,6BACR,QACA,qBACoB;AACpB,QAAM,EAAE,MAAM,MAAM,MAAM,KAAK,IAAI;AACnC,QAAM,SAA4B,CAAC;AAGnC,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAGA,UAAQ,QAAQ;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,GAAG;AAAA,MACJ,CAAC;AAAA,EACH;AAEA,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/SnapManager/HandleSnaps.ts"],
4
- "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { TLHandle, TLShape, TLShapeId, VecModel } from '@tldraw/tlschema'\nimport { assertExists, uniqueId } from '@tldraw/utils'\nimport { Vec } from '../../../primitives/Vec'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport type { Editor } from '../../Editor'\nimport type { PointsSnapIndicator, SnapData, SnapManager } from './SnapManager'\n\n/**\n * When dragging a handle, users can snap the handle to key geometry on other nearby shapes.\n * Customize how handles snap to a shape by returning this from\n * {@link ShapeUtil.getHandleSnapGeometry}.\n *\n * Any co-ordinates here should be in the shape's local space.\n *\n * @public\n */\nexport interface HandleSnapGeometry {\n\t/**\n\t * A `Geometry2d` that describe the outline of the shape that the handle will snap to - fills\n\t * are ignored. By default, this is the same geometry returned by {@link ShapeUtil.getGeometry}.\n\t * Set this to `null` to disable handle snapping to this shape's outline.\n\t */\n\toutline?: Geometry2d | null\n\t/**\n\t * Key points on the shape that the handle will snap to. For example, the corners of a\n\t * rectangle, or the centroid of a triangle. By default, no points are used.\n\t */\n\tpoints?: VecModel[]\n\t/**\n\t * By default, handles can't snap to their own shape because moving the handle might change the\n\t * snapping location which can cause feedback loops. You can override this by returning a\n\t * version of `outline` that won't be affected by the current handle's position to use for\n\t * self-snapping.\n\t */\n\tgetSelfSnapOutline?(handle: TLHandle): Geometry2d | null\n\t/**\n\t * By default, handles can't snap to their own shape because moving the handle might change the\n\t * snapping location which can cause feedback loops. You can override this by returning a\n\t * version of `points` that won't be affected by the current handle's position to use for\n\t * self-snapping.\n\t */\n\tgetSelfSnapPoints?(handle: TLHandle): VecModel[]\n}\n\ninterface AlignPointsSnap {\n\tsnaps: PointsSnapIndicator[]\n\tnudge: Vec\n}\n\nconst defaultGetSelfSnapOutline = () => null\nconst defaultGetSelfSnapPoints = () => []\n/** @public */\nexport class HandleSnaps {\n\treadonly editor: Editor\n\tconstructor(readonly manager: SnapManager) {\n\t\tthis.editor = manager.editor\n\t}\n\n\t@computed private getSnapGeometryCache() {\n\t\tconst { editor } = this\n\t\treturn editor.store.createComputedCache('handle snap geometry', (shape: TLShape) => {\n\t\t\tconst snapGeometry = editor.getShapeUtil(shape).getHandleSnapGeometry(shape)\n\t\t\tconst getSelfSnapOutline = snapGeometry.getSelfSnapOutline\n\t\t\t\t? snapGeometry.getSelfSnapOutline.bind(snapGeometry)\n\t\t\t\t: defaultGetSelfSnapOutline\n\t\t\tconst getSelfSnapPoints = snapGeometry.getSelfSnapPoints\n\t\t\t\t? snapGeometry.getSelfSnapPoints.bind(snapGeometry)\n\t\t\t\t: defaultGetSelfSnapPoints\n\n\t\t\treturn {\n\t\t\t\toutline:\n\t\t\t\t\tsnapGeometry.outline === undefined\n\t\t\t\t\t\t? editor.getShapeGeometry(shape)\n\t\t\t\t\t\t: snapGeometry.outline,\n\n\t\t\t\tpoints: snapGeometry.points ?? [],\n\t\t\t\tgetSelfSnapOutline,\n\t\t\t\tgetSelfSnapPoints,\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate *iterateSnapPointsInPageSpace(currentShapeId: TLShapeId, currentHandle: TLHandle) {\n\t\tconst selfSnapPoints = this.getSnapGeometryCache()\n\t\t\t.get(currentShapeId)\n\t\t\t?.getSelfSnapPoints(currentHandle)\n\t\tif (selfSnapPoints && selfSnapPoints.length) {\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\t\tfor (const point of selfSnapPoints) {\n\t\t\t\tyield shapePageTransform.applyToPoint(point)\n\t\t\t}\n\t\t}\n\n\t\tfor (const shapeId of this.manager.getSnappableShapes()) {\n\t\t\tif (shapeId === currentShapeId) continue\n\t\t\tconst snapPoints = this.getSnapGeometryCache().get(shapeId)?.points\n\t\t\tif (!snapPoints || !snapPoints.length) continue\n\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(shapeId))\n\t\t\tfor (const point of snapPoints) {\n\t\t\t\tyield shapePageTransform.applyToPoint(point)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate *iterateSnapOutlines(currentShapeId: TLShapeId, currentHandle: TLHandle) {\n\t\tconst selfSnapOutline = this.getSnapGeometryCache()\n\t\t\t.get(currentShapeId)\n\t\t\t?.getSelfSnapOutline(currentHandle)\n\t\tif (selfSnapOutline) {\n\t\t\tyield { shapeId: currentShapeId, outline: selfSnapOutline }\n\t\t}\n\n\t\tfor (const shapeId of this.manager.getSnappableShapes()) {\n\t\t\tif (shapeId === currentShapeId) continue\n\n\t\t\tconst snapOutline = this.getSnapGeometryCache().get(shapeId)?.outline\n\t\t\tif (!snapOutline) continue\n\n\t\t\tyield { shapeId, outline: snapOutline }\n\t\t}\n\t}\n\n\tprivate getHandleSnapPosition({\n\t\tcurrentShapeId,\n\t\thandle,\n\t\thandleInPageSpace,\n\t}: {\n\t\tcurrentShapeId: TLShapeId\n\t\thandle: TLHandle\n\t\thandleInPageSpace: Vec\n\t}): Vec | null {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\n\t\t// We snap to two different parts of the shape's handle snap geometry:\n\t\t// 1. The `points`. These are handles or other key points that we want to snap to with a\n\t\t// higher priority than the normal outline snapping.\n\t\t// 2. The `outline`. This describes the outline of the shape, and we just snap to the\n\t\t// nearest point on that outline.\n\n\t\t// Start with the points:\n\t\tlet minDistanceForSnapPoint = snapThreshold\n\t\tlet nearestSnapPoint: Vec | null = null\n\t\tfor (const snapPoint of this.iterateSnapPointsInPageSpace(currentShapeId, handle)) {\n\t\t\tif (Vec.DistMin(handleInPageSpace, snapPoint, minDistanceForSnapPoint)) {\n\t\t\t\tminDistanceForSnapPoint = Vec.Dist(handleInPageSpace, snapPoint)\n\t\t\t\tnearestSnapPoint = snapPoint\n\t\t\t}\n\t\t}\n\n\t\t// if we found a snap point, return it - we don't need to check the outlines because points\n\t\t// have a higher priority\n\t\tif (nearestSnapPoint) return nearestSnapPoint\n\n\t\tlet minDistanceForOutline = snapThreshold\n\t\tlet nearestPointOnOutline: Vec | null = null\n\n\t\tfor (const { shapeId, outline } of this.iterateSnapOutlines(currentShapeId, handle)) {\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(shapeId))\n\t\t\tconst pointInShapeSpace = this.editor.getPointInShapeSpace(shapeId, handleInPageSpace)\n\n\t\t\tconst nearestShapePointInShapeSpace = outline.nearestPoint(pointInShapeSpace)\n\t\t\tconst nearestInPageSpace = shapePageTransform.applyToPoint(nearestShapePointInShapeSpace)\n\n\t\t\tif (Vec.DistMin(handleInPageSpace, nearestInPageSpace, minDistanceForOutline)) {\n\t\t\t\tminDistanceForOutline = Vec.Dist(handleInPageSpace, nearestInPageSpace)\n\t\t\t\tnearestPointOnOutline = nearestInPageSpace\n\t\t\t}\n\t\t}\n\n\t\t// if we found a point on the outline, return it\n\t\tif (nearestPointOnOutline) return nearestPointOnOutline\n\n\t\t// if not, there's no nearby snap point\n\t\treturn null\n\t}\n\n\tprivate getHandleSnapData({\n\t\thandle,\n\t\tcurrentShapeId,\n\t}: {\n\t\thandle: TLHandle\n\t\tcurrentShapeId: TLShapeId\n\t}): AlignPointsSnap | null {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\t\tconst currentShapeTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\tconst handleInPageSpace = currentShapeTransform.applyToPoint(handle)\n\n\t\tlet nearestXSnap: Vec | null = null\n\t\tlet nearestYSnap: Vec | null = null\n\t\tlet minOffsetX = snapThreshold\n\t\tlet minOffsetY = snapThreshold\n\n\t\tfor (const snapPoint of this.iterateSnapPointsInPageSpace(currentShapeId, handle)) {\n\t\t\tconst offsetX = Math.abs(handleInPageSpace.x - snapPoint.x)\n\t\t\tconst offsetY = Math.abs(handleInPageSpace.y - snapPoint.y)\n\t\t\tif (offsetX < minOffsetX) {\n\t\t\t\tminOffsetX = offsetX\n\t\t\t\tnearestXSnap = snapPoint\n\t\t\t}\n\t\t\tif (offsetY < minOffsetY) {\n\t\t\t\tminOffsetY = offsetY\n\t\t\t\tnearestYSnap = snapPoint\n\t\t\t}\n\t\t}\n\n\t\tif (!nearestXSnap && !nearestYSnap) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst nudge = new Vec(\n\t\t\tnearestXSnap ? nearestXSnap.x - handleInPageSpace.x : 0,\n\t\t\tnearestYSnap ? nearestYSnap.y - handleInPageSpace.y : 0\n\t\t)\n\n\t\tconst snappedHandle = Vec.Add(handleInPageSpace, nudge)\n\t\tconst snaps: PointsSnapIndicator[] = []\n\n\t\tif (nearestXSnap) {\n\t\t\tconst snappedHandleOnX = new Vec(nearestXSnap.x, snappedHandle.y)\n\t\t\tsnaps.push({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: [nearestXSnap, snappedHandleOnX],\n\t\t\t})\n\t\t}\n\t\tif (nearestYSnap) {\n\t\t\tconst snappedHandleOnY = new Vec(snappedHandle.x, nearestYSnap.y)\n\t\t\tsnaps.push({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: [nearestYSnap, snappedHandleOnY],\n\t\t\t})\n\t\t}\n\n\t\treturn { snaps, nudge }\n\t}\n\n\tsnapHandle({\n\t\tcurrentShapeId,\n\t\thandle,\n\t}: {\n\t\tcurrentShapeId: TLShapeId\n\t\thandle: TLHandle\n\t}): SnapData | null {\n\t\tconst currentShapeTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\tconst handleInPageSpace = currentShapeTransform.applyToPoint(handle)\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tconst snapType = handle.canSnap ? 'point' : handle.snapType\n\n\t\tif (snapType === 'point') {\n\t\t\tconst snapPosition = this.getHandleSnapPosition({ currentShapeId, handle, handleInPageSpace })\n\n\t\t\tif (!snapPosition) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tthis.manager.setIndicators([\n\t\t\t\t{\n\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\ttype: 'points',\n\t\t\t\t\tpoints: [snapPosition],\n\t\t\t\t},\n\t\t\t])\n\n\t\t\treturn { nudge: Vec.Sub(snapPosition, handleInPageSpace) }\n\t\t}\n\n\t\tif (snapType === 'align') {\n\t\t\tconst snapData = this.getHandleSnapData({\n\t\t\t\thandle,\n\t\t\t\tcurrentShapeId,\n\t\t\t})\n\n\t\t\tif (!snapData) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tthis.manager.setIndicators(snapData.snaps)\n\n\t\t\treturn { nudge: snapData.nudge }\n\t\t}\n\n\t\treturn null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { computed } from '@tldraw/state'\nimport { TLHandle, TLShape, TLShapeId, VecModel } from '@tldraw/tlschema'\nimport { assertExists, uniqueId } from '@tldraw/utils'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\nimport type { PointsSnapIndicator, SnapData, SnapManager } from './SnapManager'\n\n/**\n * When dragging a handle, users can snap the handle to key geometry on other nearby shapes.\n * Customize how handles snap to a shape by returning this from\n * {@link ShapeUtil.getHandleSnapGeometry}.\n *\n * Any co-ordinates here should be in the shape's local space.\n *\n * @public\n */\nexport interface HandleSnapGeometry {\n\t/**\n\t * A `Geometry2d` that describe the outline of the shape that the handle will snap to - fills\n\t * are ignored. By default, this is the same geometry returned by {@link ShapeUtil.getGeometry}.\n\t * Set this to `null` to disable handle snapping to this shape's outline.\n\t */\n\toutline?: Geometry2d | null\n\t/**\n\t * Key points on the shape that the handle will snap to. For example, the corners of a\n\t * rectangle, or the centroid of a triangle. By default, no points are used.\n\t */\n\tpoints?: VecModel[]\n\t/**\n\t * By default, handles can't snap to their own shape because moving the handle might change the\n\t * snapping location which can cause feedback loops. You can override this by returning a\n\t * version of `outline` that won't be affected by the current handle's position to use for\n\t * self-snapping.\n\t */\n\tgetSelfSnapOutline?(handle: TLHandle): Geometry2d | null\n\t/**\n\t * By default, handles can't snap to their own shape because moving the handle might change the\n\t * snapping location which can cause feedback loops. You can override this by returning a\n\t * version of `points` that won't be affected by the current handle's position to use for\n\t * self-snapping.\n\t */\n\tgetSelfSnapPoints?(handle: TLHandle): VecModel[]\n}\n\ninterface AlignPointsSnap {\n\tsnaps: PointsSnapIndicator[]\n\tnudge: Vec\n}\n\nconst defaultGetSelfSnapOutline = () => null\nconst defaultGetSelfSnapPoints = () => []\n/** @public */\nexport class HandleSnaps {\n\treadonly editor: Editor\n\tconstructor(readonly manager: SnapManager) {\n\t\tthis.editor = manager.editor\n\t}\n\n\t@computed private getSnapGeometryCache() {\n\t\tconst { editor } = this\n\t\treturn editor.store.createComputedCache('handle snap geometry', (shape: TLShape) => {\n\t\t\tconst snapGeometry = editor.getShapeUtil(shape).getHandleSnapGeometry(shape)\n\t\t\tconst getSelfSnapOutline = snapGeometry.getSelfSnapOutline\n\t\t\t\t? snapGeometry.getSelfSnapOutline.bind(snapGeometry)\n\t\t\t\t: defaultGetSelfSnapOutline\n\t\t\tconst getSelfSnapPoints = snapGeometry.getSelfSnapPoints\n\t\t\t\t? snapGeometry.getSelfSnapPoints.bind(snapGeometry)\n\t\t\t\t: defaultGetSelfSnapPoints\n\n\t\t\treturn {\n\t\t\t\toutline:\n\t\t\t\t\tsnapGeometry.outline === undefined\n\t\t\t\t\t\t? editor.getShapeGeometry(shape)\n\t\t\t\t\t\t: snapGeometry.outline,\n\n\t\t\t\tpoints: snapGeometry.points ?? [],\n\t\t\t\tgetSelfSnapOutline,\n\t\t\t\tgetSelfSnapPoints,\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate *iterateSnapPointsInPageSpace(currentShapeId: TLShapeId, currentHandle: TLHandle) {\n\t\tconst selfSnapPoints = this.getSnapGeometryCache()\n\t\t\t.get(currentShapeId)\n\t\t\t?.getSelfSnapPoints(currentHandle)\n\t\tif (selfSnapPoints && selfSnapPoints.length) {\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\t\tfor (const point of selfSnapPoints) {\n\t\t\t\tyield shapePageTransform.applyToPoint(point)\n\t\t\t}\n\t\t}\n\n\t\tfor (const shapeId of this.manager.getSnappableShapes()) {\n\t\t\tif (shapeId === currentShapeId) continue\n\t\t\tconst snapPoints = this.getSnapGeometryCache().get(shapeId)?.points\n\t\t\tif (!snapPoints || !snapPoints.length) continue\n\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(shapeId))\n\t\t\tfor (const point of snapPoints) {\n\t\t\t\tyield shapePageTransform.applyToPoint(point)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate *iterateSnapOutlines(currentShapeId: TLShapeId, currentHandle: TLHandle) {\n\t\tconst selfSnapOutline = this.getSnapGeometryCache()\n\t\t\t.get(currentShapeId)\n\t\t\t?.getSelfSnapOutline(currentHandle)\n\t\tif (selfSnapOutline) {\n\t\t\tyield { shapeId: currentShapeId, outline: selfSnapOutline }\n\t\t}\n\n\t\tfor (const shapeId of this.manager.getSnappableShapes()) {\n\t\t\tif (shapeId === currentShapeId) continue\n\n\t\t\tconst snapOutline = this.getSnapGeometryCache().get(shapeId)?.outline\n\t\t\tif (!snapOutline) continue\n\n\t\t\tyield { shapeId, outline: snapOutline }\n\t\t}\n\t}\n\n\tprivate getHandleSnapPosition({\n\t\tcurrentShapeId,\n\t\thandle,\n\t\thandleInPageSpace,\n\t}: {\n\t\tcurrentShapeId: TLShapeId\n\t\thandle: TLHandle\n\t\thandleInPageSpace: Vec\n\t}): Vec | null {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\n\t\t// We snap to two different parts of the shape's handle snap geometry:\n\t\t// 1. The `points`. These are handles or other key points that we want to snap to with a\n\t\t// higher priority than the normal outline snapping.\n\t\t// 2. The `outline`. This describes the outline of the shape, and we just snap to the\n\t\t// nearest point on that outline.\n\n\t\t// Start with the points:\n\t\tlet minDistanceForSnapPoint = snapThreshold\n\t\tlet nearestSnapPoint: Vec | null = null\n\t\tfor (const snapPoint of this.iterateSnapPointsInPageSpace(currentShapeId, handle)) {\n\t\t\tif (Vec.DistMin(handleInPageSpace, snapPoint, minDistanceForSnapPoint)) {\n\t\t\t\tminDistanceForSnapPoint = Vec.Dist(handleInPageSpace, snapPoint)\n\t\t\t\tnearestSnapPoint = snapPoint\n\t\t\t}\n\t\t}\n\n\t\t// if we found a snap point, return it - we don't need to check the outlines because points\n\t\t// have a higher priority\n\t\tif (nearestSnapPoint) return nearestSnapPoint\n\n\t\tlet minDistanceForOutline = snapThreshold\n\t\tlet nearestPointOnOutline: Vec | null = null\n\n\t\tfor (const { shapeId, outline } of this.iterateSnapOutlines(currentShapeId, handle)) {\n\t\t\tconst shapePageTransform = assertExists(this.editor.getShapePageTransform(shapeId))\n\t\t\tconst pointInShapeSpace = this.editor.getPointInShapeSpace(shapeId, handleInPageSpace)\n\n\t\t\tconst nearestShapePointInShapeSpace = outline.nearestPoint(pointInShapeSpace)\n\t\t\tconst nearestInPageSpace = shapePageTransform.applyToPoint(nearestShapePointInShapeSpace)\n\n\t\t\tif (Vec.DistMin(handleInPageSpace, nearestInPageSpace, minDistanceForOutline)) {\n\t\t\t\tminDistanceForOutline = Vec.Dist(handleInPageSpace, nearestInPageSpace)\n\t\t\t\tnearestPointOnOutline = nearestInPageSpace\n\t\t\t}\n\t\t}\n\n\t\t// if we found a point on the outline, return it\n\t\tif (nearestPointOnOutline) return nearestPointOnOutline\n\n\t\t// if not, there's no nearby snap point\n\t\treturn null\n\t}\n\n\tprivate getHandleSnapData({\n\t\thandle,\n\t\tcurrentShapeId,\n\t}: {\n\t\thandle: TLHandle\n\t\tcurrentShapeId: TLShapeId\n\t}): AlignPointsSnap | null {\n\t\tconst snapThreshold = this.manager.getSnapThreshold()\n\t\tconst currentShapeTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\tconst handleInPageSpace = currentShapeTransform.applyToPoint(handle)\n\n\t\tlet nearestXSnap: Vec | null = null\n\t\tlet nearestYSnap: Vec | null = null\n\t\tlet minOffsetX = snapThreshold\n\t\tlet minOffsetY = snapThreshold\n\n\t\tfor (const snapPoint of this.iterateSnapPointsInPageSpace(currentShapeId, handle)) {\n\t\t\tconst offsetX = Math.abs(handleInPageSpace.x - snapPoint.x)\n\t\t\tconst offsetY = Math.abs(handleInPageSpace.y - snapPoint.y)\n\t\t\tif (offsetX < minOffsetX) {\n\t\t\t\tminOffsetX = offsetX\n\t\t\t\tnearestXSnap = snapPoint\n\t\t\t}\n\t\t\tif (offsetY < minOffsetY) {\n\t\t\t\tminOffsetY = offsetY\n\t\t\t\tnearestYSnap = snapPoint\n\t\t\t}\n\t\t}\n\n\t\tif (!nearestXSnap && !nearestYSnap) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst nudge = new Vec(\n\t\t\tnearestXSnap ? nearestXSnap.x - handleInPageSpace.x : 0,\n\t\t\tnearestYSnap ? nearestYSnap.y - handleInPageSpace.y : 0\n\t\t)\n\n\t\tconst snappedHandle = Vec.Add(handleInPageSpace, nudge)\n\t\tconst snaps: PointsSnapIndicator[] = []\n\n\t\tif (nearestXSnap) {\n\t\t\tconst snappedHandleOnX = new Vec(nearestXSnap.x, snappedHandle.y)\n\t\t\tsnaps.push({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: [nearestXSnap, snappedHandleOnX],\n\t\t\t})\n\t\t}\n\t\tif (nearestYSnap) {\n\t\t\tconst snappedHandleOnY = new Vec(snappedHandle.x, nearestYSnap.y)\n\t\t\tsnaps.push({\n\t\t\t\tid: uniqueId(),\n\t\t\t\ttype: 'points',\n\t\t\t\tpoints: [nearestYSnap, snappedHandleOnY],\n\t\t\t})\n\t\t}\n\n\t\treturn { snaps, nudge }\n\t}\n\n\tsnapHandle({\n\t\tcurrentShapeId,\n\t\thandle,\n\t}: {\n\t\tcurrentShapeId: TLShapeId\n\t\thandle: TLHandle\n\t}): SnapData | null {\n\t\tconst currentShapeTransform = assertExists(this.editor.getShapePageTransform(currentShapeId))\n\t\tconst handleInPageSpace = currentShapeTransform.applyToPoint(handle)\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tconst snapType = handle.canSnap ? 'point' : handle.snapType\n\n\t\tif (snapType === 'point') {\n\t\t\tconst snapPosition = this.getHandleSnapPosition({ currentShapeId, handle, handleInPageSpace })\n\n\t\t\tif (!snapPosition) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tthis.manager.setIndicators([\n\t\t\t\t{\n\t\t\t\t\tid: uniqueId(),\n\t\t\t\t\ttype: 'points',\n\t\t\t\t\tpoints: [snapPosition],\n\t\t\t\t},\n\t\t\t])\n\n\t\t\treturn { nudge: Vec.Sub(snapPosition, handleInPageSpace) }\n\t\t}\n\n\t\tif (snapType === 'align') {\n\t\t\tconst snapData = this.getHandleSnapData({\n\t\t\t\thandle,\n\t\t\t\tcurrentShapeId,\n\t\t\t})\n\n\t\t\tif (!snapData) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tthis.manager.setIndicators(snapData.snaps)\n\n\t\t\treturn { nudge: snapData.nudge }\n\t\t}\n\n\t\treturn null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -40,7 +40,7 @@ class SpatialIndexManager {
40
40
  const elements = [];
41
41
  for (const shape of shapes) {
42
42
  const bounds = this.editor.getShapePageBounds(shape.id);
43
- if (bounds) {
43
+ if (bounds && bounds.isValid()) {
44
44
  elements.push({
45
45
  minX: bounds.minX,
46
46
  minY: bounds.minY,
@@ -59,7 +59,7 @@ class SpatialIndexManager {
59
59
  for (const shape of objectMapValues(changes.added)) {
60
60
  if (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {
61
61
  const bounds = this.editor.getShapePageBounds(shape.id);
62
- if (bounds) {
62
+ if (bounds && bounds.isValid()) {
63
63
  this.rbush.upsert(shape.id, bounds);
64
64
  }
65
65
  processedShapeIds.add(shape.id);
@@ -77,7 +77,7 @@ class SpatialIndexManager {
77
77
  const isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId;
78
78
  if (isOnPage) {
79
79
  const bounds = this.editor.getShapePageBounds(to.id);
80
- if (bounds) {
80
+ if (bounds && bounds.isValid()) {
81
81
  this.rbush.upsert(to.id, bounds);
82
82
  }
83
83
  } else {
@@ -91,7 +91,7 @@ class SpatialIndexManager {
91
91
  const currentBounds = this.editor.getShapePageBounds(shapeId);
92
92
  const indexedBounds = this.rbush.getBounds(shapeId);
93
93
  if (!this.areBoundsEqual(currentBounds, indexedBounds)) {
94
- if (currentBounds) {
94
+ if (currentBounds && currentBounds.isValid()) {
95
95
  this.rbush.upsert(shapeId, currentBounds);
96
96
  } else {
97
97
  this.rbush.remove(shapeId);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts"],
4
- "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds && bounds.isValid()) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds && bounds.isValid()) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds && bounds.isValid()) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds && currentBounds.isValid()) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }