@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.04044ed9e96d

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 (338) hide show
  1. package/README.md +0 -2
  2. package/commenting.css +1152 -350
  3. package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
  4. package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
  5. package/dist-cjs/{ui/avatar.js → canvas/canvas-events.js} +14 -22
  6. package/dist-cjs/canvas/canvas-events.js.map +7 -0
  7. package/dist-cjs/canvas/cluster-badge.js +121 -0
  8. package/dist-cjs/canvas/cluster-badge.js.map +7 -0
  9. package/dist-cjs/canvas/cluster-fade.js +85 -0
  10. package/dist-cjs/canvas/cluster-fade.js.map +7 -0
  11. package/dist-cjs/canvas/cluster-input.js +53 -3
  12. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  13. package/dist-cjs/canvas/cluster-model.js +252 -0
  14. package/dist-cjs/canvas/cluster-model.js.map +7 -0
  15. package/dist-cjs/canvas/comment-body.js +1 -1
  16. package/dist-cjs/canvas/comment-body.js.map +2 -2
  17. package/dist-cjs/canvas/comment-drafts.js +54 -0
  18. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  19. package/dist-cjs/canvas/comment-mutations.js +152 -0
  20. package/dist-cjs/canvas/comment-mutations.js.map +7 -0
  21. package/dist-cjs/canvas/comment-reactions.js +160 -0
  22. package/dist-cjs/canvas/comment-reactions.js.map +7 -0
  23. package/dist-cjs/canvas/comment-render.js +9 -6
  24. package/dist-cjs/canvas/comment-render.js.map +2 -2
  25. package/dist-cjs/canvas/comment-store.js +19 -9
  26. package/dist-cjs/canvas/comment-store.js.map +2 -2
  27. package/dist-cjs/canvas/comment-tool.js +63 -14
  28. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  29. package/dist-cjs/canvas/comments-filter-menu.js +17 -37
  30. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  31. package/dist-cjs/canvas/comments-overflow-menu.js +25 -22
  32. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  33. package/dist-cjs/canvas/comments-overlay.js +151 -989
  34. package/dist-cjs/canvas/comments-overlay.js.map +3 -3
  35. package/dist-cjs/canvas/comments-sidebar.js +49 -41
  36. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  37. package/dist-cjs/canvas/comments-visibility-toggle.js +45 -0
  38. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  39. package/dist-cjs/canvas/context.js +17 -0
  40. package/dist-cjs/canvas/context.js.map +7 -0
  41. package/dist-cjs/canvas/hooks.js +28 -3
  42. package/dist-cjs/canvas/hooks.js.map +2 -2
  43. package/dist-cjs/canvas/license.js.map +1 -1
  44. package/dist-cjs/canvas/mobile-placement.js +91 -0
  45. package/dist-cjs/canvas/mobile-placement.js.map +7 -0
  46. package/dist-cjs/canvas/options.js +57 -2
  47. package/dist-cjs/canvas/options.js.map +2 -2
  48. package/dist-cjs/canvas/pending-composer.js +134 -0
  49. package/dist-cjs/canvas/pending-composer.js.map +7 -0
  50. package/dist-cjs/canvas/pin-stacking.js +81 -0
  51. package/dist-cjs/canvas/pin-stacking.js.map +7 -0
  52. package/dist-cjs/canvas/region-box.js +113 -0
  53. package/dist-cjs/canvas/region-box.js.map +7 -0
  54. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  55. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  56. package/dist-cjs/canvas/state.js +28 -14
  57. package/dist-cjs/canvas/state.js.map +2 -2
  58. package/dist-cjs/canvas/thread-pin.js +304 -0
  59. package/dist-cjs/canvas/thread-pin.js.map +7 -0
  60. package/dist-cjs/canvas/thread-preview.js +177 -0
  61. package/dist-cjs/canvas/thread-preview.js.map +7 -0
  62. package/dist-cjs/canvas/thread-stack.js +199 -0
  63. package/dist-cjs/canvas/thread-stack.js.map +7 -0
  64. package/dist-cjs/canvas/thread-state.js +82 -21
  65. package/dist-cjs/canvas/thread-state.js.map +2 -2
  66. package/dist-cjs/canvas/thread-view.js +450 -0
  67. package/dist-cjs/canvas/thread-view.js.map +7 -0
  68. package/dist-cjs/clustering/computeClusterTable.js +6 -6
  69. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  70. package/dist-cjs/clustering/replay.js +89 -22
  71. package/dist-cjs/clustering/replay.js.map +3 -3
  72. package/dist-cjs/clustering/runtime.js +126 -7
  73. package/dist-cjs/clustering/runtime.js.map +2 -2
  74. package/dist-cjs/clustering/types.js.map +1 -1
  75. package/dist-cjs/index.d.ts +904 -353
  76. package/dist-cjs/index.js +52 -28
  77. package/dist-cjs/index.js.map +2 -2
  78. package/dist-cjs/ui/byline.js +10 -6
  79. package/dist-cjs/ui/byline.js.map +2 -2
  80. package/dist-cjs/ui/comment-card.js +15 -8
  81. package/dist-cjs/ui/comment-card.js.map +2 -2
  82. package/dist-cjs/ui/comment-composer.js +124 -39
  83. package/dist-cjs/ui/comment-composer.js.map +2 -2
  84. package/dist-cjs/ui/comment-pin.js +11 -2
  85. package/dist-cjs/ui/comment-pin.js.map +2 -2
  86. package/dist-cjs/ui/comment-thread.js +9 -7
  87. package/dist-cjs/ui/comment-thread.js.map +2 -2
  88. package/dist-cjs/ui/comments-list.js +39 -40
  89. package/dist-cjs/ui/comments-list.js.map +2 -2
  90. package/dist-cjs/ui/count-badge.js +3 -2
  91. package/dist-cjs/ui/count-badge.js.map +2 -2
  92. package/dist-cjs/ui/emoji-picker.js +54 -0
  93. package/dist-cjs/ui/emoji-picker.js.map +7 -0
  94. package/dist-cjs/ui/empty-state.js +3 -3
  95. package/dist-cjs/ui/empty-state.js.map +2 -2
  96. package/dist-cjs/ui/format-time.js +16 -1
  97. package/dist-cjs/ui/format-time.js.map +2 -2
  98. package/dist-cjs/ui/icons.js +115 -0
  99. package/dist-cjs/ui/icons.js.map +7 -0
  100. package/dist-cjs/ui/reaction-picker.js +71 -0
  101. package/dist-cjs/ui/reaction-picker.js.map +7 -0
  102. package/dist-cjs/ui/reaction.js +58 -6
  103. package/dist-cjs/ui/reaction.js.map +2 -2
  104. package/dist-cjs/ui/reactions.js +20 -7
  105. package/dist-cjs/ui/reactions.js.map +2 -2
  106. package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
  107. package/dist-cjs/ui/reply-count.js.map +7 -0
  108. package/dist-cjs/ui/send-button.js +13 -1
  109. package/dist-cjs/ui/send-button.js.map +2 -2
  110. package/dist-cjs/ui/{comment-text.js → visual-viewport.js} +12 -9
  111. package/dist-cjs/ui/visual-viewport.js.map +7 -0
  112. package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
  113. package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
  114. package/dist-esm/canvas/canvas-events.mjs +15 -0
  115. package/dist-esm/canvas/canvas-events.mjs.map +7 -0
  116. package/dist-esm/canvas/cluster-badge.mjs +106 -0
  117. package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
  118. package/dist-esm/canvas/cluster-fade.mjs +65 -0
  119. package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
  120. package/dist-esm/canvas/cluster-input.mjs +54 -4
  121. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  122. package/dist-esm/canvas/cluster-model.mjs +236 -0
  123. package/dist-esm/canvas/cluster-model.mjs.map +7 -0
  124. package/dist-esm/canvas/comment-body.mjs +1 -1
  125. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  126. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  127. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  128. package/dist-esm/canvas/comment-mutations.mjs +132 -0
  129. package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
  130. package/dist-esm/canvas/comment-reactions.mjs +145 -0
  131. package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
  132. package/dist-esm/canvas/comment-render.mjs +10 -7
  133. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  134. package/dist-esm/canvas/comment-store.mjs +19 -9
  135. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  136. package/dist-esm/canvas/comment-tool.mjs +66 -17
  137. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  138. package/dist-esm/canvas/comments-filter-menu.mjs +17 -36
  139. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  140. package/dist-esm/canvas/comments-overflow-menu.mjs +27 -22
  141. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  142. package/dist-esm/canvas/comments-overlay.mjs +157 -1010
  143. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  144. package/dist-esm/canvas/comments-sidebar.mjs +53 -46
  145. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  146. package/dist-esm/canvas/comments-visibility-toggle.mjs +25 -0
  147. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  148. package/dist-esm/canvas/context.mjs +1 -0
  149. package/dist-esm/canvas/context.mjs.map +7 -0
  150. package/dist-esm/canvas/hooks.mjs +35 -5
  151. package/dist-esm/canvas/hooks.mjs.map +2 -2
  152. package/dist-esm/canvas/license.mjs.map +1 -1
  153. package/dist-esm/canvas/mobile-placement.mjs +71 -0
  154. package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
  155. package/dist-esm/canvas/options.mjs +59 -3
  156. package/dist-esm/canvas/options.mjs.map +2 -2
  157. package/dist-esm/canvas/pending-composer.mjs +126 -0
  158. package/dist-esm/canvas/pending-composer.mjs.map +7 -0
  159. package/dist-esm/canvas/pin-stacking.mjs +61 -0
  160. package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
  161. package/dist-esm/canvas/region-box.mjs +93 -0
  162. package/dist-esm/canvas/region-box.mjs.map +7 -0
  163. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  164. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  165. package/dist-esm/canvas/state.mjs +29 -19
  166. package/dist-esm/canvas/state.mjs.map +2 -2
  167. package/dist-esm/canvas/thread-pin.mjs +310 -0
  168. package/dist-esm/canvas/thread-pin.mjs.map +7 -0
  169. package/dist-esm/canvas/thread-preview.mjs +163 -0
  170. package/dist-esm/canvas/thread-preview.mjs.map +7 -0
  171. package/dist-esm/canvas/thread-stack.mjs +189 -0
  172. package/dist-esm/canvas/thread-stack.mjs.map +7 -0
  173. package/dist-esm/canvas/thread-state.mjs +86 -22
  174. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  175. package/dist-esm/canvas/thread-view.mjs +461 -0
  176. package/dist-esm/canvas/thread-view.mjs.map +7 -0
  177. package/dist-esm/clustering/computeClusterTable.mjs +6 -6
  178. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  179. package/dist-esm/clustering/replay.mjs +89 -22
  180. package/dist-esm/clustering/replay.mjs.map +3 -3
  181. package/dist-esm/clustering/runtime.mjs +126 -7
  182. package/dist-esm/clustering/runtime.mjs.map +2 -2
  183. package/dist-esm/index.d.mts +904 -353
  184. package/dist-esm/index.mjs +102 -43
  185. package/dist-esm/index.mjs.map +2 -2
  186. package/dist-esm/ui/byline.mjs +11 -7
  187. package/dist-esm/ui/byline.mjs.map +2 -2
  188. package/dist-esm/ui/comment-card.mjs +15 -8
  189. package/dist-esm/ui/comment-card.mjs.map +2 -2
  190. package/dist-esm/ui/comment-composer.mjs +135 -39
  191. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  192. package/dist-esm/ui/comment-pin.mjs +11 -2
  193. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  194. package/dist-esm/ui/comment-thread.mjs +9 -7
  195. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  196. package/dist-esm/ui/comments-list.mjs +51 -50
  197. package/dist-esm/ui/comments-list.mjs.map +2 -2
  198. package/dist-esm/ui/count-badge.mjs +3 -2
  199. package/dist-esm/ui/count-badge.mjs.map +2 -2
  200. package/dist-esm/ui/emoji-picker.mjs +34 -0
  201. package/dist-esm/ui/emoji-picker.mjs.map +7 -0
  202. package/dist-esm/ui/empty-state.mjs +3 -3
  203. package/dist-esm/ui/empty-state.mjs.map +2 -2
  204. package/dist-esm/ui/format-time.mjs +16 -1
  205. package/dist-esm/ui/format-time.mjs.map +2 -2
  206. package/dist-esm/ui/icons.mjs +95 -0
  207. package/dist-esm/ui/icons.mjs.map +7 -0
  208. package/dist-esm/ui/reaction-picker.mjs +59 -0
  209. package/dist-esm/ui/reaction-picker.mjs.map +7 -0
  210. package/dist-esm/ui/reaction.mjs +59 -7
  211. package/dist-esm/ui/reaction.mjs.map +2 -2
  212. package/dist-esm/ui/reactions.mjs +21 -8
  213. package/dist-esm/ui/reactions.mjs.map +2 -2
  214. package/dist-esm/ui/reply-count.mjs +9 -0
  215. package/dist-esm/ui/reply-count.mjs.map +7 -0
  216. package/dist-esm/ui/send-button.mjs +13 -1
  217. package/dist-esm/ui/send-button.mjs.map +2 -2
  218. package/dist-esm/ui/visual-viewport.mjs +12 -0
  219. package/dist-esm/ui/visual-viewport.mjs.map +7 -0
  220. package/package.json +6 -6
  221. package/src/canvas/anchor-lifecycle.test.ts +290 -0
  222. package/src/canvas/anchor-lifecycle.ts +191 -0
  223. package/src/canvas/canvas-events.ts +18 -0
  224. package/src/canvas/canvas.css +464 -55
  225. package/src/canvas/cluster-badge.tsx +133 -0
  226. package/src/canvas/cluster-fade.ts +102 -0
  227. package/src/canvas/cluster-input.test.ts +178 -36
  228. package/src/canvas/cluster-input.ts +95 -9
  229. package/src/canvas/cluster-model.ts +378 -0
  230. package/src/canvas/comment-body.tsx +1 -1
  231. package/src/canvas/comment-drafts.test.ts +55 -0
  232. package/src/canvas/comment-drafts.ts +46 -0
  233. package/src/canvas/comment-mutations.test.ts +452 -0
  234. package/src/canvas/comment-mutations.ts +299 -0
  235. package/src/canvas/comment-reactions.test.ts +154 -0
  236. package/src/canvas/comment-reactions.tsx +246 -0
  237. package/src/canvas/comment-render.test.ts +1 -1
  238. package/src/canvas/comment-render.ts +17 -13
  239. package/src/canvas/comment-store.ts +68 -33
  240. package/src/canvas/comment-tool.test.ts +162 -0
  241. package/src/canvas/comment-tool.tsx +103 -25
  242. package/src/canvas/comments-filter-menu.tsx +20 -36
  243. package/src/canvas/comments-overflow-menu.tsx +17 -19
  244. package/src/canvas/comments-overlay.tsx +252 -1369
  245. package/src/canvas/comments-sidebar.test.ts +45 -0
  246. package/src/canvas/comments-sidebar.tsx +112 -73
  247. package/src/canvas/comments-visibility-toggle.tsx +25 -0
  248. package/src/canvas/context.ts +48 -0
  249. package/src/canvas/hooks.test.ts +94 -0
  250. package/src/canvas/hooks.ts +66 -11
  251. package/src/canvas/license.ts +1 -1
  252. package/src/canvas/mobile-placement.ts +115 -0
  253. package/src/canvas/options.test.ts +176 -4
  254. package/src/canvas/options.ts +276 -18
  255. package/src/canvas/pending-composer.tsx +157 -0
  256. package/src/canvas/pin-stacking.test.ts +224 -0
  257. package/src/canvas/pin-stacking.ts +103 -0
  258. package/src/canvas/region-box.tsx +124 -0
  259. package/src/canvas/sidebar-filters.ts +4 -3
  260. package/src/canvas/state.ts +88 -39
  261. package/src/canvas/thread-pin.tsx +418 -0
  262. package/src/canvas/thread-preview.test.ts +91 -0
  263. package/src/canvas/thread-preview.tsx +294 -0
  264. package/src/canvas/thread-stack.tsx +242 -0
  265. package/src/canvas/thread-state.test.ts +260 -0
  266. package/src/canvas/thread-state.ts +171 -37
  267. package/src/canvas/thread-view.test.ts +72 -0
  268. package/src/canvas/thread-view.tsx +614 -0
  269. package/src/clustering/computeClusterTable.test.ts +24 -23
  270. package/src/clustering/computeClusterTable.ts +17 -8
  271. package/src/clustering/replay.test.ts +0 -7
  272. package/src/clustering/replay.ts +131 -32
  273. package/src/clustering/runtime.test.ts +267 -13
  274. package/src/clustering/runtime.ts +176 -19
  275. package/src/clustering/schedule.test.ts +0 -6
  276. package/src/clustering/screen-offsets.test.ts +171 -0
  277. package/src/clustering/types.ts +19 -9
  278. package/src/index.ts +91 -42
  279. package/src/ui/byline.tsx +21 -9
  280. package/src/ui/comment-card.tsx +20 -10
  281. package/src/ui/comment-composer.tsx +178 -56
  282. package/src/ui/comment-pin.tsx +13 -2
  283. package/src/ui/comment-thread.tsx +13 -6
  284. package/src/ui/comments-list.test.ts +31 -0
  285. package/src/ui/comments-list.tsx +82 -47
  286. package/src/ui/comments.css +577 -294
  287. package/src/ui/count-badge.tsx +9 -2
  288. package/src/ui/emoji-picker.test.ts +25 -0
  289. package/src/ui/emoji-picker.tsx +76 -0
  290. package/src/ui/empty-state.tsx +3 -3
  291. package/src/ui/format-time.test.ts +69 -0
  292. package/src/ui/format-time.ts +27 -2
  293. package/src/ui/icons.tsx +116 -0
  294. package/src/ui/reaction-picker.tsx +102 -0
  295. package/src/ui/reaction.tsx +125 -6
  296. package/src/ui/reactions.tsx +69 -10
  297. package/src/ui/reply-count.ts +16 -0
  298. package/src/ui/send-button.tsx +13 -3
  299. package/src/ui/visual-viewport.test.ts +36 -0
  300. package/src/ui/visual-viewport.ts +27 -0
  301. package/dist-cjs/canvas/region-options.js +0 -49
  302. package/dist-cjs/canvas/region-options.js.map +0 -7
  303. package/dist-cjs/ui/avatar.js.map +0 -7
  304. package/dist-cjs/ui/comment-mention.js +0 -42
  305. package/dist-cjs/ui/comment-mention.js.map +0 -7
  306. package/dist-cjs/ui/comment-text.js.map +0 -7
  307. package/dist-cjs/ui/mention-list.js +0 -70
  308. package/dist-cjs/ui/mention-list.js.map +0 -7
  309. package/dist-cjs/ui/mention-suggestion.js +0 -206
  310. package/dist-cjs/ui/mention-suggestion.js.map +0 -7
  311. package/dist-cjs/ui/mention.js.map +0 -7
  312. package/dist-cjs/ui/render-markdown.js +0 -63
  313. package/dist-cjs/ui/render-markdown.js.map +0 -7
  314. package/dist-esm/canvas/region-options.mjs +0 -29
  315. package/dist-esm/canvas/region-options.mjs.map +0 -7
  316. package/dist-esm/ui/avatar.mjs +0 -23
  317. package/dist-esm/ui/avatar.mjs.map +0 -7
  318. package/dist-esm/ui/comment-mention.mjs +0 -22
  319. package/dist-esm/ui/comment-mention.mjs.map +0 -7
  320. package/dist-esm/ui/comment-text.mjs +0 -9
  321. package/dist-esm/ui/comment-text.mjs.map +0 -7
  322. package/dist-esm/ui/mention-list.mjs +0 -50
  323. package/dist-esm/ui/mention-list.mjs.map +0 -7
  324. package/dist-esm/ui/mention-suggestion.mjs +0 -186
  325. package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
  326. package/dist-esm/ui/mention.mjs +0 -11
  327. package/dist-esm/ui/mention.mjs.map +0 -7
  328. package/dist-esm/ui/render-markdown.mjs +0 -45
  329. package/dist-esm/ui/render-markdown.mjs.map +0 -7
  330. package/src/canvas/region-options.ts +0 -57
  331. package/src/ui/avatar.tsx +0 -31
  332. package/src/ui/comment-mention.ts +0 -47
  333. package/src/ui/comment-text.tsx +0 -12
  334. package/src/ui/mention-list.tsx +0 -106
  335. package/src/ui/mention-suggestion.test.ts +0 -18
  336. package/src/ui/mention-suggestion.tsx +0 -285
  337. package/src/ui/mention.tsx +0 -9
  338. package/src/ui/render-markdown.tsx +0 -72
@@ -1,8 +1,8 @@
1
1
  const D_FLOOR = 1e-9;
2
- function cappedReplay(leaves, edges, opts) {
2
+ function cappedReplay(leaves, edges, opts, screenOffsets) {
3
3
  validateOptions(opts);
4
4
  if (edges.length === 0) return [];
5
- const clusters = new ClusterState(leaves);
5
+ const clusters = new ClusterState(leaves, screenOffsets);
6
6
  const heap = new EdgeMaxHeap(edges, leaves);
7
7
  const incident = leaves.map(() => []);
8
8
  for (let i = 0; i < edges.length; i++) {
@@ -58,11 +58,31 @@ function zForEdge(edge, clusters, opts) {
58
58
  return zForRoots(edge, clusters.find(edge.a), clusters.find(edge.b), clusters, opts);
59
59
  }
60
60
  function zForRoots(edge, aRoot, bRoot, clusters, opts) {
61
- if (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY;
62
- const gap = opts.Tc / clusters.centroidDistance(aRoot, bRoot);
61
+ const off = clusters.offsetDelta(aRoot, bRoot);
62
+ if (off === null) {
63
+ if (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY;
64
+ const gap2 = opts.Tc / clusters.centroidDistance(aRoot, bRoot);
65
+ const fit2 = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot);
66
+ return Math.min(gap2, fit2);
67
+ }
68
+ const dc = clusters.centroidDelta(aRoot, bRoot);
69
+ if (dc.x === 0 && dc.y === 0) {
70
+ return Math.hypot(off.x, off.y) < opts.Tc ? Number.POSITIVE_INFINITY : 0;
71
+ }
72
+ const gap = largestVisualCrossing(dc.x, dc.y, off.x, off.y, opts.Tc);
73
+ if (gap === null) return 0;
63
74
  const fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot);
64
75
  return Math.min(gap, fit);
65
76
  }
77
+ function largestVisualCrossing(dcx, dcy, dox, doy, level) {
78
+ const a = dcx * dcx + dcy * dcy;
79
+ const b = 2 * (dcx * dox + dcy * doy);
80
+ const c = dox * dox + doy * doy - level * level;
81
+ const disc = b * b - 4 * a * c;
82
+ if (disc < 0) return null;
83
+ const z = (-b + Math.sqrt(disc)) / (2 * a);
84
+ return z > 0 ? z : null;
85
+ }
66
86
  class ClusterState {
67
87
  parent;
68
88
  minX;
@@ -75,7 +95,12 @@ class ClusterState {
75
95
  nodes;
76
96
  memberLists;
77
97
  minMemberIds;
78
- constructor(leaves) {
98
+ // Per-cluster sums of member render offsets (screen px), maintained like the centroid sums.
99
+ // Null when no offsets were passed — offsetDelta() then answers null unconditionally, which
100
+ // routes every pricing call down the offset-unaware path.
101
+ offsetX;
102
+ offsetY;
103
+ constructor(leaves, screenOffsets) {
79
104
  const n = leaves.length;
80
105
  this.parent = new Int32Array(n);
81
106
  this.minX = new Float64Array(n);
@@ -88,6 +113,20 @@ class ClusterState {
88
113
  this.nodes = new Array(n);
89
114
  this.memberLists = new Array(n);
90
115
  this.minMemberIds = new Array(n);
116
+ if (screenOffsets !== void 0 && screenOffsets.size > 0) {
117
+ this.offsetX = new Float64Array(n);
118
+ this.offsetY = new Float64Array(n);
119
+ for (let i = 0; i < n; i++) {
120
+ const offset = screenOffsets.get(leaves[i].id);
121
+ if (offset) {
122
+ this.offsetX[i] = offset.x;
123
+ this.offsetY[i] = offset.y;
124
+ }
125
+ }
126
+ } else {
127
+ this.offsetX = null;
128
+ this.offsetY = null;
129
+ }
91
130
  for (let i = 0; i < n; i++) {
92
131
  const leaf = leaves[i];
93
132
  this.parent[i] = i;
@@ -126,6 +165,22 @@ class ClusterState {
126
165
  this.centroidY[aRoot] - this.centroidY[bRoot]
127
166
  );
128
167
  }
168
+ centroidDelta(aRoot, bRoot) {
169
+ return {
170
+ x: this.centroidX[aRoot] - this.centroidX[bRoot],
171
+ y: this.centroidY[aRoot] - this.centroidY[bRoot]
172
+ };
173
+ }
174
+ /** The difference of the two clusters' mean render offsets (screen px), or null when it's
175
+ * zero — including always when no offsets were passed. Null routes pricing down the
176
+ * offset-unaware path. */
177
+ offsetDelta(aRoot, bRoot) {
178
+ if (this.offsetX === null || this.offsetY === null) return null;
179
+ const x = this.offsetX[aRoot] / this.counts[aRoot] - this.offsetX[bRoot] / this.counts[bRoot];
180
+ const y = this.offsetY[aRoot] / this.counts[aRoot] - this.offsetY[bRoot] / this.counts[bRoot];
181
+ if (x === 0 && y === 0) return null;
182
+ return { x, y };
183
+ }
129
184
  unionBboxDiag(aRoot, bRoot) {
130
185
  const minX = Math.min(this.minX[aRoot], this.minX[bRoot]);
131
186
  const minY = Math.min(this.minY[aRoot], this.minY[bRoot]);
@@ -163,6 +218,10 @@ class ClusterState {
163
218
  this.nodes[leftRoot] = result;
164
219
  this.memberLists[leftRoot] = members;
165
220
  this.minMemberIds[leftRoot] = members[0];
221
+ if (this.offsetX !== null && this.offsetY !== null) {
222
+ this.offsetX[leftRoot] += this.offsetX[rightRoot];
223
+ this.offsetY[leftRoot] += this.offsetY[rightRoot];
224
+ }
166
225
  return { z, children: [left, right], result };
167
226
  }
168
227
  }
@@ -183,13 +242,29 @@ function mergeSortedMembers(a, b) {
183
242
  return out;
184
243
  }
185
244
  class EdgeMaxHeap {
245
+ items = [];
246
+ // Per-edge normalized (lo, hi) id pair for the z tie-break, precomputed once so comparisons
247
+ // allocate nothing. With coincident anchors every edge prices to the same z (+Infinity), so
248
+ // the tie-break runs on nearly every comparison of a rebuild — allocating the pair there
249
+ // churned millions of short-lived tuples.
250
+ loIds;
251
+ hiIds;
186
252
  constructor(edges, leaves) {
187
- this.edges = edges;
188
- this.leaves = leaves;
253
+ const n = edges.length;
254
+ this.loIds = new Array(n);
255
+ this.hiIds = new Array(n);
256
+ for (let i = 0; i < n; i++) {
257
+ const aId = leaves[edges[i].a].id;
258
+ const bId = leaves[edges[i].b].id;
259
+ if (aId < bId) {
260
+ this.loIds[i] = aId;
261
+ this.hiIds[i] = bId;
262
+ } else {
263
+ this.loIds[i] = bId;
264
+ this.hiIds[i] = aId;
265
+ }
266
+ }
189
267
  }
190
- edges;
191
- leaves;
192
- items = [];
193
268
  peek() {
194
269
  return this.items[0];
195
270
  }
@@ -210,7 +285,10 @@ class EdgeMaxHeap {
210
285
  higherPriority(a, b) {
211
286
  if (a.z > b.z) return true;
212
287
  if (a.z < b.z) return false;
213
- return edgeIdPairLess(this.edges[a.edgeIndex], this.edges[b.edgeIndex], this.leaves);
288
+ const aLo = this.loIds[a.edgeIndex];
289
+ const bLo = this.loIds[b.edgeIndex];
290
+ if (aLo !== bLo) return aLo < bLo;
291
+ return this.hiIds[a.edgeIndex] < this.hiIds[b.edgeIndex];
214
292
  }
215
293
  siftUp(index) {
216
294
  while (index > 0) {
@@ -237,17 +315,6 @@ class EdgeMaxHeap {
237
315
  }
238
316
  }
239
317
  }
240
- function edgeIdPairLess(a, b, leaves) {
241
- const [aLo, aHi] = normalizedEdgeIds(a, leaves);
242
- const [bLo, bHi] = normalizedEdgeIds(b, leaves);
243
- if (aLo !== bLo) return aLo < bLo;
244
- return aHi < bHi;
245
- }
246
- function normalizedEdgeIds(edge, leaves) {
247
- const aId = leaves[edge.a].id;
248
- const bId = leaves[edge.b].id;
249
- return aId < bId ? [aId, bId] : [bId, aId];
250
- }
251
318
  export {
252
319
  D_FLOOR,
253
320
  cappedReplay
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/clustering/replay.ts"],
4
- "sourcesContent": ["import type { ClusterNode, LeafInput, MstEdge, RawMergeEvent } from './types'\n\nexport const D_FLOOR = 1e-9\n\nexport function cappedReplay(\n\tleaves: readonly LeafInput[],\n\tedges: readonly MstEdge[],\n\topts: { Tc: number; Dmax: number }\n): RawMergeEvent[] {\n\tvalidateOptions(opts)\n\n\tif (edges.length === 0) return []\n\n\tconst clusters = new ClusterState(leaves)\n\tconst heap = new EdgeMaxHeap(edges, leaves)\n\t// Edges incident to each current cluster root, for eager repricing: a merge\n\t// moves the result's centroid, which can RAISE an incident edge's key (the\n\t// centroid may move toward a neighbor), so the lazy pop-time recheck alone\n\t// is not enough \u2014 every price change pushes a fresh heap entry.\n\tconst incident: number[][] = leaves.map(() => [])\n\tfor (let i = 0; i < edges.length; i++) {\n\t\theap.push({ edgeIndex: i, z: zForEdge(edges[i], clusters, opts) })\n\t\tincident[edges[i].a].push(i)\n\t\tincident[edges[i].b].push(i)\n\t}\n\n\tconst events: RawMergeEvent[] = []\n\t// Emitted thresholds are clamped non-increasing: with centroid pricing a\n\t// later merge's raw price can in principle exceed an earlier one's, and the\n\t// table's descending sort is load-bearing for the runtime cursor.\n\tlet lastZ = Number.POSITIVE_INFINITY\n\twhile (events.length < edges.length) {\n\t\tconst entry = heap.pop()\n\t\tif (!entry) break\n\n\t\tconst edge = edges[entry.edgeIndex]\n\t\tconst aRoot = clusters.find(edge.a)\n\t\tconst bRoot = clusters.find(edge.b)\n\t\tif (aRoot === bRoot) continue // stale duplicate of an already-fired edge\n\n\t\tconst current = { edgeIndex: entry.edgeIndex, z: zForRoots(edge, aRoot, bRoot, clusters, opts) }\n\t\tconst next = heap.peek()\n\t\tif (next && heap.higherPriority(next, current)) {\n\t\t\theap.push(current)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst z = Math.min(current.z, lastZ)\n\t\tlastZ = z\n\t\tevents.push(clusters.merge(aRoot, bRoot, z))\n\n\t\t// eager reprice: the new cluster's centroid and bbox changed, so every\n\t\t// surviving incident edge gets a fresh entry at its current price\n\t\tconst root = clusters.find(edge.a)\n\t\tconst other = root === aRoot ? bRoot : aRoot\n\t\tconst survivors: number[] = []\n\t\tfor (const idx of [...incident[root], ...incident[other]]) {\n\t\t\tconst e2 = edges[idx]\n\t\t\tconst ra = clusters.find(e2.a)\n\t\t\tconst rb = clusters.find(e2.b)\n\t\t\tif (ra === rb) continue\n\t\t\tsurvivors.push(idx)\n\t\t\theap.push({ edgeIndex: idx, z: zForRoots(e2, ra, rb, clusters, opts) })\n\t\t}\n\t\tincident[root] = survivors\n\t}\n\n\treturn events\n}\n\nfunction validateOptions(opts: { Tc: number; Dmax: number }) {\n\tif (!Number.isFinite(opts.Tc) || opts.Tc <= 0) {\n\t\tthrow new Error('Tc must be greater than 0')\n\t}\n\tif (!Number.isFinite(opts.Dmax) || opts.Dmax <= 0) {\n\t\tthrow new Error('Dmax must be greater than 0')\n\t}\n\tif (opts.Dmax < opts.Tc) {\n\t\tthrow new Error('Dmax must be greater than or equal to Tc')\n\t}\n}\n\nfunction zForEdge(\n\tedge: MstEdge,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\treturn zForRoots(edge, clusters.find(edge.a), clusters.find(edge.b), clusters, opts)\n}\n\nfunction zForRoots(\n\tedge: MstEdge,\n\taRoot: number,\n\tbRoot: number,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\tif (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY\n\t// Badge-anchored gap pricing: clusters render as badges at their centroids,\n\t// so the merge is priced by the distance between the rendered centers, not\n\t// the nearest members. For leaves the two are identical; for clusters the\n\t// centroid distance is larger, so groups merge later than their closest\n\t// members would suggest \u2014 matching what the user actually sees.\n\t// (Coincident CENTROIDS with non-coincident members leave the gap term\n\t// Infinity and the fit term finite \u2014 the min stays finite, no special case.)\n\tconst gap = opts.Tc / clusters.centroidDistance(aRoot, bRoot)\n\tconst fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot)\n\treturn Math.min(gap, fit)\n}\n\nclass ClusterState {\n\tprivate readonly parent: Int32Array\n\tprivate readonly minX: Float64Array\n\tprivate readonly minY: Float64Array\n\tprivate readonly maxX: Float64Array\n\tprivate readonly maxY: Float64Array\n\tprivate readonly centroidX: Float64Array\n\tprivate readonly centroidY: Float64Array\n\tprivate readonly counts: Int32Array\n\tprivate readonly nodes: ClusterNode[]\n\tprivate readonly memberLists: string[][]\n\tprivate readonly minMemberIds: string[]\n\n\tconstructor(leaves: readonly LeafInput[]) {\n\t\tconst n = leaves.length\n\t\tthis.parent = new Int32Array(n)\n\t\tthis.minX = new Float64Array(n)\n\t\tthis.minY = new Float64Array(n)\n\t\tthis.maxX = new Float64Array(n)\n\t\tthis.maxY = new Float64Array(n)\n\t\tthis.centroidX = new Float64Array(n)\n\t\tthis.centroidY = new Float64Array(n)\n\t\tthis.counts = new Int32Array(n)\n\t\tthis.nodes = new Array(n)\n\t\tthis.memberLists = new Array(n)\n\t\tthis.minMemberIds = new Array(n)\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst leaf = leaves[i]\n\t\t\tthis.parent[i] = i\n\t\t\tthis.minX[i] = leaf.point.x\n\t\t\tthis.minY[i] = leaf.point.y\n\t\t\tthis.maxX[i] = leaf.point.x\n\t\t\tthis.maxY[i] = leaf.point.y\n\t\t\tthis.centroidX[i] = leaf.point.x\n\t\t\tthis.centroidY[i] = leaf.point.y\n\t\t\tthis.counts[i] = 1\n\t\t\tthis.memberLists[i] = [leaf.id]\n\t\t\tthis.minMemberIds[i] = leaf.id\n\t\t\tthis.nodes[i] = {\n\t\t\t\tid: leaf.id,\n\t\t\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\t\t\tcount: 1,\n\t\t\t\tmembers: [leaf.id],\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(index: number): number {\n\t\tlet root = index\n\t\twhile (this.parent[root] !== root) {\n\t\t\troot = this.parent[root]\n\t\t}\n\t\twhile (this.parent[index] !== index) {\n\t\t\tconst next = this.parent[index]\n\t\t\tthis.parent[index] = root\n\t\t\tindex = next\n\t\t}\n\t\treturn root\n\t}\n\n\tcentroidDistance(aRoot: number, bRoot: number): number {\n\t\treturn Math.hypot(\n\t\t\tthis.centroidX[aRoot] - this.centroidX[bRoot],\n\t\t\tthis.centroidY[aRoot] - this.centroidY[bRoot]\n\t\t)\n\t}\n\n\tunionBboxDiag(aRoot: number, bRoot: number): number {\n\t\tconst minX = Math.min(this.minX[aRoot], this.minX[bRoot])\n\t\tconst minY = Math.min(this.minY[aRoot], this.minY[bRoot])\n\t\tconst maxX = Math.max(this.maxX[aRoot], this.maxX[bRoot])\n\t\tconst maxY = Math.max(this.maxY[aRoot], this.maxY[bRoot])\n\t\treturn Math.hypot(maxX - minX, maxY - minY)\n\t}\n\n\tmerge(aRoot: number, bRoot: number, z: number): RawMergeEvent {\n\t\tconst leftRoot = this.minMemberIds[aRoot] < this.minMemberIds[bRoot] ? aRoot : bRoot\n\t\tconst rightRoot = leftRoot === aRoot ? bRoot : aRoot\n\t\tconst left = this.nodes[leftRoot]\n\t\tconst right = this.nodes[rightRoot]\n\t\tconst count = this.counts[leftRoot] + this.counts[rightRoot]\n\t\tconst members = mergeSortedMembers(this.memberLists[leftRoot], this.memberLists[rightRoot])\n\n\t\tconst minX = Math.min(this.minX[leftRoot], this.minX[rightRoot])\n\t\tconst minY = Math.min(this.minY[leftRoot], this.minY[rightRoot])\n\t\tconst maxX = Math.max(this.maxX[leftRoot], this.maxX[rightRoot])\n\t\tconst maxY = Math.max(this.maxY[leftRoot], this.maxY[rightRoot])\n\t\tconst centroidX =\n\t\t\t(this.counts[leftRoot] * this.centroidX[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidX[rightRoot]) /\n\t\t\tcount\n\t\tconst centroidY =\n\t\t\t(this.counts[leftRoot] * this.centroidY[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidY[rightRoot]) /\n\t\t\tcount\n\n\t\t// Leaf ids are assumed not to start with `cluster:`; see the step 2 contract.\n\t\tconst result: ClusterNode = {\n\t\t\tid: `cluster:${count}:${members[0]}`,\n\t\t\tcentroid: { x: centroidX, y: centroidY },\n\t\t\tcount,\n\t\t\tmembers,\n\t\t}\n\n\t\tthis.parent[rightRoot] = leftRoot\n\t\tthis.minX[leftRoot] = minX\n\t\tthis.minY[leftRoot] = minY\n\t\tthis.maxX[leftRoot] = maxX\n\t\tthis.maxY[leftRoot] = maxY\n\t\tthis.centroidX[leftRoot] = centroidX\n\t\tthis.centroidY[leftRoot] = centroidY\n\t\tthis.counts[leftRoot] = count\n\t\tthis.nodes[leftRoot] = result\n\t\tthis.memberLists[leftRoot] = members\n\t\tthis.minMemberIds[leftRoot] = members[0]\n\n\t\treturn { z, children: [left, right], result }\n\t}\n}\n\nfunction mergeSortedMembers(a: readonly string[], b: readonly string[]): string[] {\n\tconst out = new Array<string>(a.length + b.length)\n\tlet i = 0\n\tlet j = 0\n\tlet k = 0\n\twhile (i < a.length && j < b.length) {\n\t\tif (a[i] < b[j]) {\n\t\t\tout[k++] = a[i++]\n\t\t} else {\n\t\t\tout[k++] = b[j++]\n\t\t}\n\t}\n\twhile (i < a.length) out[k++] = a[i++]\n\twhile (j < b.length) out[k++] = b[j++]\n\treturn out\n}\n\ninterface HeapEntry {\n\tedgeIndex: number\n\tz: number\n}\n\nclass EdgeMaxHeap {\n\tprivate readonly items: HeapEntry[] = []\n\n\tconstructor(\n\t\tprivate readonly edges: readonly MstEdge[],\n\t\tprivate readonly leaves: readonly LeafInput[]\n\t) {}\n\n\tpeek(): HeapEntry | undefined {\n\t\treturn this.items[0]\n\t}\n\n\tpush(entry: HeapEntry) {\n\t\tthis.items.push(entry)\n\t\tthis.siftUp(this.items.length - 1)\n\t}\n\n\tpop(): HeapEntry | undefined {\n\t\tif (this.items.length === 0) return undefined\n\t\tconst first = this.items[0]\n\t\tconst last = this.items.pop()!\n\t\tif (this.items.length > 0) {\n\t\t\tthis.items[0] = last\n\t\t\tthis.siftDown(0)\n\t\t}\n\t\treturn first\n\t}\n\n\thigherPriority(a: HeapEntry, b: HeapEntry): boolean {\n\t\tif (a.z > b.z) return true\n\t\tif (a.z < b.z) return false\n\t\treturn edgeIdPairLess(this.edges[a.edgeIndex], this.edges[b.edgeIndex], this.leaves)\n\t}\n\n\tprivate siftUp(index: number) {\n\t\twhile (index > 0) {\n\t\t\tconst parent = (index - 1) >> 1\n\t\t\tif (!this.higherPriority(this.items[index], this.items[parent])) break\n\t\t\t;[this.items[index], this.items[parent]] = [this.items[parent], this.items[index]]\n\t\t\tindex = parent\n\t\t}\n\t}\n\n\tprivate siftDown(index: number) {\n\t\twhile (true) {\n\t\t\tconst left = index * 2 + 1\n\t\t\tconst right = left + 1\n\t\t\tlet best = index\n\t\t\tif (left < this.items.length && this.higherPriority(this.items[left], this.items[best])) {\n\t\t\t\tbest = left\n\t\t\t}\n\t\t\tif (right < this.items.length && this.higherPriority(this.items[right], this.items[best])) {\n\t\t\t\tbest = right\n\t\t\t}\n\t\t\tif (best === index) break\n\t\t\t;[this.items[index], this.items[best]] = [this.items[best], this.items[index]]\n\t\t\tindex = best\n\t\t}\n\t}\n}\n\nfunction edgeIdPairLess(a: MstEdge, b: MstEdge, leaves: readonly LeafInput[]): boolean {\n\tconst [aLo, aHi] = normalizedEdgeIds(a, leaves)\n\tconst [bLo, bHi] = normalizedEdgeIds(b, leaves)\n\tif (aLo !== bLo) return aLo < bLo\n\treturn aHi < bHi\n}\n\nfunction normalizedEdgeIds(edge: MstEdge, leaves: readonly LeafInput[]): [string, string] {\n\tconst aId = leaves[edge.a].id\n\tconst bId = leaves[edge.b].id\n\treturn aId < bId ? [aId, bId] : [bId, aId]\n}\n"],
5
- "mappings": "AAEO,MAAM,UAAU;AAEhB,SAAS,aACf,QACA,OACA,MACkB;AAClB,kBAAgB,IAAI;AAEpB,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAM,WAAW,IAAI,aAAa,MAAM;AACxC,QAAM,OAAO,IAAI,YAAY,OAAO,MAAM;AAK1C,QAAM,WAAuB,OAAO,IAAI,MAAM,CAAC,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,SAAK,KAAK,EAAE,WAAW,GAAG,GAAG,SAAS,MAAM,CAAC,GAAG,UAAU,IAAI,EAAE,CAAC;AACjE,aAAS,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC;AAC3B,aAAS,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC;AAAA,EAC5B;AAEA,QAAM,SAA0B,CAAC;AAIjC,MAAI,QAAQ,OAAO;AACnB,SAAO,OAAO,SAAS,MAAM,QAAQ;AACpC,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,OAAO,MAAM,MAAM,SAAS;AAClC,UAAM,QAAQ,SAAS,KAAK,KAAK,CAAC;AAClC,UAAM,QAAQ,SAAS,KAAK,KAAK,CAAC;AAClC,QAAI,UAAU,MAAO;AAErB,UAAM,UAAU,EAAE,WAAW,MAAM,WAAW,GAAG,UAAU,MAAM,OAAO,OAAO,UAAU,IAAI,EAAE;AAC/F,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,QAAQ,KAAK,eAAe,MAAM,OAAO,GAAG;AAC/C,WAAK,KAAK,OAAO;AACjB;AAAA,IACD;AAEA,UAAM,IAAI,KAAK,IAAI,QAAQ,GAAG,KAAK;AACnC,YAAQ;AACR,WAAO,KAAK,SAAS,MAAM,OAAO,OAAO,CAAC,CAAC;AAI3C,UAAM,OAAO,SAAS,KAAK,KAAK,CAAC;AACjC,UAAM,QAAQ,SAAS,QAAQ,QAAQ;AACvC,UAAM,YAAsB,CAAC;AAC7B,eAAW,OAAO,CAAC,GAAG,SAAS,IAAI,GAAG,GAAG,SAAS,KAAK,CAAC,GAAG;AAC1D,YAAM,KAAK,MAAM,GAAG;AACpB,YAAM,KAAK,SAAS,KAAK,GAAG,CAAC;AAC7B,YAAM,KAAK,SAAS,KAAK,GAAG,CAAC;AAC7B,UAAI,OAAO,GAAI;AACf,gBAAU,KAAK,GAAG;AAClB,WAAK,KAAK,EAAE,WAAW,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,UAAU,IAAI,EAAE,CAAC;AAAA,IACvE;AACA,aAAS,IAAI,IAAI;AAAA,EAClB;AAEA,SAAO;AACR;AAEA,SAAS,gBAAgB,MAAoC;AAC5D,MAAI,CAAC,OAAO,SAAS,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG;AAC9C,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC5C;AACA,MAAI,CAAC,OAAO,SAAS,KAAK,IAAI,KAAK,KAAK,QAAQ,GAAG;AAClD,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC9C;AACA,MAAI,KAAK,OAAO,KAAK,IAAI;AACxB,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC3D;AACD;AAEA,SAAS,SACR,MACA,UACA,MACS;AACT,SAAO,UAAU,MAAM,SAAS,KAAK,KAAK,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,GAAG,UAAU,IAAI;AACpF;AAEA,SAAS,UACR,MACA,OACA,OACA,UACA,MACS;AACT,MAAI,KAAK,IAAI,QAAS,QAAO,OAAO;AAQpC,QAAM,MAAM,KAAK,KAAK,SAAS,iBAAiB,OAAO,KAAK;AAC5D,QAAM,MAAM,KAAK,OAAO,SAAS,cAAc,OAAO,KAAK;AAC3D,SAAO,KAAK,IAAI,KAAK,GAAG;AACzB;AAEA,MAAM,aAAa;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA8B;AACzC,UAAM,IAAI,OAAO;AACjB,SAAK,SAAS,IAAI,WAAW,CAAC;AAC9B,SAAK,OAAO,IAAI,aAAa,CAAC;AAC9B,SAAK,OAAO,IAAI,aAAa,CAAC;AAC9B,SAAK,OAAO,IAAI,aAAa,CAAC;AAC9B,SAAK,OAAO,IAAI,aAAa,CAAC;AAC9B,SAAK,YAAY,IAAI,aAAa,CAAC;AACnC,SAAK,YAAY,IAAI,aAAa,CAAC;AACnC,SAAK,SAAS,IAAI,WAAW,CAAC;AAC9B,SAAK,QAAQ,IAAI,MAAM,CAAC;AACxB,SAAK,cAAc,IAAI,MAAM,CAAC;AAC9B,SAAK,eAAe,IAAI,MAAM,CAAC;AAE/B,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC3B,YAAM,OAAO,OAAO,CAAC;AACrB,WAAK,OAAO,CAAC,IAAI;AACjB,WAAK,KAAK,CAAC,IAAI,KAAK,MAAM;AAC1B,WAAK,KAAK,CAAC,IAAI,KAAK,MAAM;AAC1B,WAAK,KAAK,CAAC,IAAI,KAAK,MAAM;AAC1B,WAAK,KAAK,CAAC,IAAI,KAAK,MAAM;AAC1B,WAAK,UAAU,CAAC,IAAI,KAAK,MAAM;AAC/B,WAAK,UAAU,CAAC,IAAI,KAAK,MAAM;AAC/B,WAAK,OAAO,CAAC,IAAI;AACjB,WAAK,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE;AAC9B,WAAK,aAAa,CAAC,IAAI,KAAK;AAC5B,WAAK,MAAM,CAAC,IAAI;AAAA,QACf,IAAI,KAAK;AAAA,QACT,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,EAAE;AAAA,QAC7C,OAAO;AAAA,QACP,SAAS,CAAC,KAAK,EAAE;AAAA,MAClB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,KAAK,OAAuB;AAC3B,QAAI,OAAO;AACX,WAAO,KAAK,OAAO,IAAI,MAAM,MAAM;AAClC,aAAO,KAAK,OAAO,IAAI;AAAA,IACxB;AACA,WAAO,KAAK,OAAO,KAAK,MAAM,OAAO;AACpC,YAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,WAAK,OAAO,KAAK,IAAI;AACrB,cAAQ;AAAA,IACT;AACA,WAAO;AAAA,EACR;AAAA,EAEA,iBAAiB,OAAe,OAAuB;AACtD,WAAO,KAAK;AAAA,MACX,KAAK,UAAU,KAAK,IAAI,KAAK,UAAU,KAAK;AAAA,MAC5C,KAAK,UAAU,KAAK,IAAI,KAAK,UAAU,KAAK;AAAA,IAC7C;AAAA,EACD;AAAA,EAEA,cAAc,OAAe,OAAuB;AACnD,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC;AACxD,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC;AACxD,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC;AACxD,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC;AACxD,WAAO,KAAK,MAAM,OAAO,MAAM,OAAO,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAe,OAAe,GAA0B;AAC7D,UAAM,WAAW,KAAK,aAAa,KAAK,IAAI,KAAK,aAAa,KAAK,IAAI,QAAQ;AAC/E,UAAM,YAAY,aAAa,QAAQ,QAAQ;AAC/C,UAAM,OAAO,KAAK,MAAM,QAAQ;AAChC,UAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,UAAM,QAAQ,KAAK,OAAO,QAAQ,IAAI,KAAK,OAAO,SAAS;AAC3D,UAAM,UAAU,mBAAmB,KAAK,YAAY,QAAQ,GAAG,KAAK,YAAY,SAAS,CAAC;AAE1F,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC;AAC/D,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC;AAC/D,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC;AAC/D,UAAM,OAAO,KAAK,IAAI,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC;AAC/D,UAAM,aACJ,KAAK,OAAO,QAAQ,IAAI,KAAK,UAAU,QAAQ,IAC/C,KAAK,OAAO,SAAS,IAAI,KAAK,UAAU,SAAS,KAClD;AACD,UAAM,aACJ,KAAK,OAAO,QAAQ,IAAI,KAAK,UAAU,QAAQ,IAC/C,KAAK,OAAO,SAAS,IAAI,KAAK,UAAU,SAAS,KAClD;AAGD,UAAM,SAAsB;AAAA,MAC3B,IAAI,WAAW,KAAK,IAAI,QAAQ,CAAC,CAAC;AAAA,MAClC,UAAU,EAAE,GAAG,WAAW,GAAG,UAAU;AAAA,MACvC;AAAA,MACA;AAAA,IACD;AAEA,SAAK,OAAO,SAAS,IAAI;AACzB,SAAK,KAAK,QAAQ,IAAI;AACtB,SAAK,KAAK,QAAQ,IAAI;AACtB,SAAK,KAAK,QAAQ,IAAI;AACtB,SAAK,KAAK,QAAQ,IAAI;AACtB,SAAK,UAAU,QAAQ,IAAI;AAC3B,SAAK,UAAU,QAAQ,IAAI;AAC3B,SAAK,OAAO,QAAQ,IAAI;AACxB,SAAK,MAAM,QAAQ,IAAI;AACvB,SAAK,YAAY,QAAQ,IAAI;AAC7B,SAAK,aAAa,QAAQ,IAAI,QAAQ,CAAC;AAEvC,WAAO,EAAE,GAAG,UAAU,CAAC,MAAM,KAAK,GAAG,OAAO;AAAA,EAC7C;AACD;AAEA,SAAS,mBAAmB,GAAsB,GAAgC;AACjF,QAAM,MAAM,IAAI,MAAc,EAAE,SAAS,EAAE,MAAM;AACjD,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,IAAI,EAAE,UAAU,IAAI,EAAE,QAAQ;AACpC,QAAI,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG;AAChB,UAAI,GAAG,IAAI,EAAE,GAAG;AAAA,IACjB,OAAO;AACN,UAAI,GAAG,IAAI,EAAE,GAAG;AAAA,IACjB;AAAA,EACD;AACA,SAAO,IAAI,EAAE,OAAQ,KAAI,GAAG,IAAI,EAAE,GAAG;AACrC,SAAO,IAAI,EAAE,OAAQ,KAAI,GAAG,IAAI,EAAE,GAAG;AACrC,SAAO;AACR;AAOA,MAAM,YAAY;AAAA,EAGjB,YACkB,OACA,QAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAJD,QAAqB,CAAC;AAAA,EAOvC,OAA8B;AAC7B,WAAO,KAAK,MAAM,CAAC;AAAA,EACpB;AAAA,EAEA,KAAK,OAAkB;AACtB,SAAK,MAAM,KAAK,KAAK;AACrB,SAAK,OAAO,KAAK,MAAM,SAAS,CAAC;AAAA,EAClC;AAAA,EAEA,MAA6B;AAC5B,QAAI,KAAK,MAAM,WAAW,EAAG,QAAO;AACpC,UAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,QAAI,KAAK,MAAM,SAAS,GAAG;AAC1B,WAAK,MAAM,CAAC,IAAI;AAChB,WAAK,SAAS,CAAC;AAAA,IAChB;AACA,WAAO;AAAA,EACR;AAAA,EAEA,eAAe,GAAc,GAAuB;AACnD,QAAI,EAAE,IAAI,EAAE,EAAG,QAAO;AACtB,QAAI,EAAE,IAAI,EAAE,EAAG,QAAO;AACtB,WAAO,eAAe,KAAK,MAAM,EAAE,SAAS,GAAG,KAAK,MAAM,EAAE,SAAS,GAAG,KAAK,MAAM;AAAA,EACpF;AAAA,EAEQ,OAAO,OAAe;AAC7B,WAAO,QAAQ,GAAG;AACjB,YAAM,SAAU,QAAQ,KAAM;AAC9B,UAAI,CAAC,KAAK,eAAe,KAAK,MAAM,KAAK,GAAG,KAAK,MAAM,MAAM,CAAC,EAAG;AAChE,OAAC,KAAK,MAAM,KAAK,GAAG,KAAK,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,MAAM,GAAG,KAAK,MAAM,KAAK,CAAC;AACjF,cAAQ;AAAA,IACT;AAAA,EACD;AAAA,EAEQ,SAAS,OAAe;AAC/B,WAAO,MAAM;AACZ,YAAM,OAAO,QAAQ,IAAI;AACzB,YAAM,QAAQ,OAAO;AACrB,UAAI,OAAO;AACX,UAAI,OAAO,KAAK,MAAM,UAAU,KAAK,eAAe,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG;AACxF,eAAO;AAAA,MACR;AACA,UAAI,QAAQ,KAAK,MAAM,UAAU,KAAK,eAAe,KAAK,MAAM,KAAK,GAAG,KAAK,MAAM,IAAI,CAAC,GAAG;AAC1F,eAAO;AAAA,MACR;AACA,UAAI,SAAS,MAAO;AACnB,OAAC,KAAK,MAAM,KAAK,GAAG,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AAC7E,cAAQ;AAAA,IACT;AAAA,EACD;AACD;AAEA,SAAS,eAAe,GAAY,GAAY,QAAuC;AACtF,QAAM,CAAC,KAAK,GAAG,IAAI,kBAAkB,GAAG,MAAM;AAC9C,QAAM,CAAC,KAAK,GAAG,IAAI,kBAAkB,GAAG,MAAM;AAC9C,MAAI,QAAQ,IAAK,QAAO,MAAM;AAC9B,SAAO,MAAM;AACd;AAEA,SAAS,kBAAkB,MAAe,QAAgD;AACzF,QAAM,MAAM,OAAO,KAAK,CAAC,EAAE;AAC3B,QAAM,MAAM,OAAO,KAAK,CAAC,EAAE;AAC3B,SAAO,MAAM,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;AAC1C;",
6
- "names": []
4
+ "sourcesContent": ["import type { ClusterNode, LeafInput, LeafScreenOffsets, MstEdge, RawMergeEvent } from './types'\n\nexport const D_FLOOR = 1e-9\n\nexport function cappedReplay(\n\tleaves: readonly LeafInput[],\n\tedges: readonly MstEdge[],\n\topts: { Tc: number; Dmax: number },\n\t// Render offsets for markers that draw off their anchor (imprecise pins). Omitted or empty,\n\t// every code path below is the offset-unaware original \u2014 pricing, events, and floats alike.\n\tscreenOffsets?: LeafScreenOffsets\n): RawMergeEvent[] {\n\tvalidateOptions(opts)\n\n\tif (edges.length === 0) return []\n\n\tconst clusters = new ClusterState(leaves, screenOffsets)\n\tconst heap = new EdgeMaxHeap(edges, leaves)\n\t// Edges incident to each current cluster root, for eager repricing: a merge\n\t// moves the result's centroid, which can RAISE an incident edge's key (the\n\t// centroid may move toward a neighbor), so the lazy pop-time recheck alone\n\t// is not enough \u2014 every price change pushes a fresh heap entry.\n\tconst incident: number[][] = leaves.map(() => [])\n\tfor (let i = 0; i < edges.length; i++) {\n\t\theap.push({ edgeIndex: i, z: zForEdge(edges[i], clusters, opts) })\n\t\tincident[edges[i].a].push(i)\n\t\tincident[edges[i].b].push(i)\n\t}\n\n\tconst events: RawMergeEvent[] = []\n\t// Emitted thresholds are clamped non-increasing: with centroid pricing a\n\t// later merge's raw price can in principle exceed an earlier one's, and the\n\t// table's descending sort is load-bearing for the runtime cursor.\n\tlet lastZ = Number.POSITIVE_INFINITY\n\twhile (events.length < edges.length) {\n\t\tconst entry = heap.pop()\n\t\tif (!entry) break\n\n\t\tconst edge = edges[entry.edgeIndex]\n\t\tconst aRoot = clusters.find(edge.a)\n\t\tconst bRoot = clusters.find(edge.b)\n\t\tif (aRoot === bRoot) continue // stale duplicate of an already-fired edge\n\n\t\tconst current = { edgeIndex: entry.edgeIndex, z: zForRoots(edge, aRoot, bRoot, clusters, opts) }\n\t\tconst next = heap.peek()\n\t\tif (next && heap.higherPriority(next, current)) {\n\t\t\theap.push(current)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst z = Math.min(current.z, lastZ)\n\t\tlastZ = z\n\t\tevents.push(clusters.merge(aRoot, bRoot, z))\n\n\t\t// eager reprice: the new cluster's centroid and bbox changed, so every\n\t\t// surviving incident edge gets a fresh entry at its current price\n\t\tconst root = clusters.find(edge.a)\n\t\tconst other = root === aRoot ? bRoot : aRoot\n\t\tconst survivors: number[] = []\n\t\tfor (const idx of [...incident[root], ...incident[other]]) {\n\t\t\tconst e2 = edges[idx]\n\t\t\tconst ra = clusters.find(e2.a)\n\t\t\tconst rb = clusters.find(e2.b)\n\t\t\tif (ra === rb) continue\n\t\t\tsurvivors.push(idx)\n\t\t\theap.push({ edgeIndex: idx, z: zForRoots(e2, ra, rb, clusters, opts) })\n\t\t}\n\t\tincident[root] = survivors\n\t}\n\n\treturn events\n}\n\nfunction validateOptions(opts: { Tc: number; Dmax: number }) {\n\tif (!Number.isFinite(opts.Tc) || opts.Tc <= 0) {\n\t\tthrow new Error('Tc must be greater than 0')\n\t}\n\tif (!Number.isFinite(opts.Dmax) || opts.Dmax <= 0) {\n\t\tthrow new Error('Dmax must be greater than 0')\n\t}\n\tif (opts.Dmax < opts.Tc) {\n\t\tthrow new Error('Dmax must be greater than or equal to Tc')\n\t}\n}\n\nfunction zForEdge(\n\tedge: MstEdge,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\treturn zForRoots(edge, clusters.find(edge.a), clusters.find(edge.b), clusters, opts)\n}\n\nfunction zForRoots(\n\tedge: MstEdge,\n\taRoot: number,\n\tbRoot: number,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\t// Null unless offset pricing is active AND this pair's mean render offsets differ. Every\n\t// other pair \u2014 including every pair when no offsets were passed \u2014 takes the branch below,\n\t// which is the offset-unaware pricing verbatim.\n\tconst off = clusters.offsetDelta(aRoot, bRoot)\n\tif (off === null) {\n\t\tif (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY\n\t\t// Badge-anchored gap pricing: clusters render as badges at their centroids,\n\t\t// so the merge is priced by the distance between the rendered centers, not\n\t\t// the nearest members. For leaves the two are identical; for clusters the\n\t\t// centroid distance is larger, so groups merge later than their closest\n\t\t// members would suggest \u2014 matching what the user actually sees.\n\t\t// (Coincident CENTROIDS with non-coincident members leave the gap term\n\t\t// Infinity and the fit term finite \u2014 the min stays finite, no special case.)\n\t\tconst gap = opts.Tc / clusters.centroidDistance(aRoot, bRoot)\n\t\tconst fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot)\n\t\treturn Math.min(gap, fit)\n\t}\n\n\t// Offset-aware gap pricing: these markers render at `z\u00B7centroid + meanOffset`, so their\n\t// visual distance at zoom z is |z\u00B7\u0394C + \u0394\u014D| and the merge prices at its Tc crossing. When\n\t// that distance never reaches Tc (the constant offsets hold the visuals apart harder than\n\t// the anchors close), the pair prices at 0 \u2014 visually never mergeable, pruned by finalize's\n\t// minZoom cut. No D_FLOOR fast path here: coincident anchors with differing offsets are a\n\t// constant |\u0394\u014D| apart on screen, which is exactly the \u0394C = 0 case below.\n\tconst dc = clusters.centroidDelta(aRoot, bRoot)\n\tif (dc.x === 0 && dc.y === 0) {\n\t\treturn Math.hypot(off.x, off.y) < opts.Tc ? Number.POSITIVE_INFINITY : 0\n\t}\n\tconst gap = largestVisualCrossing(dc.x, dc.y, off.x, off.y, opts.Tc)\n\tif (gap === null) return 0\n\tconst fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot)\n\treturn Math.min(gap, fit)\n}\n\n/**\n * The largest positive root of `|z\u00B7\u0394C + \u0394\u014D| = level`, i.e. of\n * `|\u0394C|\u00B2\u00B7z\u00B2 + 2(\u0394C\u00B7\u0394\u014D)\u00B7z + (|\u0394\u014D|\u00B2 \u2212 level\u00B2) = 0` \u2014 the zoom at which two offset markers are\n * exactly `level` screen px apart, with the distance below the level for every smaller zoom\n * (matching the \"merged at z \u2264 threshold\" model; a lower second crossing, where opposed offsets\n * push the visuals back above the level near z = 0, is deliberately collapsed). Null when the\n * visual distance never reaches the level. Callers handle \u0394C = 0 (constant distance) themselves.\n */\nfunction largestVisualCrossing(\n\tdcx: number,\n\tdcy: number,\n\tdox: number,\n\tdoy: number,\n\tlevel: number\n): number | null {\n\tconst a = dcx * dcx + dcy * dcy\n\tconst b = 2 * (dcx * dox + dcy * doy)\n\tconst c = dox * dox + doy * doy - level * level\n\tconst disc = b * b - 4 * a * c\n\tif (disc < 0) return null\n\tconst z = (-b + Math.sqrt(disc)) / (2 * a)\n\treturn z > 0 ? z : null\n}\n\nclass ClusterState {\n\tprivate readonly parent: Int32Array\n\tprivate readonly minX: Float64Array\n\tprivate readonly minY: Float64Array\n\tprivate readonly maxX: Float64Array\n\tprivate readonly maxY: Float64Array\n\tprivate readonly centroidX: Float64Array\n\tprivate readonly centroidY: Float64Array\n\tprivate readonly counts: Int32Array\n\tprivate readonly nodes: ClusterNode[]\n\tprivate readonly memberLists: string[][]\n\tprivate readonly minMemberIds: string[]\n\t// Per-cluster sums of member render offsets (screen px), maintained like the centroid sums.\n\t// Null when no offsets were passed \u2014 offsetDelta() then answers null unconditionally, which\n\t// routes every pricing call down the offset-unaware path.\n\tprivate readonly offsetX: Float64Array | null\n\tprivate readonly offsetY: Float64Array | null\n\n\tconstructor(leaves: readonly LeafInput[], screenOffsets?: LeafScreenOffsets) {\n\t\tconst n = leaves.length\n\t\tthis.parent = new Int32Array(n)\n\t\tthis.minX = new Float64Array(n)\n\t\tthis.minY = new Float64Array(n)\n\t\tthis.maxX = new Float64Array(n)\n\t\tthis.maxY = new Float64Array(n)\n\t\tthis.centroidX = new Float64Array(n)\n\t\tthis.centroidY = new Float64Array(n)\n\t\tthis.counts = new Int32Array(n)\n\t\tthis.nodes = new Array(n)\n\t\tthis.memberLists = new Array(n)\n\t\tthis.minMemberIds = new Array(n)\n\n\t\tif (screenOffsets !== undefined && screenOffsets.size > 0) {\n\t\t\tthis.offsetX = new Float64Array(n)\n\t\t\tthis.offsetY = new Float64Array(n)\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tconst offset = screenOffsets.get(leaves[i].id)\n\t\t\t\tif (offset) {\n\t\t\t\t\tthis.offsetX[i] = offset.x\n\t\t\t\t\tthis.offsetY[i] = offset.y\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.offsetX = null\n\t\t\tthis.offsetY = null\n\t\t}\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst leaf = leaves[i]\n\t\t\tthis.parent[i] = i\n\t\t\tthis.minX[i] = leaf.point.x\n\t\t\tthis.minY[i] = leaf.point.y\n\t\t\tthis.maxX[i] = leaf.point.x\n\t\t\tthis.maxY[i] = leaf.point.y\n\t\t\tthis.centroidX[i] = leaf.point.x\n\t\t\tthis.centroidY[i] = leaf.point.y\n\t\t\tthis.counts[i] = 1\n\t\t\tthis.memberLists[i] = [leaf.id]\n\t\t\tthis.minMemberIds[i] = leaf.id\n\t\t\tthis.nodes[i] = {\n\t\t\t\tid: leaf.id,\n\t\t\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\t\t\tcount: 1,\n\t\t\t\tmembers: [leaf.id],\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(index: number): number {\n\t\tlet root = index\n\t\twhile (this.parent[root] !== root) {\n\t\t\troot = this.parent[root]\n\t\t}\n\t\twhile (this.parent[index] !== index) {\n\t\t\tconst next = this.parent[index]\n\t\t\tthis.parent[index] = root\n\t\t\tindex = next\n\t\t}\n\t\treturn root\n\t}\n\n\tcentroidDistance(aRoot: number, bRoot: number): number {\n\t\treturn Math.hypot(\n\t\t\tthis.centroidX[aRoot] - this.centroidX[bRoot],\n\t\t\tthis.centroidY[aRoot] - this.centroidY[bRoot]\n\t\t)\n\t}\n\n\tcentroidDelta(aRoot: number, bRoot: number): { x: number; y: number } {\n\t\treturn {\n\t\t\tx: this.centroidX[aRoot] - this.centroidX[bRoot],\n\t\t\ty: this.centroidY[aRoot] - this.centroidY[bRoot],\n\t\t}\n\t}\n\n\t/** The difference of the two clusters' mean render offsets (screen px), or null when it's\n\t * zero \u2014 including always when no offsets were passed. Null routes pricing down the\n\t * offset-unaware path. */\n\toffsetDelta(aRoot: number, bRoot: number): { x: number; y: number } | null {\n\t\tif (this.offsetX === null || this.offsetY === null) return null\n\t\tconst x = this.offsetX[aRoot] / this.counts[aRoot] - this.offsetX[bRoot] / this.counts[bRoot]\n\t\tconst y = this.offsetY[aRoot] / this.counts[aRoot] - this.offsetY[bRoot] / this.counts[bRoot]\n\t\tif (x === 0 && y === 0) return null\n\t\treturn { x, y }\n\t}\n\n\tunionBboxDiag(aRoot: number, bRoot: number): number {\n\t\tconst minX = Math.min(this.minX[aRoot], this.minX[bRoot])\n\t\tconst minY = Math.min(this.minY[aRoot], this.minY[bRoot])\n\t\tconst maxX = Math.max(this.maxX[aRoot], this.maxX[bRoot])\n\t\tconst maxY = Math.max(this.maxY[aRoot], this.maxY[bRoot])\n\t\treturn Math.hypot(maxX - minX, maxY - minY)\n\t}\n\n\tmerge(aRoot: number, bRoot: number, z: number): RawMergeEvent {\n\t\tconst leftRoot = this.minMemberIds[aRoot] < this.minMemberIds[bRoot] ? aRoot : bRoot\n\t\tconst rightRoot = leftRoot === aRoot ? bRoot : aRoot\n\t\tconst left = this.nodes[leftRoot]\n\t\tconst right = this.nodes[rightRoot]\n\t\tconst count = this.counts[leftRoot] + this.counts[rightRoot]\n\t\tconst members = mergeSortedMembers(this.memberLists[leftRoot], this.memberLists[rightRoot])\n\n\t\tconst minX = Math.min(this.minX[leftRoot], this.minX[rightRoot])\n\t\tconst minY = Math.min(this.minY[leftRoot], this.minY[rightRoot])\n\t\tconst maxX = Math.max(this.maxX[leftRoot], this.maxX[rightRoot])\n\t\tconst maxY = Math.max(this.maxY[leftRoot], this.maxY[rightRoot])\n\t\tconst centroidX =\n\t\t\t(this.counts[leftRoot] * this.centroidX[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidX[rightRoot]) /\n\t\t\tcount\n\t\tconst centroidY =\n\t\t\t(this.counts[leftRoot] * this.centroidY[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidY[rightRoot]) /\n\t\t\tcount\n\n\t\t// Leaf ids are assumed not to start with `cluster:`; see the step 2 contract.\n\t\tconst result: ClusterNode = {\n\t\t\tid: `cluster:${count}:${members[0]}`,\n\t\t\tcentroid: { x: centroidX, y: centroidY },\n\t\t\tcount,\n\t\t\tmembers,\n\t\t}\n\n\t\tthis.parent[rightRoot] = leftRoot\n\t\tthis.minX[leftRoot] = minX\n\t\tthis.minY[leftRoot] = minY\n\t\tthis.maxX[leftRoot] = maxX\n\t\tthis.maxY[leftRoot] = maxY\n\t\tthis.centroidX[leftRoot] = centroidX\n\t\tthis.centroidY[leftRoot] = centroidY\n\t\tthis.counts[leftRoot] = count\n\t\tthis.nodes[leftRoot] = result\n\t\tthis.memberLists[leftRoot] = members\n\t\tthis.minMemberIds[leftRoot] = members[0]\n\t\tif (this.offsetX !== null && this.offsetY !== null) {\n\t\t\tthis.offsetX[leftRoot] += this.offsetX[rightRoot]\n\t\t\tthis.offsetY[leftRoot] += this.offsetY[rightRoot]\n\t\t}\n\n\t\treturn { z, children: [left, right], result }\n\t}\n}\n\nfunction mergeSortedMembers(a: readonly string[], b: readonly string[]): string[] {\n\tconst out = new Array<string>(a.length + b.length)\n\tlet i = 0\n\tlet j = 0\n\tlet k = 0\n\twhile (i < a.length && j < b.length) {\n\t\tif (a[i] < b[j]) {\n\t\t\tout[k++] = a[i++]\n\t\t} else {\n\t\t\tout[k++] = b[j++]\n\t\t}\n\t}\n\twhile (i < a.length) out[k++] = a[i++]\n\twhile (j < b.length) out[k++] = b[j++]\n\treturn out\n}\n\ninterface HeapEntry {\n\tedgeIndex: number\n\tz: number\n}\n\nclass EdgeMaxHeap {\n\tprivate readonly items: HeapEntry[] = []\n\t// Per-edge normalized (lo, hi) id pair for the z tie-break, precomputed once so comparisons\n\t// allocate nothing. With coincident anchors every edge prices to the same z (+Infinity), so\n\t// the tie-break runs on nearly every comparison of a rebuild \u2014 allocating the pair there\n\t// churned millions of short-lived tuples.\n\tprivate readonly loIds: string[]\n\tprivate readonly hiIds: string[]\n\n\tconstructor(edges: readonly MstEdge[], leaves: readonly LeafInput[]) {\n\t\tconst n = edges.length\n\t\tthis.loIds = new Array(n)\n\t\tthis.hiIds = new Array(n)\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst aId = leaves[edges[i].a].id\n\t\t\tconst bId = leaves[edges[i].b].id\n\t\t\tif (aId < bId) {\n\t\t\t\tthis.loIds[i] = aId\n\t\t\t\tthis.hiIds[i] = bId\n\t\t\t} else {\n\t\t\t\tthis.loIds[i] = bId\n\t\t\t\tthis.hiIds[i] = aId\n\t\t\t}\n\t\t}\n\t}\n\n\tpeek(): HeapEntry | undefined {\n\t\treturn this.items[0]\n\t}\n\n\tpush(entry: HeapEntry) {\n\t\tthis.items.push(entry)\n\t\tthis.siftUp(this.items.length - 1)\n\t}\n\n\tpop(): HeapEntry | undefined {\n\t\tif (this.items.length === 0) return undefined\n\t\tconst first = this.items[0]\n\t\tconst last = this.items.pop()!\n\t\tif (this.items.length > 0) {\n\t\t\tthis.items[0] = last\n\t\t\tthis.siftDown(0)\n\t\t}\n\t\treturn first\n\t}\n\n\thigherPriority(a: HeapEntry, b: HeapEntry): boolean {\n\t\tif (a.z > b.z) return true\n\t\tif (a.z < b.z) return false\n\t\t// Tie-break on the normalized leaf-id pair, ascending.\n\t\tconst aLo = this.loIds[a.edgeIndex]\n\t\tconst bLo = this.loIds[b.edgeIndex]\n\t\tif (aLo !== bLo) return aLo < bLo\n\t\treturn this.hiIds[a.edgeIndex] < this.hiIds[b.edgeIndex]\n\t}\n\n\tprivate siftUp(index: number) {\n\t\twhile (index > 0) {\n\t\t\tconst parent = (index - 1) >> 1\n\t\t\tif (!this.higherPriority(this.items[index], this.items[parent])) break\n\t\t\t;[this.items[index], this.items[parent]] = [this.items[parent], this.items[index]]\n\t\t\tindex = parent\n\t\t}\n\t}\n\n\tprivate siftDown(index: number) {\n\t\twhile (true) {\n\t\t\tconst left = index * 2 + 1\n\t\t\tconst right = left + 1\n\t\t\tlet best = index\n\t\t\tif (left < this.items.length && this.higherPriority(this.items[left], this.items[best])) {\n\t\t\t\tbest = left\n\t\t\t}\n\t\t\tif (right < this.items.length && this.higherPriority(this.items[right], this.items[best])) {\n\t\t\t\tbest = right\n\t\t\t}\n\t\t\tif (best === index) break\n\t\t\t;[this.items[index], this.items[best]] = [this.items[best], this.items[index]]\n\t\t\tindex = best\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["gap", "fit"]
7
7
  }
@@ -9,16 +9,41 @@ class ClusterRuntimeImpl {
9
9
  table;
10
10
  // mutable internally; readonly through the ClusterRuntime interface
11
11
  k = 0;
12
+ version = 0;
13
+ // Structural partition: exactly leaves + applied events, never patched. The cursor invariants
14
+ // live here, untouched by detaches — resolution to the displayed partition happens at read
15
+ // time in getVisible().
12
16
  visible = /* @__PURE__ */ new Map();
13
17
  seeded = false;
18
+ // Indices (< k) of band events held unmerged by seedFrom carryover. The single cursor can only
19
+ // express "merged up to here", but a carried-over partition can be "merged except these". Self-
20
+ // draining: an entry leaves via onCamera when the zoom crosses its zMerge or its zSplit.
21
+ suppressed = /* @__PURE__ */ new Set();
22
+ // Leaves removed from the displayed partition (deleted / popped out / opened). Patches map
23
+ // each structural node containing a detached leaf to its displayed replacement (or null to
24
+ // drop it). Rebuilt from the detached set; cleared by seed/seedFrom.
25
+ detached = /* @__PURE__ */ new Set();
26
+ patched = /* @__PURE__ */ new Map();
27
+ leafById = null;
28
+ resolvedCache = null;
29
+ getSuppressedCount() {
30
+ return this.suppressed.size;
31
+ }
32
+ getDetachedCount() {
33
+ return this.detached.size;
34
+ }
14
35
  seed(zoom) {
15
36
  validateZoom(zoom);
16
37
  this.k = seedCount(this.table.events, zoom);
38
+ this.suppressed.clear();
39
+ this.detached.clear();
40
+ this.patched.clear();
17
41
  this.resetVisible();
18
42
  for (let i = 0; i < this.k; i++) {
19
43
  applyEvent(this.visible, this.table.events[i]);
20
44
  }
21
45
  this.seeded = true;
46
+ this.version++;
22
47
  }
23
48
  seedFrom(zoom, previous) {
24
49
  validateZoom(zoom);
@@ -30,35 +55,129 @@ class ClusterRuntimeImpl {
30
55
  }
31
56
  const events = this.table.events;
32
57
  let k = 0;
33
- while (k < events.length) {
34
- const event = events[k];
35
- if (zoom >= event.zSplit) break;
36
- if (zoom > event.zMerge && !wasMergedTogether(event.result.members, ownerByMember)) break;
58
+ while (k < events.length && zoom < events[k].zSplit) {
37
59
  k++;
38
60
  }
39
61
  this.k = k;
62
+ this.suppressed.clear();
63
+ this.detached.clear();
64
+ this.patched.clear();
40
65
  this.resetVisible();
41
66
  for (let i = 0; i < k; i++) {
42
- applyEvent(this.visible, events[i]);
67
+ const event = events[i];
68
+ if (zoom > event.zMerge && !wasMergedTogether(event.result.members, ownerByMember)) {
69
+ this.suppressed.add(i);
70
+ } else {
71
+ applyEvent(this.visible, event);
72
+ }
43
73
  }
44
74
  this.seeded = true;
75
+ this.version++;
45
76
  }
46
77
  onCamera(zoom) {
47
78
  validateZoom(zoom);
48
79
  if (!this.seeded) {
49
80
  throw new Error("Cluster runtime must be seeded before onCamera");
50
81
  }
82
+ let changed = false;
83
+ if (this.suppressed.size > 0) {
84
+ for (const i of this.suppressed) {
85
+ if (zoom <= this.table.events[i].zMerge) {
86
+ this.suppressed.delete(i);
87
+ applyEvent(this.visible, this.table.events[i]);
88
+ changed = true;
89
+ }
90
+ }
91
+ }
51
92
  while (this.k < this.table.events.length && zoom <= this.table.events[this.k].zMerge) {
52
93
  applyEvent(this.visible, this.table.events[this.k]);
53
94
  this.k++;
95
+ changed = true;
54
96
  }
55
97
  while (this.k > 0 && zoom >= this.table.events[this.k - 1].zSplit) {
56
98
  this.k--;
57
- unapplyEvent(this.visible, this.table.events[this.k]);
99
+ if (!this.suppressed.delete(this.k)) {
100
+ unapplyEvent(this.visible, this.table.events[this.k]);
101
+ changed = true;
102
+ }
58
103
  }
104
+ if (changed) this.version++;
105
+ }
106
+ detachLeaf(leafId) {
107
+ this.detachLeaves([leafId]);
108
+ }
109
+ detachLeaves(leafIds) {
110
+ const leafById = this.getLeafById();
111
+ let changed = false;
112
+ for (const leafId of leafIds) {
113
+ if (this.detached.has(leafId)) continue;
114
+ if (!leafById.has(leafId)) continue;
115
+ this.detached.add(leafId);
116
+ changed = true;
117
+ }
118
+ if (!changed) return;
119
+ this.rebuildPatches();
120
+ this.version++;
59
121
  }
60
122
  getVisible() {
61
- return this.visible;
123
+ if (this.resolvedCache && this.resolvedCache.version === this.version) {
124
+ return this.resolvedCache.map;
125
+ }
126
+ let map;
127
+ if (this.patched.size === 0) {
128
+ map = this.visible;
129
+ } else {
130
+ map = /* @__PURE__ */ new Map();
131
+ for (const node of this.visible.values()) {
132
+ const resolved = this.patched.has(node.id) ? this.patched.get(node.id) : node;
133
+ if (resolved) map.set(resolved.id, resolved);
134
+ }
135
+ }
136
+ this.resolvedCache = { version: this.version, map };
137
+ return map;
138
+ }
139
+ getLeafById() {
140
+ if (!this.leafById) {
141
+ this.leafById = new Map(this.table.leaves.map((leaf) => [leaf.id, leaf]));
142
+ }
143
+ return this.leafById;
144
+ }
145
+ /** Recompute the patch map from the detached set. A node is patched iff it contains a detached
146
+ * member; the patch drops those members and recomputes count/centroid, collapsing to the surviving
147
+ * leaf at count 1 and to nothing at count 0. Patched nodes keep their structural id. */
148
+ rebuildPatches() {
149
+ this.patched.clear();
150
+ const leafById = this.getLeafById();
151
+ for (const id of this.detached) {
152
+ this.patched.set(id, null);
153
+ }
154
+ const patch = (node) => {
155
+ if (node.members.length === 1 || !node.members.some((m) => this.detached.has(m))) return;
156
+ const members = node.members.filter((m) => !this.detached.has(m));
157
+ if (members.length === 0) {
158
+ this.patched.set(node.id, null);
159
+ } else if (members.length === 1) {
160
+ this.patched.set(node.id, leafById.get(members[0]));
161
+ } else {
162
+ let x = 0;
163
+ let y = 0;
164
+ for (const m of members) {
165
+ const leaf = leafById.get(m);
166
+ x += leaf.centroid.x;
167
+ y += leaf.centroid.y;
168
+ }
169
+ this.patched.set(node.id, {
170
+ id: node.id,
171
+ centroid: { x: x / members.length, y: y / members.length },
172
+ count: members.length,
173
+ members
174
+ });
175
+ }
176
+ };
177
+ for (const event of this.table.events) {
178
+ patch(event.result);
179
+ for (const child of event.children) patch(child);
180
+ }
62
181
  }
63
182
  resetVisible() {
64
183
  this.visible = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/clustering/runtime.ts"],
4
- "sourcesContent": ["import type { ClusterNode, ClusterTable, MergeEvent } from './types'\n\n/** @public */\nexport interface ClusterRuntime {\n\t/** Events[0..k) are active. Exposed for tests and debugging. */\n\treadonly k: number\n\t/** Reset state from scratch for the given zoom (cold start / after rebuild). */\n\tseed(zoom: number): void\n\t/**\n\t * Reset state for the given zoom, carrying hysteresis state over from a previous partition\n\t * (the visible map of the model being replaced). Threshold-forced events ignore history:\n\t * zoom \\<= zMerge is always active, zoom \\>= zSplit always inactive. An event inside its band\n\t * is active iff its members were merged together in `previous`; a band event with no history\n\t * (e.g. introduced by the rebuild) stays inactive until the next zoom-out crosses its zMerge.\n\t * Active events must form a prefix of the table, so classification stops at the first\n\t * inactive event; later band-active events conservatively resolve to split.\n\t */\n\tseedFrom(zoom: number, previous: ReadonlyMap<string, ClusterNode>): void\n\t/** Advance/retreat the cursor for a camera change. No-op if zoom sits inside all bands. */\n\tonCamera(zoom: number): void\n\t/** The current partition: cluster id \u2192 node. Do not mutate. */\n\tgetVisible(): ReadonlyMap<string, ClusterNode>\n}\n\n/** @public */\nexport function createClusterRuntime(table: ClusterTable): ClusterRuntime {\n\treturn new ClusterRuntimeImpl(table)\n}\n\nclass ClusterRuntimeImpl implements ClusterRuntime {\n\t// mutable internally; readonly through the ClusterRuntime interface\n\tk = 0\n\tprivate visible = new Map<string, ClusterNode>()\n\tprivate seeded = false\n\n\tconstructor(private readonly table: ClusterTable) {\n\t\tthis.resetVisible()\n\t}\n\n\tseed(zoom: number): void {\n\t\tvalidateZoom(zoom)\n\t\tthis.k = seedCount(this.table.events, zoom)\n\t\tthis.resetVisible()\n\t\tfor (let i = 0; i < this.k; i++) {\n\t\t\tapplyEvent(this.visible, this.table.events[i])\n\t\t}\n\t\tthis.seeded = true\n\t}\n\n\tseedFrom(zoom: number, previous: ReadonlyMap<string, ClusterNode>): void {\n\t\tvalidateZoom(zoom)\n\t\tconst ownerByMember = new Map<string, string>()\n\t\tfor (const node of previous.values()) {\n\t\t\tfor (const member of node.members) {\n\t\t\t\townerByMember.set(member, node.id)\n\t\t\t}\n\t\t}\n\n\t\tconst events = this.table.events\n\t\tlet k = 0\n\t\twhile (k < events.length) {\n\t\t\tconst event = events[k]\n\t\t\tif (zoom >= event.zSplit) break\n\t\t\tif (zoom > event.zMerge && !wasMergedTogether(event.result.members, ownerByMember)) break\n\t\t\tk++\n\t\t}\n\n\t\tthis.k = k\n\t\tthis.resetVisible()\n\t\tfor (let i = 0; i < k; i++) {\n\t\t\tapplyEvent(this.visible, events[i])\n\t\t}\n\t\tthis.seeded = true\n\t}\n\n\tonCamera(zoom: number): void {\n\t\tvalidateZoom(zoom)\n\t\tif (!this.seeded) {\n\t\t\tthrow new Error('Cluster runtime must be seeded before onCamera')\n\t\t}\n\n\t\twhile (this.k < this.table.events.length && zoom <= this.table.events[this.k].zMerge) {\n\t\t\tapplyEvent(this.visible, this.table.events[this.k])\n\t\t\tthis.k++\n\t\t}\n\t\twhile (this.k > 0 && zoom >= this.table.events[this.k - 1].zSplit) {\n\t\t\tthis.k--\n\t\t\tunapplyEvent(this.visible, this.table.events[this.k])\n\t\t}\n\t}\n\n\tgetVisible(): ReadonlyMap<string, ClusterNode> {\n\t\treturn this.visible\n\t}\n\n\tprivate resetVisible() {\n\t\tthis.visible = new Map()\n\t\tfor (const leaf of this.table.leaves) {\n\t\t\tthis.visible.set(leaf.id, leaf)\n\t\t}\n\t}\n}\n\n/** True iff every member id belonged to the same node of the previous partition. */\nfunction wasMergedTogether(\n\tmembers: readonly string[],\n\townerByMember: ReadonlyMap<string, string>\n): boolean {\n\tconst owner = ownerByMember.get(members[0])\n\tif (owner === undefined) return false\n\tfor (let i = 1; i < members.length; i++) {\n\t\tif (ownerByMember.get(members[i]) !== owner) return false\n\t}\n\treturn true\n}\n\nfunction seedCount(events: readonly MergeEvent[], zoom: number): number {\n\tlet lo = 0\n\tlet hi = events.length\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >> 1\n\t\tif (zoom <= seedThreshold(events[mid])) {\n\t\t\tlo = mid + 1\n\t\t} else {\n\t\t\thi = mid\n\t\t}\n\t}\n\treturn lo\n}\n\nfunction seedThreshold(event: MergeEvent): number {\n\treturn Math.sqrt(event.zMerge * event.zSplit)\n}\n\nfunction applyEvent(visible: Map<string, ClusterNode>, event: MergeEvent) {\n\tfor (const child of event.children) {\n\t\tvisible.delete(child.id)\n\t}\n\tvisible.set(event.result.id, event.result)\n}\n\nfunction unapplyEvent(visible: Map<string, ClusterNode>, event: MergeEvent) {\n\tvisible.delete(event.result.id)\n\tfor (const child of event.children) {\n\t\tvisible.set(child.id, child)\n\t}\n}\n\nfunction validateZoom(zoom: number) {\n\tif (!Number.isFinite(zoom) || zoom <= 0) {\n\t\tthrow new Error('zoom must be finite and greater than 0')\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ClusterNode, ClusterTable, MergeEvent } from './types'\n\n/** @internal */\nexport interface ClusterRuntime {\n\t/** Events[0..k) are in the cursor (applied unless suppressed). Exposed for tests/debugging. */\n\treadonly k: number\n\t/**\n\t * Increments whenever the displayed partition changes. Subscribe to this, not `k`: detaches\n\t * and suppressed-event healing change the partition without moving the cursor.\n\t */\n\treadonly version: number\n\t/** Number of suppressed band events currently inside the cursor. Exposed for tests. */\n\tgetSuppressedCount(): number\n\t/** Number of leaves currently detached from the displayed partition. Exposed for tests. */\n\tgetDetachedCount(): number\n\t/** Reset state from scratch for the given zoom (cold start / after rebuild). Clears detaches. */\n\tseed(zoom: number): void\n\t/**\n\t * Reset state for the given zoom, carrying hysteresis state over from a previous partition.\n\t * Threshold-forced events ignore history: zoom \\<= zMerge is always merged, zoom \\>= zSplit always\n\t * split. An event inside its band keeps its previous state; one that was unmerged (or has no\n\t * history) stays suppressed inside the cursor until a zoom-out crosses its own zMerge. Carryover is\n\t * exact \u2014 no group changes state because of the swap alone. Clears detaches.\n\t */\n\tseedFrom(zoom: number, previous: ReadonlyMap<string, ClusterNode>): void\n\t/** Advance/retreat the cursor for a camera change. No-op if zoom sits inside all bands. */\n\tonCamera(zoom: number): void\n\t/**\n\t * Remove one leaf from the displayed partition without touching the event table. Local by\n\t * construction: only the nodes containing the leaf change, so a deletion, pop-out, or thread-open\n\t * never re-flows the rest of the document. The table's thresholds around the detached leaf go\n\t * stale; the caller is expected to adopt a corrected rebuild (with seedFrom) at the next zoom-out.\n\t * Unknown or already-detached ids are no-ops.\n\t */\n\tdetachLeaf(leafId: string): void\n\t/**\n\t * Batch form of {@link ClusterRuntime.detachLeaf}: detach every given leaf with a single\n\t * patch rebuild and a single version bump. Ids that are unknown or already detached are\n\t * skipped; if nothing new detaches, nothing changes.\n\t */\n\tdetachLeaves(leafIds: Iterable<string>): void\n\t/** The displayed partition: cluster id \u2192 node, with detaches applied. Do not mutate. */\n\tgetVisible(): ReadonlyMap<string, ClusterNode>\n}\n\n/** @internal */\nexport function createClusterRuntime(table: ClusterTable): ClusterRuntime {\n\treturn new ClusterRuntimeImpl(table)\n}\n\nclass ClusterRuntimeImpl implements ClusterRuntime {\n\t// mutable internally; readonly through the ClusterRuntime interface\n\tk = 0\n\tversion = 0\n\t// Structural partition: exactly leaves + applied events, never patched. The cursor invariants\n\t// live here, untouched by detaches \u2014 resolution to the displayed partition happens at read\n\t// time in getVisible().\n\tprivate visible = new Map<string, ClusterNode>()\n\tprivate seeded = false\n\t// Indices (< k) of band events held unmerged by seedFrom carryover. The single cursor can only\n\t// express \"merged up to here\", but a carried-over partition can be \"merged except these\". Self-\n\t// draining: an entry leaves via onCamera when the zoom crosses its zMerge or its zSplit.\n\tprivate suppressed = new Set<number>()\n\t// Leaves removed from the displayed partition (deleted / popped out / opened). Patches map\n\t// each structural node containing a detached leaf to its displayed replacement (or null to\n\t// drop it). Rebuilt from the detached set; cleared by seed/seedFrom.\n\tprivate detached = new Set<string>()\n\tprivate patched = new Map<string, ClusterNode | null>()\n\tprivate leafById: Map<string, ClusterNode> | null = null\n\tprivate resolvedCache: { version: number; map: Map<string, ClusterNode> } | null = null\n\n\tconstructor(private readonly table: ClusterTable) {\n\t\tthis.resetVisible()\n\t}\n\n\tgetSuppressedCount(): number {\n\t\treturn this.suppressed.size\n\t}\n\n\tgetDetachedCount(): number {\n\t\treturn this.detached.size\n\t}\n\n\tseed(zoom: number): void {\n\t\tvalidateZoom(zoom)\n\t\tthis.k = seedCount(this.table.events, zoom)\n\t\tthis.suppressed.clear()\n\t\tthis.detached.clear()\n\t\tthis.patched.clear()\n\t\tthis.resetVisible()\n\t\tfor (let i = 0; i < this.k; i++) {\n\t\t\tapplyEvent(this.visible, this.table.events[i])\n\t\t}\n\t\tthis.seeded = true\n\t\tthis.version++\n\t}\n\n\tseedFrom(zoom: number, previous: ReadonlyMap<string, ClusterNode>): void {\n\t\tvalidateZoom(zoom)\n\t\tconst ownerByMember = new Map<string, string>()\n\t\tfor (const node of previous.values()) {\n\t\t\tfor (const member of node.members) {\n\t\t\t\townerByMember.set(member, node.id)\n\t\t\t}\n\t\t}\n\n\t\t// Cut the cursor purely on split thresholds. zSplit is non-increasing down the table, so\n\t\t// everything past the first mandatory split (zoom >= zSplit) is also mandatorily split \u2014\n\t\t// this cut can never conflict with an event that should be merged.\n\t\tconst events = this.table.events\n\t\tlet k = 0\n\t\twhile (k < events.length && zoom < events[k].zSplit) {\n\t\t\tk++\n\t\t}\n\n\t\tthis.k = k\n\t\tthis.suppressed.clear()\n\t\tthis.detached.clear()\n\t\tthis.patched.clear()\n\t\tthis.resetVisible()\n\t\tfor (let i = 0; i < k; i++) {\n\t\t\tconst event = events[i]\n\t\t\tif (zoom > event.zMerge && !wasMergedTogether(event.result.members, ownerByMember)) {\n\t\t\t\t// In its band and previously unmerged: keep it unmerged, as an exception inside the cursor.\n\t\t\t\t// Dependency-safe, since an applied event can never consume a suppressed result.\n\t\t\t\tthis.suppressed.add(i)\n\t\t\t} else {\n\t\t\t\tapplyEvent(this.visible, event)\n\t\t\t}\n\t\t}\n\t\tthis.seeded = true\n\t\tthis.version++\n\t}\n\n\tonCamera(zoom: number): void {\n\t\tvalidateZoom(zoom)\n\t\tif (!this.seeded) {\n\t\t\tthrow new Error('Cluster runtime must be seeded before onCamera')\n\t\t}\n\n\t\tlet changed = false\n\t\t// Heal suppressed events at their own merge threshold BEFORE the merge walk, so a zoom-out past\n\t\t// zMerge merges a held-out band event as if it had still been ahead of the cursor. Healing must come\n\t\t// first: one zoom jump can cross both a suppressed event and an event consuming its result, and\n\t\t// applying the consumer first would double-count the producer's leaves. Iterated live \u2014 deleting only\n\t\t// the entry being visited is safe, and Set iteration still yields the rest in insertion order.\n\t\tif (this.suppressed.size > 0) {\n\t\t\tfor (const i of this.suppressed) {\n\t\t\t\tif (zoom <= this.table.events[i].zMerge) {\n\t\t\t\t\tthis.suppressed.delete(i)\n\t\t\t\t\tapplyEvent(this.visible, this.table.events[i])\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\twhile (this.k < this.table.events.length && zoom <= this.table.events[this.k].zMerge) {\n\t\t\tapplyEvent(this.visible, this.table.events[this.k])\n\t\t\tthis.k++\n\t\t\tchanged = true\n\t\t}\n\t\twhile (this.k > 0 && zoom >= this.table.events[this.k - 1].zSplit) {\n\t\t\tthis.k--\n\t\t\t// A suppressed event was never applied \u2014 retreating past it is bookkeeping only.\n\t\t\tif (!this.suppressed.delete(this.k)) {\n\t\t\t\tunapplyEvent(this.visible, this.table.events[this.k])\n\t\t\t\tchanged = true\n\t\t\t}\n\t\t}\n\t\tif (changed) this.version++\n\t}\n\n\tdetachLeaf(leafId: string): void {\n\t\tthis.detachLeaves([leafId])\n\t}\n\n\tdetachLeaves(leafIds: Iterable<string>): void {\n\t\tconst leafById = this.getLeafById()\n\t\tlet changed = false\n\t\tfor (const leafId of leafIds) {\n\t\t\tif (this.detached.has(leafId)) continue\n\t\t\tif (!leafById.has(leafId)) continue\n\t\t\tthis.detached.add(leafId)\n\t\t\tchanged = true\n\t\t}\n\t\tif (!changed) return\n\t\tthis.rebuildPatches()\n\t\tthis.version++\n\t}\n\n\tgetVisible(): ReadonlyMap<string, ClusterNode> {\n\t\tif (this.resolvedCache && this.resolvedCache.version === this.version) {\n\t\t\treturn this.resolvedCache.map\n\t\t}\n\t\tlet map: Map<string, ClusterNode>\n\t\tif (this.patched.size === 0) {\n\t\t\tmap = this.visible\n\t\t} else {\n\t\t\tmap = new Map()\n\t\t\tfor (const node of this.visible.values()) {\n\t\t\t\tconst resolved = this.patched.has(node.id) ? this.patched.get(node.id)! : node\n\t\t\t\tif (resolved) map.set(resolved.id, resolved)\n\t\t\t}\n\t\t}\n\t\tthis.resolvedCache = { version: this.version, map }\n\t\treturn map\n\t}\n\n\tprivate getLeafById(): Map<string, ClusterNode> {\n\t\tif (!this.leafById) {\n\t\t\tthis.leafById = new Map(this.table.leaves.map((leaf) => [leaf.id, leaf]))\n\t\t}\n\t\treturn this.leafById\n\t}\n\n\t/** Recompute the patch map from the detached set. A node is patched iff it contains a detached\n\t * member; the patch drops those members and recomputes count/centroid, collapsing to the surviving\n\t * leaf at count 1 and to nothing at count 0. Patched nodes keep their structural id. */\n\tprivate rebuildPatches() {\n\t\tthis.patched.clear()\n\t\tconst leafById = this.getLeafById()\n\t\tfor (const id of this.detached) {\n\t\t\tthis.patched.set(id, null)\n\t\t}\n\t\tconst patch = (node: ClusterNode) => {\n\t\t\tif (node.members.length === 1 || !node.members.some((m) => this.detached.has(m))) return\n\t\t\tconst members = node.members.filter((m) => !this.detached.has(m))\n\t\t\tif (members.length === 0) {\n\t\t\t\tthis.patched.set(node.id, null)\n\t\t\t} else if (members.length === 1) {\n\t\t\t\tthis.patched.set(node.id, leafById.get(members[0])!)\n\t\t\t} else {\n\t\t\t\tlet x = 0\n\t\t\t\tlet y = 0\n\t\t\t\tfor (const m of members) {\n\t\t\t\t\tconst leaf = leafById.get(m)!\n\t\t\t\t\tx += leaf.centroid.x\n\t\t\t\t\ty += leaf.centroid.y\n\t\t\t\t}\n\t\t\t\tthis.patched.set(node.id, {\n\t\t\t\t\tid: node.id,\n\t\t\t\t\tcentroid: { x: x / members.length, y: y / members.length },\n\t\t\t\t\tcount: members.length,\n\t\t\t\t\tmembers,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tfor (const event of this.table.events) {\n\t\t\tpatch(event.result)\n\t\t\tfor (const child of event.children) patch(child)\n\t\t}\n\t}\n\n\tprivate resetVisible() {\n\t\tthis.visible = new Map()\n\t\tfor (const leaf of this.table.leaves) {\n\t\t\tthis.visible.set(leaf.id, leaf)\n\t\t}\n\t}\n}\n\n/** True iff every member id belonged to the same node of the previous partition. */\nfunction wasMergedTogether(\n\tmembers: readonly string[],\n\townerByMember: ReadonlyMap<string, string>\n): boolean {\n\tconst owner = ownerByMember.get(members[0])\n\tif (owner === undefined) return false\n\tfor (let i = 1; i < members.length; i++) {\n\t\tif (ownerByMember.get(members[i]) !== owner) return false\n\t}\n\treturn true\n}\n\nfunction seedCount(events: readonly MergeEvent[], zoom: number): number {\n\tlet lo = 0\n\tlet hi = events.length\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >> 1\n\t\tif (zoom <= seedThreshold(events[mid])) {\n\t\t\tlo = mid + 1\n\t\t} else {\n\t\t\thi = mid\n\t\t}\n\t}\n\treturn lo\n}\n\nfunction seedThreshold(event: MergeEvent): number {\n\treturn Math.sqrt(event.zMerge * event.zSplit)\n}\n\nfunction applyEvent(visible: Map<string, ClusterNode>, event: MergeEvent) {\n\tfor (const child of event.children) {\n\t\tvisible.delete(child.id)\n\t}\n\tvisible.set(event.result.id, event.result)\n}\n\nfunction unapplyEvent(visible: Map<string, ClusterNode>, event: MergeEvent) {\n\tvisible.delete(event.result.id)\n\tfor (const child of event.children) {\n\t\tvisible.set(child.id, child)\n\t}\n}\n\nfunction validateZoom(zoom: number) {\n\tif (!Number.isFinite(zoom) || zoom <= 0) {\n\t\tthrow new Error('zoom must be finite and greater than 0')\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }