@tldraw/commenting 0.0.0-bootstrap → 5.3.0-internal.5f14a24c179e

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 (174) hide show
  1. package/commenting.css +280 -151
  2. package/dist-cjs/canvas/comment-body.js +1 -1
  3. package/dist-cjs/canvas/comment-body.js.map +2 -2
  4. package/dist-cjs/canvas/comment-drafts.js +54 -0
  5. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  6. package/dist-cjs/canvas/comment-render.js +2 -0
  7. package/dist-cjs/canvas/comment-render.js.map +2 -2
  8. package/dist-cjs/canvas/comment-tool.js +55 -11
  9. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  10. package/dist-cjs/canvas/comments-filter-menu.js +55 -56
  11. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  12. package/dist-cjs/canvas/comments-overflow-menu.js +21 -19
  13. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  14. package/dist-cjs/canvas/comments-overlay.js +271 -109
  15. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  16. package/dist-cjs/canvas/comments-sidebar.js +12 -18
  17. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  18. package/dist-cjs/canvas/comments-visibility-toggle.js +67 -0
  19. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  20. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  21. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  22. package/dist-cjs/canvas/state.js +11 -0
  23. package/dist-cjs/canvas/state.js.map +2 -2
  24. package/dist-cjs/canvas/thread-state.js +8 -1
  25. package/dist-cjs/canvas/thread-state.js.map +2 -2
  26. package/dist-cjs/clustering/runtime.js +119 -7
  27. package/dist-cjs/clustering/runtime.js.map +2 -2
  28. package/dist-cjs/index.d.ts +100 -39
  29. package/dist-cjs/index.js +7 -1
  30. package/dist-cjs/index.js.map +2 -2
  31. package/dist-cjs/ui/avatar.js +14 -6
  32. package/dist-cjs/ui/avatar.js.map +2 -2
  33. package/dist-cjs/ui/byline.js +4 -4
  34. package/dist-cjs/ui/byline.js.map +2 -2
  35. package/dist-cjs/ui/comment-author.js +17 -0
  36. package/dist-cjs/ui/comment-author.js.map +7 -0
  37. package/dist-cjs/ui/comment-card.js +4 -4
  38. package/dist-cjs/ui/comment-card.js.map +2 -2
  39. package/dist-cjs/ui/comment-composer.js +92 -29
  40. package/dist-cjs/ui/comment-composer.js.map +2 -2
  41. package/dist-cjs/ui/comment-mention.js +1 -1
  42. package/dist-cjs/ui/comment-mention.js.map +2 -2
  43. package/dist-cjs/ui/comment-pin.js +23 -3
  44. package/dist-cjs/ui/comment-pin.js.map +2 -2
  45. package/dist-cjs/ui/comment-text.js +1 -1
  46. package/dist-cjs/ui/comment-text.js.map +2 -2
  47. package/dist-cjs/ui/comment-thread.js +6 -6
  48. package/dist-cjs/ui/comment-thread.js.map +2 -2
  49. package/dist-cjs/ui/comments-list.js +12 -12
  50. package/dist-cjs/ui/comments-list.js.map +2 -2
  51. package/dist-cjs/ui/count-badge.js +1 -1
  52. package/dist-cjs/ui/count-badge.js.map +2 -2
  53. package/dist-cjs/ui/empty-state.js +3 -3
  54. package/dist-cjs/ui/empty-state.js.map +2 -2
  55. package/dist-cjs/ui/format-time.js +3 -0
  56. package/dist-cjs/ui/format-time.js.map +2 -2
  57. package/dist-cjs/ui/mention-list.js +8 -8
  58. package/dist-cjs/ui/mention-list.js.map +2 -2
  59. package/dist-cjs/ui/mention-suggestion.js +3 -3
  60. package/dist-cjs/ui/mention-suggestion.js.map +2 -2
  61. package/dist-cjs/ui/mention.js +1 -1
  62. package/dist-cjs/ui/mention.js.map +2 -2
  63. package/dist-cjs/ui/reaction.js +11 -4
  64. package/dist-cjs/ui/reaction.js.map +2 -2
  65. package/dist-cjs/ui/reactions.js +10 -2
  66. package/dist-cjs/ui/reactions.js.map +2 -2
  67. package/dist-cjs/ui/send-button.js +1 -1
  68. package/dist-cjs/ui/send-button.js.map +2 -2
  69. package/dist-esm/canvas/comment-body.mjs +1 -1
  70. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  71. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  72. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  73. package/dist-esm/canvas/comment-render.mjs +2 -0
  74. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  75. package/dist-esm/canvas/comment-tool.mjs +56 -12
  76. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  77. package/dist-esm/canvas/comments-filter-menu.mjs +55 -56
  78. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  79. package/dist-esm/canvas/comments-overflow-menu.mjs +22 -19
  80. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  81. package/dist-esm/canvas/comments-overlay.mjs +289 -111
  82. package/dist-esm/canvas/comments-overlay.mjs.map +2 -2
  83. package/dist-esm/canvas/comments-sidebar.mjs +15 -21
  84. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  85. package/dist-esm/canvas/comments-visibility-toggle.mjs +47 -0
  86. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  87. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  88. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  89. package/dist-esm/canvas/state.mjs +11 -0
  90. package/dist-esm/canvas/state.mjs.map +2 -2
  91. package/dist-esm/canvas/thread-state.mjs +8 -1
  92. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  93. package/dist-esm/clustering/runtime.mjs +119 -7
  94. package/dist-esm/clustering/runtime.mjs.map +2 -2
  95. package/dist-esm/index.d.mts +100 -39
  96. package/dist-esm/index.mjs +11 -1
  97. package/dist-esm/index.mjs.map +2 -2
  98. package/dist-esm/ui/avatar.mjs +14 -6
  99. package/dist-esm/ui/avatar.mjs.map +2 -2
  100. package/dist-esm/ui/byline.mjs +4 -4
  101. package/dist-esm/ui/byline.mjs.map +2 -2
  102. package/dist-esm/ui/comment-author.mjs +1 -0
  103. package/dist-esm/ui/comment-author.mjs.map +7 -0
  104. package/dist-esm/ui/comment-card.mjs +4 -4
  105. package/dist-esm/ui/comment-card.mjs.map +2 -2
  106. package/dist-esm/ui/comment-composer.mjs +93 -30
  107. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  108. package/dist-esm/ui/comment-mention.mjs +1 -1
  109. package/dist-esm/ui/comment-mention.mjs.map +2 -2
  110. package/dist-esm/ui/comment-pin.mjs +23 -3
  111. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  112. package/dist-esm/ui/comment-text.mjs +1 -1
  113. package/dist-esm/ui/comment-text.mjs.map +2 -2
  114. package/dist-esm/ui/comment-thread.mjs +6 -6
  115. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  116. package/dist-esm/ui/comments-list.mjs +12 -12
  117. package/dist-esm/ui/comments-list.mjs.map +2 -2
  118. package/dist-esm/ui/count-badge.mjs +1 -1
  119. package/dist-esm/ui/count-badge.mjs.map +2 -2
  120. package/dist-esm/ui/empty-state.mjs +3 -3
  121. package/dist-esm/ui/empty-state.mjs.map +2 -2
  122. package/dist-esm/ui/format-time.mjs +3 -0
  123. package/dist-esm/ui/format-time.mjs.map +2 -2
  124. package/dist-esm/ui/mention-list.mjs +8 -8
  125. package/dist-esm/ui/mention-list.mjs.map +2 -2
  126. package/dist-esm/ui/mention-suggestion.mjs +3 -3
  127. package/dist-esm/ui/mention-suggestion.mjs.map +2 -2
  128. package/dist-esm/ui/mention.mjs +1 -1
  129. package/dist-esm/ui/mention.mjs.map +2 -2
  130. package/dist-esm/ui/reaction.mjs +11 -4
  131. package/dist-esm/ui/reaction.mjs.map +2 -2
  132. package/dist-esm/ui/reactions.mjs +10 -2
  133. package/dist-esm/ui/reactions.mjs.map +2 -2
  134. package/dist-esm/ui/send-button.mjs +1 -1
  135. package/dist-esm/ui/send-button.mjs.map +2 -2
  136. package/package.json +3 -3
  137. package/src/canvas/canvas.css +79 -36
  138. package/src/canvas/comment-body.tsx +1 -1
  139. package/src/canvas/comment-drafts.test.ts +55 -0
  140. package/src/canvas/comment-drafts.ts +46 -0
  141. package/src/canvas/comment-render.test.ts +1 -1
  142. package/src/canvas/comment-render.ts +7 -2
  143. package/src/canvas/comment-tool.tsx +87 -15
  144. package/src/canvas/comments-filter-menu.tsx +14 -16
  145. package/src/canvas/comments-overflow-menu.tsx +11 -15
  146. package/src/canvas/comments-overlay.tsx +399 -141
  147. package/src/canvas/comments-sidebar.tsx +27 -28
  148. package/src/canvas/comments-visibility-toggle.tsx +51 -0
  149. package/src/canvas/sidebar-filters.ts +4 -3
  150. package/src/canvas/state.ts +22 -0
  151. package/src/canvas/thread-state.ts +13 -1
  152. package/src/clustering/runtime.test.ts +183 -7
  153. package/src/clustering/runtime.ts +164 -15
  154. package/src/index.ts +6 -0
  155. package/src/ui/avatar.tsx +15 -11
  156. package/src/ui/byline.tsx +6 -5
  157. package/src/ui/comment-author.ts +15 -0
  158. package/src/ui/comment-card.tsx +6 -5
  159. package/src/ui/comment-composer.tsx +122 -33
  160. package/src/ui/comment-mention.ts +2 -2
  161. package/src/ui/comment-pin.tsx +35 -4
  162. package/src/ui/comment-text.tsx +1 -1
  163. package/src/ui/comment-thread.tsx +8 -6
  164. package/src/ui/comments-list.tsx +19 -14
  165. package/src/ui/comments.css +201 -115
  166. package/src/ui/count-badge.tsx +1 -1
  167. package/src/ui/empty-state.tsx +3 -3
  168. package/src/ui/format-time.ts +5 -0
  169. package/src/ui/mention-list.tsx +13 -16
  170. package/src/ui/mention-suggestion.tsx +3 -3
  171. package/src/ui/mention.tsx +1 -1
  172. package/src/ui/reaction.tsx +6 -3
  173. package/src/ui/reactions.tsx +6 -2
  174. package/src/ui/send-button.tsx +1 -1
@@ -13,6 +13,11 @@ import {
13
13
  createCommentThread,
14
14
  getFirstCharacter,
15
15
  react,
16
+ TldrawUiDropdownMenuContent,
17
+ TldrawUiDropdownMenuGroup,
18
+ TldrawUiDropdownMenuItem,
19
+ TldrawUiDropdownMenuRoot,
20
+ TldrawUiDropdownMenuTrigger,
16
21
  TldrawUiIcon,
17
22
  useContainer,
18
23
  useEditor,
@@ -32,7 +37,14 @@ import { CountBadge } from "../ui/count-badge.mjs";
32
37
  import { isMentionPickerOpen } from "../ui/mention-suggestion.mjs";
33
38
  import { collectClusterLeaves } from "./cluster-input.mjs";
34
39
  import { CommentBody } from "./comment-body.mjs";
35
- import { UNKNOWN_AUTHOR } from "./comment-render.mjs";
40
+ import {
41
+ clearCommentDraft,
42
+ getCommentDraft,
43
+ NEW_COMMENT_DRAFT,
44
+ replyDraftSlot,
45
+ saveCommentDraft
46
+ } from "./comment-drafts.mjs";
47
+ import { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
36
48
  import { getCommentRecord, putCommentRecords, removeCommentRecords } from "./comment-store.mjs";
37
49
  import { useCommentThreads, useThreadComments } from "./hooks.mjs";
38
50
  import { useCommentingEnabled } from "./license.mjs";
@@ -53,12 +65,33 @@ import {
53
65
  toggleCommentsHidden,
54
66
  usePendingComment
55
67
  } from "./state.mjs";
56
- import { anchorPagePoint, regionPinPoint, shapeAnchorAt } from "./thread-state.mjs";
68
+ import {
69
+ anchorPagePoint,
70
+ regionAnchorPinCorner,
71
+ regionPinPoint,
72
+ shapeAnchorAt
73
+ } from "./thread-state.mjs";
57
74
  const stop = (e) => e.stopPropagation();
75
+ const IMPRECISE_PIN_INSET_PX = 20;
76
+ function impreciseShapePinInset(anchor, spot) {
77
+ if (anchor.type !== "shape" || anchor.isPrecise) return null;
78
+ return {
79
+ x: Math.sign(0.5 - spot.x) * IMPRECISE_PIN_INSET_PX,
80
+ y: Math.sign(0.5 - spot.y) * IMPRECISE_PIN_INSET_PX
81
+ };
82
+ }
83
+ const isCanvasPanGesture = (editor, e) => e.button !== 0 || editor.inputs.keys.has("Space");
84
+ function forwardPointerEventToCanvas(container, e) {
85
+ const cvs = container.querySelector(".tl-canvas");
86
+ if (!cvs) return;
87
+ const newEvent = new PointerEvent(e.type, e.nativeEvent);
88
+ newEvent.isSpecialRedispatchedEvent = true;
89
+ cvs.dispatchEvent(newEvent);
90
+ }
58
91
  const initialOf = (name) => (getFirstCharacter(name.trim()) || "?").toUpperCase();
59
92
  const CLUSTER_FADE_MS = 150;
60
93
  const CLUSTER_EXPAND_ZOOM_MS = 450;
61
- const draftAvatar = /* @__PURE__ */ jsx(CommentPin, { children: /* @__PURE__ */ jsxs(
94
+ const draftAvatar = (color) => /* @__PURE__ */ jsx(CommentPin, { color, children: /* @__PURE__ */ jsxs(
62
95
  "svg",
63
96
  {
64
97
  viewBox: "0 0 24 24",
@@ -76,11 +109,11 @@ const draftAvatar = /* @__PURE__ */ jsx(CommentPin, { children: /* @__PURE__ */
76
109
  ]
77
110
  }
78
111
  ) });
79
- function toCardProps(comment, props, components) {
112
+ function toCardProps(comment, props, components, resolveName) {
80
113
  const Body = components.CommentBody;
81
- const body = Body ? /* @__PURE__ */ jsx(Body, { comment }) : /* @__PURE__ */ jsx(CommentBody, { richText: comment.body, resolveName: props.resolveName });
114
+ const body = Body ? /* @__PURE__ */ jsx(Body, { comment }) : /* @__PURE__ */ jsx(CommentBody, { richText: comment.body, resolveName });
82
115
  return {
83
- author: props.resolveName(comment.authorId) ?? UNKNOWN_AUTHOR,
116
+ author: props.resolveAuthor(comment.authorId) ?? UNKNOWN_COMMENT_AUTHOR,
84
117
  body,
85
118
  date: new Date(comment.createdAt).toISOString(),
86
119
  you: comment.authorId === props.currentUserId,
@@ -102,24 +135,23 @@ function CanvasCommentsLayer(props) {
102
135
  );
103
136
  useEffect(() => setRegionCommentOptions(editor, regionOptions), [editor, regionOptions]);
104
137
  const layerRef = useRef(null);
105
- usePassThroughWheelEvents(layerRef);
106
138
  usePassThroughMouseOverEvents(layerRef);
107
139
  const deepLinkHandled = useRef(false);
108
140
  const threads = useCommentThreads(editor);
109
141
  const pending = usePendingComment();
110
142
  const openId = useValue("open thread id", () => openThreadId.get(editor), [editor]);
111
143
  const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
112
- const [movedThreadIds, setMovedThreadIds] = useState(EMPTY_SET);
144
+ const [heldThreadIds, setHeldThreadIds] = useState(EMPTY_SET);
113
145
  const adoptOnRebuild = useRef(false);
114
146
  const clusterLeaves = useValue(
115
147
  "comment cluster leaves",
116
148
  () => collectClusterLeaves(
117
149
  editor,
118
- threads.filter((thread) => !movedThreadIds.has(thread.id)),
150
+ threads.filter((thread) => !heldThreadIds.has(thread.id)),
119
151
  openThreadId.get(editor),
120
152
  impreciseShapeAnchor
121
153
  ),
122
- [editor, threads, impreciseShapeAnchor, movedThreadIds]
154
+ [editor, threads, impreciseShapeAnchor, heldThreadIds]
123
155
  );
124
156
  const clusterZoomBounds = useValue(
125
157
  "comment cluster zoom bounds",
@@ -134,21 +166,41 @@ function CanvasCommentsLayer(props) {
134
166
  }, [clusterLeaves, clusterZoomBounds, editor]);
135
167
  const [renderedModel, setRenderedModel] = useState(latestModel);
136
168
  let clusterModel = renderedModel;
137
- if (renderedModel !== latestModel && (adoptOnRebuild.current || hasRemovedLeaves(renderedModel.table, latestModel.table))) {
169
+ const pageId = useValue("comment cluster page", () => editor.getCurrentPageId(), [editor]);
170
+ const pageRef = useRef(pageId);
171
+ if (pageRef.current !== pageId) {
172
+ pageRef.current = pageId;
173
+ adoptOnRebuild.current = false;
174
+ latestModel.runtime.seed(editor.getZoomLevel());
175
+ if (heldThreadIds.size > 0) setHeldThreadIds(EMPTY_SET);
176
+ setRenderedModel(latestModel);
177
+ clusterModel = latestModel;
178
+ }
179
+ const rejoinPending = heldThreadIds.size === 0 && adoptOnRebuild.current;
180
+ if (renderedModel !== latestModel && rejoinPending) {
138
181
  adoptOnRebuild.current = false;
139
182
  latestModel.runtime.seedFrom(editor.getZoomLevel(), renderedModel.runtime.getVisible());
140
183
  setRenderedModel(latestModel);
141
184
  clusterModel = latestModel;
185
+ } else if (heldThreadIds.size === 0 && renderedModel === latestModel) {
186
+ adoptOnRebuild.current = false;
142
187
  }
143
188
  const newlyMovedIds = findMovedClusteredLeafIds(clusterModel, latestModel);
144
189
  if (newlyMovedIds.length > 0) {
145
- console.debug(`[comments] pins popped out of clustering: ${newlyMovedIds.join(", ")}`);
146
- const next = new Set(movedThreadIds);
190
+ const next = new Set(heldThreadIds);
147
191
  for (const id of newlyMovedIds) next.add(id);
148
- setMovedThreadIds(next);
192
+ setHeldThreadIds(next);
193
+ }
194
+ {
195
+ const latestLeafIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id));
196
+ for (const leaf of clusterModel.table.leaves) {
197
+ if (!latestLeafIds.has(leaf.id)) {
198
+ clusterModel.runtime.detachLeaf(leaf.id);
199
+ }
200
+ }
149
201
  }
150
202
  useEffect(() => {
151
- if (movedThreadIds.size === 0) return;
203
+ if (heldThreadIds.size === 0) return;
152
204
  let lastZoom = editor.getZoomLevel();
153
205
  return react("rejoin moved comment pins on zoom out", () => {
154
206
  const zoom = editor.getZoomLevel();
@@ -156,9 +208,9 @@ function CanvasCommentsLayer(props) {
156
208
  lastZoom = zoom;
157
209
  if (zoom >= prevZoom) return;
158
210
  adoptOnRebuild.current = true;
159
- setMovedThreadIds(EMPTY_SET);
211
+ setHeldThreadIds(EMPTY_SET);
160
212
  });
161
- }, [movedThreadIds, editor]);
213
+ }, [heldThreadIds, editor]);
162
214
  useEffect(() => {
163
215
  if (clusterModel === latestModel) return;
164
216
  let lastZoom = editor.getZoomLevel();
@@ -171,31 +223,31 @@ function CanvasCommentsLayer(props) {
171
223
  setRenderedModel(latestModel);
172
224
  });
173
225
  }, [clusterModel, latestModel, editor]);
226
+ const partitionVersion = clusterModel.runtime.version;
174
227
  const orphanThreads = useMemo(() => {
175
228
  if (clusterModel === latestModel) return [];
176
- const renderedIds = new Set(clusterModel.table.leaves.map((leaf) => leaf.id));
229
+ const displayed = /* @__PURE__ */ new Set();
230
+ for (const node of clusterModel.runtime.getVisible().values()) {
231
+ for (const member of node.members) displayed.add(member);
232
+ }
177
233
  const latestIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id));
178
- return threads.filter((thread) => latestIds.has(thread.id) && !renderedIds.has(thread.id));
179
- }, [clusterModel, latestModel, threads]);
180
- const movedThreads = useMemo(
181
- () => threads.filter((thread) => movedThreadIds.has(thread.id) && thread.id !== openId),
182
- [threads, movedThreadIds, openId]
234
+ return threads.filter((thread) => latestIds.has(thread.id) && !displayed.has(thread.id));
235
+ }, [clusterModel, latestModel, threads, partitionVersion]);
236
+ const heldThreads = useMemo(
237
+ () => threads.filter((thread) => heldThreadIds.has(thread.id) && thread.id !== openId),
238
+ [threads, heldThreadIds, openId]
183
239
  );
184
- const clusterCursor = useValue(
185
- "comment cluster cursor",
240
+ useValue(
241
+ "comment cluster version",
186
242
  () => {
187
243
  clusterModel.runtime.onCamera(editor.getZoomLevel());
188
- return clusterModel.runtime.k;
244
+ return clusterModel.runtime.version;
189
245
  },
190
246
  [clusterModel, editor]
191
247
  );
192
248
  const visibleNodes = useMemo(() => {
193
- const nodes = Array.from(clusterModel.runtime.getVisible().values());
194
- console.debug(
195
- `[comments] cluster cursor k=${clusterCursor} \u2192 re-rendering ${nodes.length} visible nodes`
196
- );
197
- return nodes;
198
- }, [clusterModel, clusterCursor]);
249
+ return Array.from(clusterModel.runtime.getVisible().values());
250
+ }, [clusterModel, partitionVersion]);
199
251
  const fadeNodes = useFadeVisibleNodes(visibleNodes, clusterModel);
200
252
  const threadsById = useMemo(
201
253
  () => new Map(threads.map((thread) => [thread.id, thread])),
@@ -254,7 +306,7 @@ function CanvasCommentsLayer(props) {
254
306
  if (e.key !== "Escape" || openThreadId.get(editor) === null) return;
255
307
  if (isMentionPickerOpen()) return;
256
308
  const target = e.target;
257
- if (target && target.closest(".cmt-editing")) return;
309
+ if (target && target.closest(".tlui-cmt-editing")) return;
258
310
  openThreadId.set(editor, null);
259
311
  e.preventDefault();
260
312
  e.stopPropagation();
@@ -275,7 +327,7 @@ function CanvasCommentsLayer(props) {
275
327
  }, [editor]);
276
328
  if (hidden) return null;
277
329
  return createPortal(
278
- /* @__PURE__ */ jsxs("div", { ref: layerRef, className: "cmt-canvas-layer", children: [
330
+ /* @__PURE__ */ jsxs("div", { ref: layerRef, className: "tlui-cmt-canvas-layer", children: [
279
331
  options.enableClustering ? /* @__PURE__ */ jsxs(Fragment, { children: [
280
332
  fadeNodes.map(({ node, phase }) => {
281
333
  let content;
@@ -306,7 +358,7 @@ function CanvasCommentsLayer(props) {
306
358
  },
307
359
  thread.id
308
360
  )),
309
- movedThreads.map((thread) => /* @__PURE__ */ jsx(
361
+ heldThreads.map((thread) => /* @__PURE__ */ jsx(
310
362
  ThreadPin,
311
363
  {
312
364
  editor,
@@ -415,7 +467,7 @@ function cancelClusterFadeFrame(frame) {
415
467
  else window.clearTimeout(frame);
416
468
  }
417
469
  function clusterFadeClassName(phase) {
418
- return `cmt-cluster-fade cmt-cluster-fade--${phase}`;
470
+ return `tlui-cmt-cluster-fade tlui-cmt-cluster-fade--${phase}`;
419
471
  }
420
472
  function findMovedClusteredLeafIds(rendered, latest) {
421
473
  if (rendered.table === latest.table) return [];
@@ -432,11 +484,6 @@ function findMovedClusteredLeafIds(rendered, latest) {
432
484
  }
433
485
  return moved;
434
486
  }
435
- function hasRemovedLeaves(rendered, latest) {
436
- if (rendered.leaves.length === 0) return false;
437
- const latestIds = new Set(latest.leaves.map((leaf) => leaf.id));
438
- return rendered.leaves.some((leaf) => !latestIds.has(leaf.id));
439
- }
440
487
  function getClusterZoomBounds(editor) {
441
488
  const cameraOptions = editor.getCameraOptions();
442
489
  const baseZoom = cameraOptions.constraints ? editor.getBaseZoom() : 1;
@@ -488,6 +535,9 @@ const ClusterBadge = memo(function ClusterBadge2({
488
535
  node,
489
536
  onExpand
490
537
  }) {
538
+ const container = useContainer();
539
+ const badgeRef = useRef(null);
540
+ usePassThroughWheelEvents(badgeRef);
491
541
  const point = useValue(
492
542
  "cluster badge point",
493
543
  () => {
@@ -501,9 +551,16 @@ const ClusterBadge = memo(function ClusterBadge2({
501
551
  return /* @__PURE__ */ jsx(
502
552
  "div",
503
553
  {
504
- className: "cmt-canvas-cluster",
554
+ ref: badgeRef,
555
+ className: "tlui-cmt-canvas-cluster",
505
556
  style: { left: point.x, top: point.y },
506
- onPointerDown: stop,
557
+ onPointerDown: (e) => {
558
+ if (isCanvasPanGesture(editor, e)) {
559
+ forwardPointerEventToCanvas(container, e);
560
+ return;
561
+ }
562
+ e.stopPropagation();
563
+ },
507
564
  onClick: (e) => {
508
565
  e.stopPropagation();
509
566
  onExpand(node);
@@ -526,7 +583,19 @@ function ThreadPopover({
526
583
  usePassThroughWheelEvents(ref);
527
584
  usePassThroughMouseOverEvents(ref);
528
585
  return createPortal(
529
- /* @__PURE__ */ jsx("div", { ref, className: "cmt-canvas-popover", style, onPointerDown: stop, children }),
586
+ // contextmenu also stops here: portals bubble React events to the canvas's context-menu
587
+ // trigger (the layer mounts inside it), which would open the canvas menu over this panel.
588
+ /* @__PURE__ */ jsx(
589
+ "div",
590
+ {
591
+ ref,
592
+ className: "tlui-cmt-canvas-popover",
593
+ style,
594
+ onPointerDown: stop,
595
+ onContextMenu: stop,
596
+ children
597
+ }
598
+ ),
530
599
  container
531
600
  );
532
601
  }
@@ -547,12 +616,19 @@ function RegionBox({
547
616
  [editor, box.x, box.y, box.w, box.h]
548
617
  );
549
618
  const grabRef = useRef(null);
619
+ const container = useContainer();
620
+ const boxRef = useRef(null);
621
+ usePassThroughWheelEvents(boxRef);
550
622
  const translated = (e) => {
551
623
  const g = grabRef.current;
552
624
  const p = editor.screenToPage({ x: e.clientX, y: e.clientY });
553
625
  return { ...g.box, x: g.box.x + (p.x - g.page.x), y: g.box.y + (p.y - g.page.y) };
554
626
  };
555
627
  const startMove = (e) => {
628
+ if (isCanvasPanGesture(editor, e)) {
629
+ forwardPointerEventToCanvas(container, e);
630
+ return;
631
+ }
556
632
  e.stopPropagation();
557
633
  grabRef.current = { page: editor.screenToPage({ x: e.clientX, y: e.clientY }), box };
558
634
  e.currentTarget.setPointerCapture(e.pointerId);
@@ -571,7 +647,8 @@ function RegionBox({
571
647
  return /* @__PURE__ */ jsx(
572
648
  "div",
573
649
  {
574
- className: movable ? "cmt-canvas-region cmt-canvas-region--movable" : "cmt-canvas-region",
650
+ ref: boxRef,
651
+ className: movable ? "tlui-cmt-canvas-region tlui-cmt-canvas-region--movable" : "tlui-cmt-canvas-region",
575
652
  style: rect,
576
653
  onPointerDown: movable ? startMove : void 0,
577
654
  onPointerMove: movable ? onMove : void 0,
@@ -645,7 +722,7 @@ function RegionResizeHandles({
645
722
  return /* @__PURE__ */ jsx(Fragment, { children: points.map((h) => /* @__PURE__ */ jsx(
646
723
  "div",
647
724
  {
648
- className: "cmt-canvas-region-handle",
725
+ className: "tlui-cmt-canvas-region-handle",
649
726
  style: { left: h.left, top: h.top, cursor: h.cursor },
650
727
  onPointerDown: startResize,
651
728
  onPointerMove: onResize(h),
@@ -662,13 +739,15 @@ const ThreadPin = memo(function ThreadPin2({
662
739
  }) {
663
740
  const {
664
741
  currentUserId,
665
- resolveName,
742
+ resolveAuthor,
666
743
  onPostComment,
667
744
  isCommentUnread,
668
745
  onCommentRead,
669
746
  getMentionSuggestions,
670
747
  renderMentionSuggestion
671
748
  } = props;
749
+ const resolveName = useCallback((id) => resolveAuthor(id)?.name, [resolveAuthor]);
750
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
672
751
  const options = useCommentingOptions();
673
752
  const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
674
753
  const container = useContainer();
@@ -678,7 +757,9 @@ const ThreadPin = memo(function ThreadPin2({
678
757
  editor,
679
758
  thread.id
680
759
  ]);
681
- const [reply, setReply] = useState(EMPTY_COMMENT);
760
+ const [reply, setReply] = useState(
761
+ () => getCommentDraft(replyDraftSlot(thread.id)) ?? EMPTY_COMMENT
762
+ );
682
763
  const [editingId, setEditingId] = useState(null);
683
764
  const [editText, setEditText] = useState(EMPTY_COMMENT);
684
765
  const [dragPagePoint, setDragPagePoint] = useState(null);
@@ -697,24 +778,24 @@ const ThreadPin = memo(function ThreadPin2({
697
778
  [editor, thread.anchor, thread.pageId]
698
779
  );
699
780
  const revealed = open || resizeBounds != null || regionOptions.reveal === "pointer" && pointerInRegion || regionOptions.reveal === "pin-hover" && pinHovered;
781
+ const pinCorner = thread.anchor.type === "region" ? regionAnchorPinCorner(editor, thread.anchor) : regionOptions.pinCorner;
700
782
  const resizeHandles = useMemo(
701
- () => regionOptions.resize === "edges" ? REGION_EDGES : REGION_CORNERS.filter(
702
- (c) => c.x !== regionOptions.pinCorner.x || c.y !== regionOptions.pinCorner.y
703
- ),
704
- [regionOptions.resize, regionOptions.pinCorner]
783
+ () => regionOptions.resize === "edges" ? REGION_EDGES : REGION_CORNERS.filter((c) => c.x !== pinCorner.x || c.y !== pinCorner.y),
784
+ [regionOptions.resize, pinCorner]
705
785
  );
706
786
  const dragRef = useRef(null);
707
787
  const markerRef = useRef(null);
788
+ usePassThroughWheelEvents(markerRef);
708
789
  useEffect(() => {
709
790
  if (!open) return;
710
791
  const onPointerDown = (e) => {
711
792
  const target = e.target;
712
793
  if (!target) return;
713
- if (target.closest(".cmt-canvas-popover")) return;
794
+ if (target.closest(".tlui-cmt-canvas-popover")) return;
714
795
  const marker = markerRef.current;
715
796
  if (marker && marker.contains(target)) return;
716
- if (target.closest(".cmt-canvas-region-handle, .cmt-canvas-region--movable")) return;
717
- if (target.closest(".tlui-menu, [data-radix-popper-content-wrapper], .cmt-mention-popup"))
797
+ if (target.closest(".tlui-cmt-canvas-region-handle, .tlui-cmt-canvas-region--movable")) return;
798
+ if (target.closest(".tlui-menu, [data-radix-popper-content-wrapper], .tlui-cmt-mention-popup"))
718
799
  return;
719
800
  openThreadId.set(editor, null);
720
801
  };
@@ -726,7 +807,10 @@ const ThreadPin = memo(function ThreadPin2({
726
807
  () => {
727
808
  if (thread.pageId !== editor.getCurrentPageId()) return null;
728
809
  const pagePoint = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor);
729
- return pagePoint ? editor.pageToViewport(pagePoint) : null;
810
+ if (!pagePoint) return null;
811
+ const viewportPoint = editor.pageToViewport(pagePoint);
812
+ const inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor);
813
+ return inset ? { x: viewportPoint.x + inset.x, y: viewportPoint.y + inset.y } : viewportPoint;
730
814
  },
731
815
  [editor, thread.anchor, thread.pageId, impreciseShapeAnchor]
732
816
  );
@@ -753,6 +837,7 @@ const ThreadPin = memo(function ThreadPin2({
753
837
  if (onPostComment) onPostComment(comment);
754
838
  });
755
839
  setReply(EMPTY_COMMENT);
840
+ clearCommentDraft(replyDraftSlot(thread.id));
756
841
  };
757
842
  const toggleResolve = () => {
758
843
  if (!currentUserId) return;
@@ -776,6 +861,16 @@ const ThreadPin = memo(function ThreadPin2({
776
861
  setEditingId(comment.id);
777
862
  setEditText(comment.body);
778
863
  };
864
+ const deleteComment = (comment) => {
865
+ commitCommentMutation(editor, () => {
866
+ if (comments.length === 1) {
867
+ openThreadId.set(editor, null);
868
+ removeCommentRecords(editor, [thread.id, comment.id]);
869
+ } else {
870
+ removeCommentRecords(editor, [comment.id]);
871
+ }
872
+ });
873
+ };
779
874
  const saveEdit = () => {
780
875
  const comment = comments.find((c) => c.id === editingId);
781
876
  if (!comment || isCommentEmpty(editText)) return;
@@ -790,7 +885,7 @@ const ThreadPin = memo(function ThreadPin2({
790
885
  return /* @__PURE__ */ jsx(
791
886
  "div",
792
887
  {
793
- className: "cmt-editing",
888
+ className: "tlui-cmt-editing",
794
889
  onKeyDown: (e) => {
795
890
  if (e.key === "Escape") {
796
891
  setEditingId(null);
@@ -819,15 +914,46 @@ const ThreadPin = memo(function ThreadPin2({
819
914
  CommentCard,
820
915
  {
821
916
  ...card,
822
- actions: comment.authorId === currentUserId ? /* @__PURE__ */ jsx(
823
- "button",
824
- {
825
- className: "cmt-thread__action",
826
- title: msg("comments.edit"),
827
- onClick: () => startEdit(comment),
828
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-horizontal", label: msg("comments.edit"), small: true })
829
- }
830
- ) : void 0
917
+ actions: comment.authorId === currentUserId ? /* @__PURE__ */ jsxs(TldrawUiDropdownMenuRoot, { id: `comment-actions-${comment.id}`, children: [
918
+ /* @__PURE__ */ jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsx(
919
+ "button",
920
+ {
921
+ type: "button",
922
+ className: "tlui-cmt-thread__action",
923
+ title: msg("comments.more-options"),
924
+ children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
925
+ }
926
+ ) }),
927
+ /* @__PURE__ */ jsx(
928
+ TldrawUiDropdownMenuContent,
929
+ {
930
+ className: "tlui-cmt-menu",
931
+ side: "bottom",
932
+ align: "end",
933
+ alignOffset: 0,
934
+ children: /* @__PURE__ */ jsxs(TldrawUiDropdownMenuGroup, { children: [
935
+ /* @__PURE__ */ jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsx(
936
+ "button",
937
+ {
938
+ type: "button",
939
+ className: "tlui-cmt-menu-item",
940
+ onClick: () => startEdit(comment),
941
+ children: /* @__PURE__ */ jsx("span", { children: msg("comments.edit-comment") })
942
+ }
943
+ ) }),
944
+ /* @__PURE__ */ jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsx(
945
+ "button",
946
+ {
947
+ type: "button",
948
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
949
+ onClick: () => deleteComment(comment),
950
+ children: /* @__PURE__ */ jsx("span", { children: msg("comments.delete-comment") })
951
+ }
952
+ ) })
953
+ ] })
954
+ }
955
+ )
956
+ ] }) : void 0
831
957
  }
832
958
  );
833
959
  };
@@ -835,7 +961,7 @@ const ThreadPin = memo(function ThreadPin2({
835
961
  currentUserId && /* @__PURE__ */ jsx(
836
962
  "button",
837
963
  {
838
- className: "cmt-thread__action",
964
+ className: "tlui-cmt-thread__action",
839
965
  title: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
840
966
  onClick: toggleResolve,
841
967
  children: /* @__PURE__ */ jsx(
@@ -848,33 +974,63 @@ const ThreadPin = memo(function ThreadPin2({
848
974
  )
849
975
  }
850
976
  ),
851
- currentUserId && /* @__PURE__ */ jsx(
852
- "button",
853
- {
854
- className: "cmt-thread__action",
855
- title: msg("comments.delete"),
856
- onClick: deleteThread,
857
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "trash", label: msg("comments.delete"), small: true })
858
- }
859
- ),
860
- /* @__PURE__ */ jsx(
861
- "button",
862
- {
863
- className: "cmt-thread__action",
864
- title: msg("comments.dismiss"),
865
- onClick: () => openThreadId.set(editor, null),
866
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "cross-2", label: msg("comments.dismiss"), small: true })
867
- }
868
- )
977
+ currentUserId && /* @__PURE__ */ jsxs(TldrawUiDropdownMenuRoot, { id: `comment-thread-actions-${thread.id}`, children: [
978
+ /* @__PURE__ */ jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsx(
979
+ "button",
980
+ {
981
+ type: "button",
982
+ className: "tlui-cmt-thread__action",
983
+ title: msg("comments.more-options"),
984
+ children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
985
+ }
986
+ ) }),
987
+ /* @__PURE__ */ jsx(
988
+ TldrawUiDropdownMenuContent,
989
+ {
990
+ className: "tlui-cmt-menu",
991
+ side: "bottom",
992
+ align: "end",
993
+ alignOffset: 0,
994
+ children: /* @__PURE__ */ jsx(TldrawUiDropdownMenuGroup, { children: /* @__PURE__ */ jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsx(
995
+ "button",
996
+ {
997
+ type: "button",
998
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
999
+ onClick: deleteThread,
1000
+ children: /* @__PURE__ */ jsx("span", { children: msg("comments.delete") })
1001
+ }
1002
+ ) }) })
1003
+ }
1004
+ )
1005
+ ] })
869
1006
  ] });
870
1007
  const PinContent = options.components.PinContent;
871
- const pinContent = PinContent ? /* @__PURE__ */ jsx(PinContent, { thread, comments }) : initialOf(resolveName(thread.createdBy) ?? UNKNOWN_AUTHOR);
1008
+ const threadAuthor = resolveAuthor(thread.createdBy);
1009
+ const pinContent = PinContent ? /* @__PURE__ */ jsx(PinContent, { thread, comments }) : initialOf(threadAuthor?.name ?? UNKNOWN_AUTHOR);
872
1010
  const isRegion = thread.anchor.type === "region";
873
1011
  const pinMovable = regionOptions.move !== "body";
874
1012
  const bodyMovable = regionOptions.move !== "pin";
875
1013
  const startDrag = (e) => {
1014
+ if (isCanvasPanGesture(editor, e)) {
1015
+ forwardPointerEventToCanvas(container, e);
1016
+ return;
1017
+ }
876
1018
  e.stopPropagation();
877
- dragRef.current = { startX: e.clientX, startY: e.clientY, moved: false };
1019
+ const grabPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
1020
+ const anchorPage = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor);
1021
+ const inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor);
1022
+ if (anchorPage && inset) {
1023
+ const zoom = editor.getZoomLevel();
1024
+ anchorPage.x += inset.x / zoom;
1025
+ anchorPage.y += inset.y / zoom;
1026
+ }
1027
+ dragRef.current = {
1028
+ startX: e.clientX,
1029
+ startY: e.clientY,
1030
+ moved: false,
1031
+ offsetX: anchorPage ? anchorPage.x - grabPage.x : 0,
1032
+ offsetY: anchorPage ? anchorPage.y - grabPage.y : 0
1033
+ };
878
1034
  e.currentTarget.setPointerCapture(e.pointerId);
879
1035
  };
880
1036
  const onDrag = (e) => {
@@ -883,7 +1039,8 @@ const ThreadPin = memo(function ThreadPin2({
883
1039
  if (isRegion && !pinMovable) return;
884
1040
  if (!drag.moved && Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < 4) return;
885
1041
  drag.moved = true;
886
- setDragPagePoint(editor.screenToPage({ x: e.clientX, y: e.clientY }));
1042
+ const cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
1043
+ setDragPagePoint({ x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY });
887
1044
  };
888
1045
  const endDrag = (e) => {
889
1046
  const drag = dragRef.current;
@@ -896,14 +1053,15 @@ const ThreadPin = memo(function ThreadPin2({
896
1053
  openThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id);
897
1054
  return;
898
1055
  }
899
- const pagePoint = editor.screenToPage({ x: e.clientX, y: e.clientY });
1056
+ const cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
1057
+ const pagePoint = { x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY };
900
1058
  setDragPagePoint(null);
901
1059
  let anchor;
902
1060
  if (thread.anchor.type === "region") {
903
1061
  anchor = {
904
1062
  ...thread.anchor,
905
- x: pagePoint.x - regionOptions.pinCorner.x * thread.anchor.w,
906
- y: pagePoint.y - regionOptions.pinCorner.y * thread.anchor.h
1063
+ x: pagePoint.x - pinCorner.x * thread.anchor.w,
1064
+ y: pagePoint.y - pinCorner.y * thread.anchor.h
907
1065
  };
908
1066
  } else {
909
1067
  const hit = editor.getShapeAtPoint(pagePoint, { hitInside: true });
@@ -911,19 +1069,21 @@ const ThreadPin = memo(function ThreadPin2({
911
1069
  }
912
1070
  commitCommentMutation(editor, () => putCommentRecords(editor, [{ ...thread, anchor }]), "drag");
913
1071
  };
914
- const livePinPage = resizeBounds ? regionPinPoint(resizeBounds, regionOptions.pinCorner) : dragPagePoint;
915
- const renderPoint = livePinPage ? editor.pageToViewport(livePinPage) : point;
1072
+ const livePinPage = resizeBounds ? regionPinPoint(resizeBounds, pinCorner) : dragPagePoint;
1073
+ const renderPointBase = livePinPage ? editor.pageToViewport(livePinPage) : point;
1074
+ const renderPoint = isRegion ? { x: renderPointBase.x - 17, y: renderPointBase.y + 17 } : renderPointBase;
916
1075
  const regionAnchor = thread.anchor.type === "region" ? thread.anchor : void 0;
917
1076
  const movedRegion = regionAnchor && dragPagePoint ? {
918
1077
  ...regionAnchor,
919
- x: dragPagePoint.x - regionOptions.pinCorner.x * regionAnchor.w,
920
- y: dragPagePoint.y - regionOptions.pinCorner.y * regionAnchor.h
1078
+ x: dragPagePoint.x - pinCorner.x * regionAnchor.w,
1079
+ y: dragPagePoint.y - pinCorner.y * regionAnchor.h
921
1080
  } : regionAnchor;
922
1081
  const regionBoxBounds = resizeBounds ?? movedRegion;
923
1082
  const commitResize = (bounds) => {
924
1083
  setResizeBounds(null);
925
1084
  editor.run(
926
- () => putCommentRecords(editor, [{ ...thread, anchor: { type: "region", ...bounds } }]),
1085
+ // Spread the existing anchor first so the region's pin corner survives a resize.
1086
+ () => putCommentRecords(editor, [{ ...thread, anchor: { ...regionAnchor, ...bounds } }]),
927
1087
  {
928
1088
  history: "ignore"
929
1089
  }
@@ -953,44 +1113,51 @@ const ThreadPin = memo(function ThreadPin2({
953
1113
  /* @__PURE__ */ jsxs(
954
1114
  "div",
955
1115
  {
956
- className: open ? "cmt-canvas-pin cmt-canvas-pin--open" : "cmt-canvas-pin",
1116
+ className: [
1117
+ "tlui-cmt-canvas-pin",
1118
+ open && "tlui-cmt-canvas-pin--open",
1119
+ dragPagePoint && "tlui-cmt-canvas-pin--dragging"
1120
+ ].filter(Boolean).join(" "),
957
1121
  style: { left: renderPoint.x, top: renderPoint.y },
958
1122
  children: [
959
1123
  /* @__PURE__ */ jsx(
960
1124
  "div",
961
1125
  {
962
1126
  ref: markerRef,
963
- className: "cmt-canvas-pin__marker",
1127
+ className: "tlui-cmt-canvas-pin__marker",
964
1128
  onPointerDown: startDrag,
965
1129
  onPointerMove: onDrag,
966
1130
  onPointerUp: endDrag,
967
1131
  onPointerEnter: () => setPinHovered(true),
968
1132
  onPointerLeave: () => setPinHovered(false),
969
- children: /* @__PURE__ */ jsx(CommentPin, { resolved: thread.resolved != null, open, children: pinContent })
1133
+ children: /* @__PURE__ */ jsx(CommentPin, { resolved: thread.resolved != null, open, color: threadAuthor?.color, children: pinContent })
970
1134
  }
971
1135
  ),
972
1136
  open && /* @__PURE__ */ jsx(
973
1137
  ThreadPopover,
974
1138
  {
975
1139
  container,
976
- style: { left: renderPoint.x + 36, top: renderPoint.y - 28 },
1140
+ style: { left: renderPoint.x + 48, top: renderPoint.y - 54 },
977
1141
  children: /* @__PURE__ */ jsx(
978
1142
  CommentThread,
979
1143
  {
980
1144
  header: msg("comments.thread-title"),
981
1145
  headerActions,
982
1146
  renderComment,
983
- comments: comments.map((c) => toCardProps(c, props, options.components)),
1147
+ comments: comments.map((c) => toCardProps(c, props, options.components, resolveName)),
984
1148
  resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
985
1149
  "{name}",
986
- resolveName(thread.resolved.by) ?? UNKNOWN_AUTHOR
1150
+ resolveAuthor(thread.resolved.by)?.name ?? UNKNOWN_AUTHOR
987
1151
  ) : void 0,
988
1152
  composer: currentUserId && !thread.resolved ? {
989
- author: resolveName(currentUserId) ?? UNKNOWN_AUTHOR,
1153
+ author: me ?? UNKNOWN_COMMENT_AUTHOR,
990
1154
  placeholder: msg("comments.reply-placeholder"),
991
1155
  sendLabel: msg("comments.send"),
992
1156
  value: reply,
993
- onChange: setReply,
1157
+ onChange: (value) => {
1158
+ setReply(value);
1159
+ saveCommentDraft(replyDraftSlot(thread.id), value);
1160
+ },
994
1161
  onSubmit: postReply,
995
1162
  disabled: isCommentEmpty(reply),
996
1163
  getMentionSuggestions,
@@ -1009,12 +1176,15 @@ function PendingComposer({
1009
1176
  editor,
1010
1177
  pending,
1011
1178
  currentUserId,
1012
- resolveName,
1179
+ resolveAuthor,
1013
1180
  onPostComment,
1014
1181
  getMentionSuggestions,
1015
1182
  renderMentionSuggestion
1016
1183
  }) {
1017
- const [text, setText] = useState(EMPTY_COMMENT);
1184
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
1185
+ const [text, setText] = useState(
1186
+ () => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT
1187
+ );
1018
1188
  const ref = useRef(null);
1019
1189
  const msg = useTranslation();
1020
1190
  const container = useContainer();
@@ -1029,7 +1199,7 @@ function PendingComposer({
1029
1199
  const el = ref.current;
1030
1200
  const target = e.target;
1031
1201
  if (!el || !target) return;
1032
- if (el.contains(target) || target.closest(".cmt-mention-popup")) return;
1202
+ if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
1033
1203
  pendingComment.set(editor, null);
1034
1204
  };
1035
1205
  document.addEventListener("pointerdown", onPointerDown, true);
@@ -1054,6 +1224,7 @@ function PendingComposer({
1054
1224
  if (onPostComment) onPostComment(comment);
1055
1225
  });
1056
1226
  setText(EMPTY_COMMENT);
1227
+ clearCommentDraft(NEW_COMMENT_DRAFT);
1057
1228
  pendingComment.set(editor, null);
1058
1229
  };
1059
1230
  return createPortal(
@@ -1061,26 +1232,33 @@ function PendingComposer({
1061
1232
  "div",
1062
1233
  {
1063
1234
  ref,
1064
- className: "cmt-canvas-composer",
1235
+ className: [
1236
+ "tlui-cmt-canvas-composer",
1237
+ pending.anchor.type === "region" && "tlui-cmt-canvas-composer--region"
1238
+ ].filter(Boolean).join(" "),
1065
1239
  style: { left: point.x, top: point.y },
1066
1240
  onPointerDown: stop,
1241
+ onContextMenu: stop,
1067
1242
  onKeyDown: (e) => {
1068
1243
  if (e.key === "Escape" && !isMentionPickerOpen()) pendingComment.set(editor, null);
1069
1244
  },
1070
1245
  children: /* @__PURE__ */ jsx(
1071
1246
  CommentComposer,
1072
1247
  {
1073
- author: currentUserId ? resolveName(currentUserId) ?? UNKNOWN_AUTHOR : "",
1248
+ author: me ?? UNKNOWN_COMMENT_AUTHOR,
1074
1249
  placeholder: msg("comments.add-placeholder"),
1075
1250
  sendLabel: msg("comments.send"),
1076
1251
  value: text,
1077
- onChange: setText,
1252
+ onChange: (value) => {
1253
+ setText(value);
1254
+ saveCommentDraft(NEW_COMMENT_DRAFT, value);
1255
+ },
1078
1256
  onSubmit: submit,
1079
1257
  disabled: isCommentEmpty(text),
1080
1258
  getMentionSuggestions,
1081
1259
  renderMentionSuggestion,
1082
1260
  autoFocus: true,
1083
- leading: draftAvatar
1261
+ leading: draftAvatar(me?.color)
1084
1262
  }
1085
1263
  )
1086
1264
  }