@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8

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 (279) hide show
  1. package/README.md +0 -2
  2. package/commenting.css +1139 -329
  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/canvas/cluster-input.js +2 -2
  6. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  7. package/dist-cjs/canvas/comment-body.js +1 -1
  8. package/dist-cjs/canvas/comment-body.js.map +2 -2
  9. package/dist-cjs/canvas/comment-drafts.js +54 -0
  10. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  11. package/dist-cjs/canvas/comment-mutations.js +123 -0
  12. package/dist-cjs/canvas/comment-mutations.js.map +7 -0
  13. package/dist-cjs/canvas/comment-reactions.js +163 -0
  14. package/dist-cjs/canvas/comment-reactions.js.map +7 -0
  15. package/dist-cjs/canvas/comment-render.js +5 -3
  16. package/dist-cjs/canvas/comment-render.js.map +2 -2
  17. package/dist-cjs/canvas/comment-store.js +19 -9
  18. package/dist-cjs/canvas/comment-store.js.map +2 -2
  19. package/dist-cjs/canvas/comment-tool.js +64 -14
  20. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  21. package/dist-cjs/canvas/comments-filter-menu.js +20 -38
  22. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  23. package/dist-cjs/canvas/comments-overflow-menu.js +22 -27
  24. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overlay.js +429 -474
  26. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  27. package/dist-cjs/canvas/comments-sidebar.js +35 -32
  28. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  29. package/dist-cjs/canvas/comments-visibility-toggle.js +65 -0
  30. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  31. package/dist-cjs/canvas/context.js +17 -0
  32. package/dist-cjs/canvas/context.js.map +7 -0
  33. package/dist-cjs/canvas/hooks.js +22 -3
  34. package/dist-cjs/canvas/hooks.js.map +2 -2
  35. package/dist-cjs/canvas/options.js +19 -2
  36. package/dist-cjs/canvas/options.js.map +2 -2
  37. package/dist-cjs/canvas/pin-stacking.js +56 -0
  38. package/dist-cjs/canvas/pin-stacking.js.map +7 -0
  39. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  40. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  41. package/dist-cjs/canvas/state.js +28 -14
  42. package/dist-cjs/canvas/state.js.map +2 -2
  43. package/dist-cjs/canvas/thread-preview.js +185 -0
  44. package/dist-cjs/canvas/thread-preview.js.map +7 -0
  45. package/dist-cjs/canvas/thread-stack.js +199 -0
  46. package/dist-cjs/canvas/thread-stack.js.map +7 -0
  47. package/dist-cjs/canvas/thread-state.js +67 -21
  48. package/dist-cjs/canvas/thread-state.js.map +2 -2
  49. package/dist-cjs/canvas/thread-view.js +397 -0
  50. package/dist-cjs/canvas/thread-view.js.map +7 -0
  51. package/dist-cjs/clustering/computeClusterTable.js +4 -4
  52. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  53. package/dist-cjs/clustering/runtime.js +119 -7
  54. package/dist-cjs/clustering/runtime.js.map +2 -2
  55. package/dist-cjs/clustering/types.js.map +1 -1
  56. package/dist-cjs/index.d.ts +763 -333
  57. package/dist-cjs/index.js +46 -28
  58. package/dist-cjs/index.js.map +2 -2
  59. package/dist-cjs/ui/byline.js +6 -4
  60. package/dist-cjs/ui/byline.js.map +2 -2
  61. package/dist-cjs/ui/comment-card.js +15 -8
  62. package/dist-cjs/ui/comment-card.js.map +2 -2
  63. package/dist-cjs/ui/comment-composer.js +137 -36
  64. package/dist-cjs/ui/comment-composer.js.map +2 -2
  65. package/dist-cjs/ui/comment-pin.js +24 -2
  66. package/dist-cjs/ui/comment-pin.js.map +2 -2
  67. package/dist-cjs/ui/comment-thread.js +9 -7
  68. package/dist-cjs/ui/comment-thread.js.map +2 -2
  69. package/dist-cjs/ui/comments-list.js +30 -18
  70. package/dist-cjs/ui/comments-list.js.map +2 -2
  71. package/dist-cjs/ui/count-badge.js +3 -2
  72. package/dist-cjs/ui/count-badge.js.map +2 -2
  73. package/dist-cjs/ui/emoji-picker.js +54 -0
  74. package/dist-cjs/ui/emoji-picker.js.map +7 -0
  75. package/dist-cjs/ui/empty-state.js +3 -3
  76. package/dist-cjs/ui/empty-state.js.map +2 -2
  77. package/dist-cjs/ui/format-time.js +4 -1
  78. package/dist-cjs/ui/format-time.js.map +2 -2
  79. package/dist-cjs/ui/reaction-picker.js +73 -0
  80. package/dist-cjs/ui/reaction-picker.js.map +7 -0
  81. package/dist-cjs/ui/reaction.js +59 -6
  82. package/dist-cjs/ui/reaction.js.map +2 -2
  83. package/dist-cjs/ui/reactions.js +20 -7
  84. package/dist-cjs/ui/reactions.js.map +2 -2
  85. package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
  86. package/dist-cjs/ui/reply-count.js.map +7 -0
  87. package/dist-cjs/ui/send-button.js +20 -1
  88. package/dist-cjs/ui/send-button.js.map +2 -2
  89. package/dist-cjs/ui/{comment-text.js → tooltip-button.js} +12 -9
  90. package/dist-cjs/ui/tooltip-button.js.map +7 -0
  91. package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
  92. package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
  93. package/dist-esm/canvas/cluster-input.mjs +2 -2
  94. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  95. package/dist-esm/canvas/comment-body.mjs +1 -1
  96. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  97. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  98. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  99. package/dist-esm/canvas/comment-mutations.mjs +103 -0
  100. package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
  101. package/dist-esm/canvas/comment-reactions.mjs +152 -0
  102. package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
  103. package/dist-esm/canvas/comment-render.mjs +5 -3
  104. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  105. package/dist-esm/canvas/comment-store.mjs +19 -9
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +67 -17
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +20 -38
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +23 -27
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +450 -479
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +38 -35
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +45 -0
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  119. package/dist-esm/canvas/context.mjs +1 -0
  120. package/dist-esm/canvas/context.mjs.map +7 -0
  121. package/dist-esm/canvas/hooks.mjs +28 -5
  122. package/dist-esm/canvas/hooks.mjs.map +2 -2
  123. package/dist-esm/canvas/options.mjs +21 -3
  124. package/dist-esm/canvas/options.mjs.map +2 -2
  125. package/dist-esm/canvas/pin-stacking.mjs +36 -0
  126. package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
  127. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  128. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  129. package/dist-esm/canvas/state.mjs +29 -19
  130. package/dist-esm/canvas/state.mjs.map +2 -2
  131. package/dist-esm/canvas/thread-preview.mjs +170 -0
  132. package/dist-esm/canvas/thread-preview.mjs.map +7 -0
  133. package/dist-esm/canvas/thread-stack.mjs +190 -0
  134. package/dist-esm/canvas/thread-stack.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-state.mjs +71 -22
  136. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-view.mjs +402 -0
  138. package/dist-esm/canvas/thread-view.mjs.map +7 -0
  139. package/dist-esm/clustering/computeClusterTable.mjs +4 -4
  140. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  141. package/dist-esm/clustering/runtime.mjs +119 -7
  142. package/dist-esm/clustering/runtime.mjs.map +2 -2
  143. package/dist-esm/index.d.mts +763 -333
  144. package/dist-esm/index.mjs +87 -41
  145. package/dist-esm/index.mjs.map +2 -2
  146. package/dist-esm/ui/byline.mjs +6 -4
  147. package/dist-esm/ui/byline.mjs.map +2 -2
  148. package/dist-esm/ui/comment-card.mjs +15 -8
  149. package/dist-esm/ui/comment-card.mjs.map +2 -2
  150. package/dist-esm/ui/comment-composer.mjs +148 -36
  151. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  152. package/dist-esm/ui/comment-pin.mjs +24 -2
  153. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  154. package/dist-esm/ui/comment-thread.mjs +9 -7
  155. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  156. package/dist-esm/ui/comments-list.mjs +43 -29
  157. package/dist-esm/ui/comments-list.mjs.map +2 -2
  158. package/dist-esm/ui/count-badge.mjs +3 -2
  159. package/dist-esm/ui/count-badge.mjs.map +2 -2
  160. package/dist-esm/ui/emoji-picker.mjs +34 -0
  161. package/dist-esm/ui/emoji-picker.mjs.map +7 -0
  162. package/dist-esm/ui/empty-state.mjs +3 -3
  163. package/dist-esm/ui/empty-state.mjs.map +2 -2
  164. package/dist-esm/ui/format-time.mjs +4 -1
  165. package/dist-esm/ui/format-time.mjs.map +2 -2
  166. package/dist-esm/ui/reaction-picker.mjs +60 -0
  167. package/dist-esm/ui/reaction-picker.mjs.map +7 -0
  168. package/dist-esm/ui/reaction.mjs +60 -7
  169. package/dist-esm/ui/reaction.mjs.map +2 -2
  170. package/dist-esm/ui/reactions.mjs +21 -8
  171. package/dist-esm/ui/reactions.mjs.map +2 -2
  172. package/dist-esm/ui/reply-count.mjs +9 -0
  173. package/dist-esm/ui/reply-count.mjs.map +7 -0
  174. package/dist-esm/ui/send-button.mjs +20 -1
  175. package/dist-esm/ui/send-button.mjs.map +2 -2
  176. package/dist-esm/ui/tooltip-button.mjs +12 -0
  177. package/dist-esm/ui/tooltip-button.mjs.map +7 -0
  178. package/package.json +6 -6
  179. package/src/canvas/anchor-lifecycle.test.ts +290 -0
  180. package/src/canvas/anchor-lifecycle.ts +205 -0
  181. package/src/canvas/canvas.css +461 -46
  182. package/src/canvas/cluster-input.test.ts +31 -23
  183. package/src/canvas/cluster-input.ts +3 -4
  184. package/src/canvas/comment-body.tsx +1 -1
  185. package/src/canvas/comment-drafts.test.ts +55 -0
  186. package/src/canvas/comment-drafts.ts +46 -0
  187. package/src/canvas/comment-mutations.test.ts +410 -0
  188. package/src/canvas/comment-mutations.ts +288 -0
  189. package/src/canvas/comment-reactions.test.ts +132 -0
  190. package/src/canvas/comment-reactions.tsx +245 -0
  191. package/src/canvas/comment-render.test.ts +1 -1
  192. package/src/canvas/comment-render.ts +9 -5
  193. package/src/canvas/comment-store.ts +67 -27
  194. package/src/canvas/comment-tool.test.ts +162 -0
  195. package/src/canvas/comment-tool.tsx +105 -23
  196. package/src/canvas/comments-filter-menu.tsx +23 -32
  197. package/src/canvas/comments-overflow-menu.tsx +14 -21
  198. package/src/canvas/comments-overlay.tsx +695 -622
  199. package/src/canvas/comments-sidebar.test.ts +45 -0
  200. package/src/canvas/comments-sidebar.tsx +76 -58
  201. package/src/canvas/comments-visibility-toggle.tsx +49 -0
  202. package/src/canvas/context.ts +47 -0
  203. package/src/canvas/hooks.ts +52 -11
  204. package/src/canvas/options.test.ts +52 -2
  205. package/src/canvas/options.ts +133 -9
  206. package/src/canvas/pin-stacking.test.ts +114 -0
  207. package/src/canvas/pin-stacking.ts +57 -0
  208. package/src/canvas/sidebar-filters.ts +4 -3
  209. package/src/canvas/state.ts +92 -35
  210. package/src/canvas/thread-preview.test.ts +91 -0
  211. package/src/canvas/thread-preview.tsx +304 -0
  212. package/src/canvas/thread-stack.tsx +252 -0
  213. package/src/canvas/thread-state.test.ts +209 -0
  214. package/src/canvas/thread-state.ts +137 -36
  215. package/src/canvas/thread-view.tsx +516 -0
  216. package/src/clustering/computeClusterTable.test.ts +24 -23
  217. package/src/clustering/computeClusterTable.ts +5 -5
  218. package/src/clustering/runtime.test.ts +217 -7
  219. package/src/clustering/runtime.ts +171 -17
  220. package/src/clustering/types.ts +9 -9
  221. package/src/index.ts +76 -40
  222. package/src/ui/byline.tsx +7 -5
  223. package/src/ui/comment-card.tsx +20 -10
  224. package/src/ui/comment-composer.tsx +206 -40
  225. package/src/ui/comment-pin.tsx +26 -2
  226. package/src/ui/comment-thread.tsx +13 -6
  227. package/src/ui/comments-list.test.ts +31 -0
  228. package/src/ui/comments-list.tsx +45 -21
  229. package/src/ui/comments.css +566 -281
  230. package/src/ui/count-badge.tsx +9 -2
  231. package/src/ui/emoji-picker.test.ts +25 -0
  232. package/src/ui/emoji-picker.tsx +76 -0
  233. package/src/ui/empty-state.tsx +3 -3
  234. package/src/ui/format-time.ts +7 -2
  235. package/src/ui/reaction-picker.tsx +109 -0
  236. package/src/ui/reaction.tsx +121 -6
  237. package/src/ui/reactions.tsx +69 -10
  238. package/src/ui/reply-count.ts +16 -0
  239. package/src/ui/send-button.tsx +18 -3
  240. package/src/ui/tooltip-button.tsx +20 -0
  241. package/dist-cjs/canvas/region-options.js +0 -49
  242. package/dist-cjs/canvas/region-options.js.map +0 -7
  243. package/dist-cjs/ui/avatar.js +0 -43
  244. package/dist-cjs/ui/avatar.js.map +0 -7
  245. package/dist-cjs/ui/comment-mention.js +0 -42
  246. package/dist-cjs/ui/comment-mention.js.map +0 -7
  247. package/dist-cjs/ui/comment-text.js.map +0 -7
  248. package/dist-cjs/ui/mention-list.js +0 -70
  249. package/dist-cjs/ui/mention-list.js.map +0 -7
  250. package/dist-cjs/ui/mention-suggestion.js +0 -206
  251. package/dist-cjs/ui/mention-suggestion.js.map +0 -7
  252. package/dist-cjs/ui/mention.js.map +0 -7
  253. package/dist-cjs/ui/render-markdown.js +0 -63
  254. package/dist-cjs/ui/render-markdown.js.map +0 -7
  255. package/dist-esm/canvas/region-options.mjs +0 -29
  256. package/dist-esm/canvas/region-options.mjs.map +0 -7
  257. package/dist-esm/ui/avatar.mjs +0 -23
  258. package/dist-esm/ui/avatar.mjs.map +0 -7
  259. package/dist-esm/ui/comment-mention.mjs +0 -22
  260. package/dist-esm/ui/comment-mention.mjs.map +0 -7
  261. package/dist-esm/ui/comment-text.mjs +0 -9
  262. package/dist-esm/ui/comment-text.mjs.map +0 -7
  263. package/dist-esm/ui/mention-list.mjs +0 -50
  264. package/dist-esm/ui/mention-list.mjs.map +0 -7
  265. package/dist-esm/ui/mention-suggestion.mjs +0 -186
  266. package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
  267. package/dist-esm/ui/mention.mjs +0 -11
  268. package/dist-esm/ui/mention.mjs.map +0 -7
  269. package/dist-esm/ui/render-markdown.mjs +0 -45
  270. package/dist-esm/ui/render-markdown.mjs.map +0 -7
  271. package/src/canvas/region-options.ts +0 -57
  272. package/src/ui/avatar.tsx +0 -31
  273. package/src/ui/comment-mention.ts +0 -47
  274. package/src/ui/comment-text.tsx +0 -12
  275. package/src/ui/mention-list.tsx +0 -106
  276. package/src/ui/mention-suggestion.test.ts +0 -18
  277. package/src/ui/mention-suggestion.tsx +0 -285
  278. package/src/ui/mention.tsx +0 -9
  279. package/src/ui/render-markdown.tsx +0 -72
@@ -1,14 +1,26 @@
1
+ import { Avatar } from '@tldraw/mentions';
2
+ import { AvatarProps } from '@tldraw/mentions';
3
+ import { CommentAuthor } from '@tldraw/mentions';
1
4
  import { ComponentType } from 'react';
5
+ import { createMentionSuggestion } from '@tldraw/mentions';
2
6
  import { Editor } from 'tldraw';
3
7
  import { EditorAtom } from 'tldraw';
8
+ import { filterMentionMembers } from '@tldraw/mentions';
4
9
  import { JSX } from 'react/jsx-runtime';
5
- import type { MentionNodeAttrs } from '@tiptap/extension-mention';
10
+ import { Mention } from '@tldraw/mentions';
11
+ import { MentionList } from '@tldraw/mentions';
12
+ import { MentionListProps } from '@tldraw/mentions';
13
+ import { MentionMember } from '@tldraw/mentions';
14
+ import { MentionProps } from '@tldraw/mentions';
15
+ import { MentionSuggestionOptions } from '@tldraw/mentions';
16
+ import { MouseEvent as MouseEvent_2 } from 'react';
6
17
  import { ReactNode } from 'react';
7
18
  import { StateNode } from 'tldraw';
8
- import type { SuggestionOptions } from '@tiptap/suggestion';
9
19
  import { TLComment } from 'tldraw';
10
20
  import { TLCommentAnchor } from 'tldraw';
11
21
  import { TLCommentId } from 'tldraw';
22
+ import { TLCommentReaction } from 'tldraw';
23
+ import { TLCommentReactionId } from 'tldraw';
12
24
  import { TLCommentThread } from 'tldraw';
13
25
  import { TLCommentThreadId } from 'tldraw';
14
26
  import { TLHistoryBatchOptions } from 'tldraw';
@@ -20,46 +32,35 @@ import { VecLike } from 'tldraw';
20
32
 
21
33
  /**
22
34
  * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. For imprecise
23
- * shape anchors the pin uses `impreciseShapeAnchor` (a normalized 0–1 spot, top-right by default)
24
- * rather than the stored `x`/`y`.
35
+ * shape anchors the pin uses the editor's {@link CommentingOptions.impreciseShapeAnchor} (a
36
+ * normalized 0–1 spot, top-right by default) rather than the stored `x`/`y`.
37
+ *
38
+ * A shape anchor's `x`/`y` are normalized within the shape's own bounds and resolved through the
39
+ * shape's page transform, so the pin rides every part of that transform — rotating the shape
40
+ * carries the pin around with it instead of leaving it behind in the bounding box.
25
41
  * @public
26
42
  */
27
- export declare function anchorPagePoint(editor: Editor, anchor: TLCommentAnchor, impreciseShapeAnchor?: {
28
- x: number;
29
- y: number;
30
- }): {
43
+ export declare function anchorPagePoint(editor: Editor, anchor: TLCommentAnchor): {
31
44
  x: number;
32
45
  y: number;
33
46
  } | null;
34
47
 
35
- /** A commenter's avatar — their image if provided, otherwise a single-initial coloured circle.
36
- * @public @react */
37
- export declare function Avatar({ name, color, image }: AvatarProps): JSX.Element;
48
+ export { Avatar }
38
49
 
39
- /** @public */
40
- export declare interface AvatarProps {
41
- name: string;
42
- /** Background colour, used when there's no `image`. Falls back to the default avatar tint. */
43
- color?: string;
44
- /** Avatar image URL. When set, shows the image instead of the coloured initial. */
45
- image?: string;
46
- }
50
+ export { AvatarProps }
47
51
 
48
52
  /** A comment's metadata line: author name, relative time, and an edited marker. @public @react */
49
53
  export declare function Byline({ author, date, edited }: BylineProps): JSX.Element;
50
54
 
51
55
  /** @public */
52
56
  export declare interface BylineProps {
53
- author: string;
57
+ author: CommentAuthor;
54
58
  /** ISO datetime; formatted to relative time by the component. */
55
59
  date: string;
56
60
  /** Shows an "edited" marker when the comment has been edited. */
57
61
  edited?: boolean;
58
62
  }
59
63
 
60
- /** @public @react */
61
- export declare function CanvasComments(props: CanvasCommentsProps): JSX.Element | null;
62
-
63
64
  /**
64
65
  * A ready-to-use comments layer for a tldraw canvas: pins each thread at its anchor, opens a
65
66
  * thread popover (with a reply composer) on click, and shows a composer where the comment tool
@@ -69,143 +70,48 @@ export declare function CanvasComments(props: CanvasCommentsProps): JSX.Element
69
70
  * and `PinContent` slots on `CommentTool.configure({ components })`), and the pieces it composes
70
71
  * (`CommentPin`, `CommentThread`, `CommentComposer`, the hooks, the tool) are all exported, so a
71
72
  * consumer can rebuild this from parts instead.
73
+ *
74
+ * The host wiring — who the viewer is, how ids become names, read status, mentions — is the
75
+ * {@link CommentingContext}, which `CanvasCommentsSidebar` takes too, so a host mounting both can
76
+ * build it once and spread it into each.
77
+ *
78
+ * @public @react
79
+ */
80
+ export declare function CanvasComments(props: CanvasCommentsProps): JSX.Element | null;
81
+
82
+ /**
83
+ * The host wiring for {@link CanvasComments} — see {@link CommentingContext}, which the sidebar
84
+ * takes the same fields from.
85
+ *
72
86
  * @public
73
87
  */
74
- export declare interface CanvasCommentsProps {
75
- /** The signed-in user's id, or null for a read-only viewer. Only a signed-in user composes. */
76
- currentUserId: null | string;
77
- /** Map an author id to a display name, or `undefined` when the id can't be named. */
78
- resolveName(id: string): string | undefined;
79
- /** Called after any comment (a new thread's first comment, or a reply) is posted. */
80
- onPostComment?(comment: TLComment): void;
81
- /** Whether a comment is unread for the current user (return true for unread). */
82
- isCommentUnread?(commentId: TLCommentId): boolean;
83
- /**
84
- * Called for each unread comment shown to the user in an open thread popover, so hosts can
85
- * record a read receipt. Needs `isCommentUnread` to know what's unread.
86
- */
87
- onCommentRead?(commentId: TLCommentId): void;
88
- /** Resolve the members matching an `@`-query in the composers (sync or async). */
89
- getMentionSuggestions?(query: string): MentionMember[] | Promise<MentionMember[]>;
90
- /** Override a mention-picker row's content. */
91
- renderMentionSuggestion?(member: MentionMember): ReactNode;
92
- /** Where imprecise shape pins sit — a normalized (0–1) spot within the shape. Default top-right. */
93
- impreciseShapeAnchor?: {
94
- x: number;
95
- y: number;
96
- };
97
- /** Region comment behaviour. Region is off by default — omit this for click-only point/shape
98
- * comments. Anything unset falls back to {@link DEFAULT_REGION_COMMENT_OPTIONS}. */
99
- regionOptions?: Partial<RegionCommentOptions>;
100
- }
88
+ export declare type CanvasCommentsProps = CommentingContext;
101
89
 
102
90
  /**
103
- * A comments list panel for the current page, shown while the comment tool is active. Clicking a
104
- * thread brings its pin into view and opens it. Batteries-included over the store (a sibling to
105
- * `CanvasComments`); `CommentsList` is exported for a differently-placed or always-on list.
91
+ * A comments list panel for the current page, shown while {@link commentsSidebarOpen} is set (e.g.
92
+ * toggled by a button). Clicking a thread brings its pin into view and opens it. Batteries-included
93
+ * over the store (a sibling to `CanvasComments`); `CommentsList` is exported for a differently-placed
94
+ * or always-on list.
106
95
  * @public @react
107
96
  */
108
97
  export declare function CanvasCommentsSidebar(props: CanvasCommentsSidebarProps): JSX.Element | null;
109
98
 
110
- /** @public */
111
- export declare interface CanvasCommentsSidebarProps {
112
- /** Map an author id to a display name, or `undefined` when the id can't be named. */
113
- resolveName(id: string): string | undefined;
114
- /** The signed-in user's id. Enables the "only your threads" filter when present. */
115
- currentUserId?: string;
116
- /**
117
- * Whether a comment is unread for the current user (return true for unread). Enables the
118
- * "only unread" filter when present.
119
- */
120
- isCommentUnread?(commentId: TLCommentId): boolean;
121
- /** Tool ids that show the sidebar. Defaults to the comment tool. */
122
- tools?: string[];
99
+ /**
100
+ * The host wiring for {@link CanvasCommentsSidebar}: the {@link CommentingContext} fields it reads,
101
+ * plus the panel's own slots. A non-null `currentUserId` enables the "only your threads" filter, and
102
+ * an `isCommentUnread` the "only unread" one. `CanvasComments` takes the same fields, so a host
103
+ * mounting both can spread one object into each.
104
+ *
105
+ * @public
106
+ */
107
+ export declare interface CanvasCommentsSidebarProps extends Pick<CommentingContext, 'currentUserId' | 'getThreadHref' | 'isCommentUnread' | 'resolveAuthor'> {
123
108
  /** Header above the list. */
124
109
  header?: ReactNode;
125
110
  /** Shown when the page has no threads. */
126
111
  empty?: ReactNode;
127
- /** Where imprecise shape pins sit, so navigation centres on the same spot. Default top-right. */
128
- impreciseShapeAnchor?: {
129
- x: number;
130
- y: number;
131
- };
132
112
  }
133
113
 
134
- /**
135
- * A cluster in the merge tree: a leaf (one thread) or a merged group.
136
- * @public
137
- */
138
- export declare interface ClusterNode {
139
- /** Leaves: the thread id verbatim. Merged nodes: `cluster:${count}:${minMemberId}`. */
140
- id: string;
141
- /** Page space; count-weighted mean of all member leaf anchors. */
142
- centroid: VecLike;
143
- /** Number of member leaves. Leaves = 1. */
144
- count: number;
145
- /** All member thread ids, sorted lexicographically ascending. */
146
- members: string[];
147
- }
148
-
149
- /** @public */
150
- export declare interface ClusterOptions {
151
- /** Cluster (merge) distance, screen px. Default 40. */
152
- Tc?: number;
153
- /** Uncluster (split) distance, screen px. Must be \> Tc. Default 1.5 · Tc. */
154
- Tu?: number;
155
- /** Contraction window ratio. Default 0.12. */
156
- eps?: number;
157
- /** Max cluster screen extent at birth, screen px. Must be \>= Tc. Default 3 · Tc. */
158
- Dmax?: number;
159
- /** Camera zoom bounds — pass the editor's camera constraints. Required. */
160
- minZoom: number;
161
- maxZoom: number;
162
- /**
163
- * Zoom by which every cluster has split, no matter how close its members are — including
164
- * coincident anchors, which otherwise never split. Merge thresholds are capped at
165
- * `maxSplitZoom / (Tu/Tc)` so the hysteresis band keeps its shape below the cap.
166
- * Default 6 (600%).
167
- */
168
- maxSplitZoom?: number;
169
- }
170
-
171
- /** @public */
172
- export declare interface ClusterRuntime {
173
- /** Events[0..k) are active. Exposed for tests and debugging. */
174
- readonly k: number;
175
- /** Reset state from scratch for the given zoom (cold start / after rebuild). */
176
- seed(zoom: number): void;
177
- /**
178
- * Reset state for the given zoom, carrying hysteresis state over from a previous partition
179
- * (the visible map of the model being replaced). Threshold-forced events ignore history:
180
- * zoom \<= zMerge is always active, zoom \>= zSplit always inactive. An event inside its band
181
- * is active iff its members were merged together in `previous`; a band event with no history
182
- * (e.g. introduced by the rebuild) stays inactive until the next zoom-out crosses its zMerge.
183
- * Active events must form a prefix of the table, so classification stops at the first
184
- * inactive event; later band-active events conservatively resolve to split.
185
- */
186
- seedFrom(zoom: number, previous: ReadonlyMap<string, ClusterNode>): void;
187
- /** Advance/retreat the cursor for a camera change. No-op if zoom sits inside all bands. */
188
- onCamera(zoom: number): void;
189
- /** The current partition: cluster id → node. Do not mutate. */
190
- getVisible(): ReadonlyMap<string, ClusterNode>;
191
- }
192
-
193
- /**
194
- * The precomputed clustering schedule for one page's comments.
195
- * @public
196
- */
197
- export declare interface ClusterTable {
198
- /** Sorted non-increasing by zMerge; satisfies the invariants of CLUSTERING.md §7.6. */
199
- events: readonly MergeEvent[];
200
- /** One node per input leaf, in input order. */
201
- leaves: readonly ClusterNode[];
202
- }
203
-
204
- /** @public */
205
- export declare function collectClusterLeaves(editor: Editor, threads: readonly TLCommentThread[], openThreadId: null | string, impreciseShapeAnchor?: {
206
- x: number;
207
- y: number;
208
- }): LeafInput[];
114
+ export { CommentAuthor }
209
115
 
210
116
  /**
211
117
  * Renders a comment's rich-text body read-only through the limited comment extension set (no
@@ -223,13 +129,13 @@ export declare interface CommentBodyProps {
223
129
  }
224
130
 
225
131
  /** A single comment: Avatar, Byline, and a body slot the consumer renders. @public @react */
226
- export declare function CommentCard({ author, body, date, you, edited, actions }: CommentCardProps): JSX.Element;
132
+ export declare function CommentCard({ author, body, date, you, edited, actions, footer }: CommentCardProps): JSX.Element;
227
133
 
228
134
  /** @public */
229
135
  export declare interface CommentCardProps {
230
- author: string;
231
- /** The rendered comment body. The card doesn't dictate a format — pass a `<CommentText>`
232
- * for markdown, a rich-text render, or any node. */
136
+ author: CommentAuthor;
137
+ /** The rendered comment body. The card doesn't dictate a format — pass a `<CommentBody>`
138
+ * for a comment's rich text, or any node of your own. */
233
139
  body: ReactNode;
234
140
  /** ISO datetime; formatted to relative time by the component. */
235
141
  date: string;
@@ -238,6 +144,8 @@ export declare interface CommentCardProps {
238
144
  edited?: boolean;
239
145
  /** Hover-revealed controls at the card's top-right (e.g. an edit affordance). */
240
146
  actions?: ReactNode;
147
+ /** Content under the body, aligned with it rather than the avatar (e.g. a `<Reactions>` row). */
148
+ footer?: ReactNode;
241
149
  }
242
150
 
243
151
  /**
@@ -247,11 +155,11 @@ export declare interface CommentCardProps {
247
155
  * floating toolbar. Presentational by default; pass value/onChange/onSubmit to drive it as a form.
248
156
  * @public @react
249
157
  */
250
- export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion }: CommentComposerProps): JSX.Element;
158
+ export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion }: CommentComposerProps): JSX.Element;
251
159
 
252
160
  /** @public */
253
161
  export declare interface CommentComposerProps {
254
- author: string;
162
+ author: CommentAuthor;
255
163
  placeholder: string;
256
164
  /** Controlled rich-text value. Omit for the presentational (display-only) composer. */
257
165
  value?: TLRichText;
@@ -259,6 +167,9 @@ export declare interface CommentComposerProps {
259
167
  /** Called on Send click or Enter. When set, the composer is interactive. */
260
168
  onSubmit?(): void;
261
169
  sendLabel?: string;
170
+ /** Called when Up is pressed in an empty composer — e.g. to start editing the comment above,
171
+ * the way chat apps edit your last message. With content in the field, Up moves the cursor. */
172
+ onArrowUpWhenEmpty?(): void;
262
173
  disabled?: boolean;
263
174
  autoFocus?: boolean;
264
175
  /** The leading element before the field. Defaults to the author's avatar. */
@@ -289,12 +200,88 @@ export declare interface CommentingComponents {
289
200
  ThreadPreview?: ComponentType<{
290
201
  comment: TLComment;
291
202
  }>;
203
+ /**
204
+ * A reaction's visual, given its token. The default renders the token string for the OS emoji
205
+ * font to draw (so the token is the emoji glyph). Override this to render your own palette —
206
+ * return an `<img>` for custom emoji, an SVG, or anything. The token is whatever your picker
207
+ * emits and is what gets stored/synced; this only controls how it's drawn.
208
+ */
209
+ ReactionContent?: ComponentType<{
210
+ token: string;
211
+ }>;
212
+ /**
213
+ * What the add-reaction button opens: the thing that produces a reaction token. Replaces the
214
+ * default `<EmojiPicker>` grid. Pairs with `ReactionContent` (which draws whatever tokens this
215
+ * emits) and with `isAllowedReaction` (which has to accept them).
216
+ */
217
+ ReactionPalette?: ComponentType<EmojiPickerProps>;
218
+ /**
219
+ * The hover affordance naming who reacted with an emoji. It receives the reactors and the pill
220
+ * (as `children`) and returns the whole thing — so it owns the tooltip, its box, size, shape, and
221
+ * position. Replaces the default (`DefaultReactionTooltip`). For a simple wording change, translate
222
+ * the `comments.reacted-*` strings instead; reach for this to change the structure — a different
223
+ * box, avatars, a banner anywhere on screen.
224
+ */
225
+ ReactionTooltip?: ComponentType<ReactionTooltipProps>;
226
+ /** Shown where a composer would sit when the viewer can't compose (see
227
+ * {@link CommentingOptions.canComment} — a signed-out viewer, a viewer role, a host that
228
+ * turns commenting off). `context` says which surface is rendering it: the bottom of an open
229
+ * thread popover (`'thread'`) or the placement popover the comment tool opens (`'pending'`).
230
+ * Unset, those surfaces render nothing. */
231
+ ComposerFallback?: ComponentType<{
232
+ context: 'pending' | 'thread';
233
+ }>;
234
+ }
235
+
236
+ /**
237
+ * The live, host-supplied half of commenting: who the viewer is, how author ids become names, read
238
+ * status, and the mention roster. (The static half is {@link CommentingOptions}, configured once on
239
+ * the tool.)
240
+ *
241
+ * Every commenting surface reads the same set, so a host mounting more than one — `CanvasComments`
242
+ * and `CanvasCommentsSidebar`, say — can build it once and spread it into each rather than
243
+ * repeating the props:
244
+ *
245
+ * ```tsx
246
+ * const commenting: CommentingContext = { currentUserId, resolveAuthor, isCommentUnread }
247
+ *
248
+ * <CanvasComments {...commenting} />
249
+ * <CanvasCommentsSidebar {...commenting} />
250
+ * ```
251
+ *
252
+ * @public
253
+ */
254
+ export declare interface CommentingContext {
255
+ /** The signed-in user's id, or null for a read-only viewer. Only a signed-in user composes. */
256
+ currentUserId: null | string;
257
+ /** Map an author id to their display info, or `undefined` when the id can't be resolved. */
258
+ resolveAuthor(id: string): CommentAuthor | undefined;
259
+ /** Called after any comment (a new thread's first comment, or a reply) is posted. */
260
+ onPostComment?(comment: TLComment): void;
261
+ /** Whether a comment is unread for the current user (return true for unread). */
262
+ isCommentUnread?(commentId: TLCommentId): boolean;
263
+ /**
264
+ * Called for each unread comment shown to the user in an open thread popover, so hosts can
265
+ * record a read receipt. Needs `isCommentUnread` to know what's unread.
266
+ */
267
+ onCommentRead?(commentId: TLCommentId): void;
268
+ /** Resolve the members matching an `@`-query in the composers (sync or async). */
269
+ getMentionSuggestions?(query: string): MentionMember[] | Promise<MentionMember[]>;
270
+ /** Override a mention-picker row's content. */
271
+ renderMentionSuggestion?(member: MentionMember): ReactNode;
272
+ /**
273
+ * The host's URL for a thread, so surfaces can link to it — e.g. the sidebar's rows render as
274
+ * anchors when this is present, letting ctrl/cmd-click and middle-click open the thread in a
275
+ * new tab (a plain click still selects the thread in place; the href isn't followed).
276
+ */
277
+ getThreadHref?(threadId: TLCommentThreadId): string | undefined;
292
278
  }
293
279
 
294
280
  /**
295
281
  * Configuration for the commenting layer. Static config only — pass it once via
296
282
  * `CommentTool.configure({ ... })`, mirroring `ShapeUtil.configure`. Live, reactive values
297
- * (`currentUserId`, `resolveName`, read-status callbacks) stay as props on `<CanvasComments>`.
283
+ * (`currentUserId`, author resolution, read-status callbacks) are the `CommentingContext`, passed as
284
+ * props to each commenting surface.
298
285
  *
299
286
  * For defaults, see {@link defaultCommentingOptions}.
300
287
  *
@@ -321,15 +308,60 @@ export declare interface CommentingOptions {
321
308
  readonly dragHistory: TLHistoryBatchOptions['history'] | undefined;
322
309
  /** Fold nearby pins into count badges as the camera zooms out. */
323
310
  readonly enableClustering: boolean;
311
+ /**
312
+ * Whether a user may hold several emoji reactions on one comment. `true` (the default) is the
313
+ * Slack model: each emoji toggles independently. `false` is single-select: picking a new emoji
314
+ * replaces the user's existing reaction. Note this is enforced client-side; the server accepts
315
+ * per-emoji records either way.
316
+ */
317
+ readonly allowMultipleReactions: boolean;
318
+ /**
319
+ * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji} against
320
+ * the built-in emoji palette, which is what keeps a scripted client from writing junk `emoji`
321
+ * values the picker would never offer. Override it alongside a custom `ReactionPalette` so the
322
+ * tokens that palette emits get through. Removals aren't checked — a reaction carrying an
323
+ * off-palette token must still be clearable.
324
+ */
325
+ isAllowedReaction(token: string): boolean;
326
+ /**
327
+ * Whether dragging the comment tool out creates a region anchor — a comment attached to a
328
+ * rectangular area of the page, drawn as a dashed box with the thread's pin on the corner the
329
+ * drag released on. Off by default: comments attach to points and shapes only, and a drag just
330
+ * trails the composer. A region reveals its box while the pointer is inside it, moves by its
331
+ * pin, and resizes from its corners.
332
+ */
333
+ readonly enableRegions: boolean;
334
+ /**
335
+ * Whether the viewer may participate in commenting: composing new threads and replies, editing
336
+ * and deleting comments, resolving threads, and moving pins or regions. Composers render when
337
+ * it returns true; when it returns false, the {@link CommentingComponents.ComposerFallback}
338
+ * slot renders in their place (or nothing, if that slot is unset) and the action affordances
339
+ * are hidden. Unset, participation is allowed exactly when `currentUserId` is set.
340
+ *
341
+ * Called during render via {@link useCanComment}, so reactive reads (signals) are tracked.
342
+ * The comment tool itself stays registered and selectable — hosts that want its toolbar button
343
+ * to do something else (e.g. open a sign-in dialog) can override the tool item's `onSelect`.
344
+ * Note posting still requires a `currentUserId` to author the records, so a callback that
345
+ * returns true for a signed-out viewer yields a composer whose send button stays disabled.
346
+ */
347
+ readonly canComment: ((ctx: {
348
+ currentUserId: null | string;
349
+ editor: Editor;
350
+ }) => boolean) | undefined;
324
351
  /** Normalized (0–1) spot within a shape where imprecise shape pins sit. Default top-right. */
325
352
  readonly impreciseShapeAnchor: {
326
353
  readonly x: number;
327
354
  readonly y: number;
328
355
  };
329
- /** Screen-pixel margin by which the viewport is inflated when culling cluster badges. */
330
- readonly clusterCullMargin: number;
331
- /** How far past a cluster's split zoom to land when expanding it (1.05 = 5% overshoot). */
332
- readonly clusterSplitZoomFactor: number;
356
+ /**
357
+ * Whether a comment landing on a shape anchors precisely — pinned to the exact clicked spot
358
+ * within the shape or imprecisely pinned to the shape as a whole, rendered at
359
+ * `impreciseShapeAnchor`. Called wherever a shape anchor is created (placing with the comment
360
+ * tool, dropping a dragged pin onto a shape). Always precise by default. Return `false` for
361
+ * shape-level anchoring, or decide from the context — the Alt key's state, or the shape itself,
362
+ * e.g. precise only on notes. Governs new placements only; existing anchors render as stored.
363
+ */
364
+ shouldBePrecise(editor: Editor, context: ShapeCommentPrecisionContext): boolean;
333
365
  /** Component overrides. See {@link CommentingComponents}. */
334
366
  readonly components: CommentingComponents;
335
367
  }
@@ -337,7 +369,7 @@ export declare interface CommentingOptions {
337
369
  /** @public */
338
370
  export declare interface CommentListItemProps {
339
371
  id: string;
340
- author: string;
372
+ author: CommentAuthor;
341
373
  /** A short preview of the thread — e.g. the first comment's body. */
342
374
  preview: ReactNode;
343
375
  /** ISO datetime of the thread's first comment. */
@@ -349,6 +381,11 @@ export declare interface CommentListItemProps {
349
381
  count?: number;
350
382
  /** Whether this thread is the open one. */
351
383
  selected?: boolean;
384
+ /**
385
+ * Link target for the item. When set, the row renders as an anchor so browser affordances
386
+ * (ctrl/cmd-click, middle-click) open it in a new tab; a plain click still calls `onSelect`.
387
+ */
388
+ href?: string;
352
389
  }
353
390
 
354
391
  /** A canvas comment marker: shows its `children` (or a check when resolved). Purely
@@ -366,6 +403,40 @@ export declare interface CommentPinProps {
366
403
  open?: boolean;
367
404
  }
368
405
 
406
+ /**
407
+ * The add-reaction button for one comment. Belongs with the comment card's hover actions (under
408
+ * the ⋯ button) rather than in the reaction row, so opening it doesn't chase the row as reactions
409
+ * are added.
410
+ * @public @react
411
+ */
412
+ export declare function CommentReactionPicker({ comment, currentUserId, emoji }: CommentReactionPickerProps): JSX.Element | null;
413
+
414
+ /** @public */
415
+ export declare interface CommentReactionPickerProps {
416
+ comment: TLComment;
417
+ /** The reacting user. Null/omitted renders nothing — there's nobody to react as. */
418
+ currentUserId?: null | string;
419
+ /** The emoji the picker offers. Defaults to `DEFAULT_REACTION_EMOJI`. */
420
+ emoji?: string[];
421
+ }
422
+
423
+ /**
424
+ * The tallied reaction row under one comment. Pair with `CommentReactionPicker`, which is what
425
+ * adds a reaction.
426
+ * @public @react
427
+ */
428
+ export declare function CommentReactions({ comment, currentUserId, resolveName }: CommentReactionsProps): JSX.Element;
429
+
430
+ /** @public */
431
+ export declare interface CommentReactionsProps {
432
+ comment: TLComment;
433
+ /** The reacting user. Null/omitted gives a read-only row (signed out): counts show, but the
434
+ * pills don't toggle. */
435
+ currentUserId?: null | string;
436
+ /** Names a reactor id for the hover list. Ids it can't name fall back to the id. */
437
+ resolveName?(userId: string): string | undefined;
438
+ }
439
+
369
440
  /** The funnel dropdown in the sidebar header: toggles for which threads the list shows.
370
441
  * @public @react */
371
442
  export declare function CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread }: CommentsFilterMenuProps): JSX.Element;
@@ -424,14 +495,19 @@ export declare function CommentsMenuItem(): JSX.Element;
424
495
  * @public @react */
425
496
  export declare function CommentsOverflowMenu(): JSX.Element;
426
497
 
427
- /** A comment's body, rendered as markdown (bold, italic, code, links, lists). @public @react */
428
- export declare function CommentText({ text }: CommentTextProps): JSX.Element;
498
+ /**
499
+ * Whether the comments sidebar (the thread list) is open. Driven by an explicit control — a button
500
+ * next to Share on dotcom — rather than by which tool is active, so browsing threads is separate
501
+ * from placing them. The comment tool additionally closes it on enter, keeping placement
502
+ * canvas-focused.
503
+ * @public
504
+ */
505
+ export declare const commentsSidebarOpen: EditorAtom<boolean>;
429
506
 
430
- /** @public */
431
- export declare interface CommentTextProps {
432
- /** Rich text body, authored as markdown. */
433
- text: string;
434
- }
507
+ /** The sidebar header's show/hide toggle for comment pins — an eye that closes while comments
508
+ * are hidden. The same state as the Shift+C shortcut.
509
+ * @public @react */
510
+ export declare function CommentsVisibilityToggle(): JSX.Element;
435
511
 
436
512
  /**
437
513
  * A comment thread: an optional header, an optional resolved banner, the comments, and an
@@ -439,7 +515,7 @@ export declare interface CommentTextProps {
439
515
  * composer's handlers; how each comment renders is overridable via `renderComment`.
440
516
  * @public @react
441
517
  */
442
- export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, renderComment }: CommentThreadProps): JSX.Element;
518
+ export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment }: CommentThreadProps): JSX.Element;
443
519
 
444
520
  /** @public */
445
521
  export declare interface CommentThreadProps {
@@ -453,14 +529,19 @@ export declare interface CommentThreadProps {
453
529
  resolvedBanner?: ReactNode;
454
530
  /** Reply composer props. Omit for a read-only thread (no composer). */
455
531
  composer?: CommentComposerProps;
532
+ /** Shown at the bottom of the thread (e.g. a sign-in prompt when there's no composer). When
533
+ * both are set, the footer renders after the composer. */
534
+ footer?: ReactNode;
456
535
  /** Override how each comment renders. Defaults to `<CommentCard>`. */
457
536
  renderComment?(comment: CommentCardProps, index: number): ReactNode;
458
537
  }
459
538
 
460
539
  /**
461
- * The comment tool. A click starts a point/shape thread (the pin tracks the shape it lands on);
462
- * dragging out a rectangle starts a region thread anchored to that area. Placement only opens a
463
- * composer (via `pendingComment`); the records are created when the comment is posted.
540
+ * The comment tool. Pressing down opens the comment composer immediately at the pointer, and it
541
+ * follows the pointer until release — like placing a sticky note settling on a point, or on a
542
+ * shape when released over one. (With region comments enabled, dragging past the threshold instead
543
+ * draws a region rectangle.) Placement only opens a composer (via `pendingComment`); the records
544
+ * are created when the comment is posted.
464
545
  * @public
465
546
  */
466
547
  export declare class CommentTool extends StateNode {
@@ -486,6 +567,7 @@ export declare class CommentTool extends StateNode {
486
567
  */
487
568
  options: CommentingOptions;
488
569
  onEnter(): void;
570
+ onExit(): void;
489
571
  onCancel(): void;
490
572
  }
491
573
 
@@ -497,50 +579,32 @@ export declare const commentToolOverrides: TLUiOverrides;
497
579
  /** @public */
498
580
  export declare const commentTools: typeof CommentTool[];
499
581
 
500
- /**
501
- * Commit a comment mutation with the configured undo/redo behavior. All comment writes go through
502
- * here so the {@link CommentingOptions.history} option (and {@link CommentingOptions.dragHistory}
503
- * for pin re-anchors) governs whether they land on the undo stack. Defaults to `'ignore'`.
504
- * @public
505
- */
506
- export declare function commitCommentMutation<T>(editor: Editor, fn: () => T, kind?: 'drag' | 'mutation'): T;
507
-
508
- /** @public */
509
- export declare function computeClusterTable(leaves: readonly LeafInput[], options: ClusterOptions): ClusterTable;
510
-
511
582
  /** @public @react */
512
- export declare function CountBadge({ count }: CountBadgeProps): JSX.Element;
583
+ export declare function CountBadge({ count, open }: CountBadgeProps): JSX.Element;
513
584
 
514
585
  /** @public */
515
586
  export declare interface CountBadgeProps {
516
587
  count: number;
588
+ /** The badge's list is showing — shows the active/selected indicator state, as an open pin does. */
589
+ open?: boolean;
517
590
  }
518
591
 
519
- /** @public */
520
- export declare function createClusterRuntime(table: ClusterTable): ClusterRuntime;
592
+ export { createMentionSuggestion }
521
593
 
522
594
  /**
523
- * Build the TipTap `suggestion` config for the comment \@-picker. `getSuggestions(query)` is the
524
- * host's resolver it returns the members matching the query (sync or async); the SDK owns neither
525
- * the roster nor the filtering. The plugin runs outside React, so `render` mounts `MentionPopup` via
526
- * a `ReactRenderer`, forwards navigation keys through the popup's imperative handle, and lets it call
527
- * `command` to insert.
595
+ * The reaction emoji palette: the small, opinionated set the picker offers *and* the set a
596
+ * reaction is allowed to use. It's deliberately short a quick affordance under a comment, not a
597
+ * full emoji keyboard — and it's the single source of truth for "which emoji may a reaction
598
+ * carry". A reaction's `emoji` field is a free-form `string`, so this palette is what keeps it to
599
+ * real, offered emoji; check membership with {@link isAllowedReactionEmoji}. Pass a custom `emoji`
600
+ * array to `ReactionPicker` to offer a different set — validate against that same set if you do.
601
+ *
528
602
  * @public
529
603
  */
530
- export declare function createMentionSuggestion(getSuggestions: (query: string) => MentionMember[] | Promise<MentionMember[]>, options?: MentionSuggestionOptions): Omit<SuggestionOptions<MentionMember, MentionNodeAttrs>, 'editor'>;
531
-
532
- /** Where an imprecise shape comment sits by default: the shape's top-right corner. Overridable. @public */
533
- export declare const DEFAULT_IMPRECISE_SHAPE_ANCHOR: {
534
- x: number;
535
- y: number;
536
- };
604
+ export declare const DEFAULT_REACTION_EMOJI: string[];
537
605
 
538
- /** The out-of-the-box region config: disabled, and when a consumer enables it — the current
539
- * bottom-right / pointer-reveal / pin-move / corner-resize behaviour.
606
+ /** The out-of-the-box view: every page's threads, resolved ones hidden until asked for.
540
607
  * @public */
541
- export declare const DEFAULT_REGION_COMMENT_OPTIONS: RegionCommentOptions;
542
-
543
- /** @public */
544
608
  export declare const DEFAULT_SIDEBAR_FILTERS: SidebarFilters;
545
609
 
546
610
  /**
@@ -549,18 +613,120 @@ export declare const DEFAULT_SIDEBAR_FILTERS: SidebarFilters;
549
613
  * @public
550
614
  */
551
615
  export declare const defaultCommentingOptions: {
552
- readonly clusterCullMargin: 120;
553
- readonly clusterSplitZoomFactor: 1.05;
616
+ readonly allowMultipleReactions: true;
617
+ readonly canComment: undefined;
554
618
  readonly components: {};
555
619
  readonly dragHistory: undefined;
556
620
  readonly enableClustering: true;
621
+ readonly enableRegions: false;
557
622
  readonly history: "ignore";
558
623
  readonly impreciseShapeAnchor: {
559
624
  readonly x: 1;
560
625
  readonly y: 0;
561
626
  };
627
+ readonly isAllowedReaction: typeof isAllowedReactionEmoji;
628
+ readonly shouldBePrecise: () => true;
562
629
  };
563
630
 
631
+ /**
632
+ * The default reactor tooltip: hangs the built-in inline sentence (`DefaultReactionTooltipContent`)
633
+ * in a standard tooltip below the pill — the same tooltip the toolbar uses. Replace it via
634
+ * `CommentingComponents.ReactionTooltip` to present the reactor list any other way (a different box,
635
+ * avatars, a banner, anywhere on screen); it receives the pill as `children`.
636
+ * @public @react
637
+ */
638
+ export declare function DefaultReactionTooltip({ reactors, children }: ReactionTooltipProps): JSX.Element;
639
+
640
+ /**
641
+ * The default reactor sentence naming who reacted — up to three names spelled out, then "and N
642
+ * others" (e.g. "You reacted", "You and Bo reacted", "You, Bo and Ada reacted", "You, Bo, Ada and 2
643
+ * others reacted"). The wording lives in the `comments.reacted-*` translation strings so each locale
644
+ * controls the grammar. Exported so a custom `ReactionTooltip` can reuse it inside its own box.
645
+ * @public @react
646
+ */
647
+ export declare function DefaultReactionTooltipContent({ reactors }: {
648
+ reactors: ReactionReactor[];
649
+ }): JSX.Element | null;
650
+
651
+ /** The default reaction renderer: emits the token string for the OS emoji font to draw. @public */
652
+ export declare function defaultRenderReaction(token: string): ReactNode;
653
+
654
+ /**
655
+ * Delete a comment.
656
+ *
657
+ * This is a soft delete: it sets the record's `isDeleted` flag rather than removing it, and the
658
+ * server prunes the comment and its reactions once the flag is persisted. That way no client ever
659
+ * removes records it doesn't own — a reaction belongs to whoever left it — and a server enforcing
660
+ * per-record permissions has a write it can check rather than a deletion it can only refuse.
661
+ * Deleting is the author's to do; the built-in UI only offers it on your own comments.
662
+ *
663
+ * The write is never undoable, whatever {@link CommentingOptions.history} says: the flag is
664
+ * write-once server-side, so an undo clearing it would be vetoed and rebased rather than bring the
665
+ * comment back.
666
+ *
667
+ * Deleting a thread's last comment leaves the thread with no surface — an empty thread renders
668
+ * nothing — and closes it if it's open. The thread record is left for the server to prune, since
669
+ * the deleter may not be its creator.
670
+ *
671
+ * A comment that's already deleted, or already pruned, is a no-op.
672
+ *
673
+ * @public
674
+ */
675
+ export declare function deleteComment(editor: Editor, comment: TLComment): void;
676
+
677
+ /**
678
+ * Delete a thread and, with it, the whole conversation.
679
+ *
680
+ * A soft delete on the same model as {@link deleteComment}: the flag goes on the thread record and
681
+ * the server prunes the thread, its comments, and their reactions once it's persisted. Deleting a
682
+ * thread is its creator's to do — a server enforcing per-record permissions vetoes anyone else —
683
+ * and the write is never undoable.
684
+ *
685
+ * Closes the thread if it's the open one. A thread that's already pruned is a no-op.
686
+ *
687
+ * @public
688
+ */
689
+ export declare function deleteThread(editor: Editor, thread: TLCommentThread): void;
690
+
691
+ /**
692
+ * Replace a comment's body and stamp it as edited, which is what renders the "(edited)" marker on
693
+ * its byline.
694
+ *
695
+ * Editing is the author's to do. The built-in UI only offers it on your own comments, and a server
696
+ * that enforces per-record permissions rejects anyone else's edit.
697
+ *
698
+ * The `comment` you pass says which comment to edit; the body lands on the version the store
699
+ * currently holds, so a copy you've held on to can't revert a change made since it, and can't
700
+ * re-create a comment that's already been removed — editing one of those does nothing.
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * editComment(editor, comment, toRichText('Actually, make it dashed'))
705
+ * ```
706
+ *
707
+ * @public
708
+ */
709
+ export declare function editComment(editor: Editor, comment: TLComment, body: TLRichText): void;
710
+
711
+ /**
712
+ * A grid of emoji to react with. Presentational and unpositioned — the host owns placement and
713
+ * dismissal; `ReactionPicker` renders it in a dropdown under the add-reaction button.
714
+ * @public @react
715
+ */
716
+ export declare function EmojiPicker({ emoji, selected, onSelect, renderReaction }: EmojiPickerProps): JSX.Element;
717
+
718
+ /** @public */
719
+ export declare interface EmojiPickerProps {
720
+ /** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */
721
+ emoji?: string[];
722
+ /** Emoji the current user has already reacted with; shown as pressed. */
723
+ selected?: string[];
724
+ /** Called when an emoji is chosen. */
725
+ onSelect?(emoji: string): void;
726
+ /** How to draw each emoji token. Defaults to the token string (OS emoji font). */
727
+ renderReaction?: RenderReaction;
728
+ }
729
+
564
730
  /** The empty state shown when a thread has no comments yet. @public @react */
565
731
  export declare function EmptyState({ message }: EmptyStateProps): JSX.Element;
566
732
 
@@ -569,23 +735,30 @@ export declare interface EmptyStateProps {
569
735
  message: string;
570
736
  }
571
737
 
572
- /** Members whose name contains the query (case-insensitive), capped to the popup's length.
573
- * @public */
574
- export declare function filterMentionMembers(members: MentionMember[], query: string): MentionMember[];
738
+ export { filterMentionMembers }
575
739
 
576
740
  /** Open a thread and bring it into view — switch to its page if needed, then center its pin. @public */
577
- export declare function focusThread(editor: Editor, thread: TLCommentThread, impreciseShapeAnchor?: {
578
- x: number;
579
- y: number;
580
- }): void;
741
+ export declare function focusThread(editor: Editor, thread: TLCommentThread): void;
581
742
 
582
743
  /**
583
- * Format an ISO datetime as relative time ("2 hours ago", "yesterday", "last week").
744
+ * Format an ISO datetime as short relative time ("2 hr. ago", "yesterday", "last wk.").
584
745
  * Locale-aware via Intl.RelativeTimeFormat.
585
746
  * @public
586
747
  */
587
748
  export declare function formatRelativeTime(iso: string, locale?: string): string;
588
749
 
750
+ /**
751
+ * Whether the viewer may participate in commenting, per {@link CommentingOptions.canComment}
752
+ * (defaulting to `currentUserId != null` when unset). Where this is false, composers give way to
753
+ * the {@link CommentingComponents.ComposerFallback} slot and action affordances are hidden.
754
+ *
755
+ * This is a plain, untracked read — a `canComment` callback that reads signals is not observed.
756
+ * In React, use {@link useCanComment} instead.
757
+ *
758
+ * @public
759
+ */
760
+ export declare function getCanComment(editor: Editor, currentUserId: null | string | undefined): boolean;
761
+
589
762
  /**
590
763
  * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which
591
764
  * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when
@@ -596,161 +769,328 @@ export declare function formatRelativeTime(iso: string, locale?: string): string
596
769
  */
597
770
  export declare function getCommentingOptions(editor: Editor): CommentingOptions;
598
771
 
772
+ /** All comment reactions currently in the store (non-reactive; wrap in `useValue` to react). @public */
773
+ export declare function getCommentReactions(editor: Editor): TLCommentReaction[];
774
+
599
775
  /** Read one comment record by id, or `undefined` if the id isn't a present comment record. @public */
600
776
  export declare function getCommentRecord(editor: Editor, id: string): TLCommentRecord | undefined;
601
777
 
602
- /** All comments currently in the store (non-reactive; wrap in `useValue` to react). @public */
778
+ /**
779
+ * Every comment in the store, **including soft-deleted ones** awaiting the server's prune. For the
780
+ * set the UI shows, use {@link getLiveComments}.
781
+ *
782
+ * Non-reactive; wrap in `useValue`, or use `useComments`, to react.
783
+ * @public
784
+ */
603
785
  export declare function getComments(editor: Editor): TLComment[];
604
786
 
605
- /** All comment threads currently in the store (non-reactive; wrap in `useValue` to react). @public */
787
+ /**
788
+ * Every comment thread in the store, **including soft-deleted ones** and ones left empty by their
789
+ * last comment's delete — records that are still present but awaiting the server's prune, and that
790
+ * nothing renders. For the set the UI shows, use {@link getLiveCommentThreads}.
791
+ *
792
+ * Non-reactive; wrap in `useValue`, or use `useCommentThreads`, to react.
793
+ * @public
794
+ */
606
795
  export declare function getCommentThreads(editor: Editor): TLCommentThread[];
607
796
 
608
797
  /**
609
- * One comment thread's pin, already resolved to a page-space anchor point.
798
+ * The comments that should render: not soft-deleted. A deleted record lingers in the store until
799
+ * the server prunes it, so this — not {@link getComments} — is what a count or a list of your own
800
+ * should be built from.
801
+ *
802
+ * Non-reactive; the reactive equivalent is `useComments` (which also sorts oldest first).
610
803
  * @public
611
804
  */
612
- export declare interface LeafInput {
613
- /** Unique. Thread id. Uniqueness is a precondition — throw on duplicates. */
614
- id: string;
615
- /** Page-space coordinates. Must be finite — throw on NaN/Infinity. */
616
- point: VecLike;
617
- }
618
-
619
- /** An \@-mention pill for referencing a person in a comment. @public @react */
620
- export declare function Mention({ name }: MentionProps): JSX.Element;
805
+ export declare function getLiveComments(editor: Editor): TLComment[];
621
806
 
622
807
  /**
623
- * The \@-mention member picker: a popover list of people to mention, shown as the user types after
624
- * `@`. Presentational the composer's suggestion plugin owns resolution, keyboard navigation, and
625
- * placement, and drives this with the host-resolved `members` and the highlighted `activeIndex`.
626
- * @public @react
808
+ * The comment threads that should render (pins, sidebar): live not soft-deleted and still
809
+ * holding at least one live comment. A soft-deleted thread or comment is awaiting the server's
810
+ * prune, as is a thread emptied by its last comment's delete; until the prune lands, the emptied
811
+ * thread record lingers with no surface.
812
+ *
813
+ * Non-reactive; the reactive equivalent is `useCommentThreads`.
814
+ * @public
627
815
  */
628
- export declare function MentionList({ members, activeIndex, onSelect, emptyLabel, renderMember }: MentionListProps): JSX.Element;
816
+ export declare function getLiveCommentThreads(editor: Editor): TLCommentThread[];
629
817
 
630
- /** @public */
631
- export declare interface MentionListProps {
632
- /** Members to choose from already resolved for the current query by the host. */
633
- members: MentionMember[];
634
- /** Index of the highlighted row, driven by the composer's keyboard navigation. */
635
- activeIndex?: number;
636
- /** Called when a member is chosen (click, or Enter on the active row). */
637
- onSelect?(member: MentionMember): void;
638
- /** Shown when no member matches the query. Defaults to a translated "No matches". */
639
- emptyLabel?: string;
640
- /** Override a row's content (inside the selectable button). Defaults to avatar + name (+ secondary). */
641
- renderMember?(member: MentionMember): ReactNode;
642
- }
643
-
644
- /** A person the composer's \@-mention picker can offer. @public */
645
- export declare interface MentionMember {
646
- id: string;
647
- name: string;
648
- /** Avatar image URL. Falls back to a coloured initial when omitted. */
649
- avatar?: string;
650
- /** Avatar background colour, used when there's no `avatar` image. */
651
- color?: string;
652
- /** A secondary line under the name — e.g. an email or handle. Omit for a single-line row. */
653
- secondary?: string;
654
- /** Marks the current user; shown as a "(You)" suffix after the name. */
655
- you?: boolean;
656
- /** Hosts may carry extra fields; a custom `renderMember` can read them. */
657
- [key: string]: unknown;
658
- }
659
-
660
- /** @public */
661
- export declare interface MentionProps {
662
- name: string;
663
- }
818
+ /**
819
+ * The id passed to the most recent {@link revealThread} call that `CanvasComments` hasn't served
820
+ * yet, or null when there's nothing outstanding. A request also clears when `CanvasComments`
821
+ * unmounts, since nothing is left to serve it.
822
+ *
823
+ * This is a plain, untracked read. In React, use {@link useRevealThreadPending} — but reach for
824
+ * this one inside a timer or callback that needs the value as of *now* rather than as of the
825
+ * render it closed over.
826
+ *
827
+ * @public
828
+ */
829
+ export declare function getRevealThreadPending(editor: Editor): null | string;
664
830
 
665
- /** @public */
666
- export declare interface MentionSuggestionOptions {
667
- /** Override a member row's content in the picker. Defaults to avatar + name (+ secondary). */
668
- renderMember?(member: MentionMember): ReactNode;
669
- /**
670
- * The tldraw editor the composer lives in. When provided, the popup re-anchors reactively as the
671
- * canvas camera moves (the composer rides it) instead of polling every frame. Omit off-canvas.
672
- */
673
- editor?: null | Editor;
674
- }
831
+ /**
832
+ * Whether `emoji` belongs to a reaction palette (defaults to {@link DEFAULT_REACTION_EMOJI}). Use
833
+ * it to reject arbitrary strings before writing a reaction, so a scripted client can't spam a
834
+ * comment with junk `emoji` values that the picker would never offer. Pass a custom `palette` to
835
+ * match a customized picker.
836
+ *
837
+ * @public
838
+ */
839
+ export declare function isAllowedReactionEmoji(emoji: string, palette?: readonly string[]): boolean;
675
840
 
676
841
  /**
677
- * A finalized merge event, ready for the runtime.
842
+ * Whether a click should be left to the browser's link handling (new tab, new window, or download).
678
843
  * @public
679
844
  */
680
- export declare interface MergeEvent {
681
- zMerge: number;
682
- /** Reverses (splits) when zoom \>= zSplit. Always \> zMerge. May be +Infinity. */
683
- zSplit: number;
684
- children: ClusterNode[];
685
- result: ClusterNode;
686
- }
845
+ export declare function isOpenInNewTabClick(e: MouseEvent_2): boolean;
846
+
847
+ export { Mention }
848
+
849
+ export { MentionList }
850
+
851
+ export { MentionListProps }
852
+
853
+ export { MentionMember }
854
+
855
+ export { MentionProps }
856
+
857
+ export { MentionSuggestionOptions }
687
858
 
688
859
  /** The id of the one open thread (only one popover is open at a time), or null when all closed.
689
860
  * @public */
690
861
  export declare const openThreadId: EditorAtom<null | string>;
691
862
 
692
- /** A comment being placed but not yet posted: where its composer sits and what it will anchor
693
- * to. Shared between the tool (which sets it on click) and the overlay (which renders the
694
- * composer). Null when nothing is being placed. The atom itself lives in `./state`
695
- * ({@link pendingComment}), scoped per editor.
696
- * @public */
697
- export declare interface PendingComment {
698
- anchor: TLCommentAnchor;
699
- /** Page point where the composer opens (the click location, or a region's pin corner). */
700
- point: VecLike;
701
- }
863
+ /**
864
+ * Write comment records to the store, under the configured
865
+ * {@link CommentingOptions.history} behavior so a record you write lands on the undo stack (or
866
+ * doesn't) exactly like one the built-in UI writes. Defaults to `'ignore'`.
867
+ *
868
+ * Use it to seed or import threads, and to save an edit. To delete, prefer
869
+ * {@link deleteComment} and {@link deleteThread} over {@link removeCommentRecords}: comments are
870
+ * soft-deleted, and a synced server rejects the hard delete.
871
+ *
872
+ * @public
873
+ */
874
+ export declare function putCommentRecords(editor: Editor, records: TLCommentRecord[]): void;
702
875
 
703
- /** The comment currently being placed (composer open, not yet posted), or null.
704
- * @public */
705
- export declare const pendingComment: EditorAtom<null | PendingComment>;
876
+ /**
877
+ * A single emoji reaction pill with a count, highlighted when the user reacted. Hovering it shows a
878
+ * tooltip naming who reacted (the current user included), when `enableHoverList` is set.
879
+ * @public @react
880
+ */
881
+ export declare function Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick }: ReactionProps): JSX.Element;
706
882
 
707
- /** Write comment records to the store. @public */
708
- export declare function putCommentRecords(editor: Editor, records: TLCommentRecord[]): void;
883
+ /**
884
+ * The add-reaction affordance: a smiley button that opens an emoji grid.
885
+ *
886
+ * Anchored to its own button rather than to the reactions row, so the menu keeps its position as
887
+ * reactions are added and the row reflows.
888
+ *
889
+ * Picking an emoji dismisses the grid — adding and removing alike, since either way the picker has
890
+ * done its job — and hands focus back to the trigger.
891
+ * @public @react
892
+ */
893
+ export declare function ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className }: ReactionPickerProps): JSX.Element;
709
894
 
710
- /** A single emoji reaction pill with a count, highlighted when the user reacted. @public @react */
711
- export declare function Reaction({ emoji, count, active }: ReactionProps): JSX.Element;
895
+ /** @public */
896
+ export declare interface ReactionPickerProps {
897
+ /** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */
898
+ emoji?: string[];
899
+ /** Emoji the current user has already reacted with; shown as pressed in the grid. */
900
+ selected?: string[];
901
+ /** Called when an emoji is chosen. */
902
+ onSelect?(emoji: string): void;
903
+ /** How to draw each emoji token. Defaults to the token string (OS emoji font). */
904
+ renderReaction?: RenderReaction;
905
+ /**
906
+ * What the button opens — the thing that produces a token. Defaults to `EmojiPicker`, the grid of
907
+ * emoji. Swap it for any component taking the same props to offer something else entirely (the
908
+ * drawn reactions example replaces it with a canvas you draw in); the props are passed straight
909
+ * through either way.
910
+ */
911
+ palette?: ComponentType<EmojiPickerProps>;
912
+ /**
913
+ * Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.
914
+ * A thread renders one picker per comment, so this must differ per comment — sharing an id
915
+ * makes every picker in the thread open at once. Defaults to a per-instance generated id;
916
+ * pass one only for a stabler, more debuggable value.
917
+ */
918
+ menuId?: string;
919
+ /** Class for the trigger button. Defaults to the card-action style, matching the ⋯ button. */
920
+ className?: string;
921
+ }
712
922
 
713
923
  /** @public */
714
924
  export declare interface ReactionProps {
715
925
  emoji: string;
716
926
  count: number;
717
927
  active: boolean;
928
+ /** How to draw the emoji token. Defaults to the token string (OS emoji font). */
929
+ renderReaction?: RenderReaction;
930
+ /** Who reacted with this emoji, in reaction order — shown when the pill is hovered. */
931
+ reactors: ReactionReactor[];
932
+ /** Whether hovering shows the reactor list. Pass false to suppress it — e.g. while another
933
+ * popup menu on the comment is open. Defaults to true. */
934
+ enableHoverList?: boolean;
935
+ /** The hover affordance for the pill, naming who reacted. It receives the reactors and the pill
936
+ * itself (as `children`) and returns the whole thing — so it owns the tooltip, its box, size,
937
+ * shape, and position, not just the text inside. Defaults to `DefaultReactionTooltip`, which
938
+ * hangs the built-in inline sentence below the pill. */
939
+ ReactionTooltip?: ComponentType<ReactionTooltipProps>;
940
+ /** Called when the pill is pressed — toggles the current user's reaction. */
941
+ onClick?(): void;
942
+ }
943
+
944
+ /** One person who reacted with a given emoji, for the hover list. @public */
945
+ export declare interface ReactionReactor {
946
+ /** Display name of the person who reacted. */
947
+ name: string;
948
+ /** True for the current user. */
949
+ you: boolean;
950
+ }
951
+
952
+ /**
953
+ * The row of tallied reactions under a comment. Presentational — the host supplies the summaries
954
+ * and owns what toggling does. Hovering a pill lists who reacted (see `Reaction`).
955
+ *
956
+ * The add-reaction affordance is a separate component (`ReactionPicker`) so it can live outside
957
+ * this row — on a comment card it sits with the card's hover actions, which keeps its position
958
+ * fixed as reactions are added here.
959
+ * @public @react
960
+ */
961
+ export declare function Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip }: ReactionsProps): JSX.Element | null;
962
+
963
+ /** @public */
964
+ export declare interface ReactionsProps {
965
+ /** The comment's reactions, grouped by emoji. Empty renders nothing. */
966
+ reactions: ReactionSummary[];
967
+ /** Toggles the current user's reaction for an emoji. */
968
+ onToggle?(emoji: string): void;
969
+ /** Whether the current user may react. False makes the pills inert (e.g. a signed-out
970
+ * viewer, or a read-only thread) while still showing counts. */
971
+ canReact?: boolean;
972
+ /** Whether hovering a pill shows its reactor list. Pass false to suppress it — e.g. while
973
+ * another popup menu on the comment is open. Defaults to true. */
974
+ enableHoverList?: boolean;
975
+ /** How to draw each emoji token. Defaults to the token string (OS emoji font). */
976
+ renderReaction?: RenderReaction;
977
+ /** The tooltip naming who reacted, shown when a pill is hovered. Defaults to an inline sentence
978
+ * (`DefaultReactionTooltip`). Swap it to render the reactor list however you like. */
979
+ ReactionTooltip?: ComponentType<ReactionTooltipProps>;
980
+ }
981
+
982
+ /** One emoji's reactions on a comment, already tallied. @public */
983
+ export declare interface ReactionSummary {
984
+ emoji: string;
985
+ /** How many people reacted with this emoji. */
986
+ count: number;
987
+ /** True when the current user is one of them — renders the pill highlighted. */
988
+ active: boolean;
989
+ /** Who reacted with this emoji, in reaction order — shown in the hover list. */
990
+ reactors: ReactionReactor[];
718
991
  }
719
992
 
720
- /** The row of reactions under a comment, plus an add-reaction button. @public @react */
721
- export declare function Reactions(): JSX.Element;
993
+ /** @public */
994
+ export declare interface ReactionTooltipProps {
995
+ /** Who reacted with this emoji, in reaction order. */
996
+ reactors: ReactionReactor[];
997
+ /** The reaction pill to wrap. Anchor the hover affordance to this and render it. */
998
+ children: ReactNode;
999
+ }
722
1000
 
723
1001
  /**
724
- * The configurable dimensions of region comments a design surface for prototyping the interaction
725
- * before settling it. Region is **off by default**, so a consumer that leaves it unset keeps plain
726
- * click-only point/shape commenting. Set it per editor through `<CanvasComments regionOptions>`.
1002
+ * Keep shape-anchored threads alive across their shape's lifecycle:
1003
+ *
1004
+ * - When the shape is deleted, the thread converts to a `point` anchor at the spot its pin last
1005
+ * occupied, so the conversation outlives the shape instead of becoming invisible (a missing
1006
+ * shape has no bounds, which hides the pin).
1007
+ * - When the shape moves to another page, the thread follows: its `pageId` and each comment's
1008
+ * denormalized `pageId` update to the shape's new page, and the anchor keeps riding the shape.
1009
+ * - When a deleted shape comes back (a page move re-creates it with its id preserved; undoing a
1010
+ * delete restores it), the thread re-attaches — unless its pin was manually moved in the
1011
+ * meantime, in which case the manual placement wins.
1012
+ *
1013
+ * A page move is `deleteShapes` + re-create with preserved ids inside one `editor.run`, but each
1014
+ * store write is its own operation — so at no single moment does "this shape is being moved"
1015
+ * exist as an observable event. The handlers therefore cooperate across operations:
1016
+ * `beforeDelete` snapshots where each affected pin sits (every record is still in the store at
1017
+ * that point, so page bounds resolve even for children of a deleted frame); the
1018
+ * operation-complete pass converts threads whose shape is really gone, remembering the original
1019
+ * anchor; `afterCreate` notes a reappeared shape id, and the operation-complete pass restores
1020
+ * the anchor once the store has settled (creation order within an operation is arbitrary, so a
1021
+ * fresh shape may not resolve an ancestor page mid-operation). Undo/redo of a move
1022
+ * replays as a `parentId` update (remove + add of one id squash to an update in the history
1023
+ * diff), so an `afterChange` handler re-homes threads on cross-page reparents — including
1024
+ * threads anchored to descendants, which move with their parent without change events of their
1025
+ * own.
1026
+ *
1027
+ * Remote changes are ignored: the client that performed the operation runs this same
1028
+ * maintenance and syncs the resulting thread updates. Writes honour the
1029
+ * {@link CommentingOptions.history} option, so the consumer decides whether they're undoable.
1030
+ *
1031
+ * Registered by `CanvasComments` on mount; parts-built consumers can call this directly.
1032
+ * Returns a cleanup function that unregisters all handlers.
1033
+ *
727
1034
  * @public
728
1035
  */
729
- export declare interface RegionCommentOptions {
730
- /** Whether dragging the comment tool out creates a region anchor. Off → click-only. */
731
- enabled: boolean;
732
- /** Which corner the pin and composer sit on, as a normalized 0–1 offset. Default bottom-right. */
733
- pinCorner: VecLike;
734
- /** When the dashed box and its handles reveal: while the pointer is within the region, while the
735
- * pin is hovered, or only while the thread is open. */
736
- reveal: 'open' | 'pin-hover' | 'pointer';
737
- /** How a region is moved: dragging its pin, dragging its body, or either. */
738
- move: 'body' | 'both' | 'pin';
739
- /** The resize affordance: corner handles, edge handles, or none. */
740
- resize: 'corners' | 'edges' | 'none';
741
- }
1036
+ export declare function registerCommentAnchorLifecycle(editor: Editor): () => void;
742
1037
 
743
- /** Remove comment records from the store by id. @public */
744
- export declare function removeCommentRecords(editor: Editor, ids: (TLCommentId | TLCommentThreadId)[]): void;
1038
+ /**
1039
+ * Remove comment records from the store by id, under the configured
1040
+ * {@link CommentingOptions.history} behavior.
1041
+ *
1042
+ * This is a hard delete, which is rarely what you want for a comment or a thread: the built-in UI
1043
+ * soft-deletes them ({@link deleteComment}, {@link deleteThread}) so the server can prune the
1044
+ * records — including reactions, which belong to whoever left them rather than to the deleter. A
1045
+ * server that enforces per-record permissions will veto a hard delete outright. Reach for this on
1046
+ * a local, unsynced comment store, or to drop a reaction (which is a hard delete — see
1047
+ * {@link toggleCommentReaction}).
1048
+ *
1049
+ * @public
1050
+ */
1051
+ export declare function removeCommentRecords(editor: Editor, ids: (TLCommentId | TLCommentReactionId | TLCommentThreadId)[]): void;
1052
+
1053
+ /** Render a reaction token to its visual — the emoji glyph by default. @public */
1054
+ export declare type RenderReaction = (token: string) => ReactNode;
745
1055
 
746
1056
  /**
747
- * Minimal markdown for comment bodies: paragraphs, line breaks, bullet lists, and
748
- * inline **bold**, *italic*, `code`, and [links](url). Not a full CommonMark parser
749
- * just the markdown people actually write in comments. Renders React elements only —
750
- * never raw HTML — so there's no HTML-injection surface.
1057
+ * Reopen a resolved thread, clearing the resolution. A no-op on a thread that isn't resolved, and
1058
+ * on one that's gone. Like {@link resolveThread}, it touches only the resolution.
1059
+ *
1060
+ * @public
1061
+ */
1062
+ export declare function reopenThread(editor: Editor, thread: TLCommentThread): void;
1063
+
1064
+ /**
1065
+ * Mark a thread resolved, stamping who resolved it and when. Resolved threads keep their pin (a
1066
+ * checked one) and are hidden from the sidebar until its "show resolved" filter is on.
1067
+ *
1068
+ * Only the resolution is written: the rest of the thread is read fresh, so resolving with a stale
1069
+ * copy in hand won't drag a pin back to where it used to be. A no-op on a thread that's gone.
1070
+ *
1071
+ * @public
1072
+ */
1073
+ export declare function resolveThread(editor: Editor, thread: TLCommentThread, userId: string): void;
1074
+
1075
+ /**
1076
+ * Open a thread and bring it into view, given a thread id or the id of any comment in it. Use it
1077
+ * to jump to a thread from outside the canvas — a notification, a deep link, your own list.
1078
+ *
1079
+ * The request is served by `CanvasComments`, so it works before the records have arrived: the
1080
+ * layer waits for them to sync in, switches pages if it needs to, unhides pins, zooms in far
1081
+ * enough to split the thread out of any cluster it's folded into, and then opens it. That also
1082
+ * means nothing happens if `CanvasComments` isn't mounted.
1083
+ *
1084
+ * To open a thread you already hold and skip the wait, see {@link focusThread}.
1085
+ *
1086
+ * @example
1087
+ * ```ts
1088
+ * revealThread(editor, new URLSearchParams(location.search).get('comment')!)
1089
+ * ```
1090
+ *
751
1091
  * @public
752
1092
  */
753
- export declare function renderMarkdown(text: string): ReactNode;
1093
+ export declare function revealThread(editor: Editor, threadOrCommentId: string): void;
754
1094
 
755
1095
  /**
756
1096
  * Flatten a rich-text comment body to plaintext through the limited comment extension set,
@@ -760,11 +1100,12 @@ export declare function renderMarkdown(text: string): ReactNode;
760
1100
  */
761
1101
  export declare function richTextToPlaintext(body: TLRichText, resolveName?: (id: string) => string | undefined): string;
762
1102
 
763
- /** The button that posts a comment. @public @react */
1103
+ /** The button that posts a comment — an up arrow. @public @react */
764
1104
  export declare function SendButton({ label, disabled, onClick }: SendButtonProps): JSX.Element;
765
1105
 
766
1106
  /** @public */
767
1107
  export declare interface SendButtonProps {
1108
+ /** The button's accessible name (e.g. "Send"). Shown as an up-arrow icon, so this is its label. */
768
1109
  label: string;
769
1110
  disabled?: boolean;
770
1111
  onClick?(): void;
@@ -772,8 +1113,11 @@ export declare interface SendButtonProps {
772
1113
 
773
1114
  /**
774
1115
  * A shape anchor for a page point. `x`/`y` are the point's normalized (0–1) offset within the
775
- * shape's page bounds, remembered either way. When `precise` (Alt held) the pin sits at exactly
776
- * `x`/`y`; otherwise it sits at the consumer's imprecise default (top-right out of the box).
1116
+ * shape's own bounds taken in the shape's own space, so a pin placed on a rotated shape records
1117
+ * the spot it was dropped on rather than a spot in the bounding box. Remembered either way: when
1118
+ * `precise` the pin sits at exactly `x`/`y`; otherwise it sits at the consumer's imprecise default
1119
+ * (top-right out of the box). Placement gestures get `precise` from the `shouldBePrecise`
1120
+ * commenting option (always precise, by default).
777
1121
  * @public
778
1122
  */
779
1123
  export declare function shapeAnchorAt(editor: Editor, shapeId: TLShapeId, page: {
@@ -781,6 +1125,19 @@ export declare function shapeAnchorAt(editor: Editor, shapeId: TLShapeId, page:
781
1125
  y: number;
782
1126
  }, precise: boolean): TLCommentAnchor;
783
1127
 
1128
+ /**
1129
+ * The gesture that's creating a shape anchor, passed to
1130
+ * {@link CommentingOptions.shouldBePrecise}: the target shape, the page point of the release, and
1131
+ * whether Alt was held.
1132
+ *
1133
+ * @public
1134
+ */
1135
+ export declare interface ShapeCommentPrecisionContext {
1136
+ readonly shapeId: TLShapeId;
1137
+ readonly point: VecLike;
1138
+ readonly altKey: boolean;
1139
+ }
1140
+
784
1141
  /**
785
1142
  * Which threads the comments sidebar shows. Held as an editor-scoped signal (the `sidebarFilters`
786
1143
  * `EditorAtom` in `./state`) rather than component state so it survives the sidebar
@@ -804,7 +1161,18 @@ export declare interface SidebarFilters {
804
1161
  export declare const sidebarFilters: EditorAtom<SidebarFilters>;
805
1162
 
806
1163
  /**
807
- * Typed access to comment records on the editor store.
1164
+ * Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used
1165
+ * so the row stays stable as later reactions arrive. `active` marks the emoji the current user
1166
+ * reacted with (the pills render those highlighted), and `reactors` lists who reacted with it, in
1167
+ * reaction order, for the hover list. `resolveName` names each reactor; an id it can't name falls
1168
+ * back to the id itself.
1169
+ *
1170
+ * @public
1171
+ */
1172
+ export declare function summarizeReactions(reactions: TLCommentReaction[], currentUserId?: null | string, resolveName?: (userId: string) => string | undefined): ReactionSummary[];
1173
+
1174
+ /**
1175
+ * Typed reads of comment records on the editor store.
808
1176
  *
809
1177
  * Comment threads and comments live on the editor's local store so the canvas can render them
810
1178
  * reactively, but they are opt-in records that aren't part of the `TLRecord` union (they ride the
@@ -812,14 +1180,49 @@ export declare const sidebarFilters: EditorAtom<SidebarFilters>;
812
1180
  * statically typed `Store<TLRecord>` and doesn't know about them, so every access has to reinterpret
813
1181
  * the type. These helpers own that reinterpretation — an `unknown` hop to exactly the type the store
814
1182
  * expects, so the rest of each call stays checked — behind one boundary, and keep call sites typed.
1183
+ *
1184
+ * Writes do the same reinterpretation, but they also answer to the undo/redo policy, so they live
1185
+ * with the rest of the write path in `comment-mutations.ts`.
815
1186
  */
816
- /** A record that lives in a comment thread: the thread itself or one of its messages. @public */
817
- export declare type TLCommentRecord = TLComment | TLCommentThread;
1187
+ /**
1188
+ * A record that lives in a comment thread: the thread itself, one of its messages, or a reaction
1189
+ * to one of those messages.
1190
+ * @public
1191
+ */
1192
+ export declare type TLCommentRecord = TLComment | TLCommentReaction | TLCommentThread;
1193
+
1194
+ /**
1195
+ * Toggle one user's reaction with a given emoji on a comment.
1196
+ *
1197
+ * Each reaction is its own record keyed by (comment, user, emoji), so this only ever touches that
1198
+ * one user's own records — two people reacting at once never conflict. Behaviour depends on the
1199
+ * `allowMultipleReactions` option:
1200
+ *
1201
+ * - **multiple** (default): the emoji toggles independently — add it if absent, remove it if
1202
+ * present, leaving the user's other reactions alone.
1203
+ * - **single**: picking a new emoji first removes the user's existing reaction(s) on the comment,
1204
+ * then adds this one (a replace); picking the one they already have removes it.
1205
+ *
1206
+ * @public
1207
+ */
1208
+ export declare function toggleCommentReaction(editor: Editor, comment: TLComment, userId: string, emoji: string, now?: number): void;
818
1209
 
819
1210
  /** Toggle comment-pin visibility for an editor.
820
1211
  * @public */
821
1212
  export declare function toggleCommentsHidden(editor: Editor): void;
822
1213
 
1214
+ /** Open or close the comments sidebar for an editor.
1215
+ * @public */
1216
+ export declare function toggleCommentsSidebar(editor: Editor): void;
1217
+
1218
+ /**
1219
+ * Reactive React hook for {@link getCanComment}: a `canComment` callback that reads signals
1220
+ * re-evaluates when they change.
1221
+ *
1222
+ * @public
1223
+ */
1224
+ export declare function useCanComment(currentUserId: null | string | undefined): boolean;
1225
+
823
1226
  /**
824
1227
  * Whether commenting is licensed for this editor. Enabled in development; in production it requires
825
1228
  * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants
@@ -840,29 +1243,56 @@ export declare function useCommentingEnabled(): boolean;
840
1243
  */
841
1244
  export declare function useCommentingOptions(): CommentingOptions;
842
1245
 
843
- /** Every comment in the store, oldest first, reactively. Group by `threadId` for per-thread lists. @public */
1246
+ /**
1247
+ * One comment's reactions, oldest first, reactively.
1248
+ *
1249
+ * @public
1250
+ */
1251
+ export declare function useCommentReactions(editor: Editor, commentId: TLComment['id']): TLCommentReaction[];
1252
+
1253
+ /** Every live comment in the store ({@link getLiveComments}), oldest first, reactively. Group by
1254
+ * `threadId` for per-thread lists. @public */
844
1255
  export declare function useComments(editor: Editor): TLComment[];
845
1256
 
846
1257
  /** React hook for whether comment pins are hidden.
847
1258
  * @public */
848
1259
  export declare function useCommentsHidden(): boolean;
849
1260
 
850
- /** All comment threads in the store, reactively. @public */
1261
+ /** React hook for whether the comments sidebar is open.
1262
+ * @public */
1263
+ export declare function useCommentsSidebarOpen(): boolean;
1264
+
1265
+ /**
1266
+ * The comment threads that should render (pins, sidebar), reactively — the live set described by
1267
+ * {@link getLiveCommentThreads}. Use `getCommentThreads` for the unfiltered set, including
1268
+ * soft-deleted threads awaiting the server's prune.
1269
+ *
1270
+ * @public
1271
+ */
851
1272
  export declare function useCommentThreads(editor: Editor): TLCommentThread[];
852
1273
 
853
1274
  /** React hook for the open thread id.
854
1275
  * @public */
855
1276
  export declare function useOpenThreadId(): null | string;
856
1277
 
857
- /** React hook for the pending (being-placed) comment.
858
- * @public */
859
- export declare function usePendingComment(): null | PendingComment;
1278
+ /**
1279
+ * Reactive React hook for {@link getRevealThreadPending}.
1280
+ *
1281
+ * Use it to notice a reveal that never lands — most often a deep link to a comment that has since
1282
+ * been deleted. Give it a grace period before you act: a request also sits here while its records
1283
+ * are still syncing in, which is the normal case on a cold load. Re-check with
1284
+ * {@link getRevealThreadPending} when the grace period elapses, since the request can clear inside
1285
+ * it without this hook's value having caught up yet.
1286
+ *
1287
+ * @public
1288
+ */
1289
+ export declare function useRevealThreadPending(): null | string;
860
1290
 
861
1291
  /** React hook for the current sidebar filters.
862
1292
  * @public */
863
1293
  export declare function useSidebarFilters(): SidebarFilters;
864
1294
 
865
- /** A thread's comments, oldest first, reactively. @public */
1295
+ /** A thread's live comments, oldest first, reactively. @public */
866
1296
  export declare function useThreadComments(editor: Editor, threadId: TLCommentThreadId): TLComment[];
867
1297
 
868
1298
  export { }