@tldraw/commenting 0.0.0-bootstrap → 5.3.0-internal.41291fb579ed

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