@tldraw/commenting 5.3.0-next.d7d8ced023d5 → 5.3.0-next.dec36c5c2930

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 (239) hide show
  1. package/commenting.css +85 -87
  2. package/dist-cjs/canvas/anchor-lifecycle.js +2 -2
  3. package/dist-cjs/canvas/anchor-lifecycle.js.map +2 -2
  4. package/dist-cjs/{ui/tooltip-button.js → canvas/canvas-events.js} +16 -13
  5. package/dist-cjs/canvas/canvas-events.js.map +7 -0
  6. package/dist-cjs/canvas/cluster-badge.js +121 -0
  7. package/dist-cjs/canvas/cluster-badge.js.map +7 -0
  8. package/dist-cjs/canvas/cluster-fade.js +85 -0
  9. package/dist-cjs/canvas/cluster-fade.js.map +7 -0
  10. package/dist-cjs/canvas/cluster-input.js +51 -1
  11. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  12. package/dist-cjs/canvas/cluster-model.js +252 -0
  13. package/dist-cjs/canvas/cluster-model.js.map +7 -0
  14. package/dist-cjs/canvas/comment-mutations.js +57 -28
  15. package/dist-cjs/canvas/comment-mutations.js.map +2 -2
  16. package/dist-cjs/canvas/comment-reactions.js +4 -7
  17. package/dist-cjs/canvas/comment-reactions.js.map +2 -2
  18. package/dist-cjs/canvas/comment-render.js +4 -3
  19. package/dist-cjs/canvas/comment-render.js.map +2 -2
  20. package/dist-cjs/canvas/comment-store.js.map +2 -2
  21. package/dist-cjs/canvas/comment-tool.js +12 -7
  22. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  23. package/dist-cjs/canvas/comments-filter-menu.js +11 -13
  24. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overflow-menu.js +10 -2
  26. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  27. package/dist-cjs/canvas/comments-overlay.js +81 -871
  28. package/dist-cjs/canvas/comments-overlay.js.map +3 -3
  29. package/dist-cjs/canvas/comments-sidebar.js +16 -11
  30. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  31. package/dist-cjs/canvas/comments-visibility-toggle.js +5 -25
  32. package/dist-cjs/canvas/comments-visibility-toggle.js.map +2 -2
  33. package/dist-cjs/canvas/context.js.map +1 -1
  34. package/dist-cjs/canvas/hooks.js +8 -2
  35. package/dist-cjs/canvas/hooks.js.map +2 -2
  36. package/dist-cjs/canvas/license.js.map +1 -1
  37. package/dist-cjs/canvas/mobile-placement.js +91 -0
  38. package/dist-cjs/canvas/mobile-placement.js.map +7 -0
  39. package/dist-cjs/canvas/options.js +39 -1
  40. package/dist-cjs/canvas/options.js.map +2 -2
  41. package/dist-cjs/canvas/pending-composer.js +135 -0
  42. package/dist-cjs/canvas/pending-composer.js.map +7 -0
  43. package/dist-cjs/canvas/pin-stacking.js +26 -1
  44. package/dist-cjs/canvas/pin-stacking.js.map +2 -2
  45. package/dist-cjs/canvas/region-box.js +113 -0
  46. package/dist-cjs/canvas/region-box.js.map +7 -0
  47. package/dist-cjs/canvas/state.js.map +2 -2
  48. package/dist-cjs/canvas/thread-pin.js +304 -0
  49. package/dist-cjs/canvas/thread-pin.js.map +7 -0
  50. package/dist-cjs/canvas/thread-preview.js +41 -49
  51. package/dist-cjs/canvas/thread-preview.js.map +2 -2
  52. package/dist-cjs/canvas/thread-stack.js +7 -7
  53. package/dist-cjs/canvas/thread-stack.js.map +2 -2
  54. package/dist-cjs/canvas/thread-state.js +15 -0
  55. package/dist-cjs/canvas/thread-state.js.map +2 -2
  56. package/dist-cjs/canvas/thread-view.js +120 -67
  57. package/dist-cjs/canvas/thread-view.js.map +3 -3
  58. package/dist-cjs/clustering/computeClusterTable.js +2 -2
  59. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  60. package/dist-cjs/clustering/replay.js +89 -22
  61. package/dist-cjs/clustering/replay.js.map +3 -3
  62. package/dist-cjs/clustering/runtime.js +19 -12
  63. package/dist-cjs/clustering/runtime.js.map +2 -2
  64. package/dist-cjs/clustering/types.js.map +1 -1
  65. package/dist-cjs/index.d.ts +326 -203
  66. package/dist-cjs/index.js +7 -1
  67. package/dist-cjs/index.js.map +2 -2
  68. package/dist-cjs/ui/byline.js +5 -3
  69. package/dist-cjs/ui/byline.js.map +2 -2
  70. package/dist-cjs/ui/comment-composer.js +2 -18
  71. package/dist-cjs/ui/comment-composer.js.map +2 -2
  72. package/dist-cjs/ui/comment-pin.js +2 -15
  73. package/dist-cjs/ui/comment-pin.js.map +2 -2
  74. package/dist-cjs/ui/comments-list.js +11 -24
  75. package/dist-cjs/ui/comments-list.js.map +2 -2
  76. package/dist-cjs/ui/format-time.js +13 -1
  77. package/dist-cjs/ui/format-time.js.map +2 -2
  78. package/dist-cjs/ui/icons.js +138 -0
  79. package/dist-cjs/ui/icons.js.map +7 -0
  80. package/dist-cjs/ui/reaction-picker.js +11 -13
  81. package/dist-cjs/ui/reaction-picker.js.map +2 -2
  82. package/dist-cjs/ui/reaction.js +3 -4
  83. package/dist-cjs/ui/reaction.js.map +2 -2
  84. package/dist-cjs/ui/send-button.js +2 -9
  85. package/dist-cjs/ui/send-button.js.map +2 -2
  86. package/dist-cjs/ui/visual-viewport.js +32 -0
  87. package/dist-cjs/ui/visual-viewport.js.map +7 -0
  88. package/dist-esm/canvas/anchor-lifecycle.mjs +3 -3
  89. package/dist-esm/canvas/anchor-lifecycle.mjs.map +2 -2
  90. package/dist-esm/canvas/canvas-events.mjs +15 -0
  91. package/dist-esm/canvas/canvas-events.mjs.map +7 -0
  92. package/dist-esm/canvas/cluster-badge.mjs +106 -0
  93. package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
  94. package/dist-esm/canvas/cluster-fade.mjs +65 -0
  95. package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
  96. package/dist-esm/canvas/cluster-input.mjs +52 -2
  97. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  98. package/dist-esm/canvas/cluster-model.mjs +236 -0
  99. package/dist-esm/canvas/cluster-model.mjs.map +7 -0
  100. package/dist-esm/canvas/comment-mutations.mjs +57 -28
  101. package/dist-esm/canvas/comment-mutations.mjs.map +2 -2
  102. package/dist-esm/canvas/comment-reactions.mjs +5 -12
  103. package/dist-esm/canvas/comment-reactions.mjs.map +2 -2
  104. package/dist-esm/canvas/comment-render.mjs +5 -4
  105. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +12 -7
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +12 -13
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +11 -2
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +58 -879
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +18 -14
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +5 -25
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +2 -2
  119. package/dist-esm/canvas/hooks.mjs +9 -2
  120. package/dist-esm/canvas/hooks.mjs.map +2 -2
  121. package/dist-esm/canvas/license.mjs.map +1 -1
  122. package/dist-esm/canvas/mobile-placement.mjs +71 -0
  123. package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
  124. package/dist-esm/canvas/options.mjs +39 -1
  125. package/dist-esm/canvas/options.mjs.map +2 -2
  126. package/dist-esm/canvas/pending-composer.mjs +127 -0
  127. package/dist-esm/canvas/pending-composer.mjs.map +7 -0
  128. package/dist-esm/canvas/pin-stacking.mjs +26 -1
  129. package/dist-esm/canvas/pin-stacking.mjs.map +2 -2
  130. package/dist-esm/canvas/region-box.mjs +93 -0
  131. package/dist-esm/canvas/region-box.mjs.map +7 -0
  132. package/dist-esm/canvas/state.mjs.map +2 -2
  133. package/dist-esm/canvas/thread-pin.mjs +310 -0
  134. package/dist-esm/canvas/thread-pin.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-preview.mjs +42 -49
  136. package/dist-esm/canvas/thread-preview.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-stack.mjs +8 -9
  138. package/dist-esm/canvas/thread-stack.mjs.map +2 -2
  139. package/dist-esm/canvas/thread-state.mjs +15 -0
  140. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  141. package/dist-esm/canvas/thread-view.mjs +131 -72
  142. package/dist-esm/canvas/thread-view.mjs.map +3 -3
  143. package/dist-esm/clustering/computeClusterTable.mjs +2 -2
  144. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  145. package/dist-esm/clustering/replay.mjs +89 -22
  146. package/dist-esm/clustering/replay.mjs.map +3 -3
  147. package/dist-esm/clustering/runtime.mjs +19 -12
  148. package/dist-esm/clustering/runtime.mjs.map +2 -2
  149. package/dist-esm/index.d.mts +326 -203
  150. package/dist-esm/index.mjs +16 -3
  151. package/dist-esm/index.mjs.map +2 -2
  152. package/dist-esm/ui/byline.mjs +6 -4
  153. package/dist-esm/ui/byline.mjs.map +2 -2
  154. package/dist-esm/ui/comment-composer.mjs +2 -18
  155. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  156. package/dist-esm/ui/comment-pin.mjs +2 -15
  157. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  158. package/dist-esm/ui/comments-list.mjs +10 -23
  159. package/dist-esm/ui/comments-list.mjs.map +2 -2
  160. package/dist-esm/ui/format-time.mjs +13 -1
  161. package/dist-esm/ui/format-time.mjs.map +2 -2
  162. package/dist-esm/ui/icons.mjs +118 -0
  163. package/dist-esm/ui/icons.mjs.map +7 -0
  164. package/dist-esm/ui/reaction-picker.mjs +12 -13
  165. package/dist-esm/ui/reaction-picker.mjs.map +2 -2
  166. package/dist-esm/ui/reaction.mjs +3 -4
  167. package/dist-esm/ui/reaction.mjs.map +2 -2
  168. package/dist-esm/ui/send-button.mjs +2 -9
  169. package/dist-esm/ui/send-button.mjs.map +2 -2
  170. package/dist-esm/ui/visual-viewport.mjs +12 -0
  171. package/dist-esm/ui/visual-viewport.mjs.map +7 -0
  172. package/package.json +7 -6
  173. package/src/canvas/anchor-lifecycle.test.ts +2 -2
  174. package/src/canvas/anchor-lifecycle.ts +25 -39
  175. package/src/canvas/canvas-events.ts +18 -0
  176. package/src/canvas/canvas.css +5 -11
  177. package/src/canvas/cluster-badge.tsx +133 -0
  178. package/src/canvas/cluster-fade.ts +102 -0
  179. package/src/canvas/cluster-input.test.ts +148 -14
  180. package/src/canvas/cluster-input.ts +92 -5
  181. package/src/canvas/cluster-model.ts +378 -0
  182. package/src/canvas/comment-mutations.test.ts +46 -4
  183. package/src/canvas/comment-mutations.ts +115 -104
  184. package/src/canvas/comment-reactions.test.ts +22 -0
  185. package/src/canvas/comment-reactions.tsx +33 -32
  186. package/src/canvas/comment-render.ts +8 -8
  187. package/src/canvas/comment-store.ts +12 -17
  188. package/src/canvas/comment-tool.test.ts +109 -4
  189. package/src/canvas/comment-tool.tsx +31 -19
  190. package/src/canvas/comments-filter-menu.tsx +10 -17
  191. package/src/canvas/comments-overflow-menu.tsx +8 -3
  192. package/src/canvas/comments-overlay.tsx +160 -1345
  193. package/src/canvas/comments-sidebar.tsx +40 -19
  194. package/src/canvas/comments-visibility-toggle.tsx +6 -30
  195. package/src/canvas/context.ts +4 -3
  196. package/src/canvas/hooks.test.ts +94 -0
  197. package/src/canvas/hooks.ts +17 -3
  198. package/src/canvas/license.ts +1 -1
  199. package/src/canvas/mobile-placement.ts +115 -0
  200. package/src/canvas/options.test.ts +124 -2
  201. package/src/canvas/options.ts +192 -58
  202. package/src/canvas/pending-composer.tsx +161 -0
  203. package/src/canvas/pin-stacking.test.ts +111 -1
  204. package/src/canvas/pin-stacking.ts +46 -0
  205. package/src/canvas/region-box.tsx +124 -0
  206. package/src/canvas/state.ts +20 -28
  207. package/src/canvas/thread-pin.tsx +419 -0
  208. package/src/canvas/thread-preview.tsx +77 -87
  209. package/src/canvas/thread-stack.tsx +19 -29
  210. package/src/canvas/thread-state.test.ts +51 -0
  211. package/src/canvas/thread-state.ts +56 -23
  212. package/src/canvas/thread-view.test.ts +72 -0
  213. package/src/canvas/thread-view.tsx +231 -133
  214. package/src/clustering/computeClusterTable.ts +12 -3
  215. package/src/clustering/replay.test.ts +0 -7
  216. package/src/clustering/replay.ts +131 -32
  217. package/src/clustering/runtime.test.ts +50 -6
  218. package/src/clustering/runtime.ts +42 -39
  219. package/src/clustering/schedule.test.ts +0 -6
  220. package/src/clustering/screen-offsets.test.ts +171 -0
  221. package/src/clustering/types.ts +10 -0
  222. package/src/index.ts +15 -2
  223. package/src/ui/byline.tsx +16 -6
  224. package/src/ui/comment-composer.tsx +25 -69
  225. package/src/ui/comment-pin.tsx +3 -16
  226. package/src/ui/comments-list.tsx +40 -29
  227. package/src/ui/comments.css +80 -76
  228. package/src/ui/format-time.test.ts +69 -0
  229. package/src/ui/format-time.ts +22 -2
  230. package/src/ui/icons.tsx +138 -0
  231. package/src/ui/reaction-picker.tsx +9 -16
  232. package/src/ui/reaction.tsx +10 -6
  233. package/src/ui/send-button.tsx +3 -8
  234. package/src/ui/visual-viewport.test.ts +36 -0
  235. package/src/ui/visual-viewport.ts +27 -0
  236. package/dist-cjs/ui/tooltip-button.js.map +0 -7
  237. package/dist-esm/ui/tooltip-button.mjs +0 -12
  238. package/dist-esm/ui/tooltip-button.mjs.map +0 -7
  239. package/src/ui/tooltip-button.tsx +0 -20
@@ -13,6 +13,7 @@ import {
13
13
  } from 'tldraw'
14
14
  import { afterEach, beforeEach, describe, expect, it } from 'vitest'
15
15
  import { CommentTool } from './comment-tool'
16
+ import { commentsSidebarOpen, pendingComment, regionDraft } from './state'
16
17
 
17
18
  /**
18
19
  * Pointer-driven tests for the comment tool's placement affordances. These need a real editor
@@ -90,14 +91,80 @@ describe('comment tool placement hints', () => {
90
91
  driver.pointerMove(150, 125)
91
92
  expect(editor.getHintingShapeIds()).toEqual([id])
92
93
 
93
- // Releasing places the comment and hands back to select, which owns its own hover state.
94
- driver.pointerDown(150, 125)
95
- driver.pointerUp(150, 125)
96
- expect(editor.isIn('select')).toBe(true)
94
+ // Select owns its own hover state, so the comment hint leaves with the tool.
95
+ editor.setCurrentTool('select')
97
96
  expect(editor.getHintingShapeIds()).toEqual([])
98
97
  })
99
98
  })
100
99
 
100
+ describe('comment tool placement lifecycle', () => {
101
+ let driver: Driver
102
+
103
+ beforeEach(() => {
104
+ driver = makeEditor()
105
+ })
106
+
107
+ it('stays in the tool with the composer open after placing', () => {
108
+ editor.setCurrentTool('comment')
109
+ driver.pointerDown(400, 300)
110
+ driver.pointerUp(400, 300)
111
+ // Placement opens the composer but the interaction isn't over — the tool holds on until
112
+ // the comment is posted or dismissed, so the surrounding UI stays stable.
113
+ expect(editor.isIn('comment.idle')).toBe(true)
114
+ expect(pendingComment.get(editor)).toEqual({
115
+ anchor: { type: 'point', x: 400, y: 300 },
116
+ point: { x: 400, y: 300 },
117
+ })
118
+ })
119
+
120
+ it('re-places the composer on a second click', () => {
121
+ editor.setCurrentTool('comment')
122
+ driver.pointerDown(400, 300)
123
+ driver.pointerUp(400, 300)
124
+ driver.pointerDown(200, 200)
125
+ driver.pointerUp(200, 200)
126
+ expect(editor.isIn('comment.idle')).toBe(true)
127
+ expect(pendingComment.get(editor)).toEqual({
128
+ anchor: { type: 'point', x: 200, y: 200 },
129
+ point: { x: 200, y: 200 },
130
+ })
131
+ })
132
+
133
+ it('drops the pending composer when the tool exits', () => {
134
+ editor.setCurrentTool('comment')
135
+ driver.pointerDown(400, 300)
136
+ driver.pointerUp(400, 300)
137
+ expect(pendingComment.get(editor)).not.toBeNull()
138
+
139
+ // A direct tool switch (toolbar, shortcut) abandons the draft composer — it belongs to
140
+ // the tool. The text itself survives in the comment draft store.
141
+ editor.setCurrentTool('select')
142
+ expect(pendingComment.get(editor)).toBeNull()
143
+ })
144
+
145
+ it('closes the comments sidebar for the whole interaction', () => {
146
+ commentsSidebarOpen.set(editor, true)
147
+ editor.setCurrentTool('comment')
148
+ expect(commentsSidebarOpen.get(editor)).toBe(false)
149
+
150
+ // ...and placing doesn't reopen it: the tool (and the closed sidebar) hold through the
151
+ // composer. Reopening is the host's button, never a side effect of leaving the tool.
152
+ driver.pointerDown(400, 300)
153
+ driver.pointerUp(400, 300)
154
+ expect(editor.isIn('comment.idle')).toBe(true)
155
+ expect(commentsSidebarOpen.get(editor)).toBe(false)
156
+ })
157
+
158
+ it('escape leaves the tool for select and drops the draft', () => {
159
+ editor.setCurrentTool('comment')
160
+ driver.pointerDown(400, 300)
161
+ driver.pointerUp(400, 300)
162
+ editor.cancel()
163
+ expect(editor.isIn('select')).toBe(true)
164
+ expect(pendingComment.get(editor)).toBeNull()
165
+ })
166
+ })
167
+
101
168
  describe('comment tool placement hints with regions enabled', () => {
102
169
  it('clears the hint when a drag becomes a region', () => {
103
170
  const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
@@ -113,6 +180,44 @@ describe('comment tool placement hints with regions enabled', () => {
113
180
  expect(editor.isIn('comment.dragging')).toBe(true)
114
181
  expect(editor.getHintingShapeIds()).toEqual([])
115
182
  })
183
+
184
+ it('stays in the tool with the composer open after a region drag', () => {
185
+ const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
186
+ editor.setCurrentTool('comment')
187
+ driver.pointerDown(100, 100)
188
+ driver.pointerMove(200, 180)
189
+ driver.pointerUp(200, 180)
190
+ expect(editor.isIn('comment.idle')).toBe(true)
191
+ expect(pendingComment.get(editor)).toEqual({
192
+ anchor: { type: 'region', x: 100, y: 100, w: 100, h: 80, pinX: 1, pinY: 1 },
193
+ point: { x: 200, y: 180 },
194
+ })
195
+ })
196
+
197
+ it('does not hint a shape under the pin corner of a placed region', () => {
198
+ const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
199
+ makeShape(100, 100)
200
+ editor.setCurrentTool('comment')
201
+ // Release on top of the shape: a region anchors to its rectangle, so the outline that
202
+ // normally previews a shape anchor would be a lie here.
203
+ driver.pointerDown(50, 50)
204
+ driver.pointerMove(150, 125)
205
+ driver.pointerUp(150, 125)
206
+ expect(editor.isIn('comment.idle')).toBe(true)
207
+ expect(editor.getHintingShapeIds()).toEqual([])
208
+ })
209
+
210
+ it('drops the region draft when the tool exits mid-drag', () => {
211
+ const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
212
+ editor.setCurrentTool('comment')
213
+ driver.pointerDown(100, 100)
214
+ driver.pointerMove(200, 180)
215
+ expect(regionDraft.get(editor)).not.toBeNull()
216
+
217
+ // A direct tool switch (shortcut, toolbar) mid-drag must not strand the drawn rectangle.
218
+ editor.setCurrentTool('select')
219
+ expect(regionDraft.get(editor)).toBeNull()
220
+ })
116
221
  })
117
222
 
118
223
  describe('comment tool anchoring to frames', () => {
@@ -49,11 +49,12 @@ export function regionBetween(a: VecLike, b: VecLike): BoxModel {
49
49
  }
50
50
 
51
51
  /**
52
- * The comment tool. Pressing down opens the comment composer immediately at the pointer, and it
53
- * follows the pointer until release — like placing a sticky note — settling on a point, or on a
54
- * shape when released over one. (With region comments enabled, dragging past the threshold instead
55
- * draws a region rectangle.) Placement only opens a composer (via `pendingComment`); the records
56
- * are created when the comment is posted.
52
+ * The comment tool. Pressing down opens the comment composer at the pointer and it follows until
53
+ * release — like placing a sticky note — settling on a point, or on a shape when released over one.
54
+ * With region comments enabled, dragging past the threshold draws a region rectangle instead.
55
+ * Placement only opens a composer; the records are created when the comment is posted. The tool
56
+ * stays active while the composer is open — posting returns to select, and clicking elsewhere
57
+ * re-places the composer.
57
58
  * @public
58
59
  */
59
60
  export class CommentTool extends StateNode {
@@ -99,8 +100,11 @@ export class CommentTool extends StateNode {
99
100
 
100
101
  override onExit() {
101
102
  // Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets
102
- // when the next tool takes over.
103
+ // when the next tool takes over. The draft composer and region draft belong to the tool, so
104
+ // they leave with it; the draft's text survives in the comment draft store.
103
105
  this.editor.setHintingShapes([])
106
+ pendingComment.set(this.editor, null)
107
+ regionDraft.set(this.editor, null)
104
108
  }
105
109
 
106
110
  // Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).
@@ -123,10 +127,16 @@ class CommentIdle extends StateNode {
123
127
  override onEnter() {
124
128
  // Back to hovering: restore the pin cursor (a prior placing state may have hidden it).
125
129
  this.editor.setCursor({ type: 'comment', rotation: 0 })
126
- updateAnchorHint(this.editor)
130
+ // With a region composer open, a shape outline under its pin corner would imply a shape
131
+ // anchor the region will never use — and it would stick, since moves over the composer
132
+ // never reach the canvas. Point placements keep the hint: it previews the next click.
133
+ if (pendingComment.get(this.editor)?.anchor.type === 'region') {
134
+ this.editor.setHintingShapes([])
135
+ } else {
136
+ updateAnchorHint(this.editor)
137
+ }
127
138
  }
128
139
 
129
- // Hint the shape a click would attach to.
130
140
  override onPointerMove() {
131
141
  updateAnchorHint(this.editor)
132
142
  }
@@ -187,8 +197,10 @@ class CommentPointing extends StateNode {
187
197
  )
188
198
  : { type: 'point', x: point.x, y: point.y }
189
199
  pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })
190
- // Hand back to select; the open composer is now the focus.
191
- editor.setCurrentTool('select')
200
+ // Stay in the tool while the composer is open — the interaction isn't over until the
201
+ // comment is posted or dismissed, and staying keeps the surrounding UI (style panel,
202
+ // sidebar) from churning mid-placement.
203
+ this.parent.transition('idle')
192
204
  }
193
205
 
194
206
  override onCancel() {
@@ -199,9 +211,9 @@ class CommentPointing extends StateNode {
199
211
  this.cancel()
200
212
  }
201
213
 
202
- // Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.).
214
+ // Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.). The
215
+ // tool's onExit drops the draft.
203
216
  private cancel() {
204
- pendingComment.set(this.editor, null)
205
217
  this.editor.setCurrentTool('select')
206
218
  }
207
219
  }
@@ -210,10 +222,9 @@ class CommentDragging extends StateNode {
210
222
  static override id = 'dragging'
211
223
 
212
224
  override onEnter() {
213
- // A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair
214
- // again — the composer no longer stands in for the pointer, so a hidden cursor would leave
215
- // the drag with no pointer at all. Drop the placement hint too: a region anchors to its
216
- // rectangle, so a single-shape outline under the dashed box would be stale.
225
+ // A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,
226
+ // since the composer no longer stands in for the pointer, and drop the placement hint — a region
227
+ // anchors to its rectangle, so a single-shape outline would be stale.
217
228
  pendingComment.set(this.editor, null)
218
229
  this.editor.setHintingShapes([])
219
230
  this.editor.setCursor({ type: 'cross', rotation: 0 })
@@ -237,7 +248,8 @@ class CommentDragging extends StateNode {
237
248
  anchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },
238
249
  point: regionPinPoint(region, pin),
239
250
  })
240
- editor.setCurrentTool('select')
251
+ // Same as the point placement: the tool stays active while the composer is open.
252
+ this.parent.transition('idle')
241
253
  }
242
254
 
243
255
  override onCancel() {
@@ -256,8 +268,8 @@ class CommentDragging extends StateNode {
256
268
  )
257
269
  }
258
270
 
271
+ // The tool's onExit drops the region draft.
259
272
  private cancel() {
260
- regionDraft.set(this.editor, null)
261
273
  this.editor.setCurrentTool('select')
262
274
  }
263
275
  }
@@ -266,7 +278,7 @@ class CommentDragging extends StateNode {
266
278
  export const commentTools = [CommentTool]
267
279
 
268
280
  /** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
269
- * Once registered, tldraw's `DefaultToolbarContent` shows the comment button next to the eraser.
281
+ * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.
270
282
  * @public */
271
283
  export const commentToolOverrides: TLUiOverrides = {
272
284
  tools(editor, tools) {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ TldrawUiButton,
2
3
  TldrawUiDropdownMenuContent,
3
4
  TldrawUiDropdownMenuRoot,
4
5
  TldrawUiDropdownMenuTrigger,
@@ -9,7 +10,7 @@ import {
9
10
  useTranslation,
10
11
  useValue,
11
12
  } from 'tldraw'
12
- import { TooltipButton } from '../ui/tooltip-button'
13
+ import { MoreMenuIcon } from '../ui/icons'
13
14
  import { SidebarFilters } from './sidebar-filters'
14
15
  import { sidebarFilters } from './state'
15
16
 
@@ -37,9 +38,14 @@ export function CommentsFilterMenu({
37
38
  return (
38
39
  <TldrawUiDropdownMenuRoot id="comments-filter">
39
40
  <TldrawUiDropdownMenuTrigger>
40
- <TooltipButton tooltip={msg('comments.filter')} className="tlui-cmt-header-btn">
41
- <FilterIcon />
42
- </TooltipButton>
41
+ <TldrawUiButton
42
+ type="icon"
43
+ tooltip={msg('comments.filter')}
44
+ title={msg('comments.filter')}
45
+ className="tlui-cmt-header-btn"
46
+ >
47
+ <MoreMenuIcon />
48
+ </TldrawUiButton>
43
49
  </TldrawUiDropdownMenuTrigger>
44
50
  {/* No tlui-cmt-menu here: the rows are tldraw menu items, which carry their own insets —
45
51
  the extra padding would set this menu apart from the canvas menus it should match. */}
@@ -80,16 +86,3 @@ export function CommentsFilterMenu({
80
86
  </TldrawUiDropdownMenuRoot>
81
87
  )
82
88
  }
83
-
84
- function FilterIcon() {
85
- return (
86
- <svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
87
- <path
88
- d="M5.5 3C4.67157 3 4 3.67157 4 4.5C4 5.32843 4.67157 6 5.5 6C6.32843 6 7 5.32843 7 4.5C7 3.67157 6.32843 3 5.5 3ZM3 5C3.01671 5 3.03323 4.99918 3.04952 4.99758C3.28022 6.1399 4.28967 7 5.5 7C6.71033 7 7.71978 6.1399 7.95048 4.99758C7.96677 4.99918 7.98329 5 8 5H13.5C13.7761 5 14 4.77614 14 4.5C14 4.22386 13.7761 4 13.5 4H8C7.98329 4 7.96677 4.00082 7.95048 4.00242C7.71978 2.86009 6.71033 2 5.5 2C4.28967 2 3.28022 2.86009 3.04952 4.00242C3.03323 4.00082 3.01671 4 3 4H1.5C1.22386 4 1 4.22386 1 4.5C1 4.77614 1.22386 5 1.5 5H3ZM11.9505 10.9976C11.7198 12.1399 10.7103 13 9.5 13C8.28967 13 7.28022 12.1399 7.04952 10.9976C7.03323 10.9992 7.01671 11 7 11H1.5C1.22386 11 1 10.7761 1 10.5C1 10.2239 1.22386 10 1.5 10H7C7.01671 10 7.03323 10.0008 7.04952 10.0024C7.28022 8.8601 8.28967 8 9.5 8C10.7103 8 11.7198 8.8601 11.9505 10.0024C11.9668 10.0008 11.9833 10 12 10H13.5C13.7761 10 14 10.2239 14 10.5C14 10.7761 13.7761 11 13.5 11H12C11.9833 11 11.9668 10.9992 11.9505 10.9976ZM8 10.5C8 9.67157 8.67157 9 9.5 9C10.3284 9 11 9.67157 11 10.5C11 11.3284 10.3284 12 9.5 12C8.67157 12 8 11.3284 8 10.5Z"
89
- fill="currentColor"
90
- fillRule="evenodd"
91
- clipRule="evenodd"
92
- />
93
- </svg>
94
- )
95
- }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ TldrawUiButton,
2
3
  TldrawUiDropdownMenuContent,
3
4
  TldrawUiDropdownMenuGroup,
4
5
  TldrawUiDropdownMenuItem,
@@ -9,7 +10,6 @@ import {
9
10
  useTranslation,
10
11
  useValue,
11
12
  } from 'tldraw'
12
- import { TooltipButton } from '../ui/tooltip-button'
13
13
  import { commentsHidden, toggleCommentsHidden } from './state'
14
14
 
15
15
  // A keyboard-shortcut glyph, not translatable copy — kept out of JSX as a constant.
@@ -26,9 +26,14 @@ export function CommentsOverflowMenu() {
26
26
  return (
27
27
  <TldrawUiDropdownMenuRoot id="comments-overflow">
28
28
  <TldrawUiDropdownMenuTrigger>
29
- <TooltipButton tooltip={msg('comments.more-options')} className="tlui-cmt-header-btn">
29
+ <TldrawUiButton
30
+ type="icon"
31
+ tooltip={msg('comments.more-options')}
32
+ title={msg('comments.more-options')}
33
+ className="tlui-cmt-header-btn"
34
+ >
30
35
  <TldrawUiIcon icon="dots-vertical" label={msg('comments.more-options')} small />
31
- </TooltipButton>
36
+ </TldrawUiButton>
32
37
  </TldrawUiDropdownMenuTrigger>
33
38
  <TldrawUiDropdownMenuContent
34
39
  className="tlui-cmt-menu"