@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,11 +1,64 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- function Reaction({ emoji, count, active }) {
3
- return /* @__PURE__ */ jsxs("button", { className: active ? "cmt-reaction cmt-reaction--active" : "cmt-reaction", type: "button", children: [
4
- /* @__PURE__ */ jsx("span", { className: "cmt-reaction__emoji", children: emoji }),
5
- /* @__PURE__ */ jsx("span", { className: "cmt-reaction__count", children: count })
6
- ] });
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { TldrawUiTooltip, useTranslation } from "tldraw";
3
+ function defaultRenderReaction(token) {
4
+ return token;
5
+ }
6
+ function Reaction({
7
+ emoji,
8
+ count,
9
+ active,
10
+ reactors,
11
+ enableHoverList = true,
12
+ renderReaction = defaultRenderReaction,
13
+ ReactionTooltip = DefaultReactionTooltip,
14
+ onClick
15
+ }) {
16
+ const pill = /* @__PURE__ */ jsxs(
17
+ "button",
18
+ {
19
+ className: active ? "tlui-cmt-reaction tlui-cmt-reaction--active" : "tlui-cmt-reaction",
20
+ type: "button",
21
+ "aria-label": `${emoji} ${count}`,
22
+ "aria-pressed": active,
23
+ onClick,
24
+ children: [
25
+ /* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__emoji", children: renderReaction(emoji) }),
26
+ /* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__count", children: count })
27
+ ]
28
+ }
29
+ );
30
+ if (!enableHoverList || reactors.length === 0) return pill;
31
+ return /* @__PURE__ */ jsx(ReactionTooltip, { reactors, children: pill });
32
+ }
33
+ function DefaultReactionTooltip({ reactors, children }) {
34
+ return /* @__PURE__ */ jsx(TldrawUiTooltip, { side: "bottom", content: /* @__PURE__ */ jsx(DefaultReactionTooltipContent, { reactors }), children });
35
+ }
36
+ function DefaultReactionTooltipContent({ reactors }) {
37
+ const msg = useTranslation();
38
+ const names = reactors.map(
39
+ (reactor) => reactor.you ? msg("comments.mention-you") : reactor.name
40
+ );
41
+ const [a, b, c] = names;
42
+ switch (names.length) {
43
+ case 0:
44
+ return null;
45
+ case 1:
46
+ return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-1").replace("{a}", a) });
47
+ case 2:
48
+ return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-2").replace("{a}", a).replace("{b}", b) });
49
+ case 3:
50
+ return /* @__PURE__ */ jsx(Fragment, { children: msg("comments.reacted-3").replace("{a}", a).replace("{b}", b).replace("{c}", c) });
51
+ default: {
52
+ const others = names.length - 3;
53
+ const template = others === 1 ? "comments.reacted-more-one" : "comments.reacted-more";
54
+ return /* @__PURE__ */ jsx(Fragment, { children: msg(template).replace("{a}", a).replace("{b}", b).replace("{c}", c).replace("{count}", String(others)) });
55
+ }
56
+ }
7
57
  }
8
58
  export {
9
- Reaction
59
+ DefaultReactionTooltip,
60
+ DefaultReactionTooltipContent,
61
+ Reaction,
62
+ defaultRenderReaction
10
63
  };
11
64
  //# sourceMappingURL=reaction.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/reaction.tsx"],
4
- "sourcesContent": ["/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n}\n\n/** A single emoji reaction pill with a count, highlighted when the user reacted. @public @react */\nexport function Reaction({ emoji, count, active }: ReactionProps) {\n\treturn (\n\t\t<button className={active ? 'cmt-reaction cmt-reaction--active' : 'cmt-reaction'} type=\"button\">\n\t\t\t<span className=\"cmt-reaction__emoji\">{emoji}</span>\n\t\t\t<span className=\"cmt-reaction__count\">{count}</span>\n\t\t</button>\n\t)\n}\n"],
5
- "mappings": "AAUE,SACC,KADD;AAFK,SAAS,SAAS,EAAE,OAAO,OAAO,OAAO,GAAkB;AACjE,SACC,qBAAC,YAAO,WAAW,SAAS,sCAAsC,gBAAgB,MAAK,UACtF;AAAA,wBAAC,UAAK,WAAU,uBAAuB,iBAAM;AAAA,IAC7C,oBAAC,UAAK,WAAU,uBAAuB,iBAAM;AAAA,KAC9C;AAEF;",
4
+ "sourcesContent": ["import { ComponentType, ReactNode } from 'react'\nimport { TldrawUiTooltip, useTranslation } from 'tldraw'\nimport { ReactionReactor } from './reactions'\n\n/** Render a reaction token to its visual \u2014 the emoji glyph by default. @public */\nexport type RenderReaction = (token: string) => ReactNode\n\n/** The default reaction renderer: emits the token string for the OS emoji font to draw. @public */\nexport function defaultRenderReaction(token: string): ReactNode {\n\treturn token\n}\n\n/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n\t/** How to draw the emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/** Who reacted with this emoji, in reaction order \u2014 shown when the pill is hovered. */\n\treactors: ReactionReactor[]\n\t/** Whether hovering shows the reactor list. Pass false to suppress it \u2014 e.g. while another\n\t * popup menu on the comment is open. Defaults to true. */\n\tenableHoverList?: boolean\n\t/** The hover affordance for the pill, naming who reacted. It receives the reactors and the pill\n\t * itself (as `children`) and returns the whole thing \u2014 so it owns the tooltip, its box, size,\n\t * shape, and position, not just the text inside. Defaults to `DefaultReactionTooltip`, which\n\t * hangs the built-in inline sentence below the pill. */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n\t/** Called when the pill is pressed \u2014 toggles the current user's reaction. */\n\tonClick?(): void\n}\n\n/**\n * A single emoji reaction pill with a count, highlighted when the user reacted. Hovering it shows a\n * tooltip naming who reacted (the current user included), when `enableHoverList` is set.\n * @public @react\n */\nexport function Reaction({\n\temoji,\n\tcount,\n\tactive,\n\treactors,\n\tenableHoverList = true,\n\trenderReaction = defaultRenderReaction,\n\tReactionTooltip = DefaultReactionTooltip,\n\tonClick,\n}: ReactionProps) {\n\tconst pill = (\n\t\t<button\n\t\t\tclassName={active ? 'tlui-cmt-reaction tlui-cmt-reaction--active' : 'tlui-cmt-reaction'}\n\t\t\ttype=\"button\"\n\t\t\t// The emoji alone announces as its unicode name (\"thumbs up\"); pairing it with the count\n\t\t\t// is what makes the pill's state legible to a screen reader.\n\t\t\taria-label={`${emoji} ${count}`}\n\t\t\taria-pressed={active}\n\t\t\tonClick={onClick}\n\t\t>\n\t\t\t<span className=\"tlui-cmt-reaction__emoji\">{renderReaction(emoji)}</span>\n\t\t\t<span className=\"tlui-cmt-reaction__count\">{count}</span>\n\t\t</button>\n\t)\n\n\t// A bare pill when hovering is suppressed, or when there's no one to name.\n\tif (!enableHoverList || reactors.length === 0) return pill\n\n\t// Hand the whole hover affordance to the tooltip component: it wraps the pill and owns how the\n\t// reactor list is presented.\n\treturn <ReactionTooltip reactors={reactors}>{pill}</ReactionTooltip>\n}\n\n/** @public */\nexport interface ReactionTooltipProps {\n\t/** Who reacted with this emoji, in reaction order. */\n\treactors: ReactionReactor[]\n\t/** The reaction pill to wrap. Anchor the hover affordance to this and render it. */\n\tchildren: ReactNode\n}\n\n/**\n * The default reactor tooltip: hangs the built-in inline sentence (`DefaultReactionTooltipContent`)\n * in a standard tooltip below the pill \u2014 the same tooltip the toolbar uses. Replace it via\n * `CommentingComponents.ReactionTooltip` to present the reactor list any other way (a different box,\n * avatars, a banner, anywhere on screen); it receives the pill as `children`.\n * @public @react\n */\nexport function DefaultReactionTooltip({ reactors, children }: ReactionTooltipProps) {\n\treturn (\n\t\t<TldrawUiTooltip side=\"bottom\" content={<DefaultReactionTooltipContent reactors={reactors} />}>\n\t\t\t{children}\n\t\t</TldrawUiTooltip>\n\t)\n}\n\n/**\n * The default reactor sentence naming who reacted \u2014 up to three names spelled out, then \"and N\n * others\" (e.g. \"You reacted\", \"You and Bo reacted\", \"You, Bo and Ada reacted\", \"You, Bo, Ada and 2\n * others reacted\"). The wording lives in the `comments.reacted-*` translation strings so each locale\n * controls the grammar. Exported so a custom `ReactionTooltip` can reuse it inside its own box.\n * @public @react\n */\nexport function DefaultReactionTooltipContent({ reactors }: { reactors: ReactionReactor[] }) {\n\tconst msg = useTranslation()\n\tconst names = reactors.map((reactor) =>\n\t\treactor.you ? msg('comments.mention-you') : reactor.name\n\t)\n\tconst [a, b, c] = names\n\tswitch (names.length) {\n\t\tcase 0:\n\t\t\treturn null\n\t\tcase 1:\n\t\t\treturn <>{msg('comments.reacted-1').replace('{a}', a)}</>\n\t\tcase 2:\n\t\t\treturn <>{msg('comments.reacted-2').replace('{a}', a).replace('{b}', b)}</>\n\t\tcase 3:\n\t\t\treturn <>{msg('comments.reacted-3').replace('{a}', a).replace('{b}', b).replace('{c}', c)}</>\n\t\tdefault: {\n\t\t\tconst others = names.length - 3\n\t\t\tconst template = others === 1 ? 'comments.reacted-more-one' : 'comments.reacted-more'\n\t\t\treturn (\n\t\t\t\t<>\n\t\t\t\t\t{msg(template)\n\t\t\t\t\t\t.replace('{a}', a)\n\t\t\t\t\t\t.replace('{b}', b)\n\t\t\t\t\t\t.replace('{c}', c)\n\t\t\t\t\t\t.replace('{count}', String(others))}\n\t\t\t\t</>\n\t\t\t)\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "AAiDE,SA8DQ,UArDP,KATD;AAhDF,SAAS,iBAAiB,sBAAsB;AAOzC,SAAS,sBAAsB,OAA0B;AAC/D,SAAO;AACR;AA4BO,SAAS,SAAS;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB;AACD,GAAkB;AACjB,QAAM,OACL;AAAA,IAAC;AAAA;AAAA,MACA,WAAW,SAAS,gDAAgD;AAAA,MACpE,MAAK;AAAA,MAGL,cAAY,GAAG,KAAK,IAAI,KAAK;AAAA,MAC7B,gBAAc;AAAA,MACd;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,4BAA4B,yBAAe,KAAK,GAAE;AAAA,QAClE,oBAAC,UAAK,WAAU,4BAA4B,iBAAM;AAAA;AAAA;AAAA,EACnD;AAID,MAAI,CAAC,mBAAmB,SAAS,WAAW,EAAG,QAAO;AAItD,SAAO,oBAAC,mBAAgB,UAAqB,gBAAK;AACnD;AAiBO,SAAS,uBAAuB,EAAE,UAAU,SAAS,GAAyB;AACpF,SACC,oBAAC,mBAAgB,MAAK,UAAS,SAAS,oBAAC,iCAA8B,UAAoB,GACzF,UACF;AAEF;AASO,SAAS,8BAA8B,EAAE,SAAS,GAAoC;AAC5F,QAAM,MAAM,eAAe;AAC3B,QAAM,QAAQ,SAAS;AAAA,IAAI,CAAC,YAC3B,QAAQ,MAAM,IAAI,sBAAsB,IAAI,QAAQ;AAAA,EACrD;AACA,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI;AAClB,UAAQ,MAAM,QAAQ;AAAA,IACrB,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IACvD,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IACzE,KAAK;AACJ,aAAO,gCAAG,cAAI,oBAAoB,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,GAAE;AAAA,IAC3F,SAAS;AACR,YAAM,SAAS,MAAM,SAAS;AAC9B,YAAM,WAAW,WAAW,IAAI,8BAA8B;AAC9D,aACC,gCACE,cAAI,QAAQ,EACX,QAAQ,OAAO,CAAC,EAChB,QAAQ,OAAO,CAAC,EAChB,QAAQ,OAAO,CAAC,EAChB,QAAQ,WAAW,OAAO,MAAM,CAAC,GACpC;AAAA,IAEF;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,25 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { jsx } from "react/jsx-runtime";
2
2
  import { Reaction } from "./reaction.mjs";
3
- function Reactions() {
4
- return /* @__PURE__ */ jsxs("div", { className: "cmt-reactions", children: [
5
- /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F44D}", count: 3, active: true }),
6
- /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F389}", count: 1, active: false }),
7
- /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F440}", count: 2, active: false }),
8
- /* @__PURE__ */ jsx("button", { className: "cmt-reaction cmt-reaction--add", type: "button", "aria-label": "Add reaction", children: "\uFF0B" })
9
- ] });
3
+ function Reactions({
4
+ reactions,
5
+ onToggle,
6
+ canReact = true,
7
+ enableHoverList = true,
8
+ renderReaction,
9
+ ReactionTooltip
10
+ }) {
11
+ if (reactions.length === 0) return null;
12
+ return /* @__PURE__ */ jsx("div", { className: "tlui-cmt-reactions", children: reactions.map((reaction) => /* @__PURE__ */ jsx(
13
+ Reaction,
14
+ {
15
+ ...reaction,
16
+ enableHoverList,
17
+ renderReaction,
18
+ ReactionTooltip,
19
+ onClick: canReact ? () => onToggle?.(reaction.emoji) : void 0
20
+ },
21
+ reaction.emoji
22
+ )) });
10
23
  }
11
24
  export {
12
25
  Reactions
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/reactions.tsx"],
4
- "sourcesContent": ["import { Reaction } from './reaction'\n\n/** The row of reactions under a comment, plus an add-reaction button. @public @react */\nexport function Reactions() {\n\treturn (\n\t\t<div className=\"cmt-reactions\">\n\t\t\t<Reaction emoji=\"\uD83D\uDC4D\" count={3} active={true} />\n\t\t\t<Reaction emoji=\"\uD83C\uDF89\" count={1} active={false} />\n\t\t\t<Reaction emoji=\"\uD83D\uDC40\" count={2} active={false} />\n\t\t\t<button className=\"cmt-reaction cmt-reaction--add\" type=\"button\" aria-label=\"Add reaction\">\n\t\t\t\t\uFF0B\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "AAKE,SACC,KADD;AALF,SAAS,gBAAgB;AAGlB,SAAS,YAAY;AAC3B,SACC,qBAAC,SAAI,WAAU,iBACd;AAAA,wBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,MAAM;AAAA,IAC7C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C,oBAAC,YAAO,WAAU,kCAAiC,MAAK,UAAS,cAAW,gBAAe,oBAE3F;AAAA,KACD;AAEF;",
4
+ "sourcesContent": ["import { ComponentType } from 'react'\nimport { Reaction, ReactionTooltipProps, RenderReaction } from './reaction'\n\n/** One person who reacted with a given emoji, for the hover list. @public */\nexport interface ReactionReactor {\n\t/** Display name of the person who reacted. */\n\tname: string\n\t/** True for the current user. */\n\tyou: boolean\n}\n\n/** One emoji's reactions on a comment, already tallied. @public */\nexport interface ReactionSummary {\n\temoji: string\n\t/** How many people reacted with this emoji. */\n\tcount: number\n\t/** True when the current user is one of them \u2014 renders the pill highlighted. */\n\tactive: boolean\n\t/** Who reacted with this emoji, in reaction order \u2014 shown in the hover list. */\n\treactors: ReactionReactor[]\n}\n\n/** @public */\nexport interface ReactionsProps {\n\t/** The comment's reactions, grouped by emoji. Empty renders nothing. */\n\treactions: ReactionSummary[]\n\t/** Toggles the current user's reaction for an emoji. */\n\tonToggle?(emoji: string): void\n\t/** Whether the current user may react. False makes the pills inert (e.g. a signed-out\n\t * viewer, or a read-only thread) while still showing counts. */\n\tcanReact?: boolean\n\t/** Whether hovering a pill shows its reactor list. Pass false to suppress it \u2014 e.g. while\n\t * another popup menu on the comment is open. Defaults to true. */\n\tenableHoverList?: boolean\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/** The tooltip naming who reacted, shown when a pill is hovered. Defaults to an inline sentence\n\t * (`DefaultReactionTooltip`). Swap it to render the reactor list however you like. */\n\tReactionTooltip?: ComponentType<ReactionTooltipProps>\n}\n\n/**\n * The row of tallied reactions under a comment. Presentational \u2014 the host supplies the summaries\n * and owns what toggling does. Hovering a pill lists who reacted (see `Reaction`).\n *\n * The add-reaction affordance is a separate component (`ReactionPicker`) so it can live outside\n * this row \u2014 on a comment card it sits with the card's hover actions, which keeps its position\n * fixed as reactions are added here.\n * @public @react\n */\nexport function Reactions({\n\treactions,\n\tonToggle,\n\tcanReact = true,\n\tenableHoverList = true,\n\trenderReaction,\n\tReactionTooltip,\n}: ReactionsProps) {\n\tif (reactions.length === 0) return null\n\treturn (\n\t\t<div className=\"tlui-cmt-reactions\">\n\t\t\t{reactions.map((reaction) => (\n\t\t\t\t<Reaction\n\t\t\t\t\tkey={reaction.emoji}\n\t\t\t\t\t{...reaction}\n\t\t\t\t\tenableHoverList={enableHoverList}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t\tReactionTooltip={ReactionTooltip}\n\t\t\t\t\tonClick={canReact ? () => onToggle?.(reaction.emoji) : undefined}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "AA8DI;AA7DJ,SAAS,gBAAsD;AAiDxD,SAAS,UAAU;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB;AAAA,EACA;AACD,GAAmB;AAClB,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,SACC,oBAAC,SAAI,WAAU,sBACb,oBAAU,IAAI,CAAC,aACf;AAAA,IAAC;AAAA;AAAA,MAEC,GAAG;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,WAAW,MAAM,WAAW,SAAS,KAAK,IAAI;AAAA;AAAA,IALlD,SAAS;AAAA,EAMf,CACA,GACF;AAEF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,9 @@
1
+ function replyCountLabel(msg, replyCount) {
2
+ if (replyCount <= 0) return null;
3
+ const key = replyCount === 1 ? "comments.replies-one" : "comments.replies";
4
+ return msg(key).replace("{count}", String(replyCount));
5
+ }
6
+ export {
7
+ replyCountLabel
8
+ };
9
+ //# sourceMappingURL=reply-count.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/reply-count.ts"],
4
+ "sourcesContent": ["import { useTranslation } from 'tldraw'\n\n/**\n * The localized \"N replies\" label (e.g. \"1 reply\", \"3 replies\") for a thread with `replyCount`\n * replies \u2014 the comments after the opening one. Returns null when there are none, so callers render\n * nothing rather than \"0 replies\". Shared by the sidebar row and the hover preview so both read the\n * same, with each site owning where it puts the text.\n */\nexport function replyCountLabel(\n\tmsg: ReturnType<typeof useTranslation>,\n\treplyCount: number\n): string | null {\n\tif (replyCount <= 0) return null\n\tconst key = replyCount === 1 ? 'comments.replies-one' : 'comments.replies'\n\treturn msg(key).replace('{count}', String(replyCount))\n}\n"],
5
+ "mappings": "AAQO,SAAS,gBACf,KACA,YACgB;AAChB,MAAI,cAAc,EAAG,QAAO;AAC5B,QAAM,MAAM,eAAe,IAAI,yBAAyB;AACxD,SAAO,IAAI,GAAG,EAAE,QAAQ,WAAW,OAAO,UAAU,CAAC;AACtD;",
6
+ "names": []
7
+ }
@@ -1,6 +1,25 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  function SendButton({ label, disabled, onClick }) {
3
- return /* @__PURE__ */ jsx("button", { className: "cmt-send", type: "button", disabled, onClick, children: label });
3
+ return /* @__PURE__ */ jsx(
4
+ "button",
5
+ {
6
+ className: "tlui-cmt-send",
7
+ type: "button",
8
+ disabled,
9
+ onClick,
10
+ "aria-label": label,
11
+ title: label,
12
+ children: /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
13
+ "path",
14
+ {
15
+ d: "M7.14645 2.14645C7.34171 1.95118 7.65829 1.95118 7.85355 2.14645L11.8536 6.14645C12.0488 6.34171 12.0488 6.65829 11.8536 6.85355C11.6583 7.04882 11.3417 7.04882 11.1464 6.85355L8 3.70711L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 3.70711L3.85355 6.85355C3.65829 7.04882 3.34171 7.04882 3.14645 6.85355C2.95118 6.65829 2.95118 6.34171 3.14645 6.14645L7.14645 2.14645Z",
16
+ fill: "currentColor",
17
+ fillRule: "evenodd",
18
+ clipRule: "evenodd"
19
+ }
20
+ ) })
21
+ }
22
+ );
4
23
  }
5
24
  export {
6
25
  SendButton
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/send-button.tsx"],
4
- "sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button className=\"cmt-send\" type=\"button\" disabled={disabled} onClick={onClick}>\n\t\t\t{label}\n\t\t</button>\n\t)\n}\n"],
5
- "mappings": "AAUE;AAFK,SAAS,WAAW,EAAE,OAAO,UAAU,QAAQ,GAAoB;AACzE,SACC,oBAAC,YAAO,WAAU,YAAW,MAAK,UAAS,UAAoB,SAC7D,iBACF;AAEF;",
4
+ "sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\t/** The button's accessible name (e.g. \"Send\"). Shown as an up-arrow icon, so this is its label. */\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment \u2014 an up arrow. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button\n\t\t\tclassName=\"tlui-cmt-send\"\n\t\t\ttype=\"button\"\n\t\t\tdisabled={disabled}\n\t\t\tonClick={onClick}\n\t\t\taria-label={label}\n\t\t\ttitle={label}\n\t\t>\n\t\t\t<svg width=\"15\" height=\"15\" viewBox=\"0 0 15 15\" fill=\"none\" aria-hidden=\"true\">\n\t\t\t\t<path\n\t\t\t\t\td=\"M7.14645 2.14645C7.34171 1.95118 7.65829 1.95118 7.85355 2.14645L11.8536 6.14645C12.0488 6.34171 12.0488 6.65829 11.8536 6.85355C11.6583 7.04882 11.3417 7.04882 11.1464 6.85355L8 3.70711L8 12.5C8 12.7761 7.77614 13 7.5 13C7.22386 13 7 12.7761 7 12.5L7 3.70711L3.85355 6.85355C3.65829 7.04882 3.34171 7.04882 3.14645 6.85355C2.95118 6.65829 2.95118 6.34171 3.14645 6.14645L7.14645 2.14645Z\"\n\t\t\t\t\tfill=\"currentColor\"\n\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t</button>\n\t)\n}\n"],
5
+ "mappings": "AAoBI;AAXG,SAAS,WAAW,EAAE,OAAO,UAAU,QAAQ,GAAoB;AACzE,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAU;AAAA,MACV,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,cAAY;AAAA,MACZ,OAAO;AAAA,MAEP,8BAAC,SAAI,OAAM,MAAK,QAAO,MAAK,SAAQ,aAAY,MAAK,QAAO,eAAY,QACvE;AAAA,QAAC;AAAA;AAAA,UACA,GAAE;AAAA,UACF,MAAK;AAAA,UACL,UAAS;AAAA,UACT,UAAS;AAAA;AAAA,MACV,GACD;AAAA;AAAA,EACD;AAEF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,12 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { TldrawUiTooltip } from "tldraw";
4
+ const TooltipButton = forwardRef(
5
+ function TooltipButton2({ tooltip, children, ...props }, ref) {
6
+ return /* @__PURE__ */ jsx(TldrawUiTooltip, { content: tooltip, children: /* @__PURE__ */ jsx("button", { ref, type: "button", "aria-label": tooltip, ...props, children }) });
7
+ }
8
+ );
9
+ export {
10
+ TooltipButton
11
+ };
12
+ //# sourceMappingURL=tooltip-button.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/tooltip-button.tsx"],
4
+ "sourcesContent": ["import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { TldrawUiTooltip } from 'tldraw'\n\ninterface TooltipButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n\ttooltip: string\n}\n\n/** An icon button labelled by a styled tooltip. Forwards ref and props to the underlying button\n * so `asChild` triggers (dropdown, tooltip) both compose onto the real element. */\nexport const TooltipButton = forwardRef<HTMLButtonElement, TooltipButtonProps>(\n\tfunction TooltipButton({ tooltip, children, ...props }, ref) {\n\t\treturn (\n\t\t\t<TldrawUiTooltip content={tooltip}>\n\t\t\t\t<button ref={ref} type=\"button\" aria-label={tooltip} {...props}>\n\t\t\t\t\t{children}\n\t\t\t\t</button>\n\t\t\t</TldrawUiTooltip>\n\t\t)\n\t}\n)\n"],
5
+ "mappings": "AAaI;AAbJ,SAAoC,kBAAkB;AACtD,SAAS,uBAAuB;AAQzB,MAAM,gBAAgB;AAAA,EAC5B,SAASA,eAAc,EAAE,SAAS,UAAU,GAAG,MAAM,GAAG,KAAK;AAC5D,WACC,oBAAC,mBAAgB,SAAS,SACzB,8BAAC,YAAO,KAAU,MAAK,UAAS,cAAY,SAAU,GAAG,OACvD,UACF,GACD;AAAA,EAEF;AACD;",
6
+ "names": ["TooltipButton"]
7
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/commenting",
3
3
  "description": "tldraw commenting UI components.",
4
- "version": "0.0.0-bootstrap",
4
+ "version": "5.3.0-canary.31036673d3f8",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -28,7 +28,7 @@
28
28
  "test": "yarn run -T vitest --passWithNoTests",
29
29
  "test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
30
30
  "predev": "node ./scripts/copy-css-files.mjs",
31
- "dev": "chokidar 'src/ui/comments.css' 'src/canvas/canvas.css' -c 'node ./scripts/copy-css-files.mjs'",
31
+ "dev": "chokidar '../mentions/mentions.css' 'src/ui/comments.css' 'src/canvas/canvas.css' -c 'node ./scripts/copy-css-files.mjs'",
32
32
  "prebuild": "node ./scripts/copy-css-files.mjs",
33
33
  "build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
34
34
  "build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
@@ -38,6 +38,7 @@
38
38
  "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
39
39
  },
40
40
  "devDependencies": {
41
+ "@tldraw/driver": "5.3.0-canary.31036673d3f8",
41
42
  "@types/react": "^19.2.7",
42
43
  "@types/react-dom": "^19.2.3",
43
44
  "react": "^19.2.1",
@@ -52,12 +53,11 @@
52
53
  },
53
54
  "dependencies": {
54
55
  "@tiptap/core": "^3.12.1",
55
- "@tiptap/extension-mention": "3.13.0",
56
56
  "@tiptap/pm": "^3.12.1",
57
57
  "@tiptap/react": "^3.12.1",
58
- "@tiptap/suggestion": "3.13.0",
59
- "@tldraw/utils": "5.2.5",
60
- "tldraw": "5.2.5"
58
+ "@tldraw/mentions": "5.3.0-canary.31036673d3f8",
59
+ "@tldraw/utils": "5.3.0-canary.31036673d3f8",
60
+ "tldraw": "5.3.0-canary.31036673d3f8"
61
61
  },
62
62
  "module": "dist-esm/index.mjs",
63
63
  "source": "src/index.ts",
@@ -0,0 +1,290 @@
1
+ import {
2
+ commentSchemaRecords,
3
+ createCommentThread,
4
+ createShapeId,
5
+ createTLSchema,
6
+ createTLStore,
7
+ defaultBindingUtils,
8
+ defaultShapeUtils,
9
+ defaultTools,
10
+ Editor,
11
+ toRichText,
12
+ createComment as tlCreateComment,
13
+ TLPageId,
14
+ TLShapeId,
15
+ } from 'tldraw'
16
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
17
+ import { registerCommentAnchorLifecycle } from './anchor-lifecycle'
18
+ import { commitCommentMutation, putCommentRecords } from './comment-mutations'
19
+ import { getCommentRecord, getComments } from './comment-store'
20
+ import { CommentTool } from './comment-tool'
21
+
22
+ /**
23
+ * These tests need a real editor: the behavior under test is the interplay between store
24
+ * side effects (the beforeDelete snapshot, operation-complete settlement) and real editor
25
+ * operations like `deleteShapes` and `moveShapesToPage` — exactly what a stub can't model.
26
+ */
27
+
28
+ let editor: Editor
29
+ let dispose: () => void
30
+
31
+ beforeEach(() => {
32
+ editor = new Editor({
33
+ store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
34
+ shapeUtils: defaultShapeUtils,
35
+ bindingUtils: defaultBindingUtils,
36
+ tools: defaultTools,
37
+ getContainer: () => document.body,
38
+ })
39
+ dispose = registerCommentAnchorLifecycle(editor)
40
+ })
41
+
42
+ afterEach(() => {
43
+ dispose()
44
+ editor.dispose()
45
+ })
46
+
47
+ function makeShape(x = 100, y = 100, w = 100, h = 50): TLShapeId {
48
+ const id = createShapeId()
49
+ editor.createShape({ id, type: 'geo', x, y, props: { w, h } })
50
+ return id
51
+ }
52
+
53
+ function makeThread(shapeId: TLShapeId, anchorX = 0.5, anchorY = 0.5, isPrecise = true) {
54
+ const thread = createCommentThread({
55
+ pageId: editor.getCurrentPageId(),
56
+ anchor: { type: 'shape', shapeId, x: anchorX, y: anchorY, isPrecise },
57
+ createdBy: 'me',
58
+ })
59
+ const comment = tlCreateComment({
60
+ threadId: thread.id,
61
+ pageId: thread.pageId,
62
+ authorId: 'me',
63
+ body: toRichText('hello'),
64
+ })
65
+ putCommentRecords(editor, [thread, comment])
66
+ return { thread, comment }
67
+ }
68
+
69
+ function makeSecondPage(): TLPageId {
70
+ editor.createPage({ name: 'page 2' })
71
+ return editor.getPages()[1].id
72
+ }
73
+
74
+ describe('shape deletion', () => {
75
+ it('converts a precise shape anchor to a point anchor where the pin sat', () => {
76
+ const shapeId = makeShape(100, 100, 100, 50)
77
+ const { thread } = makeThread(shapeId, 0.5, 0.5, true)
78
+
79
+ editor.deleteShape(shapeId)
80
+
81
+ const updated = getCommentRecord(editor, thread.id)!
82
+ expect(updated.typeName).toBe('comment-thread')
83
+ expect((updated as typeof thread).anchor).toEqual({ type: 'point', x: 150, y: 125 })
84
+ })
85
+
86
+ it('converts an imprecise shape anchor where the inset pin is drawn, not at the raw corner', () => {
87
+ const shapeId = makeShape(100, 100, 100, 50)
88
+ const { thread } = makeThread(shapeId, 0.2, 0.9, false)
89
+
90
+ editor.deleteShape(shapeId)
91
+
92
+ // Top-right corner (200, 100) plus the 20px screen inset toward the shape's centre.
93
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
94
+ expect(updated.anchor).toEqual({ type: 'point', x: 180, y: 120 })
95
+ })
96
+
97
+ it('resolves the imprecise spot from the commenting options when no override is passed', () => {
98
+ const configured = new Editor({
99
+ store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }),
100
+ shapeUtils: defaultShapeUtils,
101
+ bindingUtils: defaultBindingUtils,
102
+ tools: [...defaultTools, CommentTool.configure({ impreciseShapeAnchor: { x: 0, y: 1 } })],
103
+ getContainer: () => document.body,
104
+ })
105
+ const disposeConfigured = registerCommentAnchorLifecycle(configured)
106
+ try {
107
+ const shapeId = createShapeId()
108
+ configured.createShape({ id: shapeId, type: 'geo', x: 100, y: 100, props: { w: 100, h: 50 } })
109
+ const thread = createCommentThread({
110
+ pageId: configured.getCurrentPageId(),
111
+ anchor: { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: false },
112
+ createdBy: 'me',
113
+ })
114
+ putCommentRecords(configured, [thread])
115
+
116
+ configured.deleteShape(shapeId)
117
+
118
+ // Bottom-left corner (100, 150) plus the inset toward the shape's centre.
119
+ const updated = getCommentRecord(configured, thread.id) as typeof thread
120
+ expect(updated.anchor).toEqual({ type: 'point', x: 120, y: 130 })
121
+ } finally {
122
+ disposeConfigured()
123
+ configured.dispose()
124
+ }
125
+ })
126
+
127
+ it('converts anchors on children when their frame is deleted with them', () => {
128
+ const frameId = createShapeId()
129
+ editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
130
+ const childId = createShapeId()
131
+ editor.createShape({
132
+ id: childId,
133
+ type: 'geo',
134
+ parentId: frameId,
135
+ x: 50,
136
+ y: 50,
137
+ props: { w: 100, h: 100 },
138
+ })
139
+ const { thread } = makeThread(childId, 0.5, 0.5, true)
140
+
141
+ editor.deleteShape(frameId)
142
+
143
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
144
+ expect(updated.anchor).toEqual({ type: 'point', x: 100, y: 100 })
145
+ })
146
+
147
+ it('re-attaches the thread when the delete is undone', () => {
148
+ const shapeId = makeShape(100, 100, 100, 50)
149
+ const { thread } = makeThread(shapeId, 0.5, 0.5, true)
150
+ editor.markHistoryStoppingPoint()
151
+
152
+ editor.deleteShape(shapeId)
153
+ expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
154
+
155
+ editor.undo()
156
+
157
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
158
+ expect(updated.anchor).toEqual(thread.anchor)
159
+ })
160
+
161
+ it('keeps the restore when a returning shape cannot resolve its page yet', () => {
162
+ const frameId = createShapeId()
163
+ editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
164
+ const childId = createShapeId()
165
+ editor.createShape({
166
+ id: childId,
167
+ type: 'geo',
168
+ parentId: frameId,
169
+ x: 50,
170
+ y: 50,
171
+ props: { w: 100, h: 100 },
172
+ })
173
+ const { thread } = makeThread(childId, 0.5, 0.5, true)
174
+ const childRecord = editor.getShape(childId)!
175
+ const frameRecord = editor.getShape(frameId)!
176
+
177
+ editor.deleteShape(frameId)
178
+ expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
179
+
180
+ // The child returns in an earlier operation than its parent, so its ancestor page can't
181
+ // resolve when it first reappears.
182
+ editor.store.put([childRecord])
183
+ editor.store.put([frameRecord])
184
+
185
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
186
+ expect(updated.anchor).toEqual(thread.anchor)
187
+ })
188
+
189
+ it('re-attaches on undo even when the lifecycle was re-registered in between', () => {
190
+ const shapeId = makeShape(100, 100, 100, 50)
191
+ const { thread } = makeThread(shapeId, 0.5, 0.5, true)
192
+ editor.markHistoryStoppingPoint()
193
+
194
+ editor.deleteShape(shapeId)
195
+ expect((getCommentRecord(editor, thread.id) as typeof thread).anchor.type).toBe('point')
196
+
197
+ // The registering effect re-runs whenever its inputs change identity; conversion memory
198
+ // must not live and die with any one registration.
199
+ dispose()
200
+ dispose = registerCommentAnchorLifecycle(editor)
201
+
202
+ editor.undo()
203
+
204
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
205
+ expect(updated.anchor).toEqual(thread.anchor)
206
+ })
207
+
208
+ it('does not re-attach on undo when the pin was moved after the conversion', () => {
209
+ const shapeId = makeShape(100, 100, 100, 50)
210
+ const { thread } = makeThread(shapeId, 0.5, 0.5, true)
211
+ editor.markHistoryStoppingPoint()
212
+
213
+ editor.deleteShape(shapeId)
214
+ const converted = getCommentRecord(editor, thread.id) as typeof thread
215
+ commitCommentMutation(editor, () =>
216
+ putCommentRecords(editor, [{ ...converted, anchor: { type: 'point', x: 900, y: 900 } }])
217
+ )
218
+
219
+ editor.undo()
220
+
221
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
222
+ expect(updated.anchor).toEqual({ type: 'point', x: 900, y: 900 })
223
+ })
224
+
225
+ it('leaves threads on other shapes untouched', () => {
226
+ const shapeId = makeShape()
227
+ const otherId = makeShape(300, 300)
228
+ const { thread } = makeThread(otherId)
229
+
230
+ editor.deleteShape(shapeId)
231
+
232
+ const updated = getCommentRecord(editor, thread.id) as typeof thread
233
+ expect(updated.anchor.type).toBe('shape')
234
+ })
235
+ })
236
+
237
+ describe('moving a shape to another page', () => {
238
+ it('re-homes the thread and its comments to the new page, keeping the shape anchor', () => {
239
+ const shapeId = makeShape()
240
+ const { thread, comment } = makeThread(shapeId)
241
+ const page2 = makeSecondPage()
242
+
243
+ editor.moveShapesToPage([shapeId], page2)
244
+
245
+ const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
246
+ expect(updatedThread.pageId).toBe(page2)
247
+ expect(updatedThread.anchor).toEqual(thread.anchor)
248
+ const updatedComment = getComments(editor).find((c) => c.id === comment.id)!
249
+ expect(updatedComment.pageId).toBe(page2)
250
+ })
251
+
252
+ it('re-homes threads anchored to children of a moved frame', () => {
253
+ const frameId = createShapeId()
254
+ editor.createShape({ id: frameId, type: 'frame', x: 0, y: 0, props: { w: 400, h: 400 } })
255
+ const childId = createShapeId()
256
+ editor.createShape({
257
+ id: childId,
258
+ type: 'geo',
259
+ parentId: frameId,
260
+ x: 50,
261
+ y: 50,
262
+ props: { w: 100, h: 100 },
263
+ })
264
+ const { thread } = makeThread(childId)
265
+ const page2 = makeSecondPage()
266
+
267
+ editor.moveShapesToPage([frameId], page2)
268
+
269
+ const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
270
+ expect(updatedThread.pageId).toBe(page2)
271
+ expect(updatedThread.anchor.type).toBe('shape')
272
+ })
273
+
274
+ it('re-homes the thread back when the move is undone', () => {
275
+ const shapeId = makeShape()
276
+ const { thread } = makeThread(shapeId)
277
+ const page1 = editor.getCurrentPageId()
278
+ const page2 = makeSecondPage()
279
+ editor.markHistoryStoppingPoint()
280
+
281
+ editor.moveShapesToPage([shapeId], page2)
282
+ expect((getCommentRecord(editor, thread.id) as typeof thread).pageId).toBe(page2)
283
+
284
+ editor.undo()
285
+
286
+ const updatedThread = getCommentRecord(editor, thread.id) as typeof thread
287
+ expect(updatedThread.pageId).toBe(page1)
288
+ expect(updatedThread.anchor.type).toBe('shape')
289
+ })
290
+ })