@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
@@ -36,6 +36,7 @@ var import_count_badge = require("../ui/count-badge");
36
36
  var import_mention_suggestion = require("../ui/mention-suggestion");
37
37
  var import_cluster_input = require("./cluster-input");
38
38
  var import_comment_body = require("./comment-body");
39
+ var import_comment_drafts = require("./comment-drafts");
39
40
  var import_comment_render = require("./comment-render");
40
41
  var import_comment_store = require("./comment-store");
41
42
  var import_hooks = require("./hooks");
@@ -45,10 +46,26 @@ var import_region_options = require("./region-options");
45
46
  var import_state = require("./state");
46
47
  var import_thread_state = require("./thread-state");
47
48
  const stop = (e) => e.stopPropagation();
49
+ const IMPRECISE_PIN_INSET_PX = 20;
50
+ function impreciseShapePinInset(anchor, spot) {
51
+ if (anchor.type !== "shape" || anchor.isPrecise) return null;
52
+ return {
53
+ x: Math.sign(0.5 - spot.x) * IMPRECISE_PIN_INSET_PX,
54
+ y: Math.sign(0.5 - spot.y) * IMPRECISE_PIN_INSET_PX
55
+ };
56
+ }
57
+ const isCanvasPanGesture = (editor, e) => e.button !== 0 || editor.inputs.keys.has("Space");
58
+ function forwardPointerEventToCanvas(container, e) {
59
+ const cvs = container.querySelector(".tl-canvas");
60
+ if (!cvs) return;
61
+ const newEvent = new PointerEvent(e.type, e.nativeEvent);
62
+ newEvent.isSpecialRedispatchedEvent = true;
63
+ cvs.dispatchEvent(newEvent);
64
+ }
48
65
  const initialOf = (name) => ((0, import_tldraw.getFirstCharacter)(name.trim()) || "?").toUpperCase();
49
66
  const CLUSTER_FADE_MS = 150;
50
67
  const CLUSTER_EXPAND_ZOOM_MS = 450;
51
- const draftAvatar = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
68
+ const draftAvatar = (color) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { color, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
52
69
  "svg",
53
70
  {
54
71
  viewBox: "0 0 24 24",
@@ -66,11 +83,11 @@ const draftAvatar = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_p
66
83
  ]
67
84
  }
68
85
  ) });
69
- function toCardProps(comment, props, components) {
86
+ function toCardProps(comment, props, components, resolveName) {
70
87
  const Body = components.CommentBody;
71
- const body = Body ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Body, { comment }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_body.CommentBody, { richText: comment.body, resolveName: props.resolveName });
88
+ const body = Body ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Body, { comment }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_body.CommentBody, { richText: comment.body, resolveName });
72
89
  return {
73
- author: props.resolveName(comment.authorId) ?? import_comment_render.UNKNOWN_AUTHOR,
90
+ author: props.resolveAuthor(comment.authorId) ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
74
91
  body,
75
92
  date: new Date(comment.createdAt).toISOString(),
76
93
  you: comment.authorId === props.currentUserId,
@@ -92,24 +109,23 @@ function CanvasCommentsLayer(props) {
92
109
  );
93
110
  (0, import_react.useEffect)(() => (0, import_region_options.setRegionCommentOptions)(editor, regionOptions), [editor, regionOptions]);
94
111
  const layerRef = (0, import_react.useRef)(null);
95
- (0, import_tldraw.usePassThroughWheelEvents)(layerRef);
96
112
  (0, import_tldraw.usePassThroughMouseOverEvents)(layerRef);
97
113
  const deepLinkHandled = (0, import_react.useRef)(false);
98
114
  const threads = (0, import_hooks.useCommentThreads)(editor);
99
115
  const pending = (0, import_state.usePendingComment)();
100
116
  const openId = (0, import_tldraw.useValue)("open thread id", () => import_state.openThreadId.get(editor), [editor]);
101
117
  const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
102
- const [movedThreadIds, setMovedThreadIds] = (0, import_react.useState)(EMPTY_SET);
118
+ const [heldThreadIds, setHeldThreadIds] = (0, import_react.useState)(EMPTY_SET);
103
119
  const adoptOnRebuild = (0, import_react.useRef)(false);
104
120
  const clusterLeaves = (0, import_tldraw.useValue)(
105
121
  "comment cluster leaves",
106
122
  () => (0, import_cluster_input.collectClusterLeaves)(
107
123
  editor,
108
- threads.filter((thread) => !movedThreadIds.has(thread.id)),
124
+ threads.filter((thread) => !heldThreadIds.has(thread.id)),
109
125
  import_state.openThreadId.get(editor),
110
126
  impreciseShapeAnchor
111
127
  ),
112
- [editor, threads, impreciseShapeAnchor, movedThreadIds]
128
+ [editor, threads, impreciseShapeAnchor, heldThreadIds]
113
129
  );
114
130
  const clusterZoomBounds = (0, import_tldraw.useValue)(
115
131
  "comment cluster zoom bounds",
@@ -124,21 +140,41 @@ function CanvasCommentsLayer(props) {
124
140
  }, [clusterLeaves, clusterZoomBounds, editor]);
125
141
  const [renderedModel, setRenderedModel] = (0, import_react.useState)(latestModel);
126
142
  let clusterModel = renderedModel;
127
- if (renderedModel !== latestModel && (adoptOnRebuild.current || hasRemovedLeaves(renderedModel.table, latestModel.table))) {
143
+ const pageId = (0, import_tldraw.useValue)("comment cluster page", () => editor.getCurrentPageId(), [editor]);
144
+ const pageRef = (0, import_react.useRef)(pageId);
145
+ if (pageRef.current !== pageId) {
146
+ pageRef.current = pageId;
147
+ adoptOnRebuild.current = false;
148
+ latestModel.runtime.seed(editor.getZoomLevel());
149
+ if (heldThreadIds.size > 0) setHeldThreadIds(EMPTY_SET);
150
+ setRenderedModel(latestModel);
151
+ clusterModel = latestModel;
152
+ }
153
+ const rejoinPending = heldThreadIds.size === 0 && adoptOnRebuild.current;
154
+ if (renderedModel !== latestModel && rejoinPending) {
128
155
  adoptOnRebuild.current = false;
129
156
  latestModel.runtime.seedFrom(editor.getZoomLevel(), renderedModel.runtime.getVisible());
130
157
  setRenderedModel(latestModel);
131
158
  clusterModel = latestModel;
159
+ } else if (heldThreadIds.size === 0 && renderedModel === latestModel) {
160
+ adoptOnRebuild.current = false;
132
161
  }
133
162
  const newlyMovedIds = findMovedClusteredLeafIds(clusterModel, latestModel);
134
163
  if (newlyMovedIds.length > 0) {
135
- console.debug(`[comments] pins popped out of clustering: ${newlyMovedIds.join(", ")}`);
136
- const next = new Set(movedThreadIds);
164
+ const next = new Set(heldThreadIds);
137
165
  for (const id of newlyMovedIds) next.add(id);
138
- setMovedThreadIds(next);
166
+ setHeldThreadIds(next);
167
+ }
168
+ {
169
+ const latestLeafIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id));
170
+ for (const leaf of clusterModel.table.leaves) {
171
+ if (!latestLeafIds.has(leaf.id)) {
172
+ clusterModel.runtime.detachLeaf(leaf.id);
173
+ }
174
+ }
139
175
  }
140
176
  (0, import_react.useEffect)(() => {
141
- if (movedThreadIds.size === 0) return;
177
+ if (heldThreadIds.size === 0) return;
142
178
  let lastZoom = editor.getZoomLevel();
143
179
  return (0, import_tldraw.react)("rejoin moved comment pins on zoom out", () => {
144
180
  const zoom = editor.getZoomLevel();
@@ -146,9 +182,9 @@ function CanvasCommentsLayer(props) {
146
182
  lastZoom = zoom;
147
183
  if (zoom >= prevZoom) return;
148
184
  adoptOnRebuild.current = true;
149
- setMovedThreadIds(EMPTY_SET);
185
+ setHeldThreadIds(EMPTY_SET);
150
186
  });
151
- }, [movedThreadIds, editor]);
187
+ }, [heldThreadIds, editor]);
152
188
  (0, import_react.useEffect)(() => {
153
189
  if (clusterModel === latestModel) return;
154
190
  let lastZoom = editor.getZoomLevel();
@@ -161,31 +197,31 @@ function CanvasCommentsLayer(props) {
161
197
  setRenderedModel(latestModel);
162
198
  });
163
199
  }, [clusterModel, latestModel, editor]);
200
+ const partitionVersion = clusterModel.runtime.version;
164
201
  const orphanThreads = (0, import_react.useMemo)(() => {
165
202
  if (clusterModel === latestModel) return [];
166
- const renderedIds = new Set(clusterModel.table.leaves.map((leaf) => leaf.id));
203
+ const displayed = /* @__PURE__ */ new Set();
204
+ for (const node of clusterModel.runtime.getVisible().values()) {
205
+ for (const member of node.members) displayed.add(member);
206
+ }
167
207
  const latestIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id));
168
- return threads.filter((thread) => latestIds.has(thread.id) && !renderedIds.has(thread.id));
169
- }, [clusterModel, latestModel, threads]);
170
- const movedThreads = (0, import_react.useMemo)(
171
- () => threads.filter((thread) => movedThreadIds.has(thread.id) && thread.id !== openId),
172
- [threads, movedThreadIds, openId]
208
+ return threads.filter((thread) => latestIds.has(thread.id) && !displayed.has(thread.id));
209
+ }, [clusterModel, latestModel, threads, partitionVersion]);
210
+ const heldThreads = (0, import_react.useMemo)(
211
+ () => threads.filter((thread) => heldThreadIds.has(thread.id) && thread.id !== openId),
212
+ [threads, heldThreadIds, openId]
173
213
  );
174
- const clusterCursor = (0, import_tldraw.useValue)(
175
- "comment cluster cursor",
214
+ (0, import_tldraw.useValue)(
215
+ "comment cluster version",
176
216
  () => {
177
217
  clusterModel.runtime.onCamera(editor.getZoomLevel());
178
- return clusterModel.runtime.k;
218
+ return clusterModel.runtime.version;
179
219
  },
180
220
  [clusterModel, editor]
181
221
  );
182
222
  const visibleNodes = (0, import_react.useMemo)(() => {
183
- const nodes = Array.from(clusterModel.runtime.getVisible().values());
184
- console.debug(
185
- `[comments] cluster cursor k=${clusterCursor} \u2192 re-rendering ${nodes.length} visible nodes`
186
- );
187
- return nodes;
188
- }, [clusterModel, clusterCursor]);
223
+ return Array.from(clusterModel.runtime.getVisible().values());
224
+ }, [clusterModel, partitionVersion]);
189
225
  const fadeNodes = useFadeVisibleNodes(visibleNodes, clusterModel);
190
226
  const threadsById = (0, import_react.useMemo)(
191
227
  () => new Map(threads.map((thread) => [thread.id, thread])),
@@ -244,7 +280,7 @@ function CanvasCommentsLayer(props) {
244
280
  if (e.key !== "Escape" || import_state.openThreadId.get(editor) === null) return;
245
281
  if ((0, import_mention_suggestion.isMentionPickerOpen)()) return;
246
282
  const target = e.target;
247
- if (target && target.closest(".cmt-editing")) return;
283
+ if (target && target.closest(".tlui-cmt-editing")) return;
248
284
  import_state.openThreadId.set(editor, null);
249
285
  e.preventDefault();
250
286
  e.stopPropagation();
@@ -265,7 +301,7 @@ function CanvasCommentsLayer(props) {
265
301
  }, [editor]);
266
302
  if (hidden) return null;
267
303
  return (0, import_react_dom.createPortal)(
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: layerRef, className: "cmt-canvas-layer", children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: layerRef, className: "tlui-cmt-canvas-layer", children: [
269
305
  options.enableClustering ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
270
306
  fadeNodes.map(({ node, phase }) => {
271
307
  let content;
@@ -296,7 +332,7 @@ function CanvasCommentsLayer(props) {
296
332
  },
297
333
  thread.id
298
334
  )),
299
- movedThreads.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
335
+ heldThreads.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
300
336
  ThreadPin,
301
337
  {
302
338
  editor,
@@ -405,7 +441,7 @@ function cancelClusterFadeFrame(frame) {
405
441
  else window.clearTimeout(frame);
406
442
  }
407
443
  function clusterFadeClassName(phase) {
408
- return `cmt-cluster-fade cmt-cluster-fade--${phase}`;
444
+ return `tlui-cmt-cluster-fade tlui-cmt-cluster-fade--${phase}`;
409
445
  }
410
446
  function findMovedClusteredLeafIds(rendered, latest) {
411
447
  if (rendered.table === latest.table) return [];
@@ -422,11 +458,6 @@ function findMovedClusteredLeafIds(rendered, latest) {
422
458
  }
423
459
  return moved;
424
460
  }
425
- function hasRemovedLeaves(rendered, latest) {
426
- if (rendered.leaves.length === 0) return false;
427
- const latestIds = new Set(latest.leaves.map((leaf) => leaf.id));
428
- return rendered.leaves.some((leaf) => !latestIds.has(leaf.id));
429
- }
430
461
  function getClusterZoomBounds(editor) {
431
462
  const cameraOptions = editor.getCameraOptions();
432
463
  const baseZoom = cameraOptions.constraints ? editor.getBaseZoom() : 1;
@@ -478,6 +509,9 @@ const ClusterBadge = (0, import_react.memo)(function ClusterBadge2({
478
509
  node,
479
510
  onExpand
480
511
  }) {
512
+ const container = (0, import_tldraw.useContainer)();
513
+ const badgeRef = (0, import_react.useRef)(null);
514
+ (0, import_tldraw.usePassThroughWheelEvents)(badgeRef);
481
515
  const point = (0, import_tldraw.useValue)(
482
516
  "cluster badge point",
483
517
  () => {
@@ -491,9 +525,16 @@ const ClusterBadge = (0, import_react.memo)(function ClusterBadge2({
491
525
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
492
526
  "div",
493
527
  {
494
- className: "cmt-canvas-cluster",
528
+ ref: badgeRef,
529
+ className: "tlui-cmt-canvas-cluster",
495
530
  style: { left: point.x, top: point.y },
496
- onPointerDown: stop,
531
+ onPointerDown: (e) => {
532
+ if (isCanvasPanGesture(editor, e)) {
533
+ forwardPointerEventToCanvas(container, e);
534
+ return;
535
+ }
536
+ e.stopPropagation();
537
+ },
497
538
  onClick: (e) => {
498
539
  e.stopPropagation();
499
540
  onExpand(node);
@@ -516,7 +557,19 @@ function ThreadPopover({
516
557
  (0, import_tldraw.usePassThroughWheelEvents)(ref);
517
558
  (0, import_tldraw.usePassThroughMouseOverEvents)(ref);
518
559
  return (0, import_react_dom.createPortal)(
519
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className: "cmt-canvas-popover", style, onPointerDown: stop, children }),
560
+ // contextmenu also stops here: portals bubble React events to the canvas's context-menu
561
+ // trigger (the layer mounts inside it), which would open the canvas menu over this panel.
562
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
563
+ "div",
564
+ {
565
+ ref,
566
+ className: "tlui-cmt-canvas-popover",
567
+ style,
568
+ onPointerDown: stop,
569
+ onContextMenu: stop,
570
+ children
571
+ }
572
+ ),
520
573
  container
521
574
  );
522
575
  }
@@ -537,12 +590,19 @@ function RegionBox({
537
590
  [editor, box.x, box.y, box.w, box.h]
538
591
  );
539
592
  const grabRef = (0, import_react.useRef)(null);
593
+ const container = (0, import_tldraw.useContainer)();
594
+ const boxRef = (0, import_react.useRef)(null);
595
+ (0, import_tldraw.usePassThroughWheelEvents)(boxRef);
540
596
  const translated = (e) => {
541
597
  const g = grabRef.current;
542
598
  const p = editor.screenToPage({ x: e.clientX, y: e.clientY });
543
599
  return { ...g.box, x: g.box.x + (p.x - g.page.x), y: g.box.y + (p.y - g.page.y) };
544
600
  };
545
601
  const startMove = (e) => {
602
+ if (isCanvasPanGesture(editor, e)) {
603
+ forwardPointerEventToCanvas(container, e);
604
+ return;
605
+ }
546
606
  e.stopPropagation();
547
607
  grabRef.current = { page: editor.screenToPage({ x: e.clientX, y: e.clientY }), box };
548
608
  e.currentTarget.setPointerCapture(e.pointerId);
@@ -561,7 +621,8 @@ function RegionBox({
561
621
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
562
622
  "div",
563
623
  {
564
- className: movable ? "cmt-canvas-region cmt-canvas-region--movable" : "cmt-canvas-region",
624
+ ref: boxRef,
625
+ className: movable ? "tlui-cmt-canvas-region tlui-cmt-canvas-region--movable" : "tlui-cmt-canvas-region",
565
626
  style: rect,
566
627
  onPointerDown: movable ? startMove : void 0,
567
628
  onPointerMove: movable ? onMove : void 0,
@@ -635,7 +696,7 @@ function RegionResizeHandles({
635
696
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: points.map((h) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
636
697
  "div",
637
698
  {
638
- className: "cmt-canvas-region-handle",
699
+ className: "tlui-cmt-canvas-region-handle",
639
700
  style: { left: h.left, top: h.top, cursor: h.cursor },
640
701
  onPointerDown: startResize,
641
702
  onPointerMove: onResize(h),
@@ -652,13 +713,15 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
652
713
  }) {
653
714
  const {
654
715
  currentUserId,
655
- resolveName,
716
+ resolveAuthor,
656
717
  onPostComment,
657
718
  isCommentUnread,
658
719
  onCommentRead,
659
720
  getMentionSuggestions,
660
721
  renderMentionSuggestion
661
722
  } = props;
723
+ const resolveName = (0, import_react.useCallback)((id) => resolveAuthor(id)?.name, [resolveAuthor]);
724
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
662
725
  const options = (0, import_options.useCommentingOptions)();
663
726
  const impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor;
664
727
  const container = (0, import_tldraw.useContainer)();
@@ -668,7 +731,9 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
668
731
  editor,
669
732
  thread.id
670
733
  ]);
671
- const [reply, setReply] = (0, import_react.useState)(import_comment_extensions.EMPTY_COMMENT);
734
+ const [reply, setReply] = (0, import_react.useState)(
735
+ () => (0, import_comment_drafts.getCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id)) ?? import_comment_extensions.EMPTY_COMMENT
736
+ );
672
737
  const [editingId, setEditingId] = (0, import_react.useState)(null);
673
738
  const [editText, setEditText] = (0, import_react.useState)(import_comment_extensions.EMPTY_COMMENT);
674
739
  const [dragPagePoint, setDragPagePoint] = (0, import_react.useState)(null);
@@ -687,24 +752,24 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
687
752
  [editor, thread.anchor, thread.pageId]
688
753
  );
689
754
  const revealed = open || resizeBounds != null || regionOptions.reveal === "pointer" && pointerInRegion || regionOptions.reveal === "pin-hover" && pinHovered;
755
+ const pinCorner = thread.anchor.type === "region" ? (0, import_thread_state.regionAnchorPinCorner)(editor, thread.anchor) : regionOptions.pinCorner;
690
756
  const resizeHandles = (0, import_react.useMemo)(
691
- () => regionOptions.resize === "edges" ? REGION_EDGES : REGION_CORNERS.filter(
692
- (c) => c.x !== regionOptions.pinCorner.x || c.y !== regionOptions.pinCorner.y
693
- ),
694
- [regionOptions.resize, regionOptions.pinCorner]
757
+ () => regionOptions.resize === "edges" ? REGION_EDGES : REGION_CORNERS.filter((c) => c.x !== pinCorner.x || c.y !== pinCorner.y),
758
+ [regionOptions.resize, pinCorner]
695
759
  );
696
760
  const dragRef = (0, import_react.useRef)(null);
697
761
  const markerRef = (0, import_react.useRef)(null);
762
+ (0, import_tldraw.usePassThroughWheelEvents)(markerRef);
698
763
  (0, import_react.useEffect)(() => {
699
764
  if (!open) return;
700
765
  const onPointerDown = (e) => {
701
766
  const target = e.target;
702
767
  if (!target) return;
703
- if (target.closest(".cmt-canvas-popover")) return;
768
+ if (target.closest(".tlui-cmt-canvas-popover")) return;
704
769
  const marker = markerRef.current;
705
770
  if (marker && marker.contains(target)) return;
706
- if (target.closest(".cmt-canvas-region-handle, .cmt-canvas-region--movable")) return;
707
- if (target.closest(".tlui-menu, [data-radix-popper-content-wrapper], .cmt-mention-popup"))
771
+ if (target.closest(".tlui-cmt-canvas-region-handle, .tlui-cmt-canvas-region--movable")) return;
772
+ if (target.closest(".tlui-menu, [data-radix-popper-content-wrapper], .tlui-cmt-mention-popup"))
708
773
  return;
709
774
  import_state.openThreadId.set(editor, null);
710
775
  };
@@ -716,7 +781,10 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
716
781
  () => {
717
782
  if (thread.pageId !== editor.getCurrentPageId()) return null;
718
783
  const pagePoint = (0, import_thread_state.anchorPagePoint)(editor, thread.anchor, impreciseShapeAnchor);
719
- return pagePoint ? editor.pageToViewport(pagePoint) : null;
784
+ if (!pagePoint) return null;
785
+ const viewportPoint = editor.pageToViewport(pagePoint);
786
+ const inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor);
787
+ return inset ? { x: viewportPoint.x + inset.x, y: viewportPoint.y + inset.y } : viewportPoint;
720
788
  },
721
789
  [editor, thread.anchor, thread.pageId, impreciseShapeAnchor]
722
790
  );
@@ -743,6 +811,7 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
743
811
  if (onPostComment) onPostComment(comment);
744
812
  });
745
813
  setReply(import_comment_extensions.EMPTY_COMMENT);
814
+ (0, import_comment_drafts.clearCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id));
746
815
  };
747
816
  const toggleResolve = () => {
748
817
  if (!currentUserId) return;
@@ -766,6 +835,16 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
766
835
  setEditingId(comment.id);
767
836
  setEditText(comment.body);
768
837
  };
838
+ const deleteComment = (comment) => {
839
+ (0, import_state.commitCommentMutation)(editor, () => {
840
+ if (comments.length === 1) {
841
+ import_state.openThreadId.set(editor, null);
842
+ (0, import_comment_store.removeCommentRecords)(editor, [thread.id, comment.id]);
843
+ } else {
844
+ (0, import_comment_store.removeCommentRecords)(editor, [comment.id]);
845
+ }
846
+ });
847
+ };
769
848
  const saveEdit = () => {
770
849
  const comment = comments.find((c) => c.id === editingId);
771
850
  if (!comment || (0, import_comment_extensions.isCommentEmpty)(editText)) return;
@@ -780,7 +859,7 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
780
859
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
781
860
  "div",
782
861
  {
783
- className: "cmt-editing",
862
+ className: "tlui-cmt-editing",
784
863
  onKeyDown: (e) => {
785
864
  if (e.key === "Escape") {
786
865
  setEditingId(null);
@@ -809,15 +888,46 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
809
888
  import_comment_card.CommentCard,
810
889
  {
811
890
  ...card,
812
- actions: comment.authorId === currentUserId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
813
- "button",
814
- {
815
- className: "cmt-thread__action",
816
- title: msg("comments.edit"),
817
- onClick: () => startEdit(comment),
818
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-horizontal", label: msg("comments.edit"), small: true })
819
- }
820
- ) : void 0
891
+ actions: comment.authorId === currentUserId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuRoot, { id: `comment-actions-${comment.id}`, children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ className: "tlui-cmt-thread__action",
897
+ title: msg("comments.more-options"),
898
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
899
+ }
900
+ ) }),
901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
902
+ import_tldraw.TldrawUiDropdownMenuContent,
903
+ {
904
+ className: "tlui-cmt-menu",
905
+ side: "bottom",
906
+ align: "end",
907
+ alignOffset: 0,
908
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuGroup, { children: [
909
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
910
+ "button",
911
+ {
912
+ type: "button",
913
+ className: "tlui-cmt-menu-item",
914
+ onClick: () => startEdit(comment),
915
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("comments.edit-comment") })
916
+ }
917
+ ) }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
919
+ "button",
920
+ {
921
+ type: "button",
922
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
923
+ onClick: () => deleteComment(comment),
924
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("comments.delete-comment") })
925
+ }
926
+ ) })
927
+ ] })
928
+ }
929
+ )
930
+ ] }) : void 0
821
931
  }
822
932
  );
823
933
  };
@@ -825,7 +935,7 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
825
935
  currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
826
936
  "button",
827
937
  {
828
- className: "cmt-thread__action",
938
+ className: "tlui-cmt-thread__action",
829
939
  title: msg(thread.resolved ? "comments.reopen" : "comments.resolve"),
830
940
  onClick: toggleResolve,
831
941
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -838,33 +948,63 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
838
948
  )
839
949
  }
840
950
  ),
841
- currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
842
- "button",
843
- {
844
- className: "cmt-thread__action",
845
- title: msg("comments.delete"),
846
- onClick: deleteThread,
847
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "trash", label: msg("comments.delete"), small: true })
848
- }
849
- ),
850
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
851
- "button",
852
- {
853
- className: "cmt-thread__action",
854
- title: msg("comments.dismiss"),
855
- onClick: () => import_state.openThreadId.set(editor, null),
856
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "cross-2", label: msg("comments.dismiss"), small: true })
857
- }
858
- )
951
+ currentUserId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_tldraw.TldrawUiDropdownMenuRoot, { id: `comment-thread-actions-${thread.id}`, children: [
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
953
+ "button",
954
+ {
955
+ type: "button",
956
+ className: "tlui-cmt-thread__action",
957
+ title: msg("comments.more-options"),
958
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
959
+ }
960
+ ) }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
962
+ import_tldraw.TldrawUiDropdownMenuContent,
963
+ {
964
+ className: "tlui-cmt-menu",
965
+ side: "bottom",
966
+ align: "end",
967
+ alignOffset: 0,
968
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuGroup, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_tldraw.TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
969
+ "button",
970
+ {
971
+ type: "button",
972
+ className: "tlui-cmt-menu-item tlui-cmt-menu-item--danger",
973
+ onClick: deleteThread,
974
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: msg("comments.delete") })
975
+ }
976
+ ) }) })
977
+ }
978
+ )
979
+ ] })
859
980
  ] });
860
981
  const PinContent = options.components.PinContent;
861
- const pinContent = PinContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinContent, { thread, comments }) : initialOf(resolveName(thread.createdBy) ?? import_comment_render.UNKNOWN_AUTHOR);
982
+ const threadAuthor = resolveAuthor(thread.createdBy);
983
+ const pinContent = PinContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinContent, { thread, comments }) : initialOf(threadAuthor?.name ?? import_comment_render.UNKNOWN_AUTHOR);
862
984
  const isRegion = thread.anchor.type === "region";
863
985
  const pinMovable = regionOptions.move !== "body";
864
986
  const bodyMovable = regionOptions.move !== "pin";
865
987
  const startDrag = (e) => {
988
+ if (isCanvasPanGesture(editor, e)) {
989
+ forwardPointerEventToCanvas(container, e);
990
+ return;
991
+ }
866
992
  e.stopPropagation();
867
- dragRef.current = { startX: e.clientX, startY: e.clientY, moved: false };
993
+ const grabPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
994
+ const anchorPage = (0, import_thread_state.anchorPagePoint)(editor, thread.anchor, impreciseShapeAnchor);
995
+ const inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor);
996
+ if (anchorPage && inset) {
997
+ const zoom = editor.getZoomLevel();
998
+ anchorPage.x += inset.x / zoom;
999
+ anchorPage.y += inset.y / zoom;
1000
+ }
1001
+ dragRef.current = {
1002
+ startX: e.clientX,
1003
+ startY: e.clientY,
1004
+ moved: false,
1005
+ offsetX: anchorPage ? anchorPage.x - grabPage.x : 0,
1006
+ offsetY: anchorPage ? anchorPage.y - grabPage.y : 0
1007
+ };
868
1008
  e.currentTarget.setPointerCapture(e.pointerId);
869
1009
  };
870
1010
  const onDrag = (e) => {
@@ -873,7 +1013,8 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
873
1013
  if (isRegion && !pinMovable) return;
874
1014
  if (!drag.moved && Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < 4) return;
875
1015
  drag.moved = true;
876
- setDragPagePoint(editor.screenToPage({ x: e.clientX, y: e.clientY }));
1016
+ const cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
1017
+ setDragPagePoint({ x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY });
877
1018
  };
878
1019
  const endDrag = (e) => {
879
1020
  const drag = dragRef.current;
@@ -886,14 +1027,15 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
886
1027
  import_state.openThreadId.set(editor, import_state.openThreadId.get(editor) === thread.id ? null : thread.id);
887
1028
  return;
888
1029
  }
889
- const pagePoint = editor.screenToPage({ x: e.clientX, y: e.clientY });
1030
+ const cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY });
1031
+ const pagePoint = { x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY };
890
1032
  setDragPagePoint(null);
891
1033
  let anchor;
892
1034
  if (thread.anchor.type === "region") {
893
1035
  anchor = {
894
1036
  ...thread.anchor,
895
- x: pagePoint.x - regionOptions.pinCorner.x * thread.anchor.w,
896
- y: pagePoint.y - regionOptions.pinCorner.y * thread.anchor.h
1037
+ x: pagePoint.x - pinCorner.x * thread.anchor.w,
1038
+ y: pagePoint.y - pinCorner.y * thread.anchor.h
897
1039
  };
898
1040
  } else {
899
1041
  const hit = editor.getShapeAtPoint(pagePoint, { hitInside: true });
@@ -901,19 +1043,21 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
901
1043
  }
902
1044
  (0, import_state.commitCommentMutation)(editor, () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor }]), "drag");
903
1045
  };
904
- const livePinPage = resizeBounds ? (0, import_thread_state.regionPinPoint)(resizeBounds, regionOptions.pinCorner) : dragPagePoint;
905
- const renderPoint = livePinPage ? editor.pageToViewport(livePinPage) : point;
1046
+ const livePinPage = resizeBounds ? (0, import_thread_state.regionPinPoint)(resizeBounds, pinCorner) : dragPagePoint;
1047
+ const renderPointBase = livePinPage ? editor.pageToViewport(livePinPage) : point;
1048
+ const renderPoint = isRegion ? { x: renderPointBase.x - 17, y: renderPointBase.y + 17 } : renderPointBase;
906
1049
  const regionAnchor = thread.anchor.type === "region" ? thread.anchor : void 0;
907
1050
  const movedRegion = regionAnchor && dragPagePoint ? {
908
1051
  ...regionAnchor,
909
- x: dragPagePoint.x - regionOptions.pinCorner.x * regionAnchor.w,
910
- y: dragPagePoint.y - regionOptions.pinCorner.y * regionAnchor.h
1052
+ x: dragPagePoint.x - pinCorner.x * regionAnchor.w,
1053
+ y: dragPagePoint.y - pinCorner.y * regionAnchor.h
911
1054
  } : regionAnchor;
912
1055
  const regionBoxBounds = resizeBounds ?? movedRegion;
913
1056
  const commitResize = (bounds) => {
914
1057
  setResizeBounds(null);
915
1058
  editor.run(
916
- () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor: { type: "region", ...bounds } }]),
1059
+ // Spread the existing anchor first so the region's pin corner survives a resize.
1060
+ () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor: { ...regionAnchor, ...bounds } }]),
917
1061
  {
918
1062
  history: "ignore"
919
1063
  }
@@ -943,44 +1087,51 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
943
1087
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
944
1088
  "div",
945
1089
  {
946
- className: open ? "cmt-canvas-pin cmt-canvas-pin--open" : "cmt-canvas-pin",
1090
+ className: [
1091
+ "tlui-cmt-canvas-pin",
1092
+ open && "tlui-cmt-canvas-pin--open",
1093
+ dragPagePoint && "tlui-cmt-canvas-pin--dragging"
1094
+ ].filter(Boolean).join(" "),
947
1095
  style: { left: renderPoint.x, top: renderPoint.y },
948
1096
  children: [
949
1097
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
950
1098
  "div",
951
1099
  {
952
1100
  ref: markerRef,
953
- className: "cmt-canvas-pin__marker",
1101
+ className: "tlui-cmt-canvas-pin__marker",
954
1102
  onPointerDown: startDrag,
955
1103
  onPointerMove: onDrag,
956
1104
  onPointerUp: endDrag,
957
1105
  onPointerEnter: () => setPinHovered(true),
958
1106
  onPointerLeave: () => setPinHovered(false),
959
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { resolved: thread.resolved != null, open, children: pinContent })
1107
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { resolved: thread.resolved != null, open, color: threadAuthor?.color, children: pinContent })
960
1108
  }
961
1109
  ),
962
1110
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
963
1111
  ThreadPopover,
964
1112
  {
965
1113
  container,
966
- style: { left: renderPoint.x + 36, top: renderPoint.y - 28 },
1114
+ style: { left: renderPoint.x + 48, top: renderPoint.y - 54 },
967
1115
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
968
1116
  import_comment_thread.CommentThread,
969
1117
  {
970
1118
  header: msg("comments.thread-title"),
971
1119
  headerActions,
972
1120
  renderComment,
973
- comments: comments.map((c) => toCardProps(c, props, options.components)),
1121
+ comments: comments.map((c) => toCardProps(c, props, options.components, resolveName)),
974
1122
  resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
975
1123
  "{name}",
976
- resolveName(thread.resolved.by) ?? import_comment_render.UNKNOWN_AUTHOR
1124
+ resolveAuthor(thread.resolved.by)?.name ?? import_comment_render.UNKNOWN_AUTHOR
977
1125
  ) : void 0,
978
1126
  composer: currentUserId && !thread.resolved ? {
979
- author: resolveName(currentUserId) ?? import_comment_render.UNKNOWN_AUTHOR,
1127
+ author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
980
1128
  placeholder: msg("comments.reply-placeholder"),
981
1129
  sendLabel: msg("comments.send"),
982
1130
  value: reply,
983
- onChange: setReply,
1131
+ onChange: (value) => {
1132
+ setReply(value);
1133
+ (0, import_comment_drafts.saveCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id), value);
1134
+ },
984
1135
  onSubmit: postReply,
985
1136
  disabled: (0, import_comment_extensions.isCommentEmpty)(reply),
986
1137
  getMentionSuggestions,
@@ -999,12 +1150,15 @@ function PendingComposer({
999
1150
  editor,
1000
1151
  pending,
1001
1152
  currentUserId,
1002
- resolveName,
1153
+ resolveAuthor,
1003
1154
  onPostComment,
1004
1155
  getMentionSuggestions,
1005
1156
  renderMentionSuggestion
1006
1157
  }) {
1007
- const [text, setText] = (0, import_react.useState)(import_comment_extensions.EMPTY_COMMENT);
1158
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
1159
+ const [text, setText] = (0, import_react.useState)(
1160
+ () => (0, import_comment_drafts.getCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT) ?? import_comment_extensions.EMPTY_COMMENT
1161
+ );
1008
1162
  const ref = (0, import_react.useRef)(null);
1009
1163
  const msg = (0, import_tldraw.useTranslation)();
1010
1164
  const container = (0, import_tldraw.useContainer)();
@@ -1019,7 +1173,7 @@ function PendingComposer({
1019
1173
  const el = ref.current;
1020
1174
  const target = e.target;
1021
1175
  if (!el || !target) return;
1022
- if (el.contains(target) || target.closest(".cmt-mention-popup")) return;
1176
+ if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
1023
1177
  import_state.pendingComment.set(editor, null);
1024
1178
  };
1025
1179
  document.addEventListener("pointerdown", onPointerDown, true);
@@ -1044,6 +1198,7 @@ function PendingComposer({
1044
1198
  if (onPostComment) onPostComment(comment);
1045
1199
  });
1046
1200
  setText(import_comment_extensions.EMPTY_COMMENT);
1201
+ (0, import_comment_drafts.clearCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT);
1047
1202
  import_state.pendingComment.set(editor, null);
1048
1203
  };
1049
1204
  return (0, import_react_dom.createPortal)(
@@ -1051,26 +1206,33 @@ function PendingComposer({
1051
1206
  "div",
1052
1207
  {
1053
1208
  ref,
1054
- className: "cmt-canvas-composer",
1209
+ className: [
1210
+ "tlui-cmt-canvas-composer",
1211
+ pending.anchor.type === "region" && "tlui-cmt-canvas-composer--region"
1212
+ ].filter(Boolean).join(" "),
1055
1213
  style: { left: point.x, top: point.y },
1056
1214
  onPointerDown: stop,
1215
+ onContextMenu: stop,
1057
1216
  onKeyDown: (e) => {
1058
1217
  if (e.key === "Escape" && !(0, import_mention_suggestion.isMentionPickerOpen)()) import_state.pendingComment.set(editor, null);
1059
1218
  },
1060
1219
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1061
1220
  import_comment_composer.CommentComposer,
1062
1221
  {
1063
- author: currentUserId ? resolveName(currentUserId) ?? import_comment_render.UNKNOWN_AUTHOR : "",
1222
+ author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
1064
1223
  placeholder: msg("comments.add-placeholder"),
1065
1224
  sendLabel: msg("comments.send"),
1066
1225
  value: text,
1067
- onChange: setText,
1226
+ onChange: (value) => {
1227
+ setText(value);
1228
+ (0, import_comment_drafts.saveCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT, value);
1229
+ },
1068
1230
  onSubmit: submit,
1069
1231
  disabled: (0, import_comment_extensions.isCommentEmpty)(text),
1070
1232
  getMentionSuggestions,
1071
1233
  renderMentionSuggestion,
1072
1234
  autoFocus: true,
1073
- leading: draftAvatar
1235
+ leading: draftAvatar(me?.color)
1074
1236
  }
1075
1237
  )
1076
1238
  }