@tldraw/commenting 5.3.0-next.2fa9c61a8de6 → 5.3.0-next.7654e7ac2a02

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 (238) hide show
  1. package/commenting.css +63 -71
  2. package/dist-cjs/canvas/anchor-lifecycle.js +2 -2
  3. package/dist-cjs/canvas/anchor-lifecycle.js.map +2 -2
  4. package/dist-cjs/{ui/tooltip-button.js → canvas/canvas-events.js} +16 -13
  5. package/dist-cjs/canvas/canvas-events.js.map +7 -0
  6. package/dist-cjs/canvas/cluster-badge.js +121 -0
  7. package/dist-cjs/canvas/cluster-badge.js.map +7 -0
  8. package/dist-cjs/canvas/cluster-fade.js +85 -0
  9. package/dist-cjs/canvas/cluster-fade.js.map +7 -0
  10. package/dist-cjs/canvas/cluster-input.js +51 -1
  11. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  12. package/dist-cjs/canvas/cluster-model.js +252 -0
  13. package/dist-cjs/canvas/cluster-model.js.map +7 -0
  14. package/dist-cjs/canvas/comment-mutations.js +57 -28
  15. package/dist-cjs/canvas/comment-mutations.js.map +2 -2
  16. package/dist-cjs/canvas/comment-reactions.js +4 -7
  17. package/dist-cjs/canvas/comment-reactions.js.map +2 -2
  18. package/dist-cjs/canvas/comment-render.js +4 -3
  19. package/dist-cjs/canvas/comment-render.js.map +2 -2
  20. package/dist-cjs/canvas/comment-store.js.map +2 -2
  21. package/dist-cjs/canvas/comment-tool.js +0 -1
  22. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  23. package/dist-cjs/canvas/comments-filter-menu.js +11 -13
  24. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overflow-menu.js +10 -2
  26. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  27. package/dist-cjs/canvas/comments-overlay.js +77 -870
  28. package/dist-cjs/canvas/comments-overlay.js.map +3 -3
  29. package/dist-cjs/canvas/comments-sidebar.js +16 -11
  30. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  31. package/dist-cjs/canvas/comments-visibility-toggle.js +5 -25
  32. package/dist-cjs/canvas/comments-visibility-toggle.js.map +2 -2
  33. package/dist-cjs/canvas/context.js.map +1 -1
  34. package/dist-cjs/canvas/hooks.js +8 -2
  35. package/dist-cjs/canvas/hooks.js.map +2 -2
  36. package/dist-cjs/canvas/license.js.map +1 -1
  37. package/dist-cjs/canvas/mobile-placement.js +91 -0
  38. package/dist-cjs/canvas/mobile-placement.js.map +7 -0
  39. package/dist-cjs/canvas/options.js +39 -1
  40. package/dist-cjs/canvas/options.js.map +2 -2
  41. package/dist-cjs/canvas/pending-composer.js +134 -0
  42. package/dist-cjs/canvas/pending-composer.js.map +7 -0
  43. package/dist-cjs/canvas/pin-stacking.js +26 -1
  44. package/dist-cjs/canvas/pin-stacking.js.map +2 -2
  45. package/dist-cjs/canvas/region-box.js +113 -0
  46. package/dist-cjs/canvas/region-box.js.map +7 -0
  47. package/dist-cjs/canvas/state.js.map +2 -2
  48. package/dist-cjs/canvas/thread-pin.js +304 -0
  49. package/dist-cjs/canvas/thread-pin.js.map +7 -0
  50. package/dist-cjs/canvas/thread-preview.js +41 -49
  51. package/dist-cjs/canvas/thread-preview.js.map +2 -2
  52. package/dist-cjs/canvas/thread-stack.js +7 -7
  53. package/dist-cjs/canvas/thread-stack.js.map +2 -2
  54. package/dist-cjs/canvas/thread-state.js +15 -0
  55. package/dist-cjs/canvas/thread-state.js.map +2 -2
  56. package/dist-cjs/canvas/thread-view.js +120 -67
  57. package/dist-cjs/canvas/thread-view.js.map +3 -3
  58. package/dist-cjs/clustering/computeClusterTable.js +2 -2
  59. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  60. package/dist-cjs/clustering/replay.js +89 -22
  61. package/dist-cjs/clustering/replay.js.map +3 -3
  62. package/dist-cjs/clustering/runtime.js +19 -12
  63. package/dist-cjs/clustering/runtime.js.map +2 -2
  64. package/dist-cjs/clustering/types.js.map +1 -1
  65. package/dist-cjs/index.d.ts +323 -202
  66. package/dist-cjs/index.js +7 -1
  67. package/dist-cjs/index.js.map +2 -2
  68. package/dist-cjs/ui/byline.js +5 -3
  69. package/dist-cjs/ui/byline.js.map +2 -2
  70. package/dist-cjs/ui/comment-composer.js +2 -18
  71. package/dist-cjs/ui/comment-composer.js.map +2 -2
  72. package/dist-cjs/ui/comment-pin.js +2 -15
  73. package/dist-cjs/ui/comment-pin.js.map +2 -2
  74. package/dist-cjs/ui/comments-list.js +11 -24
  75. package/dist-cjs/ui/comments-list.js.map +2 -2
  76. package/dist-cjs/ui/format-time.js +12 -0
  77. package/dist-cjs/ui/format-time.js.map +2 -2
  78. package/dist-cjs/ui/icons.js +115 -0
  79. package/dist-cjs/ui/icons.js.map +7 -0
  80. package/dist-cjs/ui/reaction-picker.js +11 -13
  81. package/dist-cjs/ui/reaction-picker.js.map +2 -2
  82. package/dist-cjs/ui/reaction.js +3 -4
  83. package/dist-cjs/ui/reaction.js.map +2 -2
  84. package/dist-cjs/ui/send-button.js +2 -9
  85. package/dist-cjs/ui/send-button.js.map +2 -2
  86. package/dist-cjs/ui/visual-viewport.js +32 -0
  87. package/dist-cjs/ui/visual-viewport.js.map +7 -0
  88. package/dist-esm/canvas/anchor-lifecycle.mjs +3 -3
  89. package/dist-esm/canvas/anchor-lifecycle.mjs.map +2 -2
  90. package/dist-esm/canvas/canvas-events.mjs +15 -0
  91. package/dist-esm/canvas/canvas-events.mjs.map +7 -0
  92. package/dist-esm/canvas/cluster-badge.mjs +106 -0
  93. package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
  94. package/dist-esm/canvas/cluster-fade.mjs +65 -0
  95. package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
  96. package/dist-esm/canvas/cluster-input.mjs +52 -2
  97. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  98. package/dist-esm/canvas/cluster-model.mjs +236 -0
  99. package/dist-esm/canvas/cluster-model.mjs.map +7 -0
  100. package/dist-esm/canvas/comment-mutations.mjs +57 -28
  101. package/dist-esm/canvas/comment-mutations.mjs.map +2 -2
  102. package/dist-esm/canvas/comment-reactions.mjs +5 -12
  103. package/dist-esm/canvas/comment-reactions.mjs.map +2 -2
  104. package/dist-esm/canvas/comment-render.mjs +5 -4
  105. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +0 -1
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +12 -13
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +11 -2
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +54 -878
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +18 -14
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +5 -25
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +2 -2
  119. package/dist-esm/canvas/hooks.mjs +9 -2
  120. package/dist-esm/canvas/hooks.mjs.map +2 -2
  121. package/dist-esm/canvas/license.mjs.map +1 -1
  122. package/dist-esm/canvas/mobile-placement.mjs +71 -0
  123. package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
  124. package/dist-esm/canvas/options.mjs +39 -1
  125. package/dist-esm/canvas/options.mjs.map +2 -2
  126. package/dist-esm/canvas/pending-composer.mjs +126 -0
  127. package/dist-esm/canvas/pending-composer.mjs.map +7 -0
  128. package/dist-esm/canvas/pin-stacking.mjs +26 -1
  129. package/dist-esm/canvas/pin-stacking.mjs.map +2 -2
  130. package/dist-esm/canvas/region-box.mjs +93 -0
  131. package/dist-esm/canvas/region-box.mjs.map +7 -0
  132. package/dist-esm/canvas/state.mjs.map +2 -2
  133. package/dist-esm/canvas/thread-pin.mjs +310 -0
  134. package/dist-esm/canvas/thread-pin.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-preview.mjs +42 -49
  136. package/dist-esm/canvas/thread-preview.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-stack.mjs +8 -9
  138. package/dist-esm/canvas/thread-stack.mjs.map +2 -2
  139. package/dist-esm/canvas/thread-state.mjs +15 -0
  140. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  141. package/dist-esm/canvas/thread-view.mjs +131 -72
  142. package/dist-esm/canvas/thread-view.mjs.map +3 -3
  143. package/dist-esm/clustering/computeClusterTable.mjs +2 -2
  144. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  145. package/dist-esm/clustering/replay.mjs +89 -22
  146. package/dist-esm/clustering/replay.mjs.map +3 -3
  147. package/dist-esm/clustering/runtime.mjs +19 -12
  148. package/dist-esm/clustering/runtime.mjs.map +2 -2
  149. package/dist-esm/index.d.mts +323 -202
  150. package/dist-esm/index.mjs +16 -3
  151. package/dist-esm/index.mjs.map +2 -2
  152. package/dist-esm/ui/byline.mjs +6 -4
  153. package/dist-esm/ui/byline.mjs.map +2 -2
  154. package/dist-esm/ui/comment-composer.mjs +2 -18
  155. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  156. package/dist-esm/ui/comment-pin.mjs +2 -15
  157. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  158. package/dist-esm/ui/comments-list.mjs +10 -23
  159. package/dist-esm/ui/comments-list.mjs.map +2 -2
  160. package/dist-esm/ui/format-time.mjs +12 -0
  161. package/dist-esm/ui/format-time.mjs.map +2 -2
  162. package/dist-esm/ui/icons.mjs +95 -0
  163. package/dist-esm/ui/icons.mjs.map +7 -0
  164. package/dist-esm/ui/reaction-picker.mjs +12 -13
  165. package/dist-esm/ui/reaction-picker.mjs.map +2 -2
  166. package/dist-esm/ui/reaction.mjs +3 -4
  167. package/dist-esm/ui/reaction.mjs.map +2 -2
  168. package/dist-esm/ui/send-button.mjs +2 -9
  169. package/dist-esm/ui/send-button.mjs.map +2 -2
  170. package/dist-esm/ui/visual-viewport.mjs +12 -0
  171. package/dist-esm/ui/visual-viewport.mjs.map +7 -0
  172. package/package.json +5 -5
  173. package/src/canvas/anchor-lifecycle.test.ts +2 -2
  174. package/src/canvas/anchor-lifecycle.ts +25 -39
  175. package/src/canvas/canvas-events.ts +18 -0
  176. package/src/canvas/canvas.css +5 -11
  177. package/src/canvas/cluster-badge.tsx +133 -0
  178. package/src/canvas/cluster-fade.ts +102 -0
  179. package/src/canvas/cluster-input.test.ts +148 -14
  180. package/src/canvas/cluster-input.ts +92 -5
  181. package/src/canvas/cluster-model.ts +378 -0
  182. package/src/canvas/comment-mutations.test.ts +46 -4
  183. package/src/canvas/comment-mutations.ts +115 -104
  184. package/src/canvas/comment-reactions.test.ts +22 -0
  185. package/src/canvas/comment-reactions.tsx +33 -32
  186. package/src/canvas/comment-render.ts +8 -8
  187. package/src/canvas/comment-store.ts +12 -17
  188. package/src/canvas/comment-tool.tsx +8 -12
  189. package/src/canvas/comments-filter-menu.tsx +9 -16
  190. package/src/canvas/comments-overflow-menu.tsx +8 -3
  191. package/src/canvas/comments-overlay.tsx +154 -1344
  192. package/src/canvas/comments-sidebar.tsx +40 -19
  193. package/src/canvas/comments-visibility-toggle.tsx +6 -30
  194. package/src/canvas/context.ts +4 -3
  195. package/src/canvas/hooks.test.ts +94 -0
  196. package/src/canvas/hooks.ts +17 -3
  197. package/src/canvas/license.ts +1 -1
  198. package/src/canvas/mobile-placement.ts +115 -0
  199. package/src/canvas/options.test.ts +124 -2
  200. package/src/canvas/options.ts +192 -58
  201. package/src/canvas/pending-composer.tsx +157 -0
  202. package/src/canvas/pin-stacking.test.ts +111 -1
  203. package/src/canvas/pin-stacking.ts +46 -0
  204. package/src/canvas/region-box.tsx +124 -0
  205. package/src/canvas/state.ts +20 -28
  206. package/src/canvas/thread-pin.tsx +418 -0
  207. package/src/canvas/thread-preview.tsx +77 -87
  208. package/src/canvas/thread-stack.tsx +19 -29
  209. package/src/canvas/thread-state.test.ts +51 -0
  210. package/src/canvas/thread-state.ts +56 -23
  211. package/src/canvas/thread-view.test.ts +72 -0
  212. package/src/canvas/thread-view.tsx +231 -133
  213. package/src/clustering/computeClusterTable.ts +12 -3
  214. package/src/clustering/replay.test.ts +0 -7
  215. package/src/clustering/replay.ts +131 -32
  216. package/src/clustering/runtime.test.ts +50 -6
  217. package/src/clustering/runtime.ts +42 -39
  218. package/src/clustering/schedule.test.ts +0 -6
  219. package/src/clustering/screen-offsets.test.ts +171 -0
  220. package/src/clustering/types.ts +10 -0
  221. package/src/index.ts +15 -2
  222. package/src/ui/byline.tsx +16 -6
  223. package/src/ui/comment-composer.tsx +25 -69
  224. package/src/ui/comment-pin.tsx +3 -16
  225. package/src/ui/comments-list.tsx +40 -29
  226. package/src/ui/comments.css +58 -60
  227. package/src/ui/format-time.test.ts +69 -0
  228. package/src/ui/format-time.ts +20 -0
  229. package/src/ui/icons.tsx +116 -0
  230. package/src/ui/reaction-picker.tsx +9 -16
  231. package/src/ui/reaction.tsx +10 -6
  232. package/src/ui/send-button.tsx +3 -8
  233. package/src/ui/visual-viewport.test.ts +36 -0
  234. package/src/ui/visual-viewport.ts +27 -0
  235. package/dist-cjs/ui/tooltip-button.js.map +0 -7
  236. package/dist-esm/ui/tooltip-button.mjs +0 -12
  237. package/dist-esm/ui/tooltip-button.mjs.map +0 -7
  238. package/src/ui/tooltip-button.tsx +0 -20
@@ -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/** @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 * (the visible map of the model being replaced). Threshold-forced events ignore history:\n\t * zoom \\<= zMerge is always merged, zoom \\>= zSplit always split. An event inside its band\n\t * keeps its previous state: merged iff its members were merged together in `previous`; a band\n\t * event that was unmerged (or has no history, e.g. introduced by the rebuild) stays unmerged \u2014\n\t * inside the cursor but suppressed \u2014 until a zoom-out crosses its own zMerge, exactly like any\n\t * other pending merge. Carryover is exact: no group changes state because of the swap alone.\n\t * 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 \u2014 a badge shrinks in place (count\n\t * and centroid recomputed from its remaining members), a pair collapses to its surviving\n\t * leaf, and the leaf on its own disappears. Everything else is untouched, so a deletion,\n\t * pop-out, or thread-open never re-flows the rest of the document. The table's thresholds\n\t * around the detached leaf go stale; the caller is expected to hold a corrected rebuild and\n\t * adopt it (with seedFrom) at the next zoom-out. Unknown or already-detached ids are no-ops.\n\t */\n\tdetachLeaf(leafId: 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\n\t// only express \"merged up to here\", but a carried-over partition can be \"merged except these\"\n\t// \u2014 the exceptions live here. Self-draining: an entry leaves via onCamera when the zoom\n\t// crosses its own zMerge (merges) or its zSplit (the split walk retreats past it).\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\n\t\t\t\t// the cursor. Dependency-safe: an applied event can never consume a suppressed\n\t\t\t\t// result \u2014 merged members imply merged (subset) children, and threshold-forced\n\t\t\t\t// events force their children too (zMerge is non-increasing down the table).\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: a zoom-out\n\t\t// past zMerge merges a held-out band event exactly as if it had still been ahead of the\n\t\t// cursor. Healing must precede the walk because a single zoom jump can cross both a\n\t\t// suppressed event and an event that consumes its result; applying the consumer first\n\t\t// would leave the unapplied producer's children in `visible` (its delete is a no-op) and\n\t\t// then re-add the producer on top, double-counting those leaves. Threshold monotonicity\n\t\t// guarantees a suppressed producer's zMerge is >= its consumer's, so anything the walk\n\t\t// needs has already healed. Set iteration is insertion order (ascending index), so a\n\t\t// healed event's suppressed children (larger zMerge, smaller index) heal before it.\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\tif (this.detached.has(leafId)) return\n\t\tif (!this.getLeafById().has(leafId)) return\n\t\tthis.detached.add(leafId)\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\n\t * detached member; the patch drops those members and recomputes count/centroid, collapsing\n\t * to the surviving leaf node at count 1 and to nothing at count 0. Patched nodes keep their\n\t * structural id, so cursor events keep addressing them. */\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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",
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
  }