@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
@@ -0,0 +1,171 @@
1
+ import type { VecLike } from 'tldraw'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { computeClusterTable } from './computeClusterTable'
4
+ import type { LeafInput } from './types'
5
+
6
+ function leaf(id: string, x: number, y: number): LeafInput {
7
+ return { id, point: { x, y } }
8
+ }
9
+
10
+ function mulberry32(seed: number): () => number {
11
+ let a = seed >>> 0
12
+ return () => {
13
+ a = (a + 0x6d2b79f5) >>> 0
14
+ let t = a
15
+ t = Math.imul(t ^ (t >>> 15), t | 1)
16
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
17
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
18
+ }
19
+ }
20
+
21
+ function randomLeaves(n: number, seed: number, scale = 1000): LeafInput[] {
22
+ const rand = mulberry32(seed)
23
+ return Array.from({ length: n }, (_, i) => leaf(`leaf-${i}`, rand() * scale, rand() * scale))
24
+ }
25
+
26
+ const ZOOM_BOUNDS = { minZoom: 0.05, maxZoom: 8 }
27
+ // eps 0 keeps every merge as its own solo event, so the exact thresholds under test aren't
28
+ // snapped to a contraction window's anchor.
29
+ const EXACT_OPTS = { Tc: 22, Tu: 26.4, eps: 0, Dmax: 82.5, ...ZOOM_BOUNDS }
30
+
31
+ function offsets(entries: Record<string, VecLike>): Map<string, VecLike> {
32
+ return new Map(Object.entries(entries))
33
+ }
34
+
35
+ describe('computeClusterTable without screen offsets (the guarantee)', () => {
36
+ it('is deep-equal across omitted, undefined, and empty offset arguments', () => {
37
+ for (const seed of [3, 21]) {
38
+ const leaves = randomLeaves(40, seed)
39
+ const plain = computeClusterTable(leaves, { ...ZOOM_BOUNDS })
40
+ expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, undefined)).toEqual(plain)
41
+ expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, new Map())).toEqual(plain)
42
+ }
43
+ })
44
+
45
+ it('is deep-equal when every leaf carries the same offset (Δō is always zero)', () => {
46
+ const leaves = randomLeaves(30, 11)
47
+ const uniform = new Map(leaves.map((l) => [l.id, { x: -20, y: 20 }]))
48
+ expect(computeClusterTable(leaves, { ...ZOOM_BOUNDS }, uniform)).toEqual(
49
+ computeClusterTable(leaves, { ...ZOOM_BOUNDS })
50
+ )
51
+ })
52
+
53
+ it('never emits a zSplit override on the offset-free path', () => {
54
+ const table = computeClusterTable(randomLeaves(20, 5), { ...ZOOM_BOUNDS }, new Map())
55
+ for (const event of table.events) {
56
+ // finalize always derives ratio splits when no override was carried
57
+ expect(event.zSplit).toBeCloseTo(event.zMerge * 1.2, 10)
58
+ }
59
+ })
60
+ })
61
+
62
+ describe('offset-aware pricing', () => {
63
+ const A = leaf('a', 0, 0)
64
+ const B = leaf('b', 100, 0)
65
+
66
+ it('merges earlier when the insets point toward each other, at the exact visual crossing', () => {
67
+ // Visual positions: a at 100·0·z + 20, b at 100z − 20 → distance |100z − 40|. Tc = 22
68
+ // crosses at z = 0.62 (raw anchors would say 0.22); the split derives from the Tu/Tc
69
+ // ratio, as for every event.
70
+ const table = computeClusterTable(
71
+ [A, B],
72
+ EXACT_OPTS,
73
+ offsets({ a: { x: 20, y: 0 }, b: { x: -20, y: 0 } })
74
+ )
75
+ expect(table.events).toHaveLength(1)
76
+ expect(table.events[0].zMerge).toBeCloseTo(0.62, 10)
77
+ expect(table.events[0].zSplit).toBeCloseTo(0.62 * 1.2, 10)
78
+ })
79
+
80
+ it('merges later when the insets point apart, at the exact visual crossing', () => {
81
+ // Distance 100z + 10: Tc at z = 0.12 (raw: 0.22); ratio split at 0.144.
82
+ const table = computeClusterTable(
83
+ [A, B],
84
+ EXACT_OPTS,
85
+ offsets({ a: { x: -5, y: 0 }, b: { x: 5, y: 0 } })
86
+ )
87
+ expect(table.events).toHaveLength(1)
88
+ expect(table.events[0].zMerge).toBeCloseTo(0.12, 10)
89
+ expect(table.events[0].zSplit).toBeCloseTo(0.144, 10)
90
+ })
91
+
92
+ it('never merges a pair whose visual distance never reaches Tc', () => {
93
+ // Distance 100z + 40 ≥ 40 > Tc at every zoom: priced 0 and pruned by the minZoom cut.
94
+ const table = computeClusterTable(
95
+ [A, B],
96
+ EXACT_OPTS,
97
+ offsets({ a: { x: -20, y: 0 }, b: { x: 20, y: 0 } })
98
+ )
99
+ expect(table.events).toEqual([])
100
+ })
101
+
102
+ it('still applies the Dmax fit cap to a corrected merge', () => {
103
+ // Exaggerated inward offsets put the Tc crossing at z = 1.42, above the spread cap
104
+ // Dmax/diag = 82.5/100 = 0.825, so the cap wins the min.
105
+ const table = computeClusterTable(
106
+ [A, B],
107
+ EXACT_OPTS,
108
+ offsets({ a: { x: 60, y: 0 }, b: { x: -60, y: 0 } })
109
+ )
110
+ expect(table.events).toHaveLength(1)
111
+ expect(table.events[0].zMerge).toBeCloseTo(0.825, 10)
112
+ expect(table.events[0].zSplit).toBeCloseTo(0.825 * 1.2, 10)
113
+ })
114
+
115
+ it('treats coincident anchors with differing offsets as a constant visual distance', () => {
116
+ // Same anchor point, one pin tucked: constant 20px apart < Tc → merged at every zoom,
117
+ // entering the same maxSplitZoom band as a coincident pair (zMerge 6/1.2 = 5, zSplit 6).
118
+ const table = computeClusterTable(
119
+ [leaf('a', 50, 50), leaf('b', 50, 50)],
120
+ { ...EXACT_OPTS, maxSplitZoom: 6 },
121
+ offsets({ a: { x: 20, y: 0 } })
122
+ )
123
+ expect(table.events).toHaveLength(1)
124
+ expect(table.events[0].zMerge).toBe(5)
125
+ expect(table.events[0].zSplit).toBe(6)
126
+ })
127
+
128
+ it("dilutes a cluster's mean offset as precise members fold in", () => {
129
+ // a (tucked 20px) and b (precise) share an anchor: constant 20px apart, always merged.
130
+ // Their cluster's mean offset is (10, 0); against precise c 100 units right the visual
131
+ // distance is |100z − 10| → Tc at z = 0.32 — half the lone pin's correction.
132
+ const table = computeClusterTable(
133
+ [leaf('a', 0, 0), leaf('b', 0, 0), leaf('c', 100, 0)],
134
+ EXACT_OPTS,
135
+ offsets({ a: { x: 20, y: 0 } })
136
+ )
137
+ expect(table.events).toHaveLength(2)
138
+ const outer = table.events.find((e) => e.result.count === 3)!
139
+ expect(outer.zMerge).toBeCloseTo(0.32, 10)
140
+ expect(outer.zSplit).toBeCloseTo(0.32 * 1.2, 10)
141
+ })
142
+ })
143
+
144
+ describe('table ordering with offsets', () => {
145
+ it('keeps zMerge and zSplit non-increasing (splits are always ratio-derived)', () => {
146
+ // Mixed offset and plain leaves at varied spacings: every split is zMerge · (Tu/Tc), so
147
+ // both threshold sequences inherit the table's non-increasing order — the invariant the
148
+ // runtime's prefix cursor, split walk, and seed bisection rely on.
149
+ const leaves = [
150
+ leaf('a', 0, 0),
151
+ leaf('b', 100, 0),
152
+ leaf('c', 130, 0),
153
+ leaf('d', 400, 0),
154
+ leaf('e', 470, 0),
155
+ leaf('f', 1000, 300),
156
+ ]
157
+ const table = computeClusterTable(
158
+ leaves,
159
+ EXACT_OPTS,
160
+ offsets({ a: { x: -20, y: 0 }, b: { x: 14, y: 0 }, d: { x: 20, y: 14 } })
161
+ )
162
+ expect(table.events.length).toBeGreaterThan(2)
163
+ for (let i = 1; i < table.events.length; i++) {
164
+ expect(table.events[i].zMerge).toBeLessThanOrEqual(table.events[i - 1].zMerge)
165
+ expect(table.events[i].zSplit).toBeLessThanOrEqual(table.events[i - 1].zSplit)
166
+ }
167
+ for (const event of table.events) {
168
+ expect(event.zSplit).toBeCloseTo(event.zMerge * 1.2, 10)
169
+ }
170
+ })
171
+ })
@@ -2,7 +2,7 @@ import type { VecLike } from 'tldraw'
2
2
 
3
3
  /**
4
4
  * One comment thread's pin, already resolved to a page-space anchor point.
5
- * @public
5
+ * @internal
6
6
  */
7
7
  export interface LeafInput {
8
8
  /** Unique. Thread id. Uniqueness is a precondition — throw on duplicates. */
@@ -11,6 +11,16 @@ export interface LeafInput {
11
11
  point: VecLike
12
12
  }
13
13
 
14
+ /**
15
+ * Constant screen-px render offsets for markers that don't draw exactly on their anchor —
16
+ * imprecise shape pins, which tuck ~20px into their shape. Keyed by leaf id; leaves absent from
17
+ * the map render on their anchor. A marker's visual position at zoom z is `z · point + offset`.
18
+ * Passed as the optional third argument to `computeClusterTable`; when omitted or empty the
19
+ * algorithm's behavior (and output) is identical to an offset-unaware run.
20
+ * @internal
21
+ */
22
+ export type LeafScreenOffsets = ReadonlyMap<string, VecLike>
23
+
14
24
  /** An edge of the Euclidean MST over the leaf anchor points. */
15
25
  export interface MstEdge {
16
26
  /** Index into the input leaves array. Normalized: leaves[a].id < leaves[b].id (lexicographic). */
@@ -23,7 +33,7 @@ export interface MstEdge {
23
33
 
24
34
  /**
25
35
  * A cluster in the merge tree: a leaf (one thread) or a merged group.
26
- * @public
36
+ * @internal
27
37
  */
28
38
  export interface ClusterNode {
29
39
  /** Leaves: the thread id verbatim. Merged nodes: `cluster:${count}:${minMemberId}`. */
@@ -58,7 +68,7 @@ export interface ContractedEvent {
58
68
 
59
69
  /**
60
70
  * A finalized merge event, ready for the runtime.
61
- * @public
71
+ * @internal
62
72
  */
63
73
  export interface MergeEvent {
64
74
  zMerge: number
@@ -70,7 +80,7 @@ export interface MergeEvent {
70
80
 
71
81
  /**
72
82
  * The precomputed clustering schedule for one page's comments.
73
- * @public
83
+ * @internal
74
84
  */
75
85
  export interface ClusterTable {
76
86
  /** Sorted non-increasing by zMerge; satisfies the invariants of CLUSTERING.md §7.6. */
@@ -79,15 +89,15 @@ export interface ClusterTable {
79
89
  leaves: readonly ClusterNode[]
80
90
  }
81
91
 
82
- /** @public */
92
+ /** @internal */
83
93
  export interface ClusterOptions {
84
- /** Cluster (merge) distance, screen px. Default 40. */
94
+ /** Cluster (merge) distance, screen px. Default 22. */
85
95
  Tc?: number
86
- /** Uncluster (split) distance, screen px. Must be \> Tc. Default 1.5 · Tc. */
96
+ /** Uncluster (split) distance, screen px. Must be \> Tc. Default 1.2 · Tc. */
87
97
  Tu?: number
88
- /** Contraction window ratio. Default 0.12. */
98
+ /** Contraction window ratio. Default 0.7. */
89
99
  eps?: number
90
- /** Max cluster screen extent at birth, screen px. Must be \>= Tc. Default 3 · Tc. */
100
+ /** Max cluster screen extent at birth, screen px. Must be \>= Tc. Default 3.75 · Tc. */
91
101
  Dmax?: number
92
102
  /** Camera zoom bounds — pass the editor's camera constraints. Required. */
93
103
  minZoom: number
package/src/index.ts CHANGED
@@ -1,93 +1,142 @@
1
1
  import { registerTldrawLibraryVersion } from '@tldraw/utils'
2
2
 
3
3
  // Presentational commenting components. These are tldraw-independent and can be used to build
4
- // custom commenting UI.
5
- export { Avatar, type AvatarProps } from './ui/avatar'
4
+ // custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions
5
+ // so the commenting public API is unchanged.
6
+ export {
7
+ Avatar,
8
+ type AvatarProps,
9
+ type CommentAuthor,
10
+ createMentionSuggestion,
11
+ filterMentionMembers,
12
+ Mention,
13
+ MentionList,
14
+ type MentionListProps,
15
+ type MentionMember,
16
+ type MentionProps,
17
+ type MentionSuggestionOptions,
18
+ } from '@tldraw/mentions'
6
19
  export { Byline, type BylineProps } from './ui/byline'
7
20
  export { CommentCard, type CommentCardProps } from './ui/comment-card'
8
21
  export { CommentComposer, type CommentComposerProps } from './ui/comment-composer'
9
22
  export { CountBadge, type CountBadgeProps } from './ui/count-badge'
10
23
  export { CommentPin, type CommentPinProps } from './ui/comment-pin'
11
- export { CommentText, type CommentTextProps } from './ui/comment-text'
12
24
  export { CommentThread, type CommentThreadProps } from './ui/comment-thread'
13
- export { CommentsList, type CommentListItemProps, type CommentsListProps } from './ui/comments-list'
25
+ export {
26
+ CommentListItem,
27
+ CommentsList,
28
+ isOpenInNewTabClick,
29
+ type CommentListItemProps,
30
+ type CommentListItemRenderProps,
31
+ type CommentsListProps,
32
+ } from './ui/comments-list'
14
33
  export { EmptyState, type EmptyStateProps } from './ui/empty-state'
15
- export { formatRelativeTime } from './ui/format-time'
16
- export { Mention, type MentionProps } from './ui/mention'
17
- export { MentionList, type MentionListProps, type MentionMember } from './ui/mention-list'
34
+ export { formatFullDateTime, formatRelativeTime } from './ui/format-time'
18
35
  export {
19
- createMentionSuggestion,
20
- filterMentionMembers,
21
- type MentionSuggestionOptions,
22
- } from './ui/mention-suggestion'
23
- export { Reaction, type ReactionProps } from './ui/reaction'
24
- export { Reactions } from './ui/reactions'
25
- export { renderMarkdown } from './ui/render-markdown'
36
+ DEFAULT_REACTION_EMOJI,
37
+ EmojiPicker,
38
+ type EmojiPickerProps,
39
+ isAllowedReactionEmoji,
40
+ } from './ui/emoji-picker'
41
+ export {
42
+ Reaction,
43
+ type ReactionProps,
44
+ type RenderReaction,
45
+ defaultRenderReaction,
46
+ DefaultReactionTooltip,
47
+ DefaultReactionTooltipContent,
48
+ type ReactionTooltipProps,
49
+ } from './ui/reaction'
50
+ export { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'
51
+ export {
52
+ Reactions,
53
+ type ReactionReactor,
54
+ type ReactionsProps,
55
+ type ReactionSummary,
56
+ } from './ui/reactions'
26
57
  export { SendButton, type SendButtonProps } from './ui/send-button'
27
58
 
28
59
  // The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment
29
60
  // records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs
30
61
  // with the presentational components above.
62
+ export { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'
31
63
  export { CommentBody, type CommentBodyProps } from './canvas/comment-body'
32
64
  export {
33
- CommentTool,
34
- commentToolOverrides,
35
- commentTools,
36
- type PendingComment,
37
- } from './canvas/comment-tool'
38
- export { collectClusterLeaves } from './canvas/cluster-input'
39
- export { computeClusterTable } from './clustering/computeClusterTable'
65
+ deleteComment,
66
+ deleteThread,
67
+ editComment,
68
+ putCommentRecords,
69
+ removeCommentRecords,
70
+ reopenThread,
71
+ resolveThread,
72
+ } from './canvas/comment-mutations'
73
+ export {
74
+ CommentReactionPicker,
75
+ type CommentReactionPickerProps,
76
+ CommentReactions,
77
+ type CommentReactionsProps,
78
+ type ReactionSummaryInput,
79
+ summarizeReactions,
80
+ toggleCommentReaction,
81
+ useCommentReactions,
82
+ } from './canvas/comment-reactions'
83
+ export { CommentTool, commentToolOverrides, commentTools } from './canvas/comment-tool'
40
84
  export {
85
+ getCommentReactions,
41
86
  getCommentRecord,
42
87
  getComments,
43
88
  getCommentThreads,
44
- putCommentRecords,
45
- removeCommentRecords,
89
+ getLiveComments,
90
+ getLiveCommentThreads,
46
91
  type TLCommentRecord,
47
92
  } from './canvas/comment-store'
48
- export { createClusterRuntime, type ClusterRuntime } from './clustering/runtime'
49
- export type {
50
- ClusterNode,
51
- ClusterOptions,
52
- ClusterTable,
53
- LeafInput,
54
- MergeEvent,
55
- } from './clustering/types'
93
+ export { type CommentingContext } from './canvas/context'
56
94
  export { CommentsFilterMenu, type CommentsFilterMenuProps } from './canvas/comments-filter-menu'
57
95
  export { CommentsMenuItem } from './canvas/comments-menu-item'
58
96
  export { CanvasComments, type CanvasCommentsProps } from './canvas/comments-overlay'
59
97
  export {
60
98
  type CommentingComponents,
61
99
  type CommentingOptions,
100
+ type CommentModification,
101
+ type CommentModificationContext,
102
+ defaultCanModifyComment,
62
103
  defaultCommentingOptions,
104
+ getCanComment,
105
+ getCanModifyComment,
63
106
  getCommentingOptions,
107
+ type ShapeCommentPrecisionContext,
108
+ useCanComment,
109
+ useCanModifyComment,
64
110
  useCommentingOptions,
65
111
  } from './canvas/options'
66
112
  export { CommentsOverflowMenu } from './canvas/comments-overflow-menu'
67
- export { CanvasCommentsSidebar, type CanvasCommentsSidebarProps } from './canvas/comments-sidebar'
113
+ export { CommentsVisibilityToggle } from './canvas/comments-visibility-toggle'
114
+ export {
115
+ CanvasCommentsSidebar,
116
+ sortSidebarRows,
117
+ type CanvasCommentsSidebarProps,
118
+ type SidebarRow,
119
+ } from './canvas/comments-sidebar'
68
120
  export { useComments, useCommentThreads, useThreadComments } from './canvas/hooks'
69
121
  export { useCommentingEnabled } from './canvas/license'
70
- export { DEFAULT_REGION_COMMENT_OPTIONS, type RegionCommentOptions } from './canvas/region-options'
71
122
  export { richTextToPlaintext } from './canvas/rich-text'
72
123
  export { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './canvas/sidebar-filters'
73
124
  export {
74
125
  commentsHidden,
75
- commitCommentMutation,
126
+ commentsSidebarOpen,
127
+ getRevealThreadPending,
76
128
  openThreadId,
77
- pendingComment,
129
+ revealThread,
78
130
  sidebarFilters,
79
131
  toggleCommentsHidden,
132
+ toggleCommentsSidebar,
80
133
  useCommentsHidden,
134
+ useCommentsSidebarOpen,
81
135
  useOpenThreadId,
82
- usePendingComment,
136
+ useRevealThreadPending,
83
137
  useSidebarFilters,
84
138
  } from './canvas/state'
85
- export {
86
- anchorPagePoint,
87
- DEFAULT_IMPRECISE_SHAPE_ANCHOR,
88
- focusThread,
89
- shapeAnchorAt,
90
- } from './canvas/thread-state'
139
+ export { anchorPagePoint, focusThread, shapeAnchorAt } from './canvas/thread-state'
91
140
 
92
141
  registerTldrawLibraryVersion(
93
142
  (globalThis as any).TLDRAW_LIBRARY_NAME,
package/src/ui/byline.tsx CHANGED
@@ -1,23 +1,35 @@
1
- import { formatRelativeTime } from './format-time'
1
+ import { Avatar, type CommentAuthor } from '@tldraw/mentions'
2
+ import { TldrawUiTooltip, useCurrentTranslation } from 'tldraw'
3
+ import { formatFullDateTime, formatRelativeTime } from './format-time'
2
4
 
3
5
  /** @public */
4
6
  export interface BylineProps {
5
- author: string
7
+ author: CommentAuthor
6
8
  /** ISO datetime; formatted to relative time by the component. */
7
9
  date: string
8
10
  /** Shows an "edited" marker when the comment has been edited. */
9
11
  edited?: boolean
10
12
  }
11
13
 
12
- /** A comment's metadata line: author name, relative time, and an edited marker. @public @react */
14
+ /**
15
+ * A comment's metadata line: author name, relative time, and an edited marker. Hovering the time
16
+ * shows the full date and time in a tooltip.
17
+ * @public @react
18
+ */
13
19
  export function Byline({ author, date, edited }: BylineProps) {
20
+ // The relative time is the one piece of a byline that isn't the host's data, so it follows the
21
+ // same translation context as every other string in the UI rather than a prop of its own.
22
+ const { locale } = useCurrentTranslation()
14
23
  return (
15
- <div className="cmt-head">
16
- <span className="cmt-author">{author}</span>
17
- <span className="cmt-time">
18
- {formatRelativeTime(date)}
19
- {edited && <span className="cmt-edited"> · edited</span>}
20
- </span>
24
+ <div className="tlui-cmt-head">
25
+ <Avatar author={author} />
26
+ <span className="tlui-cmt-author">{author.name}</span>
27
+ <TldrawUiTooltip content={formatFullDateTime(date, locale)}>
28
+ <span className="tlui-cmt-time">
29
+ {formatRelativeTime(date, locale)}
30
+ {edited && <span className="tlui-cmt-edited"> · edited</span>}
31
+ </span>
32
+ </TldrawUiTooltip>
21
33
  </div>
22
34
  )
23
35
  }
@@ -1,12 +1,12 @@
1
+ import { type CommentAuthor } from '@tldraw/mentions'
1
2
  import { ReactNode } from 'react'
2
- import { Avatar } from './avatar'
3
3
  import { Byline } from './byline'
4
4
 
5
5
  /** @public */
6
6
  export interface CommentCardProps {
7
- author: string
8
- /** The rendered comment body. The card doesn't dictate a format — pass a `<CommentText>`
9
- * for markdown, a rich-text render, or any node. */
7
+ author: CommentAuthor
8
+ /** The rendered comment body. The card doesn't dictate a format — pass a `<CommentBody>`
9
+ * for a comment's rich text, or any node of your own. */
10
10
  body: ReactNode
11
11
  /** ISO datetime; formatted to relative time by the component. */
12
12
  date: string
@@ -15,18 +15,28 @@ export interface CommentCardProps {
15
15
  edited?: boolean
16
16
  /** Hover-revealed controls at the card's top-right (e.g. an edit affordance). */
17
17
  actions?: ReactNode
18
+ /** Content under the body, aligned with it rather than the avatar (e.g. a `<Reactions>` row). */
19
+ footer?: ReactNode
18
20
  }
19
21
 
20
22
  /** A single comment: Avatar, Byline, and a body slot the consumer renders. @public @react */
21
- export function CommentCard({ author, body, date, you, edited, actions }: CommentCardProps) {
23
+ export function CommentCard({
24
+ author,
25
+ body,
26
+ date,
27
+ you,
28
+ edited,
29
+ actions,
30
+ footer,
31
+ }: CommentCardProps) {
22
32
  return (
23
- <div className={you ? 'cmt-card cmt-card--you' : 'cmt-card'}>
24
- <Avatar name={author} />
25
- <div className="cmt-body">
26
- <Byline author={author} date={date} edited={edited} />
33
+ <div className={you ? 'tlui-cmt-card tlui-cmt-card--you' : 'tlui-cmt-card'}>
34
+ <Byline author={author} date={date} edited={edited} />
35
+ <div className="tlui-cmt-body">
27
36
  {body}
37
+ {footer}
28
38
  </div>
29
- {actions !== undefined && <div className="cmt-card__actions">{actions}</div>}
39
+ {actions !== undefined && <div className="tlui-cmt-card__actions">{actions}</div>}
30
40
  </div>
31
41
  )
32
42
  }