@tldraw/commenting 0.0.0-bootstrap → 5.3.0-internal.1640468db8fd

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