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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/commenting.css +284 -153
  2. package/dist-cjs/canvas/comment-body.js +1 -1
  3. package/dist-cjs/canvas/comment-body.js.map +2 -2
  4. package/dist-cjs/canvas/comment-drafts.js +54 -0
  5. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  6. package/dist-cjs/canvas/comment-render.js +2 -0
  7. package/dist-cjs/canvas/comment-render.js.map +2 -2
  8. package/dist-cjs/canvas/comment-tool.js +65 -12
  9. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  10. package/dist-cjs/canvas/comments-filter-menu.js +54 -63
  11. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  12. package/dist-cjs/canvas/comments-overflow-menu.js +21 -27
  13. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  14. package/dist-cjs/canvas/comments-overlay.js +304 -112
  15. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  16. package/dist-cjs/canvas/comments-sidebar.js +12 -18
  17. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  18. package/dist-cjs/canvas/comments-visibility-toggle.js +66 -0
  19. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  20. package/dist-cjs/canvas/options.js +1 -0
  21. package/dist-cjs/canvas/options.js.map +2 -2
  22. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  23. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  24. package/dist-cjs/canvas/state.js +11 -0
  25. package/dist-cjs/canvas/state.js.map +2 -2
  26. package/dist-cjs/canvas/thread-state.js +8 -1
  27. package/dist-cjs/canvas/thread-state.js.map +2 -2
  28. package/dist-cjs/clustering/runtime.js +119 -7
  29. package/dist-cjs/clustering/runtime.js.map +2 -2
  30. package/dist-cjs/index.d.ts +126 -41
  31. package/dist-cjs/index.js +7 -1
  32. package/dist-cjs/index.js.map +2 -2
  33. package/dist-cjs/ui/avatar.js +14 -6
  34. package/dist-cjs/ui/avatar.js.map +2 -2
  35. package/dist-cjs/ui/byline.js +4 -4
  36. package/dist-cjs/ui/byline.js.map +2 -2
  37. package/dist-cjs/ui/comment-author.js +17 -0
  38. package/dist-cjs/ui/comment-author.js.map +7 -0
  39. package/dist-cjs/ui/comment-card.js +4 -4
  40. package/dist-cjs/ui/comment-card.js.map +2 -2
  41. package/dist-cjs/ui/comment-composer.js +92 -29
  42. package/dist-cjs/ui/comment-composer.js.map +2 -2
  43. package/dist-cjs/ui/comment-mention.js +1 -1
  44. package/dist-cjs/ui/comment-mention.js.map +2 -2
  45. package/dist-cjs/ui/comment-pin.js +23 -3
  46. package/dist-cjs/ui/comment-pin.js.map +2 -2
  47. package/dist-cjs/ui/comment-text.js +1 -1
  48. package/dist-cjs/ui/comment-text.js.map +2 -2
  49. package/dist-cjs/ui/comment-thread.js +6 -6
  50. package/dist-cjs/ui/comment-thread.js.map +2 -2
  51. package/dist-cjs/ui/comments-list.js +12 -12
  52. package/dist-cjs/ui/comments-list.js.map +2 -2
  53. package/dist-cjs/ui/count-badge.js +1 -1
  54. package/dist-cjs/ui/count-badge.js.map +2 -2
  55. package/dist-cjs/ui/empty-state.js +3 -3
  56. package/dist-cjs/ui/empty-state.js.map +2 -2
  57. package/dist-cjs/ui/format-time.js +3 -0
  58. package/dist-cjs/ui/format-time.js.map +2 -2
  59. package/dist-cjs/ui/mention-list.js +8 -8
  60. package/dist-cjs/ui/mention-list.js.map +2 -2
  61. package/dist-cjs/ui/mention-suggestion.js +3 -3
  62. package/dist-cjs/ui/mention-suggestion.js.map +2 -2
  63. package/dist-cjs/ui/mention.js +1 -1
  64. package/dist-cjs/ui/mention.js.map +2 -2
  65. package/dist-cjs/ui/reaction.js +11 -4
  66. package/dist-cjs/ui/reaction.js.map +2 -2
  67. package/dist-cjs/ui/reactions.js +10 -2
  68. package/dist-cjs/ui/reactions.js.map +2 -2
  69. package/dist-cjs/ui/send-button.js +1 -1
  70. package/dist-cjs/ui/send-button.js.map +2 -2
  71. package/dist-cjs/ui/tooltip-button.js +32 -0
  72. package/dist-cjs/ui/tooltip-button.js.map +7 -0
  73. package/dist-esm/canvas/comment-body.mjs +1 -1
  74. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  75. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  76. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  77. package/dist-esm/canvas/comment-render.mjs +2 -0
  78. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  79. package/dist-esm/canvas/comment-tool.mjs +67 -14
  80. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  81. package/dist-esm/canvas/comments-filter-menu.mjs +54 -63
  82. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  83. package/dist-esm/canvas/comments-overflow-menu.mjs +22 -27
  84. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  85. package/dist-esm/canvas/comments-overlay.mjs +322 -114
  86. package/dist-esm/canvas/comments-overlay.mjs.map +2 -2
  87. package/dist-esm/canvas/comments-sidebar.mjs +15 -21
  88. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  89. package/dist-esm/canvas/comments-visibility-toggle.mjs +46 -0
  90. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  91. package/dist-esm/canvas/options.mjs +1 -0
  92. package/dist-esm/canvas/options.mjs.map +2 -2
  93. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  94. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  95. package/dist-esm/canvas/state.mjs +11 -0
  96. package/dist-esm/canvas/state.mjs.map +2 -2
  97. package/dist-esm/canvas/thread-state.mjs +8 -1
  98. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  99. package/dist-esm/clustering/runtime.mjs +119 -7
  100. package/dist-esm/clustering/runtime.mjs.map +2 -2
  101. package/dist-esm/index.d.mts +126 -41
  102. package/dist-esm/index.mjs +11 -1
  103. package/dist-esm/index.mjs.map +2 -2
  104. package/dist-esm/ui/avatar.mjs +14 -6
  105. package/dist-esm/ui/avatar.mjs.map +2 -2
  106. package/dist-esm/ui/byline.mjs +4 -4
  107. package/dist-esm/ui/byline.mjs.map +2 -2
  108. package/dist-esm/ui/comment-author.mjs +1 -0
  109. package/dist-esm/ui/comment-author.mjs.map +7 -0
  110. package/dist-esm/ui/comment-card.mjs +4 -4
  111. package/dist-esm/ui/comment-card.mjs.map +2 -2
  112. package/dist-esm/ui/comment-composer.mjs +93 -30
  113. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  114. package/dist-esm/ui/comment-mention.mjs +1 -1
  115. package/dist-esm/ui/comment-mention.mjs.map +2 -2
  116. package/dist-esm/ui/comment-pin.mjs +23 -3
  117. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  118. package/dist-esm/ui/comment-text.mjs +1 -1
  119. package/dist-esm/ui/comment-text.mjs.map +2 -2
  120. package/dist-esm/ui/comment-thread.mjs +6 -6
  121. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  122. package/dist-esm/ui/comments-list.mjs +12 -12
  123. package/dist-esm/ui/comments-list.mjs.map +2 -2
  124. package/dist-esm/ui/count-badge.mjs +1 -1
  125. package/dist-esm/ui/count-badge.mjs.map +2 -2
  126. package/dist-esm/ui/empty-state.mjs +3 -3
  127. package/dist-esm/ui/empty-state.mjs.map +2 -2
  128. package/dist-esm/ui/format-time.mjs +3 -0
  129. package/dist-esm/ui/format-time.mjs.map +2 -2
  130. package/dist-esm/ui/mention-list.mjs +8 -8
  131. package/dist-esm/ui/mention-list.mjs.map +2 -2
  132. package/dist-esm/ui/mention-suggestion.mjs +3 -3
  133. package/dist-esm/ui/mention-suggestion.mjs.map +2 -2
  134. package/dist-esm/ui/mention.mjs +1 -1
  135. package/dist-esm/ui/mention.mjs.map +2 -2
  136. package/dist-esm/ui/reaction.mjs +11 -4
  137. package/dist-esm/ui/reaction.mjs.map +2 -2
  138. package/dist-esm/ui/reactions.mjs +10 -2
  139. package/dist-esm/ui/reactions.mjs.map +2 -2
  140. package/dist-esm/ui/send-button.mjs +1 -1
  141. package/dist-esm/ui/send-button.mjs.map +2 -2
  142. package/dist-esm/ui/tooltip-button.mjs +12 -0
  143. package/dist-esm/ui/tooltip-button.mjs.map +7 -0
  144. package/package.json +3 -3
  145. package/src/canvas/canvas.css +79 -36
  146. package/src/canvas/comment-body.tsx +1 -1
  147. package/src/canvas/comment-drafts.test.ts +55 -0
  148. package/src/canvas/comment-drafts.ts +46 -0
  149. package/src/canvas/comment-render.test.ts +1 -1
  150. package/src/canvas/comment-render.ts +7 -2
  151. package/src/canvas/comment-tool.tsx +98 -17
  152. package/src/canvas/comments-filter-menu.tsx +27 -33
  153. package/src/canvas/comments-overflow-menu.tsx +13 -21
  154. package/src/canvas/comments-overlay.tsx +441 -145
  155. package/src/canvas/comments-sidebar.tsx +27 -28
  156. package/src/canvas/comments-visibility-toggle.tsx +50 -0
  157. package/src/canvas/options.ts +25 -0
  158. package/src/canvas/sidebar-filters.ts +4 -3
  159. package/src/canvas/state.ts +22 -0
  160. package/src/canvas/thread-state.ts +16 -3
  161. package/src/clustering/runtime.test.ts +183 -7
  162. package/src/clustering/runtime.ts +164 -15
  163. package/src/index.ts +7 -0
  164. package/src/ui/avatar.tsx +15 -11
  165. package/src/ui/byline.tsx +6 -5
  166. package/src/ui/comment-author.ts +15 -0
  167. package/src/ui/comment-card.tsx +6 -5
  168. package/src/ui/comment-composer.tsx +122 -33
  169. package/src/ui/comment-mention.ts +2 -2
  170. package/src/ui/comment-pin.tsx +35 -4
  171. package/src/ui/comment-text.tsx +1 -1
  172. package/src/ui/comment-thread.tsx +8 -6
  173. package/src/ui/comments-list.tsx +19 -14
  174. package/src/ui/comments.css +205 -117
  175. package/src/ui/count-badge.tsx +1 -1
  176. package/src/ui/empty-state.tsx +3 -3
  177. package/src/ui/format-time.ts +5 -0
  178. package/src/ui/mention-list.tsx +13 -16
  179. package/src/ui/mention-suggestion.tsx +3 -3
  180. package/src/ui/mention.tsx +1 -1
  181. package/src/ui/reaction.tsx +6 -3
  182. package/src/ui/reactions.tsx +6 -2
  183. package/src/ui/send-button.tsx +1 -1
  184. package/src/ui/tooltip-button.tsx +20 -0
@@ -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,38 +1042,51 @@ 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 });
900
- anchor = hit ? (0, import_thread_state.shapeAnchorAt)(editor, hit.id, pagePoint, e.altKey) : { type: "point", x: pagePoint.x, y: pagePoint.y };
1062
+ anchor = hit ? (0, import_thread_state.shapeAnchorAt)(
1063
+ editor,
1064
+ hit.id,
1065
+ pagePoint,
1066
+ (0, import_options.getCommentingOptions)(editor).shouldBePrecise(editor, {
1067
+ shapeId: hit.id,
1068
+ point: pagePoint,
1069
+ altKey: e.altKey
1070
+ })
1071
+ ) : { type: "point", x: pagePoint.x, y: pagePoint.y };
901
1072
  }
902
1073
  (0, import_state.commitCommentMutation)(editor, () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor }]), "drag");
903
1074
  };
904
- const livePinPage = resizeBounds ? (0, import_thread_state.regionPinPoint)(resizeBounds, regionOptions.pinCorner) : dragPagePoint;
905
- const renderPoint = livePinPage ? editor.pageToViewport(livePinPage) : point;
1075
+ const livePinPage = resizeBounds ? (0, import_thread_state.regionPinPoint)(resizeBounds, pinCorner) : dragPagePoint;
1076
+ const renderPointBase = livePinPage ? editor.pageToViewport(livePinPage) : point;
1077
+ const renderPoint = isRegion ? { x: renderPointBase.x - 17, y: renderPointBase.y + 17 } : renderPointBase;
906
1078
  const regionAnchor = thread.anchor.type === "region" ? thread.anchor : void 0;
907
1079
  const movedRegion = regionAnchor && dragPagePoint ? {
908
1080
  ...regionAnchor,
909
- x: dragPagePoint.x - regionOptions.pinCorner.x * regionAnchor.w,
910
- y: dragPagePoint.y - regionOptions.pinCorner.y * regionAnchor.h
1081
+ x: dragPagePoint.x - pinCorner.x * regionAnchor.w,
1082
+ y: dragPagePoint.y - pinCorner.y * regionAnchor.h
911
1083
  } : regionAnchor;
912
1084
  const regionBoxBounds = resizeBounds ?? movedRegion;
913
1085
  const commitResize = (bounds) => {
914
1086
  setResizeBounds(null);
915
1087
  editor.run(
916
- () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor: { type: "region", ...bounds } }]),
1088
+ // Spread the existing anchor first so the region's pin corner survives a resize.
1089
+ () => (0, import_comment_store.putCommentRecords)(editor, [{ ...thread, anchor: { ...regionAnchor, ...bounds } }]),
917
1090
  {
918
1091
  history: "ignore"
919
1092
  }
@@ -943,44 +1116,52 @@ const ThreadPin = (0, import_react.memo)(function ThreadPin2({
943
1116
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
944
1117
  "div",
945
1118
  {
946
- className: open ? "cmt-canvas-pin cmt-canvas-pin--open" : "cmt-canvas-pin",
1119
+ className: [
1120
+ "tlui-cmt-canvas-pin",
1121
+ open && "tlui-cmt-canvas-pin--open",
1122
+ dragPagePoint && "tlui-cmt-canvas-pin--dragging"
1123
+ ].filter(Boolean).join(" "),
947
1124
  style: { left: renderPoint.x, top: renderPoint.y },
948
1125
  children: [
949
1126
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
950
1127
  "div",
951
1128
  {
952
1129
  ref: markerRef,
953
- className: "cmt-canvas-pin__marker",
1130
+ className: "tlui-cmt-canvas-pin__marker",
954
1131
  onPointerDown: startDrag,
955
1132
  onPointerMove: onDrag,
956
1133
  onPointerUp: endDrag,
1134
+ onPointerCancel: cancelDrag,
957
1135
  onPointerEnter: () => setPinHovered(true),
958
1136
  onPointerLeave: () => setPinHovered(false),
959
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { resolved: thread.resolved != null, open, children: pinContent })
1137
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_comment_pin.CommentPin, { resolved: thread.resolved != null, open, color: threadAuthor?.color, children: pinContent })
960
1138
  }
961
1139
  ),
962
1140
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
963
1141
  ThreadPopover,
964
1142
  {
965
1143
  container,
966
- style: { left: renderPoint.x + 36, top: renderPoint.y - 28 },
1144
+ style: { left: renderPoint.x + 48, top: renderPoint.y - 54 },
967
1145
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
968
1146
  import_comment_thread.CommentThread,
969
1147
  {
970
1148
  header: msg("comments.thread-title"),
971
1149
  headerActions,
972
1150
  renderComment,
973
- comments: comments.map((c) => toCardProps(c, props, options.components)),
1151
+ comments: comments.map((c) => toCardProps(c, props, options.components, resolveName)),
974
1152
  resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
975
1153
  "{name}",
976
- resolveName(thread.resolved.by) ?? import_comment_render.UNKNOWN_AUTHOR
1154
+ resolveAuthor(thread.resolved.by)?.name ?? import_comment_render.UNKNOWN_AUTHOR
977
1155
  ) : void 0,
978
1156
  composer: currentUserId && !thread.resolved ? {
979
- author: resolveName(currentUserId) ?? import_comment_render.UNKNOWN_AUTHOR,
1157
+ author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
980
1158
  placeholder: msg("comments.reply-placeholder"),
981
1159
  sendLabel: msg("comments.send"),
982
1160
  value: reply,
983
- onChange: setReply,
1161
+ onChange: (value) => {
1162
+ setReply(value);
1163
+ (0, import_comment_drafts.saveCommentDraft)((0, import_comment_drafts.replyDraftSlot)(thread.id), value);
1164
+ },
984
1165
  onSubmit: postReply,
985
1166
  disabled: (0, import_comment_extensions.isCommentEmpty)(reply),
986
1167
  getMentionSuggestions,
@@ -999,12 +1180,15 @@ function PendingComposer({
999
1180
  editor,
1000
1181
  pending,
1001
1182
  currentUserId,
1002
- resolveName,
1183
+ resolveAuthor,
1003
1184
  onPostComment,
1004
1185
  getMentionSuggestions,
1005
1186
  renderMentionSuggestion
1006
1187
  }) {
1007
- const [text, setText] = (0, import_react.useState)(import_comment_extensions.EMPTY_COMMENT);
1188
+ const me = currentUserId ? resolveAuthor(currentUserId) : void 0;
1189
+ const [text, setText] = (0, import_react.useState)(
1190
+ () => (0, import_comment_drafts.getCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT) ?? import_comment_extensions.EMPTY_COMMENT
1191
+ );
1008
1192
  const ref = (0, import_react.useRef)(null);
1009
1193
  const msg = (0, import_tldraw.useTranslation)();
1010
1194
  const container = (0, import_tldraw.useContainer)();
@@ -1019,7 +1203,7 @@ function PendingComposer({
1019
1203
  const el = ref.current;
1020
1204
  const target = e.target;
1021
1205
  if (!el || !target) return;
1022
- if (el.contains(target) || target.closest(".cmt-mention-popup")) return;
1206
+ if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
1023
1207
  import_state.pendingComment.set(editor, null);
1024
1208
  };
1025
1209
  document.addEventListener("pointerdown", onPointerDown, true);
@@ -1044,6 +1228,7 @@ function PendingComposer({
1044
1228
  if (onPostComment) onPostComment(comment);
1045
1229
  });
1046
1230
  setText(import_comment_extensions.EMPTY_COMMENT);
1231
+ (0, import_comment_drafts.clearCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT);
1047
1232
  import_state.pendingComment.set(editor, null);
1048
1233
  };
1049
1234
  return (0, import_react_dom.createPortal)(
@@ -1051,26 +1236,33 @@ function PendingComposer({
1051
1236
  "div",
1052
1237
  {
1053
1238
  ref,
1054
- className: "cmt-canvas-composer",
1239
+ className: [
1240
+ "tlui-cmt-canvas-composer",
1241
+ pending.anchor.type === "region" && "tlui-cmt-canvas-composer--region"
1242
+ ].filter(Boolean).join(" "),
1055
1243
  style: { left: point.x, top: point.y },
1056
1244
  onPointerDown: stop,
1245
+ onContextMenu: stop,
1057
1246
  onKeyDown: (e) => {
1058
1247
  if (e.key === "Escape" && !(0, import_mention_suggestion.isMentionPickerOpen)()) import_state.pendingComment.set(editor, null);
1059
1248
  },
1060
1249
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1061
1250
  import_comment_composer.CommentComposer,
1062
1251
  {
1063
- author: currentUserId ? resolveName(currentUserId) ?? import_comment_render.UNKNOWN_AUTHOR : "",
1252
+ author: me ?? import_comment_render.UNKNOWN_COMMENT_AUTHOR,
1064
1253
  placeholder: msg("comments.add-placeholder"),
1065
1254
  sendLabel: msg("comments.send"),
1066
1255
  value: text,
1067
- onChange: setText,
1256
+ onChange: (value) => {
1257
+ setText(value);
1258
+ (0, import_comment_drafts.saveCommentDraft)(import_comment_drafts.NEW_COMMENT_DRAFT, value);
1259
+ },
1068
1260
  onSubmit: submit,
1069
1261
  disabled: (0, import_comment_extensions.isCommentEmpty)(text),
1070
1262
  getMentionSuggestions,
1071
1263
  renderMentionSuggestion,
1072
1264
  autoFocus: true,
1073
- leading: draftAvatar
1265
+ leading: draftAvatar(me?.color)
1074
1266
  }
1075
1267
  )
1076
1268
  }