@tldraw/editor 3.8.0-canary.9eec0b31251a → 3.8.0-canary.a046fbcebad9

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 (55) hide show
  1. package/dist-cjs/index.d.ts +46 -142
  2. package/dist-cjs/index.js +8 -10
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  6. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
  7. package/dist-cjs/lib/editor/Editor.js +14 -25
  8. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  10. package/dist-cjs/lib/editor/managers/TextManager.js +0 -1
  11. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  12. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  14. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  15. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  16. package/dist-cjs/lib/options.js +1 -2
  17. package/dist-cjs/lib/options.js.map +2 -2
  18. package/dist-cjs/version.js +3 -3
  19. package/dist-cjs/version.js.map +1 -1
  20. package/dist-esm/index.d.mts +46 -142
  21. package/dist-esm/index.mjs +1 -3
  22. package/dist-esm/index.mjs.map +2 -2
  23. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
  24. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  25. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  26. package/dist-esm/lib/editor/Editor.mjs +14 -25
  27. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  28. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  29. package/dist-esm/lib/editor/managers/TextManager.mjs +0 -1
  30. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  31. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  32. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  33. package/dist-esm/lib/options.mjs +1 -2
  34. package/dist-esm/lib/options.mjs.map +2 -2
  35. package/dist-esm/version.mjs +3 -3
  36. package/dist-esm/version.mjs.map +1 -1
  37. package/editor.css +1 -2
  38. package/package.json +20 -20
  39. package/src/index.ts +1 -15
  40. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
  41. package/src/lib/config/TLSessionStateSnapshot.ts +1 -3
  42. package/src/lib/editor/Editor.ts +30 -57
  43. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +4 -4
  44. package/src/lib/editor/managers/TextManager.ts +0 -1
  45. package/src/lib/editor/shapes/ShapeUtil.ts +1 -30
  46. package/src/lib/editor/types/emit-types.ts +0 -1
  47. package/src/lib/editor/types/external-content.ts +50 -90
  48. package/src/lib/exports/StyleEmbedder.ts +1 -1
  49. package/src/lib/options.ts +0 -6
  50. package/src/version.ts +3 -3
  51. package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js +0 -66
  52. package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js.map +0 -7
  53. package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs +0 -46
  54. package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs.map +0 -7
  55. package/src/lib/editor/shapes/shared/resizeScaled.ts +0 -61
@@ -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 { Editor } from '../../Editor'\nimport {\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": "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",
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 { Editor } from '../../Editor'\nimport {\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": "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",
6
6
  "names": []
7
7
  }
@@ -138,7 +138,6 @@ class TextManager {
138
138
  elm.style.setProperty("font-weight", opts.fontWeight);
139
139
  elm.style.setProperty("line-height", `${opts.lineHeight * opts.fontSize}px`);
140
140
  elm.style.setProperty("text-align", textAlignmentsForLtr[opts.textAlign]);
141
- elm.style.setProperty("font-style", opts.fontStyle);
142
141
  const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
143
142
  if (shouldTruncateToFirstLine) {
144
143
  elm.style.setProperty("overflow-wrap", "anywhere");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/managers/TextManager.ts"],
4
- "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate baseElem: HTMLDivElement\n\n\tconstructor(public editor: Editor) {\n\t\tthis.baseElem = document.createElement('div')\n\t\tthis.baseElem.classList.add('tl-text')\n\t\tthis.baseElem.classList.add('tl-text-measure')\n\t\tthis.baseElem.tabIndex = -1\n\t}\n\n\tmeasureText(\n\t\ttextToMeasure: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\t// Duplicate our base element; we don't need to clone deep\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\t\telm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')\n\t\telm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')\n\t\telm.style.setProperty('padding', opts.padding)\n\t\telm.style.setProperty(\n\t\t\t'overflow-wrap',\n\t\t\topts.disableOverflowWrapBreaking ? 'normal' : 'break-word'\n\t\t)\n\n\t\telm.textContent = normalizeTextForDom(textToMeasure)\n\t\tconst scrollWidth = elm.scrollWidth\n\t\tconst rect = elm.getBoundingClientRect()\n\t\telm.remove()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('font-size', `${opts.fontSize}px`)\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\telm.remove()\n\n\t\treturn spans\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate baseElem: HTMLDivElement\n\n\tconstructor(public editor: Editor) {\n\t\tthis.baseElem = document.createElement('div')\n\t\tthis.baseElem.classList.add('tl-text')\n\t\tthis.baseElem.classList.add('tl-text-measure')\n\t\tthis.baseElem.tabIndex = -1\n\t}\n\n\tmeasureText(\n\t\ttextToMeasure: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\t// Duplicate our base element; we don't need to clone deep\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\t\telm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')\n\t\telm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')\n\t\telm.style.setProperty('padding', opts.padding)\n\t\telm.style.setProperty(\n\t\t\t'overflow-wrap',\n\t\t\topts.disableOverflowWrapBreaking ? 'normal' : 'break-word'\n\t\t)\n\n\t\telm.textContent = normalizeTextForDom(textToMeasure)\n\t\tconst scrollWidth = elm.scrollWidth\n\t\tconst rect = elm.getBoundingClientRect()\n\t\telm.remove()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('font-size', `${opts.fontSize}px`)\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\telm.remove()\n\n\t\treturn spans\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/shapes/ShapeUtil.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { LegacyMigrations, MigrationSequence } from '@tldraw/store'\nimport {\n\tRecordProps,\n\tTLHandle,\n\tTLPropsMigrations,\n\tTLShape,\n\tTLShapeCrop,\n\tTLShapePartial,\n\tTLUnknownShape,\n} from '@tldraw/tlschema'\nimport { ReactElement } from 'react'\nimport { Box, SelectionHandle } from '../../primitives/Box'\nimport { Vec } from '../../primitives/Vec'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport type { Editor } from '../Editor'\nimport { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'\nimport { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'\nimport { SvgExportContext } from '../types/SvgExportContext'\nimport { TLResizeHandle } from '../types/selection-types'\n\n/** @public */\nexport interface TLShapeUtilConstructor<\n\tT extends TLUnknownShape,\n\tU extends ShapeUtil<T> = ShapeUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\tprops?: RecordProps<T>\n\tmigrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of\n * `fromShapeType` or `toShapeType` will belong to this shape util.\n *\n * @public\n */\nexport interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape> {\n\t/** The type of shape referenced by the `fromId` of the binding. */\n\tfromShapeType: string\n\t/** The type of shape referenced by the `toId` of the binding. */\n\ttoShapeType: string\n\t/** The type of binding. */\n\tbindingType: string\n}\n\n/** @public */\nexport interface TLShapeUtilCanvasSvgDef {\n\tkey: string\n\tcomponent: React.ComponentType\n}\n\n/** @public */\nexport abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {\n\tconstructor(public editor: Editor) {}\n\n\t/**\n\t * Props allow you to define the shape's properties in a way that the editor can understand.\n\t * This has two main uses:\n\t *\n\t * 1. Validation. Shapes will be validated using these props to stop bad data from being saved.\n\t * 2. Styles. Each {@link @tldraw/tlschema#StyleProp} in the props can be set on many shapes at\n\t * once, and will be remembered from one shape to the next.\n\t *\n\t * @example\n\t * ```tsx\n\t * import {T, TLBaseShape, TLDefaultColorStyle, DefaultColorStyle, ShapeUtil} from 'tldraw'\n\t *\n\t * type MyShape = TLBaseShape<'mine', {\n\t * color: TLDefaultColorStyle,\n\t * text: string,\n\t * }>\n\t *\n\t * class MyShapeUtil extends ShapeUtil<MyShape> {\n\t * static props = {\n\t * // we use tldraw's built-in color style:\n\t * color: DefaultColorStyle,\n\t * // validate that the text prop is a string:\n\t * text: T.string,\n\t * }\n\t * }\n\t * ```\n\t */\n\tstatic props?: RecordProps<TLUnknownShape>\n\n\t/**\n\t * Migrations allow you to make changes to a shape's props over time. Read the\n\t * {@link https://www.tldraw.dev/docs/persistence#Shape-props-migrations | shape prop migrations}\n\t * guide for more information.\n\t */\n\tstatic migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n\n\t/**\n\t * The type of the shape util, which should match the shape's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a shape.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Shape['props']\n\n\t/**\n\t * Get the shape's geometry.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract getGeometry(shape: Shape): Geometry2d\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract component(shape: Shape): any\n\n\t/**\n\t * Get JSX describing the shape's indicator (as an SVG element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract indicator(shape: Shape): any\n\n\t/**\n\t * Whether the shape can be snapped to by another shape.\n\t *\n\t * @public\n\t */\n\tcanSnap(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be scrolled while editing.\n\t *\n\t * @public\n\t */\n\tcanScroll(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be bound to. See {@link TLShapeUtilCanBindOpts} for details.\n\t *\n\t * @public\n\t */\n\tcanBind(_opts: TLShapeUtilCanBindOpts<Shape>): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be double clicked to edit.\n\t *\n\t * @public\n\t */\n\tcanEdit(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be resized.\n\t *\n\t * @public\n\t */\n\tcanResize(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be edited in read-only mode.\n\t *\n\t * @public\n\t */\n\tcanEditInReadOnly(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be cropped.\n\t *\n\t * @public\n\t */\n\tcanCrop(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape participates in stacking, aligning, and distributing.\n\t *\n\t * @public\n\t */\n\tcanBeLaidOut(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Does this shape provide a background for its children? If this is true,\n\t * then any children with a `renderBackground` method will have their\n\t * backgrounds rendered _above_ this shape. Otherwise, the children's\n\t * backgrounds will be rendered above either the next ancestor that provides\n\t * a background, or the canvas background.\n\t *\n\t * @internal\n\t */\n\tprovidesBackgroundForChildren(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its resize handles when selected.\n\t *\n\t * @public\n\t */\n\thideResizeHandles(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its rotation handles when selected.\n\t *\n\t * @public\n\t */\n\thideRotateHandle(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds background when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsBg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds foreground when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsFg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape's aspect ratio is locked.\n\t *\n\t * @public\n\t */\n\tisAspectRatioLocked(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.\n\t *\n\t * @param shape - The shape.\n\t * @internal\n\t */\n\tbackgroundComponent?(shape: Shape): any\n\n\t/**\n\t * Get the interpolated props for an animating shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getInterpolatedProps?.(startShape, endShape, t)\n\t * ```\n\t *\n\t * @param startShape - The initial shape.\n\t * @param endShape - The initial shape.\n\t * @param progress - The normalized progress between zero (start) and 1 (end).\n\t * @public\n\t */\n\tgetInterpolatedProps?(startShape: Shape, endShape: Shape, progress: number): Shape['props']\n\n\t/**\n\t * Get an array of handle models for the shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getHandles?.(myShape)\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetHandles?(shape: Shape): TLHandle[]\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape.\n\t * @param type - The shape type.\n\t * @public\n\t */\n\tcanReceiveNewChildrenOfType(_shape: Shape, _type: TLShape['type']) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape type.\n\t * @param shapes - The shapes that are being dropped.\n\t * @public\n\t */\n\tcanDropShapes(_shape: Shape, _shapes: TLShape[]) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get the shape as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoSvg?(shape: Shape, ctx: SvgExportContext): ReactElement | null | Promise<ReactElement | null>\n\n\t/**\n\t * Get the shape's background layer as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoBackgroundSvg?(\n\t\tshape: Shape,\n\t\tctx: SvgExportContext\n\t): ReactElement | null | Promise<ReactElement | null>\n\n\t/** @internal */\n\texpandSelectionOutlinePx(shape: Shape): number | Box {\n\t\treturn 0\n\t}\n\n\t/**\n\t * Return elements to be added to the \\<defs\\> section of the canvases SVG context. This can be\n\t * used to define SVG content (e.g. patterns & masks) that can be referred to by ID from svg\n\t * elements returned by `component`.\n\t *\n\t * Each def should have a unique `key`. If multiple defs from different shapes all have the same\n\t * key, only one will be used.\n\t */\n\tgetCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] {\n\t\treturn []\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping to this this shape in translate/resize operations. See\n\t * {@link BoundsSnapGeometry} for details.\n\t */\n\tgetBoundsSnapGeometry(_shape: Shape): BoundsSnapGeometry {\n\t\treturn {}\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry}\n\t * for details.\n\t */\n\tgetHandleSnapGeometry(_shape: Shape): HandleSnapGeometry {\n\t\treturn {}\n\t}\n\n\tgetText(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\t// Events\n\n\t/**\n\t * A callback called just before a shape is created. This method provides a last chance to modify\n\t * the created shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeCreate = (next) => {\n\t * \treturn { ...next, x: next.x + 1 }\n\t * }\n\t * ```\n\t *\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeCreate?(next: Shape): Shape | void\n\n\t/**\n\t * A callback called just before a shape is updated. This method provides a last chance to modify\n\t * the updated shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeUpdate = (prev, next) => {\n\t * \tif (prev.x === next.x) {\n\t * \t\treturn { ...next, x: next.x + 1 }\n\t * \t}\n\t * }\n\t * ```\n\t *\n\t * @param prev - The previous shape.\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeUpdate?(prev: Shape, next: Shape): Shape | void\n\n\t/**\n\t * A callback called when a shape changes from a crop.\n\t *\n\t * @param shape - The shape at the start of the crop.\n\t * @param info - Info about the crop.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonCrop?(\n\t\tshape: Shape,\n\t\tinfo: TLCropInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when some other shapes are dragged over this one.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onDragShapesOver = (shape, shapes) => {\n\t * \tthis.editor.reparentShapes(shapes, shape.id)\n\t * }\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged over this one.\n\t * @public\n\t */\n\tonDragShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dragged out of this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged out.\n\t * @public\n\t */\n\tonDragShapesOut?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dropped over this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dropped over this one.\n\t * @public\n\t */\n\tonDropShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when a shape starts being resized.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a resize.\n\t *\n\t * @param shape - The shape at the start of the resize.\n\t * @param info - Info about the resize.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResize?(\n\t\tshape: Shape,\n\t\tinfo: TLResizeInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when a shape finishes resizing.\n\t *\n\t * @param initial - The shape at the start of the resize.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being translated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a translation.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes translating.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's handle changes.\n\t *\n\t * @param shape - The current shape.\n\t * @param info - An object containing the handle and whether the handle is 'precise' or not.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being rotated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a rotation.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes rotating.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * Not currently used.\n\t *\n\t * @internal\n\t */\n\tonBindingChange?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's children change.\n\t *\n\t * @param shape - The shape.\n\t * @returns An array of shape updates, or void.\n\t * @public\n\t */\n\tonChildrenChange?(shape: Shape): TLShapePartial[] | void\n\n\t/**\n\t * A callback called when a shape's handle is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param handle - The handle that is double-clicked.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickHandle?(shape: Shape, handle: TLHandle): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's edge is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes being editing.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tonEditEnd?(shape: Shape): void\n}\n\n/**\n * Info about a crop.\n * @param handle - The handle being dragged.\n * @param change - The distance the handle is moved.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLCropInfo<T extends TLShape> {\n\thandle: SelectionHandle\n\tchange: Vec\n\tcrop: TLShapeCrop\n\tuncroppedSize: { w: number; h: number }\n\tinitialShape: T\n}\n\n/**\n * The type of resize.\n *\n * 'scale_shape' - The shape is being scaled, usually as part of a larger selection.\n *\n * 'resize_bounds' - The user is directly manipulating an individual shape's bounds using a resize\n * handle. It is up to shape util implementers to decide how they want to handle the two\n * situations.\n *\n * @public\n */\nexport type TLResizeMode = 'scale_shape' | 'resize_bounds'\n\n/**\n * Info about a resize.\n * @param newPoint - The new local position of the shape.\n * @param handle - The handle being dragged.\n * @param mode - The type of resize.\n * @param scaleX - The scale in the x-axis.\n * @param scaleY - The scale in the y-axis.\n * @param initialBounds - The bounds of the shape at the start of the resize.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLResizeInfo<T extends TLShape> {\n\tnewPoint: Vec\n\thandle: TLResizeHandle\n\tmode: TLResizeMode\n\tscaleX: number\n\tscaleY: number\n\tinitialBounds: Box\n\tinitialShape: T\n}\n\n/* -------------------- Dragging -------------------- */\n\n/** @public */\nexport interface TLHandleDragInfo<T extends TLShape> {\n\thandle: TLHandle\n\tisPrecise: boolean\n\tinitial?: T | undefined\n}\n"],
5
- "mappings": "AAsDO,MAAe,UAAyD;AAAA,EAC9E,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BpC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCP,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAwB;AACjC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,OAA+C;AACtD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAwB;AACjC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,QAAwB;AACzC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,QAAwB;AACpC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,8BAA8B,QAAwB;AACrD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,QAAwB;AACzC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,QAAwB;AACxC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,QAAwB;AAC7C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,QAAwB;AAC7C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,QAAwB;AAC3C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+CA,4BAA4B,QAAe,OAAwB;AAClE,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,QAAe,SAAoB;AAChD,WAAO;AAAA,EACR;AAAA;AAAA,EA0BA,yBAAyB,OAA4B;AACpD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,mBAA8C;AAC7C,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,QAAmC;AACxD,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,QAAmC;AACxD,WAAO,CAAC;AAAA,EACT;AAAA,EAEA,QAAQ,QAAmC;AAC1C,WAAO;AAAA,EACR;AA2PD;",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { LegacyMigrations, MigrationSequence } from '@tldraw/store'\nimport {\n\tRecordProps,\n\tTLHandle,\n\tTLPropsMigrations,\n\tTLShape,\n\tTLShapePartial,\n\tTLUnknownShape,\n} from '@tldraw/tlschema'\nimport { ReactElement } from 'react'\nimport { Box } from '../../primitives/Box'\nimport { Vec } from '../../primitives/Vec'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport type { Editor } from '../Editor'\nimport { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'\nimport { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'\nimport { SvgExportContext } from '../types/SvgExportContext'\nimport { TLResizeHandle } from '../types/selection-types'\n\n/** @public */\nexport interface TLShapeUtilConstructor<\n\tT extends TLUnknownShape,\n\tU extends ShapeUtil<T> = ShapeUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\tprops?: RecordProps<T>\n\tmigrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of\n * `fromShapeType` or `toShapeType` will belong to this shape util.\n *\n * @public\n */\nexport interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape> {\n\t/** The type of shape referenced by the `fromId` of the binding. */\n\tfromShapeType: string\n\t/** The type of shape referenced by the `toId` of the binding. */\n\ttoShapeType: string\n\t/** The type of binding. */\n\tbindingType: string\n}\n\n/** @public */\nexport interface TLShapeUtilCanvasSvgDef {\n\tkey: string\n\tcomponent: React.ComponentType\n}\n\n/** @public */\nexport abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {\n\tconstructor(public editor: Editor) {}\n\n\t/**\n\t * Props allow you to define the shape's properties in a way that the editor can understand.\n\t * This has two main uses:\n\t *\n\t * 1. Validation. Shapes will be validated using these props to stop bad data from being saved.\n\t * 2. Styles. Each {@link @tldraw/tlschema#StyleProp} in the props can be set on many shapes at\n\t * once, and will be remembered from one shape to the next.\n\t *\n\t * @example\n\t * ```tsx\n\t * import {T, TLBaseShape, TLDefaultColorStyle, DefaultColorStyle, ShapeUtil} from 'tldraw'\n\t *\n\t * type MyShape = TLBaseShape<'mine', {\n\t * color: TLDefaultColorStyle,\n\t * text: string,\n\t * }>\n\t *\n\t * class MyShapeUtil extends ShapeUtil<MyShape> {\n\t * static props = {\n\t * // we use tldraw's built-in color style:\n\t * color: DefaultColorStyle,\n\t * // validate that the text prop is a string:\n\t * text: T.string,\n\t * }\n\t * }\n\t * ```\n\t */\n\tstatic props?: RecordProps<TLUnknownShape>\n\n\t/**\n\t * Migrations allow you to make changes to a shape's props over time. Read the\n\t * {@link https://www.tldraw.dev/docs/persistence#Shape-props-migrations | shape prop migrations}\n\t * guide for more information.\n\t */\n\tstatic migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n\n\t/**\n\t * The type of the shape util, which should match the shape's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a shape.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Shape['props']\n\n\t/**\n\t * Get the shape's geometry.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract getGeometry(shape: Shape): Geometry2d\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract component(shape: Shape): any\n\n\t/**\n\t * Get JSX describing the shape's indicator (as an SVG element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract indicator(shape: Shape): any\n\n\t/**\n\t * Whether the shape can be snapped to by another shape.\n\t *\n\t * @public\n\t */\n\tcanSnap(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be scrolled while editing.\n\t *\n\t * @public\n\t */\n\tcanScroll(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be bound to. See {@link TLShapeUtilCanBindOpts} for details.\n\t *\n\t * @public\n\t */\n\tcanBind(_opts: TLShapeUtilCanBindOpts<Shape>): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be double clicked to edit.\n\t *\n\t * @public\n\t */\n\tcanEdit(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be resized.\n\t *\n\t * @public\n\t */\n\tcanResize(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be edited in read-only mode.\n\t *\n\t * @public\n\t */\n\tcanEditInReadOnly(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be cropped.\n\t *\n\t * @public\n\t */\n\tcanCrop(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape participates in stacking, aligning, and distributing.\n\t *\n\t * @public\n\t */\n\tcanBeLaidOut(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Does this shape provide a background for its children? If this is true,\n\t * then any children with a `renderBackground` method will have their\n\t * backgrounds rendered _above_ this shape. Otherwise, the children's\n\t * backgrounds will be rendered above either the next ancestor that provides\n\t * a background, or the canvas background.\n\t *\n\t * @internal\n\t */\n\tprovidesBackgroundForChildren(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its resize handles when selected.\n\t *\n\t * @public\n\t */\n\thideResizeHandles(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its rotation handles when selected.\n\t *\n\t * @public\n\t */\n\thideRotateHandle(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds background when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsBg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds foreground when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsFg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape's aspect ratio is locked.\n\t *\n\t * @public\n\t */\n\tisAspectRatioLocked(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.\n\t *\n\t * @param shape - The shape.\n\t * @internal\n\t */\n\tbackgroundComponent?(shape: Shape): any\n\n\t/**\n\t * Get the interpolated props for an animating shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getInterpolatedProps?.(startShape, endShape, t)\n\t * ```\n\t *\n\t * @param startShape - The initial shape.\n\t * @param endShape - The initial shape.\n\t * @param progress - The normalized progress between zero (start) and 1 (end).\n\t * @public\n\t */\n\tgetInterpolatedProps?(startShape: Shape, endShape: Shape, progress: number): Shape['props']\n\n\t/**\n\t * Get an array of handle models for the shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getHandles?.(myShape)\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetHandles?(shape: Shape): TLHandle[]\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape.\n\t * @param type - The shape type.\n\t * @public\n\t */\n\tcanReceiveNewChildrenOfType(_shape: Shape, _type: TLShape['type']) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape type.\n\t * @param shapes - The shapes that are being dropped.\n\t * @public\n\t */\n\tcanDropShapes(_shape: Shape, _shapes: TLShape[]) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get the shape as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoSvg?(shape: Shape, ctx: SvgExportContext): ReactElement | null | Promise<ReactElement | null>\n\n\t/**\n\t * Get the shape's background layer as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoBackgroundSvg?(\n\t\tshape: Shape,\n\t\tctx: SvgExportContext\n\t): ReactElement | null | Promise<ReactElement | null>\n\n\t/** @internal */\n\texpandSelectionOutlinePx(shape: Shape): number | Box {\n\t\treturn 0\n\t}\n\n\t/**\n\t * Return elements to be added to the \\<defs\\> section of the canvases SVG context. This can be\n\t * used to define SVG content (e.g. patterns & masks) that can be referred to by ID from svg\n\t * elements returned by `component`.\n\t *\n\t * Each def should have a unique `key`. If multiple defs from different shapes all have the same\n\t * key, only one will be used.\n\t */\n\tgetCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] {\n\t\treturn []\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping to this this shape in translate/resize operations. See\n\t * {@link BoundsSnapGeometry} for details.\n\t */\n\tgetBoundsSnapGeometry(_shape: Shape): BoundsSnapGeometry {\n\t\treturn {}\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry}\n\t * for details.\n\t */\n\tgetHandleSnapGeometry(_shape: Shape): HandleSnapGeometry {\n\t\treturn {}\n\t}\n\n\tgetText(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\t// Events\n\n\t/**\n\t * A callback called just before a shape is created. This method provides a last chance to modify\n\t * the created shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeCreate = (next) => {\n\t * \treturn { ...next, x: next.x + 1 }\n\t * }\n\t * ```\n\t *\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeCreate?(next: Shape): Shape | void\n\n\t/**\n\t * A callback called just before a shape is updated. This method provides a last chance to modify\n\t * the updated shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeUpdate = (prev, next) => {\n\t * \tif (prev.x === next.x) {\n\t * \t\treturn { ...next, x: next.x + 1 }\n\t * \t}\n\t * }\n\t * ```\n\t *\n\t * @param prev - The previous shape.\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeUpdate?(prev: Shape, next: Shape): Shape | void\n\n\t/**\n\t * A callback called when some other shapes are dragged over this one.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onDragShapesOver = (shape, shapes) => {\n\t * \tthis.editor.reparentShapes(shapes, shape.id)\n\t * }\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged over this one.\n\t * @public\n\t */\n\tonDragShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dragged out of this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged out.\n\t * @public\n\t */\n\tonDragShapesOut?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dropped over this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dropped over this one.\n\t * @public\n\t */\n\tonDropShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when a shape starts being resized.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a resize.\n\t *\n\t * @param shape - The shape at the start of the resize.\n\t * @param info - Info about the resize.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResize?(\n\t\tshape: Shape,\n\t\tinfo: TLResizeInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when a shape finishes resizing.\n\t *\n\t * @param initial - The shape at the start of the resize.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being translated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a translation.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes translating.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's handle changes.\n\t *\n\t * @param shape - The current shape.\n\t * @param info - An object containing the handle and whether the handle is 'precise' or not.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being rotated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a rotation.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes rotating.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * Not currently used.\n\t *\n\t * @internal\n\t */\n\tonBindingChange?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's children change.\n\t *\n\t * @param shape - The shape.\n\t * @returns An array of shape updates, or void.\n\t * @public\n\t */\n\tonChildrenChange?(shape: Shape): TLShapePartial[] | void\n\n\t/**\n\t * A callback called when a shape's handle is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param handle - The handle that is double-clicked.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickHandle?(shape: Shape, handle: TLHandle): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's edge is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes being editing.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tonEditEnd?(shape: Shape): void\n}\n\n/**\n * The type of resize.\n *\n * 'scale_shape' - The shape is being scaled, usually as part of a larger selection.\n *\n * 'resize_bounds' - The user is directly manipulating an individual shape's bounds using a resize\n * handle. It is up to shape util implementers to decide how they want to handle the two\n * situations.\n *\n * @public\n */\nexport type TLResizeMode = 'scale_shape' | 'resize_bounds'\n\n/**\n * Info about a resize.\n * @param newPoint - The new local position of the shape.\n * @param handle - The handle being dragged.\n * @param mode - The type of resize.\n * @param scaleX - The scale in the x-axis.\n * @param scaleY - The scale in the y-axis.\n * @param initialBounds - The bounds of the shape at the start of the resize.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLResizeInfo<T extends TLShape> {\n\tnewPoint: Vec\n\thandle: TLResizeHandle\n\tmode: TLResizeMode\n\tscaleX: number\n\tscaleY: number\n\tinitialBounds: Box\n\tinitialShape: T\n}\n\n/* -------------------- Dragging -------------------- */\n\n/** @public */\nexport interface TLHandleDragInfo<T extends TLShape> {\n\thandle: TLHandle\n\tisPrecise: boolean\n\tinitial?: T | undefined\n}\n"],
5
+ "mappings": "AAqDO,MAAe,UAAyD;AAAA,EAC9E,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BpC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCP,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAwB;AACjC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,OAA+C;AACtD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAwB;AACjC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,QAAwB;AACzC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,QAAwB;AAC/B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,QAAwB;AACpC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,8BAA8B,QAAwB;AACrD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,QAAwB;AACzC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,QAAwB;AACxC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,QAAwB;AAC7C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,QAAwB;AAC7C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,QAAwB;AAC3C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+CA,4BAA4B,QAAe,OAAwB;AAClE,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,QAAe,SAAoB;AAChD,WAAO;AAAA,EACR;AAAA;AAAA,EA0BA,yBAAyB,OAA4B;AACpD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,mBAA8C;AAC7C,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,QAAmC;AACxD,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,QAAmC;AACxD,WAAO,CAAC;AAAA,EACT;AAAA,EAEA,QAAQ,QAAmC;AAC1C,WAAO;AAAA,EACR;AA8OD;",
6
6
  "names": []
7
7
  }