@tldraw/editor 4.4.0-next.2248da8625f1 → 4.4.0-next.84d68f44c848

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cjs/index.js CHANGED
@@ -377,7 +377,7 @@ var import_uniq = require("./lib/utils/uniq");
377
377
  var import_window_open = require("./lib/utils/window-open");
378
378
  (0, import_utils.registerTldrawLibraryVersion)(
379
379
  "@tldraw/editor",
380
- "4.4.0-next.2248da8625f1",
380
+ "4.4.0-next.84d68f44c848",
381
381
  "cjs"
382
382
  );
383
383
  //# sourceMappingURL=index.js.map
@@ -94,17 +94,16 @@ class SpatialIndexManager {
94
94
  processedShapeIds.add(shape.id);
95
95
  }
96
96
  }
97
- for (const [from, to] of (0, import_utils.objectMapValues)(changes.updated)) {
97
+ for (const [, to] of (0, import_utils.objectMapValues)(changes.updated)) {
98
98
  if (!(0, import_tlschema.isShape)(to)) continue;
99
99
  processedShapeIds.add(to.id);
100
- const wasOnPage = this.editor.getAncestorPageId(from) === this.lastPageId;
101
100
  const isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId;
102
101
  if (isOnPage) {
103
102
  const bounds = this.editor.getShapePageBounds(to.id);
104
103
  if (bounds) {
105
104
  this.rbush.upsert(to.id, bounds);
106
105
  }
107
- } else if (wasOnPage) {
106
+ } else {
108
107
  this.rbush.remove(to.id);
109
108
  }
110
109
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts"],
4
- "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [from, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst wasOnPage = this.editor.getAncestorPageId(from) === this.lastPageId\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else if (wasOnPage) {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "4.4.0-next.2248da8625f1";
25
+ const version = "4.4.0-next.84d68f44c848";
26
26
  const publishDates = {
27
27
  major: "2025-09-18T14:39:22.803Z",
28
- minor: "2026-01-21T13:52:16.707Z",
29
- patch: "2026-01-21T13:52:16.707Z"
28
+ minor: "2026-01-22T09:49:37.857Z",
29
+ patch: "2026-01-22T09:49:37.857Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.4.0-next.2248da8625f1'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-21T13:52:16.707Z',\n\tpatch: '2026-01-21T13:52:16.707Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.4.0-next.84d68f44c848'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-22T09:49:37.857Z',\n\tpatch: '2026-01-22T09:49:37.857Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -305,7 +305,7 @@ import { uniq } from "./lib/utils/uniq.mjs";
305
305
  import { openWindow } from "./lib/utils/window-open.mjs";
306
306
  registerTldrawLibraryVersion(
307
307
  "@tldraw/editor",
308
- "4.4.0-next.2248da8625f1",
308
+ "4.4.0-next.84d68f44c848",
309
309
  "esm"
310
310
  );
311
311
  export {
@@ -71,17 +71,16 @@ class SpatialIndexManager {
71
71
  processedShapeIds.add(shape.id);
72
72
  }
73
73
  }
74
- for (const [from, to] of objectMapValues(changes.updated)) {
74
+ for (const [, to] of objectMapValues(changes.updated)) {
75
75
  if (!isShape(to)) continue;
76
76
  processedShapeIds.add(to.id);
77
- const wasOnPage = this.editor.getAncestorPageId(from) === this.lastPageId;
78
77
  const isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId;
79
78
  if (isOnPage) {
80
79
  const bounds = this.editor.getShapePageBounds(to.id);
81
80
  if (bounds) {
82
81
  this.rbush.upsert(to.id, bounds);
83
82
  }
84
- } else if (wasOnPage) {
83
+ } else {
85
84
  this.rbush.remove(to.id);
86
85
  }
87
86
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts"],
4
- "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [from, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst wasOnPage = this.editor.getAncestorPageId(from) === this.lastPageId\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else if (wasOnPage) {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAmB,aAAa,UAAU,uBAAuB;AAGjE,SAAuC,eAAe;AACtD,SAAS,uBAAuB;AAChC,SAAS,WAAW;AAEpB,SAAS,kBAAuC;AAkBzC,MAAM,oBAAoB;AAAA,EAKhC,YAA4B,QAAgB;AAAhB;AAC3B,SAAK,QAAQ,IAAI,WAAW;AAC5B,SAAK,uBAAuB,KAAK,2BAA2B;AAAA,EAC7D;AAAA,EAPQ;AAAA,EACA;AAAA,EACA,aAA8B;AAAA,EAO9B,6BAA6B;AACpC,UAAM,eAAe,KAAK,OAAO,MAAM,MAAM,cAAc,OAAO;AAElE,WAAO,SAAiB,gBAAgB,CAAC,YAAY,sBAAsB;AAC1E,UAAI,gBAAgB,UAAU,GAAG;AAChC,eAAO,KAAK,iBAAiB,iBAAiB;AAAA,MAC/C;AAEA,YAAM,YAAY,aAAa,aAAa,iBAAiB;AAE7D,UAAI,cAAc,aAAa;AAC9B,eAAO,KAAK,iBAAiB,iBAAiB;AAAA,MAC/C;AAEA,YAAM,gBAAgB,KAAK,OAAO,iBAAiB;AACnD,UAAI,KAAK,eAAe,eAAe;AACtC,eAAO,KAAK,iBAAiB,iBAAiB;AAAA,MAC/C;AAGA,UAAI,UAAU,WAAW,GAAG;AAC3B,eAAO;AAAA,MACR;AAGA,WAAK,yBAAyB,SAAS;AAEvC,aAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAuB;AAC/C,SAAK,MAAM,MAAM;AACjB,SAAK,aAAa,KAAK,OAAO,iBAAiB;AAE/C,UAAM,SAAS,KAAK,OAAO,qBAAqB;AAChD,UAAM,WAA6B,CAAC;AAGpC,eAAW,SAAS,QAAQ;AAC3B,YAAM,SAAS,KAAK,OAAO,mBAAmB,MAAM,EAAE;AACtD,UAAI,QAAQ;AACX,iBAAS,KAAK;AAAA,UACb,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,IAAI,MAAM;AAAA,QACX,CAAC;AAAA,MACF;AAAA,IACD;AAGA,SAAK,MAAM,SAAS,QAAQ;AAE5B,WAAO;AAAA,EACR;AAAA,EAEQ,yBAAyB,WAA0C;AAE1E,UAAM,oBAAoB,oBAAI,IAAe;AAG7C,eAAW,WAAW,WAAW;AAEhC,iBAAW,SAAS,gBAAgB,QAAQ,KAAK,GAAgB;AAChE,YAAI,QAAQ,KAAK,KAAK,KAAK,OAAO,kBAAkB,KAAK,MAAM,KAAK,YAAY;AAC/E,gBAAM,SAAS,KAAK,OAAO,mBAAmB,MAAM,EAAE;AACtD,cAAI,QAAQ;AACX,iBAAK,MAAM,OAAO,MAAM,IAAI,MAAM;AAAA,UACnC;AACA,4BAAkB,IAAI,MAAM,EAAE;AAAA,QAC/B;AAAA,MACD;AAGA,iBAAW,SAAS,gBAAgB,QAAQ,OAAO,GAAgB;AAClE,YAAI,QAAQ,KAAK,GAAG;AACnB,eAAK,MAAM,OAAO,MAAM,EAAE;AAC1B,4BAAkB,IAAI,MAAM,EAAE;AAAA,QAC/B;AAAA,MACD;AAGA,iBAAW,CAAC,MAAM,EAAE,KAAK,gBAAgB,QAAQ,OAAO,GAA2B;AAClF,YAAI,CAAC,QAAQ,EAAE,EAAG;AAClB,0BAAkB,IAAI,GAAG,EAAE;AAE3B,cAAM,YAAY,KAAK,OAAO,kBAAkB,IAAI,MAAM,KAAK;AAC/D,cAAM,WAAW,KAAK,OAAO,kBAAkB,EAAE,MAAM,KAAK;AAE5D,YAAI,UAAU;AACb,gBAAM,SAAS,KAAK,OAAO,mBAAmB,GAAG,EAAE;AACnD,cAAI,QAAQ;AACX,iBAAK,MAAM,OAAO,GAAG,IAAI,MAAM;AAAA,UAChC;AAAA,QACD,WAAW,WAAW;AACrB,eAAK,MAAM,OAAO,GAAG,EAAE;AAAA,QACxB;AAAA,MACD;AAAA,IACD;AAIA,UAAM,cAAc,KAAK,MAAM,eAAe;AAE9C,eAAW,WAAW,aAAa;AAClC,UAAI,kBAAkB,IAAI,OAAO,EAAG;AAEpC,YAAM,gBAAgB,KAAK,OAAO,mBAAmB,OAAO;AAC5D,YAAM,gBAAgB,KAAK,MAAM,UAAU,OAAO;AAElD,UAAI,CAAC,KAAK,eAAe,eAAe,aAAa,GAAG;AACvD,YAAI,eAAe;AAClB,eAAK,MAAM,OAAO,SAAS,aAAa;AAAA,QACzC,OAAO;AACN,eAAK,MAAM,OAAO,OAAO;AAAA,QAC1B;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,eAAe,GAAoB,GAA6B;AACvE,QAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,WAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,wBAAwB,QAA6B;AACpD,SAAK,qBAAqB,IAAI;AAC9B,WAAO,KAAK,MAAM,OAAO,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,mBAAmB,OAAiC,SAAS,GAAmB;AAC/E,SAAK,qBAAqB,IAAI;AAC9B,WAAO,KAAK,MAAM,OAAO,IAAI,IAAI,MAAM,IAAI,QAAQ,MAAM,IAAI,QAAQ,SAAS,GAAG,SAAS,CAAC,CAAC;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAgB;AACf,SAAK,MAAM,QAAQ;AACnB,SAAK,aAAa;AAAA,EACnB;AACD;",
4
+ "sourcesContent": ["import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'\nimport type { RecordsDiff } from '@tldraw/store'\nimport type { TLRecord } from '@tldraw/tlschema'\nimport { TLPageId, TLShape, TLShapeId, isShape } from '@tldraw/tlschema'\nimport { objectMapValues } from '@tldraw/utils'\nimport { Box } from '../../../primitives/Box'\nimport type { Editor } from '../../Editor'\nimport { RBushIndex, type SpatialElement } from './RBushIndex'\n\n/**\n * Manages spatial indexing for efficient shape location queries.\n *\n * Uses an R-tree (via RBush) to enable O(log n) spatial queries instead of O(n) iteration.\n * Handles shapes with computed bounds (arrows, groups, custom shapes) by checking all shapes'\n * bounds on each update using the reactive bounds cache.\n *\n * Key features:\n * - Incremental updates using filterHistory pattern\n * - Leverages existing bounds cache reactivity for dependency tracking\n * - Works with any custom shape type with computed bounds\n * - Per-page index (rebuilds on page change)\n * - Optimized for viewport culling queries\n *\n * @internal\n */\nexport class SpatialIndexManager {\n\tprivate rbush: RBushIndex\n\tprivate spatialIndexComputed: Computed<number>\n\tprivate lastPageId: TLPageId | null = null\n\n\tconstructor(public readonly editor: Editor) {\n\t\tthis.rbush = new RBushIndex()\n\t\tthis.spatialIndexComputed = this.createSpatialIndexComputed()\n\t}\n\n\tprivate createSpatialIndexComputed() {\n\t\tconst shapeHistory = this.editor.store.query.filterHistory('shape')\n\n\t\treturn computed<number>('spatialIndex', (_prevValue, lastComputedEpoch) => {\n\t\t\tif (isUninitialized(_prevValue)) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst shapeDiff = shapeHistory.getDiffSince(lastComputedEpoch)\n\n\t\t\tif (shapeDiff === RESET_VALUE) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\tconst currentPageId = this.editor.getCurrentPageId()\n\t\t\tif (this.lastPageId !== currentPageId) {\n\t\t\t\treturn this.buildFromScratch(lastComputedEpoch)\n\t\t\t}\n\n\t\t\t// No shape changes - index is already up to date\n\t\t\tif (shapeDiff.length === 0) {\n\t\t\t\treturn lastComputedEpoch\n\t\t\t}\n\n\t\t\t// Process incremental updates\n\t\t\tthis.processIncrementalUpdate(shapeDiff)\n\n\t\t\treturn lastComputedEpoch\n\t\t})\n\t}\n\n\tprivate buildFromScratch(epoch: number): number {\n\t\tthis.rbush.clear()\n\t\tthis.lastPageId = this.editor.getCurrentPageId()\n\n\t\tconst shapes = this.editor.getCurrentPageShapes()\n\t\tconst elements: SpatialElement[] = []\n\n\t\t// Collect all shape elements for bulk loading\n\t\tfor (const shape of shapes) {\n\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\tif (bounds) {\n\t\t\t\telements.push({\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tid: shape.id,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Bulk load for efficiency\n\t\tthis.rbush.bulkLoad(elements)\n\n\t\treturn epoch\n\t}\n\n\tprivate processIncrementalUpdate(shapeDiff: RecordsDiff<TLRecord>[]): void {\n\t\t// Track shapes we've already processed from the diff\n\t\tconst processedShapeIds = new Set<TLShapeId>()\n\n\t\t// 1. Process shape additions, removals, and updates from diff\n\t\tfor (const changes of shapeDiff) {\n\t\t\t// Handle additions (only for shapes on current page)\n\t\t\tfor (const shape of objectMapValues(changes.added) as TLShape[]) {\n\t\t\t\tif (isShape(shape) && this.editor.getAncestorPageId(shape) === this.lastPageId) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(shape.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(shape.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle removals\n\t\t\tfor (const shape of objectMapValues(changes.removed) as TLShape[]) {\n\t\t\t\tif (isShape(shape)) {\n\t\t\t\t\tthis.rbush.remove(shape.id)\n\t\t\t\t\tprocessedShapeIds.add(shape.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle updated shapes: page changes and bounds updates\n\t\t\tfor (const [, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {\n\t\t\t\tif (!isShape(to)) continue\n\t\t\t\tprocessedShapeIds.add(to.id)\n\n\t\t\t\tconst isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId\n\n\t\t\t\tif (isOnPage) {\n\t\t\t\t\tconst bounds = this.editor.getShapePageBounds(to.id)\n\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\tthis.rbush.upsert(to.id, bounds)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(to.id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Check remaining shapes in index for bounds changes\n\t\t// This handles shapes with computed bounds (arrows bound to moved shapes, groups with moved children, etc.)\n\t\tconst allShapeIds = this.rbush.getAllShapeIds()\n\n\t\tfor (const shapeId of allShapeIds) {\n\t\t\tif (processedShapeIds.has(shapeId)) continue\n\n\t\t\tconst currentBounds = this.editor.getShapePageBounds(shapeId)\n\t\t\tconst indexedBounds = this.rbush.getBounds(shapeId)\n\n\t\t\tif (!this.areBoundsEqual(currentBounds, indexedBounds)) {\n\t\t\t\tif (currentBounds) {\n\t\t\t\t\tthis.rbush.upsert(shapeId, currentBounds)\n\t\t\t\t} else {\n\t\t\t\t\tthis.rbush.remove(shapeId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate areBoundsEqual(a: Box | undefined, b: Box | undefined): boolean {\n\t\tif (!a && !b) return true\n\t\tif (!a || !b) return false\n\t\treturn a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY\n\t}\n\n\t/**\n\t * Get shape IDs within the given bounds.\n\t * Optimized for viewport culling queries.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsInsideBounds(bounds)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param bounds - The bounds to search within\n\t * @returns Unordered set of shape IDs within the bounds\n\t *\n\t * @public\n\t */\n\tgetShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(bounds)\n\t}\n\n\t/**\n\t * Get shape IDs at a point (with optional margin).\n\t * Creates a small bounding box around the point and searches the spatial index.\n\t *\n\t * Note: Results are unordered. If you need z-order, combine with sorted shapes:\n\t * ```ts\n\t * const candidates = editor.spatialIndex.getShapeIdsAtPoint(point, margin)\n\t * const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))\n\t * ```\n\t *\n\t * @param point - The point to search at\n\t * @param margin - The margin around the point to search (default: 0)\n\t * @returns Unordered set of shape IDs that could potentially contain the point\n\t *\n\t * @public\n\t */\n\tgetShapeIdsAtPoint(point: { x: number; y: number }, margin = 0): Set<TLShapeId> {\n\t\tthis.spatialIndexComputed.get()\n\t\treturn this.rbush.search(new Box(point.x - margin, point.y - margin, margin * 2, margin * 2))\n\t}\n\n\t/**\n\t * Dispose of the spatial index manager.\n\t * Clears the R-tree to prevent memory leaks.\n\t *\n\t * @public\n\t */\n\tdispose(): void {\n\t\tthis.rbush.dispose()\n\t\tthis.lastPageId = null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "4.4.0-next.2248da8625f1";
1
+ const version = "4.4.0-next.84d68f44c848";
2
2
  const publishDates = {
3
3
  major: "2025-09-18T14:39:22.803Z",
4
- minor: "2026-01-21T13:52:16.707Z",
5
- patch: "2026-01-21T13:52:16.707Z"
4
+ minor: "2026-01-22T09:49:37.857Z",
5
+ patch: "2026-01-22T09:49:37.857Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.4.0-next.2248da8625f1'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-21T13:52:16.707Z',\n\tpatch: '2026-01-21T13:52:16.707Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.4.0-next.84d68f44c848'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-22T09:49:37.857Z',\n\tpatch: '2026-01-22T09:49:37.857Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "tldraw infinite canvas SDK (editor).",
4
- "version": "4.4.0-next.2248da8625f1",
4
+ "version": "4.4.0-next.84d68f44c848",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -50,12 +50,12 @@
50
50
  "@tiptap/core": "^3.12.1",
51
51
  "@tiptap/pm": "^3.12.1",
52
52
  "@tiptap/react": "^3.12.1",
53
- "@tldraw/state": "4.4.0-next.2248da8625f1",
54
- "@tldraw/state-react": "4.4.0-next.2248da8625f1",
55
- "@tldraw/store": "4.4.0-next.2248da8625f1",
56
- "@tldraw/tlschema": "4.4.0-next.2248da8625f1",
57
- "@tldraw/utils": "4.4.0-next.2248da8625f1",
58
- "@tldraw/validate": "4.4.0-next.2248da8625f1",
53
+ "@tldraw/state": "4.4.0-next.84d68f44c848",
54
+ "@tldraw/state-react": "4.4.0-next.84d68f44c848",
55
+ "@tldraw/store": "4.4.0-next.84d68f44c848",
56
+ "@tldraw/tlschema": "4.4.0-next.84d68f44c848",
57
+ "@tldraw/utils": "4.4.0-next.84d68f44c848",
58
+ "@tldraw/validate": "4.4.0-next.84d68f44c848",
59
59
  "@types/core-js": "^2.5.8",
60
60
  "@use-gesture/react": "^10.3.1",
61
61
  "classnames": "^2.5.1",
@@ -117,11 +117,10 @@ export class SpatialIndexManager {
117
117
  }
118
118
 
119
119
  // Handle updated shapes: page changes and bounds updates
120
- for (const [from, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {
120
+ for (const [, to] of objectMapValues(changes.updated) as [TLShape, TLShape][]) {
121
121
  if (!isShape(to)) continue
122
122
  processedShapeIds.add(to.id)
123
123
 
124
- const wasOnPage = this.editor.getAncestorPageId(from) === this.lastPageId
125
124
  const isOnPage = this.editor.getAncestorPageId(to) === this.lastPageId
126
125
 
127
126
  if (isOnPage) {
@@ -129,7 +128,7 @@ export class SpatialIndexManager {
129
128
  if (bounds) {
130
129
  this.rbush.upsert(to.id, bounds)
131
130
  }
132
- } else if (wasOnPage) {
131
+ } else {
133
132
  this.rbush.remove(to.id)
134
133
  }
135
134
  }
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '4.4.0-next.2248da8625f1'
4
+ export const version = '4.4.0-next.84d68f44c848'
5
5
  export const publishDates = {
6
6
  major: '2025-09-18T14:39:22.803Z',
7
- minor: '2026-01-21T13:52:16.707Z',
8
- patch: '2026-01-21T13:52:16.707Z',
7
+ minor: '2026-01-22T09:49:37.857Z',
8
+ patch: '2026-01-22T09:49:37.857Z',
9
9
  }