@tldraw/commenting 5.6.0-canary.a7f2c4b47eeb → 5.6.0-canary.b8f107a1f44a
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.
- package/dist-cjs/canvas/comment-tool.js +27 -13
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/pending-composer.js +1 -0
- package/dist-cjs/canvas/pending-composer.js.map +2 -2
- package/dist-cjs/index.d.ts +16 -2
- package/dist-cjs/index.js +2 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +27 -13
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/pending-composer.mjs +1 -0
- package/dist-esm/canvas/pending-composer.mjs.map +2 -2
- package/dist-esm/index.d.mts +16 -2
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/package.json +5 -5
- package/src/canvas/comment-tool.test.ts +39 -7
- package/src/canvas/comment-tool.tsx +47 -16
- package/src/canvas/pending-composer.tsx +3 -0
- package/src/index.ts +6 -1
|
@@ -21,7 +21,8 @@ __export(comment_tool_exports, {
|
|
|
21
21
|
CommentTool: () => CommentTool,
|
|
22
22
|
commentToolOverrides: () => commentToolOverrides,
|
|
23
23
|
commentTools: () => commentTools,
|
|
24
|
-
regionBetween: () => regionBetween
|
|
24
|
+
regionBetween: () => regionBetween,
|
|
25
|
+
startCommentAt: () => startCommentAt
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(comment_tool_exports);
|
|
27
28
|
var import_tldraw = require("tldraw");
|
|
@@ -84,6 +85,26 @@ class CommentTool extends import_tldraw.StateNode {
|
|
|
84
85
|
this.editor.setCurrentTool("select");
|
|
85
86
|
}
|
|
86
87
|
}
|
|
88
|
+
function pendingCommentAt(editor, point) {
|
|
89
|
+
const hit = (0, import_thread_state.commentTargetShapeAt)(editor, point);
|
|
90
|
+
const anchor = hit ? (0, import_thread_state.shapeAnchorAt)(
|
|
91
|
+
editor,
|
|
92
|
+
hit.id,
|
|
93
|
+
point,
|
|
94
|
+
(0, import_options.getCommentingOptions)(editor).shouldBePrecise(editor, {
|
|
95
|
+
shapeId: hit.id,
|
|
96
|
+
point,
|
|
97
|
+
altKey: editor.inputs.getAltKey()
|
|
98
|
+
})
|
|
99
|
+
) : { type: "point", x: point.x, y: point.y };
|
|
100
|
+
return { anchor, point: { x: point.x, y: point.y } };
|
|
101
|
+
}
|
|
102
|
+
function startCommentAt(editor, point) {
|
|
103
|
+
editor.setCurrentTool("comment");
|
|
104
|
+
const pending = pendingCommentAt(editor, point);
|
|
105
|
+
import_state.pendingComment.set(editor, pending);
|
|
106
|
+
editor.setHintingShapes(pending.anchor.type === "shape" ? [pending.anchor.shapeId] : []);
|
|
107
|
+
}
|
|
87
108
|
function updateAnchorHint(editor) {
|
|
88
109
|
const hit = (0, import_thread_state.commentTargetShapeAt)(editor, editor.inputs.getCurrentPagePoint());
|
|
89
110
|
editor.setHintingShapes(hit ? [hit.id] : []);
|
|
@@ -102,6 +123,10 @@ class CommentIdle extends import_tldraw.StateNode {
|
|
|
102
123
|
updateAnchorHint(this.editor);
|
|
103
124
|
}
|
|
104
125
|
onPointerDown() {
|
|
126
|
+
if (import_state.pendingComment.get(this.editor)) {
|
|
127
|
+
this.editor.setCurrentTool("select");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
105
130
|
this.parent.transition("pointing");
|
|
106
131
|
}
|
|
107
132
|
}
|
|
@@ -130,18 +155,7 @@ class CommentPointing extends import_tldraw.StateNode {
|
|
|
130
155
|
onPointerUp() {
|
|
131
156
|
const { editor } = this;
|
|
132
157
|
const point = editor.inputs.getCurrentPagePoint();
|
|
133
|
-
|
|
134
|
-
const anchor = hit ? (0, import_thread_state.shapeAnchorAt)(
|
|
135
|
-
editor,
|
|
136
|
-
hit.id,
|
|
137
|
-
point,
|
|
138
|
-
(0, import_options.getCommentingOptions)(editor).shouldBePrecise(editor, {
|
|
139
|
-
shapeId: hit.id,
|
|
140
|
-
point,
|
|
141
|
-
altKey: editor.inputs.getAltKey()
|
|
142
|
-
})
|
|
143
|
-
) : { type: "point", x: point.x, y: point.y };
|
|
144
|
-
import_state.pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } });
|
|
158
|
+
import_state.pendingComment.set(editor, pendingCommentAt(editor, point));
|
|
145
159
|
this.parent.transition("idle");
|
|
146
160
|
}
|
|
147
161
|
onCancel() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comment-tool.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tBoxModel,\n\tEditor,\n\tStateNode,\n\tTLCommentAnchor,\n\tTLStateNodeConstructor,\n\tTLUiOverrides,\n\tVecLike,\n} from 'tldraw'\nimport { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'\nimport { commentsSidebarOpen, pendingComment, regionDraft } from './state'\nimport { commentTargetShapeAt, regionPinPoint, shapeAnchorAt } from './thread-state'\n\n/** A comment being placed but not yet posted: where its composer sits and what it will anchor\n * to. Shared between the tool (which sets it on click) and the overlay (which renders the\n * composer) through the internal `pendingComment` atom.\n * @internal */\nexport interface PendingComment {\n\tanchor: TLCommentAnchor\n\t/** Page point where the composer opens (the click location, or a region's pin corner). */\n\tpoint: VecLike\n}\n\n/**\n * Merge configure options over a base. Shallow for scalars; `components` is merged rather than\n * replaced so chained `configure` calls layer their slots \u2014 a later `{ components: { PinContent } }`\n * doesn't drop an earlier `{ components: { CommentBody } }`.\n */\nfunction mergeCommentingOptions(\n\tbase: CommentingOptions,\n\toverrides: Partial<CommentingOptions>\n): CommentingOptions {\n\treturn {\n\t\t...base,\n\t\t...overrides,\n\t\tcomponents: { ...base.components, ...overrides.components },\n\t}\n}\n\n/** The page-space rectangle spanned by two points, normalized so w/h are non-negative. Shared by\n * region creation (pointer-down \u2192 cursor) and corner resize (fixed corner \u2192 cursor). */\nexport function regionBetween(a: VecLike, b: VecLike): BoxModel {\n\treturn {\n\t\tx: Math.min(a.x, b.x),\n\t\ty: Math.min(a.y, b.y),\n\t\tw: Math.abs(a.x - b.x),\n\t\th: Math.abs(a.y - b.y),\n\t}\n}\n\n/**\n * The comment tool. Pressing down opens the comment composer at the pointer and it follows until\n * release \u2014 like placing a sticky note \u2014 settling on a point, or on a shape when released over one.\n * With region comments enabled, dragging past the threshold draws a region rectangle instead.\n * Placement only opens a composer; the records are created when the comment is posted. The tool\n * stays active while the composer is open \u2014 posting returns to select, and clicking elsewhere\n * re-places the composer.\n * @public\n */\nexport class CommentTool extends StateNode {\n\tstatic override id = 'comment'\n\tstatic override initial = 'idle'\n\tstatic override children(): TLStateNodeConstructor[] {\n\t\treturn [CommentIdle, CommentPointing, CommentDragging]\n\t}\n\n\t/**\n\t * Configure this tool's {@link CommentTool.options | `options`}, returning a configured subclass\n\t * to register via `tools`. Mirrors `ShapeUtil.configure`. Layers over any prior `configure`, so\n\t * calls can be chained.\n\t *\n\t * @example\n\t * ```tsx\n\t * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n\t * ```\n\t */\n\tstatic configure<T extends TLStateNodeConstructor>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- mirrors ShapeUtil.configure; extending `this` is sound at runtime.\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = mergeCommentingOptions(this.options, options)\n\t\t}\n\t}\n\n\t/**\n\t * The merged commenting options for this editor. Read from anywhere via\n\t * {@link getCommentingOptions}. Override with {@link CommentTool.configure}.\n\t */\n\toptions: CommentingOptions = defaultCommentingOptions\n\n\toverride onEnter() {\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// Placing comments is canvas-focused \u2014 the thread list gets out of the way while the tool is\n\t\t// active. Reopened via its own control (a button), never by leaving the tool.\n\t\tcommentsSidebarOpen.set(this.editor, false)\n\t}\n\n\toverride onExit() {\n\t\t// Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets\n\t\t// when the next tool takes over. The draft composer and region draft belong to the tool, so\n\t\t// they leave with it; the draft's text survives in the comment draft store.\n\t\tthis.editor.setHintingShapes([])\n\t\tpendingComment.set(this.editor, null)\n\t\tregionDraft.set(this.editor, null)\n\t}\n\n\t// Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).\n\toverride onCancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the\n * anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so\n * this shows the select-style outline while the comment tool \u2014 not select \u2014 is active. */\nfunction updateAnchorHint(editor: Editor) {\n\tconst hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint())\n\teditor.setHintingShapes(hit ? [hit.id] : [])\n}\n\nclass CommentIdle extends StateNode {\n\tstatic override id = 'idle'\n\n\toverride onEnter() {\n\t\t// Back to hovering: restore the pin cursor (a prior placing state may have hidden it).\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// With a region composer open, a shape outline under its pin corner would imply a shape\n\t\t// anchor the region will never use \u2014 and it would stick, since moves over the composer\n\t\t// never reach the canvas. Point placements keep the hint: it previews the next click.\n\t\tif (pendingComment.get(this.editor)?.anchor.type === 'region') {\n\t\t\tthis.editor.setHintingShapes([])\n\t\t} else {\n\t\t\tupdateAnchorHint(this.editor)\n\t\t}\n\t}\n\n\toverride onPointerMove() {\n\t\tupdateAnchorHint(this.editor)\n\t}\n\n\toverride onPointerDown() {\n\t\tthis.parent.transition('pointing')\n\t}\n}\n\nclass CommentPointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onEnter() {\n\t\t// Open the composer immediately at the press point so it's visible from pointer-down (not just\n\t\t// on release), and let it trail the pointer while dragging \u2014 like placing a sticky note. The\n\t\t// anchor is a bare point for now; it's resolved (shape or point) on pointer up.\n\t\tconst { editor } = this\n\t\t// Hide the cursor while placing: the draft composer is the pointer's stand-in now, so the pin\n\t\t// cursor sitting over it just reads as clutter.\n\t\teditor.setCursor({ type: 'none', rotation: 0 })\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'point', x: point.x, y: point.y },\n\t\t\tpoint: { x: point.x, y: point.y },\n\t\t})\n\t}\n\n\toverride onPointerMove() {\n\t\tconst { editor } = this\n\t\t// Once the pointer passes the drag threshold with region comments enabled, this is a region,\n\t\t// not a follow \u2014 hand off to the region drag (which clears this composer and draws the box).\n\t\tif (getCommentingOptions(editor).enableRegions && editor.inputs.getIsDragging()) {\n\t\t\tthis.parent.transition('dragging')\n\t\t\treturn\n\t\t}\n\t\t// Otherwise the composer trails the pointer \u2014 keep hinting the shape a release here would\n\t\t// anchor to, like the idle hover does.\n\t\tupdateAnchorHint(editor)\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.update(editor, (p) => (p ? { ...p, point: { x: point.x, y: point.y } } : p))\n\t}\n\n\t// Settle where the pointer is released: anchor to the shape under it, or drop a point.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tconst hit = commentTargetShapeAt(editor, point)\n\t\tconst anchor: TLCommentAnchor = hit\n\t\t\t? shapeAnchorAt(\n\t\t\t\t\teditor,\n\t\t\t\t\thit.id,\n\t\t\t\t\tpoint,\n\t\t\t\t\tgetCommentingOptions(editor).shouldBePrecise(editor, {\n\t\t\t\t\t\tshapeId: hit.id,\n\t\t\t\t\t\tpoint,\n\t\t\t\t\t\taltKey: editor.inputs.getAltKey(),\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t: { type: 'point', x: point.x, y: point.y }\n\t\tpendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })\n\t\t// Stay in the tool while the composer is open \u2014 the interaction isn't over until the\n\t\t// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,\n\t\t// sidebar) from churning mid-placement.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\t// Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.). The\n\t// tool's onExit drops the draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nclass CommentDragging extends StateNode {\n\tstatic override id = 'dragging'\n\n\toverride onEnter() {\n\t\t// A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,\n\t\t// since the composer no longer stands in for the pointer, and drop the placement hint \u2014 a region\n\t\t// anchors to its rectangle, so a single-shape outline would be stale.\n\t\tpendingComment.set(this.editor, null)\n\t\tthis.editor.setHintingShapes([])\n\t\tthis.editor.setCursor({ type: 'cross', rotation: 0 })\n\t\tthis.updateDraft()\n\t}\n\n\toverride onPointerMove() {\n\t\tthis.updateDraft()\n\t}\n\n\t// Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst origin = editor.inputs.getOriginPagePoint()\n\t\tconst current = editor.inputs.getCurrentPagePoint()\n\t\tconst region = regionBetween(origin, current)\n\t\t// The pin lives on the corner the drag released on \u2014 drag up-left, pin top-left.\n\t\tconst pin = { x: current.x >= origin.x ? 1 : 0, y: current.y >= origin.y ? 1 : 0 }\n\t\tregionDraft.set(editor, null)\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },\n\t\t\tpoint: regionPinPoint(region, pin),\n\t\t})\n\t\t// Same as the point placement: the tool stays active while the composer is open.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tprivate updateDraft() {\n\t\tconst { editor } = this\n\t\tregionDraft.set(\n\t\t\teditor,\n\t\t\tregionBetween(editor.inputs.getOriginPagePoint(), editor.inputs.getCurrentPagePoint())\n\t\t)\n\t}\n\n\t// The tool's onExit drops the region draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** @public */\nexport const commentTools = [CommentTool]\n\n/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.\n * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.\n * @public */\nexport const commentToolOverrides: TLUiOverrides = {\n\ttools(editor, tools) {\n\t\ttools.comment = {\n\t\t\tid: 'comment',\n\t\t\ticon: 'comment',\n\t\t\tlabel: 'Comment',\n\t\t\tkbd: 'c',\n\t\t\tonSelect: () => editor.setCurrentTool('comment'),\n\t\t}\n\t\treturn tools\n\t},\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAQO;AACP,qBAAuF;AACvF,mBAAiE;AACjE,0BAAoE;AAiBpE,SAAS,uBACR,MACA,WACoB;AACpB,SAAO;AAAA,IACN,GAAG;AAAA,IACH,GAAG;AAAA,IACH,YAAY,EAAE,GAAG,KAAK,YAAY,GAAG,UAAU,WAAW;AAAA,EAC3D;AACD;AAIO,SAAS,cAAc,GAAY,GAAsB;AAC/D,SAAO;AAAA,IACN,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IACpB,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IACpB,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC;AAAA,IACrB,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC;AAAA,EACtB;AACD;AAWO,MAAM,oBAAoB,wBAAU;AAAA,EAC1C,OAAgB,KAAK;AAAA,EACrB,OAAgB,UAAU;AAAA,EAC1B,OAAgB,WAAqC;AACpD,WAAO,CAAC,aAAa,iBAAiB,eAAe;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,OAAO,UAEN,SACI;AAEJ,WAAO,cAAc,KAAK;AAAA;AAAA,MAEzB,UAAU,uBAAuB,KAAK,SAAS,OAAO;AAAA,IACvD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAA6B;AAAA,EAEpB,UAAU;AAClB,SAAK,OAAO,UAAU,EAAE,MAAM,WAAW,UAAU,EAAE,CAAC;AAGtD,qCAAoB,IAAI,KAAK,QAAQ,KAAK;AAAA,EAC3C;AAAA,EAES,SAAS;AAIjB,SAAK,OAAO,iBAAiB,CAAC,CAAC;AAC/B,gCAAe,IAAI,KAAK,QAAQ,IAAI;AACpC,6BAAY,IAAI,KAAK,QAAQ,IAAI;AAAA,EAClC;AAAA;AAAA,EAGS,WAAW;AACnB,SAAK,OAAO,eAAe,QAAQ;AAAA,EACpC;AACD;AAKA,SAAS,iBAAiB,QAAgB;AACzC,QAAM,UAAM,0CAAqB,QAAQ,OAAO,OAAO,oBAAoB,CAAC;AAC5E,SAAO,iBAAiB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C;AAEA,MAAM,oBAAoB,wBAAU;AAAA,EACnC,OAAgB,KAAK;AAAA,EAEZ,UAAU;AAElB,SAAK,OAAO,UAAU,EAAE,MAAM,WAAW,UAAU,EAAE,CAAC;AAItD,QAAI,4BAAe,IAAI,KAAK,MAAM,GAAG,OAAO,SAAS,UAAU;AAC9D,WAAK,OAAO,iBAAiB,CAAC,CAAC;AAAA,IAChC,OAAO;AACN,uBAAiB,KAAK,MAAM;AAAA,IAC7B;AAAA,EACD;AAAA,EAES,gBAAgB;AACxB,qBAAiB,KAAK,MAAM;AAAA,EAC7B;AAAA,EAES,gBAAgB;
|
|
4
|
+
"sourcesContent": ["import {\n\tBoxModel,\n\tEditor,\n\tStateNode,\n\tTLCommentAnchor,\n\tTLStateNodeConstructor,\n\tTLUiOverrides,\n\tVecLike,\n} from 'tldraw'\nimport { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'\nimport { commentsSidebarOpen, pendingComment, regionDraft } from './state'\nimport { commentTargetShapeAt, regionPinPoint, shapeAnchorAt } from './thread-state'\n\n/** A comment being placed but not yet posted: where its composer sits and what it will anchor\n * to. Shared between the tool (which sets it on click) and the overlay (which renders the\n * composer) through the internal `pendingComment` atom.\n * @internal */\nexport interface PendingComment {\n\tanchor: TLCommentAnchor\n\t/** Page point where the composer opens (the click location, or a region's pin corner). */\n\tpoint: VecLike\n}\n\n/**\n * Merge configure options over a base. Shallow for scalars; `components` is merged rather than\n * replaced so chained `configure` calls layer their slots \u2014 a later `{ components: { PinContent } }`\n * doesn't drop an earlier `{ components: { CommentBody } }`.\n */\nfunction mergeCommentingOptions(\n\tbase: CommentingOptions,\n\toverrides: Partial<CommentingOptions>\n): CommentingOptions {\n\treturn {\n\t\t...base,\n\t\t...overrides,\n\t\tcomponents: { ...base.components, ...overrides.components },\n\t}\n}\n\n/** The page-space rectangle spanned by two points, normalized so w/h are non-negative. Shared by\n * region creation (pointer-down \u2192 cursor) and corner resize (fixed corner \u2192 cursor). */\nexport function regionBetween(a: VecLike, b: VecLike): BoxModel {\n\treturn {\n\t\tx: Math.min(a.x, b.x),\n\t\ty: Math.min(a.y, b.y),\n\t\tw: Math.abs(a.x - b.x),\n\t\th: Math.abs(a.y - b.y),\n\t}\n}\n\n/**\n * The comment tool. Pressing down opens the comment composer at the pointer and it follows until\n * release \u2014 like placing a sticky note \u2014 settling on a point, or on a shape when released over one.\n * With region comments enabled, dragging past the threshold draws a region rectangle instead.\n * Placement only opens a composer; the records are created when the comment is posted. The tool\n * stays active while the composer is open \u2014 posting returns to select, and so does clicking the\n * canvas elsewhere, which cancels the placement.\n * @public\n */\nexport class CommentTool extends StateNode {\n\tstatic override id = 'comment'\n\tstatic override initial = 'idle'\n\tstatic override children(): TLStateNodeConstructor[] {\n\t\treturn [CommentIdle, CommentPointing, CommentDragging]\n\t}\n\n\t/**\n\t * Configure this tool's {@link CommentTool.options | `options`}, returning a configured subclass\n\t * to register via `tools`. Mirrors `ShapeUtil.configure`. Layers over any prior `configure`, so\n\t * calls can be chained.\n\t *\n\t * @example\n\t * ```tsx\n\t * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n\t * ```\n\t */\n\tstatic configure<T extends TLStateNodeConstructor>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- mirrors ShapeUtil.configure; extending `this` is sound at runtime.\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = mergeCommentingOptions(this.options, options)\n\t\t}\n\t}\n\n\t/**\n\t * The merged commenting options for this editor. Read from anywhere via\n\t * {@link getCommentingOptions}. Override with {@link CommentTool.configure}.\n\t */\n\toptions: CommentingOptions = defaultCommentingOptions\n\n\toverride onEnter() {\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// Placing comments is canvas-focused \u2014 the thread list gets out of the way while the tool is\n\t\t// active. Reopened via its own control (a button), never by leaving the tool.\n\t\tcommentsSidebarOpen.set(this.editor, false)\n\t}\n\n\toverride onExit() {\n\t\t// Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets\n\t\t// when the next tool takes over. The draft composer and region draft belong to the tool, so\n\t\t// they leave with it; the draft's text survives in the comment draft store.\n\t\tthis.editor.setHintingShapes([])\n\t\tpendingComment.set(this.editor, null)\n\t\tregionDraft.set(this.editor, null)\n\t}\n\n\t// Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).\n\toverride onCancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nfunction pendingCommentAt(editor: Editor, point: VecLike): PendingComment {\n\tconst hit = commentTargetShapeAt(editor, point)\n\tconst anchor: TLCommentAnchor = hit\n\t\t? shapeAnchorAt(\n\t\t\t\teditor,\n\t\t\t\thit.id,\n\t\t\t\tpoint,\n\t\t\t\tgetCommentingOptions(editor).shouldBePrecise(editor, {\n\t\t\t\t\tshapeId: hit.id,\n\t\t\t\t\tpoint,\n\t\t\t\t\taltKey: editor.inputs.getAltKey(),\n\t\t\t\t})\n\t\t\t)\n\t\t: { type: 'point', x: point.x, y: point.y }\n\treturn { anchor, point: { x: point.x, y: point.y } }\n}\n\n/**\n * Open the comment composer at a page point, anchored as a comment tool click there would be. Use\n * it to start a comment from outside the tool, such as a context menu item. Enters the comment tool\n * (which must be registered), since the draft composer closes when the tool exits. It doesn't check\n * `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.\n *\n * @example\n * ```tsx\n * <TldrawUiMenuItem id=\"comment\" label=\"Comment\" onSelect={() => startCommentAt(editor, point)} />\n * ```\n * @public\n */\nexport function startCommentAt(editor: Editor, point: VecLike) {\n\teditor.setCurrentTool('comment')\n\tconst pending = pendingCommentAt(editor, point)\n\tpendingComment.set(editor, pending)\n\t// Entering the tool hints the shape under the live pointer, which is wherever the caller's UI\n\t// was (a menu item), not `point`.\n\teditor.setHintingShapes(pending.anchor.type === 'shape' ? [pending.anchor.shapeId] : [])\n}\n\n/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the\n * anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so\n * this shows the select-style outline while the comment tool \u2014 not select \u2014 is active. */\nfunction updateAnchorHint(editor: Editor) {\n\tconst hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint())\n\teditor.setHintingShapes(hit ? [hit.id] : [])\n}\n\nclass CommentIdle extends StateNode {\n\tstatic override id = 'idle'\n\n\toverride onEnter() {\n\t\t// Back to hovering: restore the pin cursor (a prior placing state may have hidden it).\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// With a region composer open, a shape outline under its pin corner would imply a shape\n\t\t// anchor the region will never use \u2014 and it would stick, since moves over the composer\n\t\t// never reach the canvas. Point placements keep the hint: it previews the next click.\n\t\tif (pendingComment.get(this.editor)?.anchor.type === 'region') {\n\t\t\tthis.editor.setHintingShapes([])\n\t\t} else {\n\t\t\tupdateAnchorHint(this.editor)\n\t\t}\n\t}\n\n\toverride onPointerMove() {\n\t\tupdateAnchorHint(this.editor)\n\t}\n\n\toverride onPointerDown() {\n\t\t// A press away from an open composer cancels the placement rather than moving it. The\n\t\t// tool's onExit drops the draft composer; its text survives in the draft store.\n\t\tif (pendingComment.get(this.editor)) {\n\t\t\tthis.editor.setCurrentTool('select')\n\t\t\treturn\n\t\t}\n\t\tthis.parent.transition('pointing')\n\t}\n}\n\nclass CommentPointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onEnter() {\n\t\t// Open the composer immediately at the press point so it's visible from pointer-down (not just\n\t\t// on release), and let it trail the pointer while dragging \u2014 like placing a sticky note. The\n\t\t// anchor is a bare point for now; it's resolved (shape or point) on pointer up.\n\t\tconst { editor } = this\n\t\t// Hide the cursor while placing: the draft composer is the pointer's stand-in now, so the pin\n\t\t// cursor sitting over it just reads as clutter.\n\t\teditor.setCursor({ type: 'none', rotation: 0 })\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'point', x: point.x, y: point.y },\n\t\t\tpoint: { x: point.x, y: point.y },\n\t\t})\n\t}\n\n\toverride onPointerMove() {\n\t\tconst { editor } = this\n\t\t// Once the pointer passes the drag threshold with region comments enabled, this is a region,\n\t\t// not a follow \u2014 hand off to the region drag (which clears this composer and draws the box).\n\t\tif (getCommentingOptions(editor).enableRegions && editor.inputs.getIsDragging()) {\n\t\t\tthis.parent.transition('dragging')\n\t\t\treturn\n\t\t}\n\t\t// Otherwise the composer trails the pointer \u2014 keep hinting the shape a release here would\n\t\t// anchor to, like the idle hover does.\n\t\tupdateAnchorHint(editor)\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.update(editor, (p) => (p ? { ...p, point: { x: point.x, y: point.y } } : p))\n\t}\n\n\t// Settle where the pointer is released: anchor to the shape under it, or drop a point.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, pendingCommentAt(editor, point))\n\t\t// Stay in the tool while the composer is open \u2014 the interaction isn't over until the\n\t\t// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,\n\t\t// sidebar) from churning mid-placement.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\t// Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.). The\n\t// tool's onExit drops the draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nclass CommentDragging extends StateNode {\n\tstatic override id = 'dragging'\n\n\toverride onEnter() {\n\t\t// A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,\n\t\t// since the composer no longer stands in for the pointer, and drop the placement hint \u2014 a region\n\t\t// anchors to its rectangle, so a single-shape outline would be stale.\n\t\tpendingComment.set(this.editor, null)\n\t\tthis.editor.setHintingShapes([])\n\t\tthis.editor.setCursor({ type: 'cross', rotation: 0 })\n\t\tthis.updateDraft()\n\t}\n\n\toverride onPointerMove() {\n\t\tthis.updateDraft()\n\t}\n\n\t// Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst origin = editor.inputs.getOriginPagePoint()\n\t\tconst current = editor.inputs.getCurrentPagePoint()\n\t\tconst region = regionBetween(origin, current)\n\t\t// The pin lives on the corner the drag released on \u2014 drag up-left, pin top-left.\n\t\tconst pin = { x: current.x >= origin.x ? 1 : 0, y: current.y >= origin.y ? 1 : 0 }\n\t\tregionDraft.set(editor, null)\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },\n\t\t\tpoint: regionPinPoint(region, pin),\n\t\t})\n\t\t// Same as the point placement: the tool stays active while the composer is open.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tprivate updateDraft() {\n\t\tconst { editor } = this\n\t\tregionDraft.set(\n\t\t\teditor,\n\t\t\tregionBetween(editor.inputs.getOriginPagePoint(), editor.inputs.getCurrentPagePoint())\n\t\t)\n\t}\n\n\t// The tool's onExit drops the region draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** @public */\nexport const commentTools = [CommentTool]\n\n/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.\n * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.\n * @public */\nexport const commentToolOverrides: TLUiOverrides = {\n\ttools(editor, tools) {\n\t\ttools.comment = {\n\t\t\tid: 'comment',\n\t\t\ticon: 'comment',\n\t\t\tlabel: 'Comment',\n\t\t\tkbd: 'c',\n\t\t\tonSelect: () => editor.setCurrentTool('comment'),\n\t\t}\n\t\treturn tools\n\t},\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -66,6 +66,7 @@ function PendingComposer({
|
|
|
66
66
|
const target = e.target;
|
|
67
67
|
if (!el || !target) return;
|
|
68
68
|
if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
|
|
69
|
+
if (editor.getContainer().querySelector(".tl-canvas")?.contains(target)) return;
|
|
69
70
|
import_state.pendingComment.set(editor, null);
|
|
70
71
|
};
|
|
71
72
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/pending-composer.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmDG;AAnDH,sBAA4C;AAC5C,mBAA4C;AAC5C,oBASO;AACP,8BAAgC;AAChC,gCAA8C;AAC9C,yBAA2B;AAC3B,4BAKO;AACP,+BAAsC;AACtC,4BAAuC;AAGvC,8BAA0D;AAC1D,qBAAoD;AACpD,mBAA+B;AAE/B,MAAM,OAAO,CAAC,MAAmC,EAAE,gBAAgB;AAO5D,SAAS,gBAAgB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAAoE;AACnE,QAAM,uBAAmB,qCAAqB,EAAE,WAAW;AAC3D,QAAM,iBAAa,8BAAc,aAAa;AAC9C,QAAM,KAAK,gBAAgB,cAAc,aAAa,IAAI;AAE1D,QAAM,cACL,4CAAC,iCACA,sDAAC,0BAAO,QAAQ,MAAM,8CAAwB,GAC/C;AAID,QAAM,CAAC,MAAM,OAAO,QAAI;AAAA,IACvB,UAAM,uCAAgB,uCAAiB,KAAK;AAAA,EAC7C;AACA,QAAM,UAAM,qBAAuB,IAAI;AACvC,QAAM,UAAM,8BAAe;AAE3B,+CAA0B,GAAG;AAE7B,QAAM,YAAQ,wBAAS,kBAAkB,MAAM,OAAO,eAAe,QAAQ,KAAK,GAAG;AAAA,IACpF;AAAA,IACA,QAAQ;AAAA,EACT,CAAC;AAGD,QAAM,eAAW,+CAAsB;AACvC,QAAM,aAAS,4CAAmB,KAAK,OAAO,QAAQ;AAGtD,8BAAU,MAAM;AACf,UAAM,gBAAgB,CAAC,MAAoB;AAC1C,YAAM,KAAK,IAAI;AACf,YAAM,SAAS,EAAE;AACjB,UAAI,CAAC,MAAM,CAAC,OAAQ;AAGpB,UAAI,GAAG,SAAS,MAAM,KAAK,OAAO,QAAQ,yBAAyB,EAAG;
|
|
4
|
+
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\t// A press on the canvas is the comment tool's to handle: it ends the placement and returns\n\t\t\t// to select. Clearing here first would let that press re-place the composer instead.\n\t\t\tif (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["comment"]
|
|
7
7
|
}
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -625,8 +625,8 @@ export declare interface CommentThreadProps {
|
|
|
625
625
|
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
626
626
|
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
627
627
|
* Placement only opens a composer; the records are created when the comment is posted. The tool
|
|
628
|
-
* stays active while the composer is open — posting returns to select, and clicking
|
|
629
|
-
*
|
|
628
|
+
* stays active while the composer is open — posting returns to select, and so does clicking the
|
|
629
|
+
* canvas elsewhere, which cancels the placement.
|
|
630
630
|
* @public
|
|
631
631
|
*/
|
|
632
632
|
export declare class CommentTool extends StateNode {
|
|
@@ -1287,6 +1287,20 @@ export declare interface SidebarRow {
|
|
|
1287
1287
|
*/
|
|
1288
1288
|
export declare function sortSidebarRows(rows: readonly SidebarRow[]): readonly SidebarRow[];
|
|
1289
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Open the comment composer at a page point, anchored as a comment tool click there would be. Use
|
|
1292
|
+
* it to start a comment from outside the tool, such as a context menu item. Enters the comment tool
|
|
1293
|
+
* (which must be registered), since the draft composer closes when the tool exits. It doesn't check
|
|
1294
|
+
* `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.
|
|
1295
|
+
*
|
|
1296
|
+
* @example
|
|
1297
|
+
* ```tsx
|
|
1298
|
+
* <TldrawUiMenuItem id="comment" label="Comment" onSelect={() => startCommentAt(editor, point)} />
|
|
1299
|
+
* ```
|
|
1300
|
+
* @public
|
|
1301
|
+
*/
|
|
1302
|
+
export declare function startCommentAt(editor: Editor, point: VecLike): void;
|
|
1303
|
+
|
|
1290
1304
|
/**
|
|
1291
1305
|
* Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used so
|
|
1292
1306
|
* the row stays stable as later reactions arrive. `active` marks the current user's emoji and
|
package/dist-cjs/index.js
CHANGED
|
@@ -88,6 +88,7 @@ __export(index_exports, {
|
|
|
88
88
|
shapeAnchorAt: () => import_thread_state.shapeAnchorAt,
|
|
89
89
|
sidebarFilters: () => import_state.sidebarFilters,
|
|
90
90
|
sortSidebarRows: () => import_comments_sidebar.sortSidebarRows,
|
|
91
|
+
startCommentAt: () => import_comment_tool.startCommentAt,
|
|
91
92
|
summarizeReactions: () => import_comment_reactions.summarizeReactions,
|
|
92
93
|
toggleCommentReaction: () => import_comment_reactions.toggleCommentReaction,
|
|
93
94
|
toggleCommentsHidden: () => import_state.toggleCommentsHidden,
|
|
@@ -144,7 +145,7 @@ var import_state = require("./canvas/state");
|
|
|
144
145
|
var import_thread_state = require("./canvas/thread-state");
|
|
145
146
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
146
147
|
"@tldraw/commenting",
|
|
147
|
-
"5.6.0-canary.
|
|
148
|
+
"5.6.0-canary.b8f107a1f44a",
|
|
148
149
|
"cjs"
|
|
149
150
|
);
|
|
150
151
|
//# sourceMappingURL=index.js.map
|
package/dist-cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAK7C,sBAYO;AACP,oBAAyC;AACzC,0BAAmD;AACnD,8BAA2D;AAC3D,yBAAiD;AACjD,yBAAiD;AACjD,4BAAuD;AACvD,2BAOO;AACP,yBAAiD;AACjD,yBAAuD;AACvD,0BAKO;AACP,sBAQO;AACP,6BAAyD;AACzD,uBAKO;AACP,yBAAiD;AAKjD,8BAA+C;AAC/C,0BAAmD;AACnD,+BAQO;AACP,+BASO;AACP,
|
|
4
|
+
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {\n\tCommentTool,\n\tcommentToolOverrides,\n\tcommentTools,\n\tstartCommentAt,\n} from './canvas/comment-tool'\nexport {\n\tgetCommentReactions,\n\tgetCommentRecord,\n\tgetComments,\n\tgetCommentThreads,\n\tgetLiveComments,\n\tgetLiveCommentThreads,\n\ttype TLCommentRecord,\n} from './canvas/comment-store'\nexport { type CommentingContext } from './canvas/context'\nexport { CommentsFilterMenu, type CommentsFilterMenuProps } from './canvas/comments-filter-menu'\nexport { CommentsMenuItem } from './canvas/comments-menu-item'\nexport { CanvasComments, type CanvasCommentsProps } from './canvas/comments-overlay'\nexport {\n\ttype CommentingComponents,\n\ttype CommentingOptions,\n\ttype CommentModification,\n\ttype CommentModificationContext,\n\tdefaultCanModifyComment,\n\tdefaultCommentingOptions,\n\tgetCanComment,\n\tgetCanModifyComment,\n\tgetCommentingOptions,\n\ttype ShapeCommentPrecisionContext,\n\tuseCanComment,\n\tuseCanModifyComment,\n\tuseCommentingOptions,\n} from './canvas/options'\nexport { CommentsOverflowMenu } from './canvas/comments-overflow-menu'\nexport { CommentsVisibilityToggle } from './canvas/comments-visibility-toggle'\nexport {\n\tCanvasCommentsSidebar,\n\tsortSidebarRows,\n\ttype CanvasCommentsSidebarProps,\n\ttype SidebarRow,\n} from './canvas/comments-sidebar'\nexport { useComments, useCommentThreads, useThreadComments } from './canvas/hooks'\nexport { useCommentingEnabled } from './canvas/license'\nexport { richTextToPlaintext } from './canvas/rich-text'\nexport { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './canvas/sidebar-filters'\nexport {\n\tcommentsHidden,\n\tcommentsSidebarOpen,\n\tgetRevealThreadPending,\n\topenThreadId,\n\trevealThread,\n\tsidebarFilters,\n\ttoggleCommentsHidden,\n\ttoggleCommentsSidebar,\n\tuseCommentsHidden,\n\tuseCommentsSidebarOpen,\n\tuseOpenThreadId,\n\tuseRevealThreadPending,\n\tuseSidebarFilters,\n} from './canvas/state'\nexport { anchorPagePoint, focusThread, shapeAnchorAt } from './canvas/thread-state'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAK7C,sBAYO;AACP,oBAAyC;AACzC,0BAAmD;AACnD,8BAA2D;AAC3D,yBAAiD;AACjD,yBAAiD;AACjD,4BAAuD;AACvD,2BAOO;AACP,yBAAiD;AACjD,yBAAuD;AACvD,0BAKO;AACP,sBAQO;AACP,6BAAyD;AACzD,uBAKO;AACP,yBAAiD;AAKjD,8BAA+C;AAC/C,0BAAmD;AACnD,+BAQO;AACP,+BASO;AACP,0BAKO;AACP,2BAQO;AAEP,kCAAiE;AACjE,gCAAiC;AACjC,8BAAyD;AACzD,qBAcO;AACP,oCAAqC;AACrC,wCAAyC;AACzC,8BAKO;AACP,mBAAkE;AAClE,qBAAqC;AACrC,uBAAoC;AACpC,6BAA6D;AAC7D,mBAcO;AACP,0BAA4D;AAAA,IAE5D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -60,6 +60,26 @@ class CommentTool extends StateNode {
|
|
|
60
60
|
this.editor.setCurrentTool("select");
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
function pendingCommentAt(editor, point) {
|
|
64
|
+
const hit = commentTargetShapeAt(editor, point);
|
|
65
|
+
const anchor = hit ? shapeAnchorAt(
|
|
66
|
+
editor,
|
|
67
|
+
hit.id,
|
|
68
|
+
point,
|
|
69
|
+
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
70
|
+
shapeId: hit.id,
|
|
71
|
+
point,
|
|
72
|
+
altKey: editor.inputs.getAltKey()
|
|
73
|
+
})
|
|
74
|
+
) : { type: "point", x: point.x, y: point.y };
|
|
75
|
+
return { anchor, point: { x: point.x, y: point.y } };
|
|
76
|
+
}
|
|
77
|
+
function startCommentAt(editor, point) {
|
|
78
|
+
editor.setCurrentTool("comment");
|
|
79
|
+
const pending = pendingCommentAt(editor, point);
|
|
80
|
+
pendingComment.set(editor, pending);
|
|
81
|
+
editor.setHintingShapes(pending.anchor.type === "shape" ? [pending.anchor.shapeId] : []);
|
|
82
|
+
}
|
|
63
83
|
function updateAnchorHint(editor) {
|
|
64
84
|
const hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint());
|
|
65
85
|
editor.setHintingShapes(hit ? [hit.id] : []);
|
|
@@ -78,6 +98,10 @@ class CommentIdle extends StateNode {
|
|
|
78
98
|
updateAnchorHint(this.editor);
|
|
79
99
|
}
|
|
80
100
|
onPointerDown() {
|
|
101
|
+
if (pendingComment.get(this.editor)) {
|
|
102
|
+
this.editor.setCurrentTool("select");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
81
105
|
this.parent.transition("pointing");
|
|
82
106
|
}
|
|
83
107
|
}
|
|
@@ -106,18 +130,7 @@ class CommentPointing extends StateNode {
|
|
|
106
130
|
onPointerUp() {
|
|
107
131
|
const { editor } = this;
|
|
108
132
|
const point = editor.inputs.getCurrentPagePoint();
|
|
109
|
-
|
|
110
|
-
const anchor = hit ? shapeAnchorAt(
|
|
111
|
-
editor,
|
|
112
|
-
hit.id,
|
|
113
|
-
point,
|
|
114
|
-
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
115
|
-
shapeId: hit.id,
|
|
116
|
-
point,
|
|
117
|
-
altKey: editor.inputs.getAltKey()
|
|
118
|
-
})
|
|
119
|
-
) : { type: "point", x: point.x, y: point.y };
|
|
120
|
-
pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } });
|
|
133
|
+
pendingComment.set(editor, pendingCommentAt(editor, point));
|
|
121
134
|
this.parent.transition("idle");
|
|
122
135
|
}
|
|
123
136
|
onCancel() {
|
|
@@ -192,6 +205,7 @@ export {
|
|
|
192
205
|
CommentTool,
|
|
193
206
|
commentToolOverrides,
|
|
194
207
|
commentTools,
|
|
195
|
-
regionBetween
|
|
208
|
+
regionBetween,
|
|
209
|
+
startCommentAt
|
|
196
210
|
};
|
|
197
211
|
//# sourceMappingURL=comment-tool.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comment-tool.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tBoxModel,\n\tEditor,\n\tStateNode,\n\tTLCommentAnchor,\n\tTLStateNodeConstructor,\n\tTLUiOverrides,\n\tVecLike,\n} from 'tldraw'\nimport { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'\nimport { commentsSidebarOpen, pendingComment, regionDraft } from './state'\nimport { commentTargetShapeAt, regionPinPoint, shapeAnchorAt } from './thread-state'\n\n/** A comment being placed but not yet posted: where its composer sits and what it will anchor\n * to. Shared between the tool (which sets it on click) and the overlay (which renders the\n * composer) through the internal `pendingComment` atom.\n * @internal */\nexport interface PendingComment {\n\tanchor: TLCommentAnchor\n\t/** Page point where the composer opens (the click location, or a region's pin corner). */\n\tpoint: VecLike\n}\n\n/**\n * Merge configure options over a base. Shallow for scalars; `components` is merged rather than\n * replaced so chained `configure` calls layer their slots \u2014 a later `{ components: { PinContent } }`\n * doesn't drop an earlier `{ components: { CommentBody } }`.\n */\nfunction mergeCommentingOptions(\n\tbase: CommentingOptions,\n\toverrides: Partial<CommentingOptions>\n): CommentingOptions {\n\treturn {\n\t\t...base,\n\t\t...overrides,\n\t\tcomponents: { ...base.components, ...overrides.components },\n\t}\n}\n\n/** The page-space rectangle spanned by two points, normalized so w/h are non-negative. Shared by\n * region creation (pointer-down \u2192 cursor) and corner resize (fixed corner \u2192 cursor). */\nexport function regionBetween(a: VecLike, b: VecLike): BoxModel {\n\treturn {\n\t\tx: Math.min(a.x, b.x),\n\t\ty: Math.min(a.y, b.y),\n\t\tw: Math.abs(a.x - b.x),\n\t\th: Math.abs(a.y - b.y),\n\t}\n}\n\n/**\n * The comment tool. Pressing down opens the comment composer at the pointer and it follows until\n * release \u2014 like placing a sticky note \u2014 settling on a point, or on a shape when released over one.\n * With region comments enabled, dragging past the threshold draws a region rectangle instead.\n * Placement only opens a composer; the records are created when the comment is posted. The tool\n * stays active while the composer is open \u2014 posting returns to select, and clicking elsewhere\n * re-places the composer.\n * @public\n */\nexport class CommentTool extends StateNode {\n\tstatic override id = 'comment'\n\tstatic override initial = 'idle'\n\tstatic override children(): TLStateNodeConstructor[] {\n\t\treturn [CommentIdle, CommentPointing, CommentDragging]\n\t}\n\n\t/**\n\t * Configure this tool's {@link CommentTool.options | `options`}, returning a configured subclass\n\t * to register via `tools`. Mirrors `ShapeUtil.configure`. Layers over any prior `configure`, so\n\t * calls can be chained.\n\t *\n\t * @example\n\t * ```tsx\n\t * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n\t * ```\n\t */\n\tstatic configure<T extends TLStateNodeConstructor>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- mirrors ShapeUtil.configure; extending `this` is sound at runtime.\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = mergeCommentingOptions(this.options, options)\n\t\t}\n\t}\n\n\t/**\n\t * The merged commenting options for this editor. Read from anywhere via\n\t * {@link getCommentingOptions}. Override with {@link CommentTool.configure}.\n\t */\n\toptions: CommentingOptions = defaultCommentingOptions\n\n\toverride onEnter() {\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// Placing comments is canvas-focused \u2014 the thread list gets out of the way while the tool is\n\t\t// active. Reopened via its own control (a button), never by leaving the tool.\n\t\tcommentsSidebarOpen.set(this.editor, false)\n\t}\n\n\toverride onExit() {\n\t\t// Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets\n\t\t// when the next tool takes over. The draft composer and region draft belong to the tool, so\n\t\t// they leave with it; the draft's text survives in the comment draft store.\n\t\tthis.editor.setHintingShapes([])\n\t\tpendingComment.set(this.editor, null)\n\t\tregionDraft.set(this.editor, null)\n\t}\n\n\t// Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).\n\toverride onCancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the\n * anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so\n * this shows the select-style outline while the comment tool \u2014 not select \u2014 is active. */\nfunction updateAnchorHint(editor: Editor) {\n\tconst hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint())\n\teditor.setHintingShapes(hit ? [hit.id] : [])\n}\n\nclass CommentIdle extends StateNode {\n\tstatic override id = 'idle'\n\n\toverride onEnter() {\n\t\t// Back to hovering: restore the pin cursor (a prior placing state may have hidden it).\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// With a region composer open, a shape outline under its pin corner would imply a shape\n\t\t// anchor the region will never use \u2014 and it would stick, since moves over the composer\n\t\t// never reach the canvas. Point placements keep the hint: it previews the next click.\n\t\tif (pendingComment.get(this.editor)?.anchor.type === 'region') {\n\t\t\tthis.editor.setHintingShapes([])\n\t\t} else {\n\t\t\tupdateAnchorHint(this.editor)\n\t\t}\n\t}\n\n\toverride onPointerMove() {\n\t\tupdateAnchorHint(this.editor)\n\t}\n\n\toverride onPointerDown() {\n\t\tthis.parent.transition('pointing')\n\t}\n}\n\nclass CommentPointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onEnter() {\n\t\t// Open the composer immediately at the press point so it's visible from pointer-down (not just\n\t\t// on release), and let it trail the pointer while dragging \u2014 like placing a sticky note. The\n\t\t// anchor is a bare point for now; it's resolved (shape or point) on pointer up.\n\t\tconst { editor } = this\n\t\t// Hide the cursor while placing: the draft composer is the pointer's stand-in now, so the pin\n\t\t// cursor sitting over it just reads as clutter.\n\t\teditor.setCursor({ type: 'none', rotation: 0 })\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'point', x: point.x, y: point.y },\n\t\t\tpoint: { x: point.x, y: point.y },\n\t\t})\n\t}\n\n\toverride onPointerMove() {\n\t\tconst { editor } = this\n\t\t// Once the pointer passes the drag threshold with region comments enabled, this is a region,\n\t\t// not a follow \u2014 hand off to the region drag (which clears this composer and draws the box).\n\t\tif (getCommentingOptions(editor).enableRegions && editor.inputs.getIsDragging()) {\n\t\t\tthis.parent.transition('dragging')\n\t\t\treturn\n\t\t}\n\t\t// Otherwise the composer trails the pointer \u2014 keep hinting the shape a release here would\n\t\t// anchor to, like the idle hover does.\n\t\tupdateAnchorHint(editor)\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.update(editor, (p) => (p ? { ...p, point: { x: point.x, y: point.y } } : p))\n\t}\n\n\t// Settle where the pointer is released: anchor to the shape under it, or drop a point.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tconst hit = commentTargetShapeAt(editor, point)\n\t\tconst anchor: TLCommentAnchor = hit\n\t\t\t? shapeAnchorAt(\n\t\t\t\t\teditor,\n\t\t\t\t\thit.id,\n\t\t\t\t\tpoint,\n\t\t\t\t\tgetCommentingOptions(editor).shouldBePrecise(editor, {\n\t\t\t\t\t\tshapeId: hit.id,\n\t\t\t\t\t\tpoint,\n\t\t\t\t\t\taltKey: editor.inputs.getAltKey(),\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t: { type: 'point', x: point.x, y: point.y }\n\t\tpendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })\n\t\t// Stay in the tool while the composer is open \u2014 the interaction isn't over until the\n\t\t// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,\n\t\t// sidebar) from churning mid-placement.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\t// Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.). The\n\t// tool's onExit drops the draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nclass CommentDragging extends StateNode {\n\tstatic override id = 'dragging'\n\n\toverride onEnter() {\n\t\t// A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,\n\t\t// since the composer no longer stands in for the pointer, and drop the placement hint \u2014 a region\n\t\t// anchors to its rectangle, so a single-shape outline would be stale.\n\t\tpendingComment.set(this.editor, null)\n\t\tthis.editor.setHintingShapes([])\n\t\tthis.editor.setCursor({ type: 'cross', rotation: 0 })\n\t\tthis.updateDraft()\n\t}\n\n\toverride onPointerMove() {\n\t\tthis.updateDraft()\n\t}\n\n\t// Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst origin = editor.inputs.getOriginPagePoint()\n\t\tconst current = editor.inputs.getCurrentPagePoint()\n\t\tconst region = regionBetween(origin, current)\n\t\t// The pin lives on the corner the drag released on \u2014 drag up-left, pin top-left.\n\t\tconst pin = { x: current.x >= origin.x ? 1 : 0, y: current.y >= origin.y ? 1 : 0 }\n\t\tregionDraft.set(editor, null)\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },\n\t\t\tpoint: regionPinPoint(region, pin),\n\t\t})\n\t\t// Same as the point placement: the tool stays active while the composer is open.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tprivate updateDraft() {\n\t\tconst { editor } = this\n\t\tregionDraft.set(\n\t\t\teditor,\n\t\t\tregionBetween(editor.inputs.getOriginPagePoint(), editor.inputs.getCurrentPagePoint())\n\t\t)\n\t}\n\n\t// The tool's onExit drops the region draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** @public */\nexport const commentTools = [CommentTool]\n\n/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.\n * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.\n * @public */\nexport const commentToolOverrides: TLUiOverrides = {\n\ttools(editor, tools) {\n\t\ttools.comment = {\n\t\t\tid: 'comment',\n\t\t\ticon: 'comment',\n\t\t\tlabel: 'Comment',\n\t\t\tkbd: 'c',\n\t\t\tonSelect: () => editor.setCurrentTool('comment'),\n\t\t}\n\t\treturn tools\n\t},\n}\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EAGC;AAAA,OAKM;AACP,SAAiC,0BAA0B,4BAA4B;AACvF,SAAS,qBAAqB,gBAAgB,mBAAmB;AACjE,SAAS,sBAAsB,gBAAgB,qBAAqB;AAiBpE,SAAS,uBACR,MACA,WACoB;AACpB,SAAO;AAAA,IACN,GAAG;AAAA,IACH,GAAG;AAAA,IACH,YAAY,EAAE,GAAG,KAAK,YAAY,GAAG,UAAU,WAAW;AAAA,EAC3D;AACD;AAIO,SAAS,cAAc,GAAY,GAAsB;AAC/D,SAAO;AAAA,IACN,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IACpB,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IACpB,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC;AAAA,IACrB,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC;AAAA,EACtB;AACD;AAWO,MAAM,oBAAoB,UAAU;AAAA,EAC1C,OAAgB,KAAK;AAAA,EACrB,OAAgB,UAAU;AAAA,EAC1B,OAAgB,WAAqC;AACpD,WAAO,CAAC,aAAa,iBAAiB,eAAe;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,OAAO,UAEN,SACI;AAEJ,WAAO,cAAc,KAAK;AAAA;AAAA,MAEzB,UAAU,uBAAuB,KAAK,SAAS,OAAO;AAAA,IACvD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAA6B;AAAA,EAEpB,UAAU;AAClB,SAAK,OAAO,UAAU,EAAE,MAAM,WAAW,UAAU,EAAE,CAAC;AAGtD,wBAAoB,IAAI,KAAK,QAAQ,KAAK;AAAA,EAC3C;AAAA,EAES,SAAS;AAIjB,SAAK,OAAO,iBAAiB,CAAC,CAAC;AAC/B,mBAAe,IAAI,KAAK,QAAQ,IAAI;AACpC,gBAAY,IAAI,KAAK,QAAQ,IAAI;AAAA,EAClC;AAAA;AAAA,EAGS,WAAW;AACnB,SAAK,OAAO,eAAe,QAAQ;AAAA,EACpC;AACD;AAKA,SAAS,iBAAiB,QAAgB;AACzC,QAAM,MAAM,qBAAqB,QAAQ,OAAO,OAAO,oBAAoB,CAAC;AAC5E,SAAO,iBAAiB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C;AAEA,MAAM,oBAAoB,UAAU;AAAA,EACnC,OAAgB,KAAK;AAAA,EAEZ,UAAU;AAElB,SAAK,OAAO,UAAU,EAAE,MAAM,WAAW,UAAU,EAAE,CAAC;AAItD,QAAI,eAAe,IAAI,KAAK,MAAM,GAAG,OAAO,SAAS,UAAU;AAC9D,WAAK,OAAO,iBAAiB,CAAC,CAAC;AAAA,IAChC,OAAO;AACN,uBAAiB,KAAK,MAAM;AAAA,IAC7B;AAAA,EACD;AAAA,EAES,gBAAgB;AACxB,qBAAiB,KAAK,MAAM;AAAA,EAC7B;AAAA,EAES,gBAAgB;
|
|
4
|
+
"sourcesContent": ["import {\n\tBoxModel,\n\tEditor,\n\tStateNode,\n\tTLCommentAnchor,\n\tTLStateNodeConstructor,\n\tTLUiOverrides,\n\tVecLike,\n} from 'tldraw'\nimport { type CommentingOptions, defaultCommentingOptions, getCommentingOptions } from './options'\nimport { commentsSidebarOpen, pendingComment, regionDraft } from './state'\nimport { commentTargetShapeAt, regionPinPoint, shapeAnchorAt } from './thread-state'\n\n/** A comment being placed but not yet posted: where its composer sits and what it will anchor\n * to. Shared between the tool (which sets it on click) and the overlay (which renders the\n * composer) through the internal `pendingComment` atom.\n * @internal */\nexport interface PendingComment {\n\tanchor: TLCommentAnchor\n\t/** Page point where the composer opens (the click location, or a region's pin corner). */\n\tpoint: VecLike\n}\n\n/**\n * Merge configure options over a base. Shallow for scalars; `components` is merged rather than\n * replaced so chained `configure` calls layer their slots \u2014 a later `{ components: { PinContent } }`\n * doesn't drop an earlier `{ components: { CommentBody } }`.\n */\nfunction mergeCommentingOptions(\n\tbase: CommentingOptions,\n\toverrides: Partial<CommentingOptions>\n): CommentingOptions {\n\treturn {\n\t\t...base,\n\t\t...overrides,\n\t\tcomponents: { ...base.components, ...overrides.components },\n\t}\n}\n\n/** The page-space rectangle spanned by two points, normalized so w/h are non-negative. Shared by\n * region creation (pointer-down \u2192 cursor) and corner resize (fixed corner \u2192 cursor). */\nexport function regionBetween(a: VecLike, b: VecLike): BoxModel {\n\treturn {\n\t\tx: Math.min(a.x, b.x),\n\t\ty: Math.min(a.y, b.y),\n\t\tw: Math.abs(a.x - b.x),\n\t\th: Math.abs(a.y - b.y),\n\t}\n}\n\n/**\n * The comment tool. Pressing down opens the comment composer at the pointer and it follows until\n * release \u2014 like placing a sticky note \u2014 settling on a point, or on a shape when released over one.\n * With region comments enabled, dragging past the threshold draws a region rectangle instead.\n * Placement only opens a composer; the records are created when the comment is posted. The tool\n * stays active while the composer is open \u2014 posting returns to select, and so does clicking the\n * canvas elsewhere, which cancels the placement.\n * @public\n */\nexport class CommentTool extends StateNode {\n\tstatic override id = 'comment'\n\tstatic override initial = 'idle'\n\tstatic override children(): TLStateNodeConstructor[] {\n\t\treturn [CommentIdle, CommentPointing, CommentDragging]\n\t}\n\n\t/**\n\t * Configure this tool's {@link CommentTool.options | `options`}, returning a configured subclass\n\t * to register via `tools`. Mirrors `ShapeUtil.configure`. Layers over any prior `configure`, so\n\t * calls can be chained.\n\t *\n\t * @example\n\t * ```tsx\n\t * <Tldraw tools={[CommentTool.configure({ history: 'ignore', enableClustering: false })]} />\n\t * ```\n\t */\n\tstatic configure<T extends TLStateNodeConstructor>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- mirrors ShapeUtil.configure; extending `this` is sound at runtime.\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = mergeCommentingOptions(this.options, options)\n\t\t}\n\t}\n\n\t/**\n\t * The merged commenting options for this editor. Read from anywhere via\n\t * {@link getCommentingOptions}. Override with {@link CommentTool.configure}.\n\t */\n\toptions: CommentingOptions = defaultCommentingOptions\n\n\toverride onEnter() {\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// Placing comments is canvas-focused \u2014 the thread list gets out of the way while the tool is\n\t\t// active. Reopened via its own control (a button), never by leaving the tool.\n\t\tcommentsSidebarOpen.set(this.editor, false)\n\t}\n\n\toverride onExit() {\n\t\t// Drop the hover hint painted while pointing at shapes (see CommentIdle). The cursor resets\n\t\t// when the next tool takes over. The draft composer and region draft belong to the tool, so\n\t\t// they leave with it; the draft's text survives in the comment draft store.\n\t\tthis.editor.setHintingShapes([])\n\t\tpendingComment.set(this.editor, null)\n\t\tregionDraft.set(this.editor, null)\n\t}\n\n\t// Escape leaves the tool, like the built-in tools (the editor dispatches `cancel` on Escape).\n\toverride onCancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nfunction pendingCommentAt(editor: Editor, point: VecLike): PendingComment {\n\tconst hit = commentTargetShapeAt(editor, point)\n\tconst anchor: TLCommentAnchor = hit\n\t\t? shapeAnchorAt(\n\t\t\t\teditor,\n\t\t\t\thit.id,\n\t\t\t\tpoint,\n\t\t\t\tgetCommentingOptions(editor).shouldBePrecise(editor, {\n\t\t\t\t\tshapeId: hit.id,\n\t\t\t\t\tpoint,\n\t\t\t\t\taltKey: editor.inputs.getAltKey(),\n\t\t\t\t})\n\t\t\t)\n\t\t: { type: 'point', x: point.x, y: point.y }\n\treturn { anchor, point: { x: point.x, y: point.y } }\n}\n\n/**\n * Open the comment composer at a page point, anchored as a comment tool click there would be. Use\n * it to start a comment from outside the tool, such as a context menu item. Enters the comment tool\n * (which must be registered), since the draft composer closes when the tool exits. It doesn't check\n * `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.\n *\n * @example\n * ```tsx\n * <TldrawUiMenuItem id=\"comment\" label=\"Comment\" onSelect={() => startCommentAt(editor, point)} />\n * ```\n * @public\n */\nexport function startCommentAt(editor: Editor, point: VecLike) {\n\teditor.setCurrentTool('comment')\n\tconst pending = pendingCommentAt(editor, point)\n\tpendingComment.set(editor, pending)\n\t// Entering the tool hints the shape under the live pointer, which is wherever the caller's UI\n\t// was (a menu item), not `point`.\n\teditor.setHintingShapes(pending.anchor.type === 'shape' ? [pending.anchor.shapeId] : [])\n}\n\n/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the\n * anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so\n * this shows the select-style outline while the comment tool \u2014 not select \u2014 is active. */\nfunction updateAnchorHint(editor: Editor) {\n\tconst hit = commentTargetShapeAt(editor, editor.inputs.getCurrentPagePoint())\n\teditor.setHintingShapes(hit ? [hit.id] : [])\n}\n\nclass CommentIdle extends StateNode {\n\tstatic override id = 'idle'\n\n\toverride onEnter() {\n\t\t// Back to hovering: restore the pin cursor (a prior placing state may have hidden it).\n\t\tthis.editor.setCursor({ type: 'comment', rotation: 0 })\n\t\t// With a region composer open, a shape outline under its pin corner would imply a shape\n\t\t// anchor the region will never use \u2014 and it would stick, since moves over the composer\n\t\t// never reach the canvas. Point placements keep the hint: it previews the next click.\n\t\tif (pendingComment.get(this.editor)?.anchor.type === 'region') {\n\t\t\tthis.editor.setHintingShapes([])\n\t\t} else {\n\t\t\tupdateAnchorHint(this.editor)\n\t\t}\n\t}\n\n\toverride onPointerMove() {\n\t\tupdateAnchorHint(this.editor)\n\t}\n\n\toverride onPointerDown() {\n\t\t// A press away from an open composer cancels the placement rather than moving it. The\n\t\t// tool's onExit drops the draft composer; its text survives in the draft store.\n\t\tif (pendingComment.get(this.editor)) {\n\t\t\tthis.editor.setCurrentTool('select')\n\t\t\treturn\n\t\t}\n\t\tthis.parent.transition('pointing')\n\t}\n}\n\nclass CommentPointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onEnter() {\n\t\t// Open the composer immediately at the press point so it's visible from pointer-down (not just\n\t\t// on release), and let it trail the pointer while dragging \u2014 like placing a sticky note. The\n\t\t// anchor is a bare point for now; it's resolved (shape or point) on pointer up.\n\t\tconst { editor } = this\n\t\t// Hide the cursor while placing: the draft composer is the pointer's stand-in now, so the pin\n\t\t// cursor sitting over it just reads as clutter.\n\t\teditor.setCursor({ type: 'none', rotation: 0 })\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'point', x: point.x, y: point.y },\n\t\t\tpoint: { x: point.x, y: point.y },\n\t\t})\n\t}\n\n\toverride onPointerMove() {\n\t\tconst { editor } = this\n\t\t// Once the pointer passes the drag threshold with region comments enabled, this is a region,\n\t\t// not a follow \u2014 hand off to the region drag (which clears this composer and draws the box).\n\t\tif (getCommentingOptions(editor).enableRegions && editor.inputs.getIsDragging()) {\n\t\t\tthis.parent.transition('dragging')\n\t\t\treturn\n\t\t}\n\t\t// Otherwise the composer trails the pointer \u2014 keep hinting the shape a release here would\n\t\t// anchor to, like the idle hover does.\n\t\tupdateAnchorHint(editor)\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.update(editor, (p) => (p ? { ...p, point: { x: point.x, y: point.y } } : p))\n\t}\n\n\t// Settle where the pointer is released: anchor to the shape under it, or drop a point.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst point = editor.inputs.getCurrentPagePoint()\n\t\tpendingComment.set(editor, pendingCommentAt(editor, point))\n\t\t// Stay in the tool while the composer is open \u2014 the interaction isn't over until the\n\t\t// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,\n\t\t// sidebar) from churning mid-placement.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\t// Abandon the follow composer if placement is interrupted (Escape, focus loss, etc.). The\n\t// tool's onExit drops the draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\nclass CommentDragging extends StateNode {\n\tstatic override id = 'dragging'\n\n\toverride onEnter() {\n\t\t// A region drag supersedes the point-follow composer opened in `pointing`. Show a crosshair again,\n\t\t// since the composer no longer stands in for the pointer, and drop the placement hint \u2014 a region\n\t\t// anchors to its rectangle, so a single-shape outline would be stale.\n\t\tpendingComment.set(this.editor, null)\n\t\tthis.editor.setHintingShapes([])\n\t\tthis.editor.setCursor({ type: 'cross', rotation: 0 })\n\t\tthis.updateDraft()\n\t}\n\n\toverride onPointerMove() {\n\t\tthis.updateDraft()\n\t}\n\n\t// Commit the dragged rectangle as a region anchor; the composer opens at its pin corner.\n\toverride onPointerUp() {\n\t\tconst { editor } = this\n\t\tconst origin = editor.inputs.getOriginPagePoint()\n\t\tconst current = editor.inputs.getCurrentPagePoint()\n\t\tconst region = regionBetween(origin, current)\n\t\t// The pin lives on the corner the drag released on \u2014 drag up-left, pin top-left.\n\t\tconst pin = { x: current.x >= origin.x ? 1 : 0, y: current.y >= origin.y ? 1 : 0 }\n\t\tregionDraft.set(editor, null)\n\t\tpendingComment.set(editor, {\n\t\t\tanchor: { type: 'region', ...region, pinX: pin.x, pinY: pin.y },\n\t\t\tpoint: regionPinPoint(region, pin),\n\t\t})\n\t\t// Same as the point placement: the tool stays active while the composer is open.\n\t\tthis.parent.transition('idle')\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tprivate updateDraft() {\n\t\tconst { editor } = this\n\t\tregionDraft.set(\n\t\t\teditor,\n\t\t\tregionBetween(editor.inputs.getOriginPagePoint(), editor.inputs.getCurrentPagePoint())\n\t\t)\n\t}\n\n\t// The tool's onExit drops the region draft.\n\tprivate cancel() {\n\t\tthis.editor.setCurrentTool('select')\n\t}\n}\n\n/** @public */\nexport const commentTools = [CommentTool]\n\n/** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.\n * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.\n * @public */\nexport const commentToolOverrides: TLUiOverrides = {\n\ttools(editor, tools) {\n\t\ttools.comment = {\n\t\t\tid: 'comment',\n\t\t\ticon: 'comment',\n\t\t\tlabel: 'Comment',\n\t\t\tkbd: 'c',\n\t\t\tonSelect: () => editor.setCurrentTool('comment'),\n\t\t}\n\t\treturn tools\n\t},\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -55,6 +55,7 @@ function PendingComposer({
|
|
|
55
55
|
const target = e.target;
|
|
56
56
|
if (!el || !target) return;
|
|
57
57
|
if (el.contains(target) || target.closest(".tlui-cmt-mention-popup")) return;
|
|
58
|
+
if (editor.getContainer().querySelector(".tl-canvas")?.contains(target)) return;
|
|
58
59
|
pendingComment.set(editor, null);
|
|
59
60
|
};
|
|
60
61
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/pending-composer.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
-
"mappings": "AAmDG;AAnDH,SAAS,QAAQ,2BAA2B;AAC5C,SAAS,WAAW,QAAQ,gBAAgB;AAC5C;AAAA,EACC;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,uBAAuB;AAChC,SAAS,eAAe,sBAAsB;AAC9C,SAAS,kBAAkB;AAC3B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,6BAA6B;AACtC,SAAS,8BAA8B;AAGvC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,eAAe,4BAA4B;AACpD,SAAS,sBAAsB;AAE/B,MAAM,OAAO,CAAC,MAAmC,EAAE,gBAAgB;AAO5D,SAAS,gBAAgB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAAoE;AACnE,QAAM,mBAAmB,qBAAqB,EAAE,WAAW;AAC3D,QAAM,aAAa,cAAc,aAAa;AAC9C,QAAM,KAAK,gBAAgB,cAAc,aAAa,IAAI;AAE1D,QAAM,cACL,oBAAC,cACA,8BAAC,UAAO,QAAQ,MAAM,wBAAwB,GAC/C;AAID,QAAM,CAAC,MAAM,OAAO,IAAI;AAAA,IACvB,MAAM,gBAAgB,iBAAiB,KAAK;AAAA,EAC7C;AACA,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,MAAM,eAAe;AAE3B,4BAA0B,GAAG;AAE7B,QAAM,QAAQ,SAAS,kBAAkB,MAAM,OAAO,eAAe,QAAQ,KAAK,GAAG;AAAA,IACpF;AAAA,IACA,QAAQ;AAAA,EACT,CAAC;AAGD,QAAM,WAAW,sBAAsB;AACvC,QAAM,SAAS,mBAAmB,KAAK,OAAO,QAAQ;AAGtD,YAAU,MAAM;AACf,UAAM,gBAAgB,CAAC,MAAoB;AAC1C,YAAM,KAAK,IAAI;AACf,YAAM,SAAS,EAAE;AACjB,UAAI,CAAC,MAAM,CAAC,OAAQ;AAGpB,UAAI,GAAG,SAAS,MAAM,KAAK,OAAO,QAAQ,yBAAyB,EAAG;
|
|
4
|
+
"sourcesContent": ["import { Avatar, isMentionPickerOpen } from '@tldraw/mentions'\nimport { useEffect, useRef, useState } from 'react'\nimport {\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tEditorPortal,\n\tTLRichText,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { PendingComment } from './comment-tool'\nimport { type CommentingContext } from './context'\nimport { useIsMobileCommenting, useMobilePlacement } from './mobile-placement'\nimport { useCanComment, useCommentingOptions } from './options'\nimport { pendingComment } from './state'\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/**\n * The composer for a thread that doesn't exist yet: the comment tool has placed a point (or\n * region) and is waiting on a first comment. Submitting creates the thread and its comment\n * together; clicking away keeps the draft for the next placement.\n */\nexport function PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CommentingContext & { editor: Editor; pending: PendingComment }) {\n\tconst ComposerFallback = useCommentingOptions().components.ComposerFallback\n\tconst canComment = useCanComment(currentUserId)\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\t// The leading pin previews the pin this draft becomes: a white pin holding the author's avatar.\n\tconst draftAvatar = (\n\t\t<CommentPin>\n\t\t\t<Avatar author={me ?? UNKNOWN_COMMENT_AUTHOR} />\n\t\t</CommentPin>\n\t)\n\t// Click-away keeps the draft (saved on every change) and the next placement composer\n\t// restores it \u2014 the flip side of dismissing without a discard warning.\n\tconst [text, setText] = useState<TLRichText>(\n\t\t() => getCommentDraft(NEW_COMMENT_DRAFT) ?? EMPTY_COMMENT\n\t)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\t// Over this floating panel, a scroll reaches the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\n\t// On mobile the composer floats free of the pin so it can clear the software keyboard; desktop\n\t// keeps it pinned to the point.\n\tconst isMobile = useIsMobileCommenting()\n\tconst placed = useMobilePlacement(ref, point, isMobile)\n\n\t// Dismiss on a click anywhere outside the composer (capture-phase, ahead of stopPropagation).\n\tuseEffect(() => {\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst el = ref.current\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!el || !target) return\n\t\t\t// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is\n\t\t\t// not \"outside\" \u2014 keep the draft open so the pick can insert.\n\t\t\tif (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return\n\t\t\t// A press on the canvas is the comment tool's to handle: it ends the placement and returns\n\t\t\t// to select. Clearing here first would let that press re-place the composer instead.\n\t\t\tif (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [editor])\n\n\tconst submit = () => {\n\t\tif (isCommentEmpty(text) || !currentUserId) return\n\t\tconst comment = commitCommentMutation(editor, ({ put }) => {\n\t\t\tconst pageId = editor.getCurrentPageId()\n\t\t\tconst thread = createCommentThread({\n\t\t\t\tpageId,\n\t\t\t\tanchor: pending.anchor,\n\t\t\t\tcreatedBy: currentUserId,\n\t\t\t})\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: text,\n\t\t\t})\n\t\t\tput([thread, comment])\n\t\t\treturn comment\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t\t// Posting ends the placement interaction \u2014 hand the comment tool back to select. Only from\n\t\t// the settled idle state: Enter can land while a fresh press is mid-gesture (the composer\n\t\t// trails the pointer), and switching tools under a held pointer would strand the gesture.\n\t\tif (editor.isIn('comment.idle')) editor.setCurrentTool('select')\n\t\t// The host's callback is its own operation, not part of the post's history scope. It runs\n\t\t// last so a throwing host can't strand the composer holding a draft of a posted comment.\n\t\tonPostComment?.(comment)\n\t}\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t\t\t!canComment && 'tlui-cmt-canvas-composer--fallback',\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tstyle={{ left: placed.left, top: placed.top }}\n\t\t\t\tonPointerDown={stop}\n\t\t\t\tonContextMenu={stop}\n\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{canComment ? (\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\tonChange={(value) => {\n\t\t\t\t\t\t\tsetText(value)\n\t\t\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonSubmit={submit}\n\t\t\t\t\t\t// No user, no author for the record \u2014 dead send button.\n\t\t\t\t\t\tdisabled={isCommentEmpty(text) || !currentUserId}\n\t\t\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\tleading={draftAvatar}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\tComposerFallback && <ComposerFallback context=\"pending\" />\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["comment"]
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -625,8 +625,8 @@ export declare interface CommentThreadProps {
|
|
|
625
625
|
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
626
626
|
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
627
627
|
* Placement only opens a composer; the records are created when the comment is posted. The tool
|
|
628
|
-
* stays active while the composer is open — posting returns to select, and clicking
|
|
629
|
-
*
|
|
628
|
+
* stays active while the composer is open — posting returns to select, and so does clicking the
|
|
629
|
+
* canvas elsewhere, which cancels the placement.
|
|
630
630
|
* @public
|
|
631
631
|
*/
|
|
632
632
|
export declare class CommentTool extends StateNode {
|
|
@@ -1287,6 +1287,20 @@ export declare interface SidebarRow {
|
|
|
1287
1287
|
*/
|
|
1288
1288
|
export declare function sortSidebarRows(rows: readonly SidebarRow[]): readonly SidebarRow[];
|
|
1289
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Open the comment composer at a page point, anchored as a comment tool click there would be. Use
|
|
1292
|
+
* it to start a comment from outside the tool, such as a context menu item. Enters the comment tool
|
|
1293
|
+
* (which must be registered), since the draft composer closes when the tool exits. It doesn't check
|
|
1294
|
+
* `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.
|
|
1295
|
+
*
|
|
1296
|
+
* @example
|
|
1297
|
+
* ```tsx
|
|
1298
|
+
* <TldrawUiMenuItem id="comment" label="Comment" onSelect={() => startCommentAt(editor, point)} />
|
|
1299
|
+
* ```
|
|
1300
|
+
* @public
|
|
1301
|
+
*/
|
|
1302
|
+
export declare function startCommentAt(editor: Editor, point: VecLike): void;
|
|
1303
|
+
|
|
1290
1304
|
/**
|
|
1291
1305
|
* Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used so
|
|
1292
1306
|
* the row stays stable as later reactions arrive. `active` marks the current user's emoji and
|
package/dist-esm/index.mjs
CHANGED
|
@@ -53,7 +53,12 @@ import {
|
|
|
53
53
|
toggleCommentReaction,
|
|
54
54
|
useCommentReactions
|
|
55
55
|
} from "./canvas/comment-reactions.mjs";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
CommentTool,
|
|
58
|
+
commentToolOverrides,
|
|
59
|
+
commentTools,
|
|
60
|
+
startCommentAt
|
|
61
|
+
} from "./canvas/comment-tool.mjs";
|
|
57
62
|
import {
|
|
58
63
|
getCommentReactions,
|
|
59
64
|
getCommentRecord,
|
|
@@ -103,7 +108,7 @@ import {
|
|
|
103
108
|
import { anchorPagePoint, focusThread, shapeAnchorAt } from "./canvas/thread-state.mjs";
|
|
104
109
|
registerTldrawLibraryVersion(
|
|
105
110
|
"@tldraw/commenting",
|
|
106
|
-
"5.6.0-canary.
|
|
111
|
+
"5.6.0-canary.b8f107a1f44a",
|
|
107
112
|
"esm"
|
|
108
113
|
);
|
|
109
114
|
export {
|
|
@@ -177,6 +182,7 @@ export {
|
|
|
177
182
|
shapeAnchorAt,
|
|
178
183
|
sidebarFilters,
|
|
179
184
|
sortSidebarRows,
|
|
185
|
+
startCommentAt,
|
|
180
186
|
summarizeReactions,
|
|
181
187
|
toggleCommentReaction,
|
|
182
188
|
toggleCommentsHidden,
|
package/dist-esm/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oCAAoC;AAK7C;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKM;AACP,SAAS,cAAgC;AACzC,SAAS,mBAA0C;AACnD,SAAS,uBAAkD;AAC3D,SAAS,kBAAwC;AACjD,SAAS,kBAAwC;AACjD,SAAS,qBAA8C;AACvD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OAIM;AACP,SAAS,kBAAwC;AACjD,SAAS,oBAAoB,0BAA0B;AACvD;AAAA,EACC;AAAA,EACA;AAAA,EAEA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,sBAAgD;AACzD;AAAA,EACC;AAAA,OAIM;AACP,SAAS,kBAAwC;AAKjD,SAAS,sCAAsC;AAC/C,SAAS,mBAA0C;AACnD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EAEA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,
|
|
4
|
+
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational commenting components. These are tldraw-independent and can be used to build\n// custom commenting UI. The mention picker, pill, and avatar are re-exported from @tldraw/mentions\n// so the commenting public API is unchanged.\nexport {\n\tAvatar,\n\ttype AvatarProps,\n\ttype CommentAuthor,\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tMention,\n\tMentionList,\n\ttype MentionListProps,\n\ttype MentionMember,\n\ttype MentionProps,\n\ttype MentionSuggestionOptions,\n} from '@tldraw/mentions'\nexport { Byline, type BylineProps } from './ui/byline'\nexport { CommentCard, type CommentCardProps } from './ui/comment-card'\nexport { CommentComposer, type CommentComposerProps } from './ui/comment-composer'\nexport { CountBadge, type CountBadgeProps } from './ui/count-badge'\nexport { CommentPin, type CommentPinProps } from './ui/comment-pin'\nexport { CommentThread, type CommentThreadProps } from './ui/comment-thread'\nexport {\n\tCommentListItem,\n\tCommentsList,\n\tisOpenInNewTabClick,\n\ttype CommentListItemProps,\n\ttype CommentListItemRenderProps,\n\ttype CommentsListProps,\n} from './ui/comments-list'\nexport { EmptyState, type EmptyStateProps } from './ui/empty-state'\nexport { formatFullDateTime, formatRelativeTime } from './ui/format-time'\nexport {\n\tDEFAULT_REACTION_EMOJI,\n\tEmojiPicker,\n\ttype EmojiPickerProps,\n\tisAllowedReactionEmoji,\n} from './ui/emoji-picker'\nexport {\n\tReaction,\n\ttype ReactionProps,\n\ttype RenderReaction,\n\tdefaultRenderReaction,\n\tDefaultReactionTooltip,\n\tDefaultReactionTooltipContent,\n\ttype ReactionTooltipProps,\n} from './ui/reaction'\nexport { ReactionPicker, type ReactionPickerProps } from './ui/reaction-picker'\nexport {\n\tReactions,\n\ttype ReactionReactor,\n\ttype ReactionsProps,\n\ttype ReactionSummary,\n} from './ui/reactions'\nexport { SendButton, type SendButtonProps } from './ui/send-button'\n\n// The tldraw-coupled commenting layer: the comment tool, reactive hooks over the comment\n// records, a rich-text body renderer, and a batteries-included <CanvasComments> overlay. Pairs\n// with the presentational components above.\nexport { registerCommentAnchorLifecycle } from './canvas/anchor-lifecycle'\nexport { CommentBody, type CommentBodyProps } from './canvas/comment-body'\nexport {\n\tdeleteComment,\n\tdeleteThread,\n\teditComment,\n\tputCommentRecords,\n\tremoveCommentRecords,\n\treopenThread,\n\tresolveThread,\n} from './canvas/comment-mutations'\nexport {\n\tCommentReactionPicker,\n\ttype CommentReactionPickerProps,\n\tCommentReactions,\n\ttype CommentReactionsProps,\n\ttype ReactionSummaryInput,\n\tsummarizeReactions,\n\ttoggleCommentReaction,\n\tuseCommentReactions,\n} from './canvas/comment-reactions'\nexport {\n\tCommentTool,\n\tcommentToolOverrides,\n\tcommentTools,\n\tstartCommentAt,\n} from './canvas/comment-tool'\nexport {\n\tgetCommentReactions,\n\tgetCommentRecord,\n\tgetComments,\n\tgetCommentThreads,\n\tgetLiveComments,\n\tgetLiveCommentThreads,\n\ttype TLCommentRecord,\n} from './canvas/comment-store'\nexport { type CommentingContext } from './canvas/context'\nexport { CommentsFilterMenu, type CommentsFilterMenuProps } from './canvas/comments-filter-menu'\nexport { CommentsMenuItem } from './canvas/comments-menu-item'\nexport { CanvasComments, type CanvasCommentsProps } from './canvas/comments-overlay'\nexport {\n\ttype CommentingComponents,\n\ttype CommentingOptions,\n\ttype CommentModification,\n\ttype CommentModificationContext,\n\tdefaultCanModifyComment,\n\tdefaultCommentingOptions,\n\tgetCanComment,\n\tgetCanModifyComment,\n\tgetCommentingOptions,\n\ttype ShapeCommentPrecisionContext,\n\tuseCanComment,\n\tuseCanModifyComment,\n\tuseCommentingOptions,\n} from './canvas/options'\nexport { CommentsOverflowMenu } from './canvas/comments-overflow-menu'\nexport { CommentsVisibilityToggle } from './canvas/comments-visibility-toggle'\nexport {\n\tCanvasCommentsSidebar,\n\tsortSidebarRows,\n\ttype CanvasCommentsSidebarProps,\n\ttype SidebarRow,\n} from './canvas/comments-sidebar'\nexport { useComments, useCommentThreads, useThreadComments } from './canvas/hooks'\nexport { useCommentingEnabled } from './canvas/license'\nexport { richTextToPlaintext } from './canvas/rich-text'\nexport { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './canvas/sidebar-filters'\nexport {\n\tcommentsHidden,\n\tcommentsSidebarOpen,\n\tgetRevealThreadPending,\n\topenThreadId,\n\trevealThread,\n\tsidebarFilters,\n\ttoggleCommentsHidden,\n\ttoggleCommentsSidebar,\n\tuseCommentsHidden,\n\tuseCommentsSidebarOpen,\n\tuseOpenThreadId,\n\tuseRevealThreadPending,\n\tuseSidebarFilters,\n} from './canvas/state'\nexport { anchorPagePoint, focusThread, shapeAnchorAt } from './canvas/thread-state'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oCAAoC;AAK7C;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKM;AACP,SAAS,cAAgC;AACzC,SAAS,mBAA0C;AACnD,SAAS,uBAAkD;AAC3D,SAAS,kBAAwC;AACjD,SAAS,kBAAwC;AACjD,SAAS,qBAA8C;AACvD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OAIM;AACP,SAAS,kBAAwC;AACjD,SAAS,oBAAoB,0BAA0B;AACvD;AAAA,EACC;AAAA,EACA;AAAA,EAEA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,sBAAgD;AACzD;AAAA,EACC;AAAA,OAIM;AACP,SAAS,kBAAwC;AAKjD,SAAS,sCAAsC;AAC/C,SAAS,mBAA0C;AACnD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EAEA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AAEP,SAAS,0BAAwD;AACjE,SAAS,wBAAwB;AACjC,SAAS,sBAAgD;AACzD;AAAA,EAKC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC;AAAA,EACC;AAAA,EACA;AAAA,OAGM;AACP,SAAS,aAAa,mBAAmB,yBAAyB;AAClE,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,+BAAoD;AAC7D;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,iBAAiB,aAAa,qBAAqB;AAE5D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/commenting",
|
|
3
3
|
"description": "tldraw commenting UI components.",
|
|
4
|
-
"version": "5.6.0-canary.
|
|
4
|
+
"version": "5.6.0-canary.b8f107a1f44a",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"src"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tldraw/driver": "5.6.0-canary.
|
|
35
|
+
"@tldraw/driver": "5.6.0-canary.b8f107a1f44a",
|
|
36
36
|
"@types/react": "^19.2.7",
|
|
37
37
|
"@types/react-dom": "^19.2.3",
|
|
38
38
|
"react": "^19.2.1",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"@tiptap/core": "^3.12.1",
|
|
51
51
|
"@tiptap/pm": "^3.12.1",
|
|
52
52
|
"@tiptap/react": "^3.12.1",
|
|
53
|
-
"@tldraw/mentions": "5.6.0-canary.
|
|
54
|
-
"@tldraw/utils": "5.6.0-canary.
|
|
55
|
-
"tldraw": "5.6.0-canary.
|
|
53
|
+
"@tldraw/mentions": "5.6.0-canary.b8f107a1f44a",
|
|
54
|
+
"@tldraw/utils": "5.6.0-canary.b8f107a1f44a",
|
|
55
|
+
"tldraw": "5.6.0-canary.b8f107a1f44a"
|
|
56
56
|
},
|
|
57
57
|
"module": "dist-esm/index.mjs",
|
|
58
58
|
"source": "src/index.ts",
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
TLStateNodeConstructor,
|
|
13
13
|
} from 'tldraw'
|
|
14
14
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
15
|
-
import { CommentTool } from './comment-tool'
|
|
15
|
+
import { CommentTool, startCommentAt } from './comment-tool'
|
|
16
16
|
import { commentsSidebarOpen, pendingComment, regionDraft } from './state'
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -117,17 +117,14 @@ describe('comment tool placement lifecycle', () => {
|
|
|
117
117
|
})
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
-
it('
|
|
120
|
+
it('cancels the placement and returns to select on a second click', () => {
|
|
121
121
|
editor.setCurrentTool('comment')
|
|
122
122
|
driver.pointerDown(400, 300)
|
|
123
123
|
driver.pointerUp(400, 300)
|
|
124
124
|
driver.pointerDown(200, 200)
|
|
125
125
|
driver.pointerUp(200, 200)
|
|
126
|
-
expect(editor.isIn('
|
|
127
|
-
expect(pendingComment.get(editor)).
|
|
128
|
-
anchor: { type: 'point', x: 200, y: 200 },
|
|
129
|
-
point: { x: 200, y: 200 },
|
|
130
|
-
})
|
|
126
|
+
expect(editor.isIn('select.idle')).toBe(true)
|
|
127
|
+
expect(pendingComment.get(editor)).toBeNull()
|
|
131
128
|
})
|
|
132
129
|
|
|
133
130
|
it('drops the pending composer when the tool exits', () => {
|
|
@@ -165,6 +162,41 @@ describe('comment tool placement lifecycle', () => {
|
|
|
165
162
|
})
|
|
166
163
|
})
|
|
167
164
|
|
|
165
|
+
describe('startCommentAt', () => {
|
|
166
|
+
let driver: Driver
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
driver = makeEditor()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('enters the tool and opens the composer at a bare point on empty canvas', () => {
|
|
173
|
+
commentsSidebarOpen.set(editor, true)
|
|
174
|
+
startCommentAt(editor, { x: 400, y: 300 })
|
|
175
|
+
expect(editor.isIn('comment.idle')).toBe(true)
|
|
176
|
+
expect(commentsSidebarOpen.get(editor)).toBe(false)
|
|
177
|
+
expect(pendingComment.get(editor)).toEqual({
|
|
178
|
+
anchor: { type: 'point', x: 400, y: 300 },
|
|
179
|
+
point: { x: 400, y: 300 },
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('anchors to the shape under the point', () => {
|
|
184
|
+
const id = makeShape()
|
|
185
|
+
startCommentAt(editor, { x: 150, y: 125 })
|
|
186
|
+
expect(pendingComment.get(editor)?.anchor).toMatchObject({ type: 'shape', shapeId: id })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('hints the anchored shape, not the one under the live pointer', () => {
|
|
190
|
+
const anchored = makeShape()
|
|
191
|
+
const underPointer = makeShape(400, 100)
|
|
192
|
+
driver.pointerMove(450, 125)
|
|
193
|
+
editor.setCurrentTool('comment')
|
|
194
|
+
expect(editor.getHintingShapeIds()).toEqual([underPointer])
|
|
195
|
+
startCommentAt(editor, { x: 150, y: 125 })
|
|
196
|
+
expect(editor.getHintingShapeIds()).toEqual([anchored])
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
168
200
|
describe('comment tool placement hints with regions enabled', () => {
|
|
169
201
|
it('clears the hint when a drag becomes a region', () => {
|
|
170
202
|
const driver = makeEditor(CommentTool.configure({ enableRegions: true }))
|
|
@@ -53,8 +53,8 @@ export function regionBetween(a: VecLike, b: VecLike): BoxModel {
|
|
|
53
53
|
* release — like placing a sticky note — settling on a point, or on a shape when released over one.
|
|
54
54
|
* With region comments enabled, dragging past the threshold draws a region rectangle instead.
|
|
55
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
|
|
57
|
-
*
|
|
56
|
+
* stays active while the composer is open — posting returns to select, and so does clicking the
|
|
57
|
+
* canvas elsewhere, which cancels the placement.
|
|
58
58
|
* @public
|
|
59
59
|
*/
|
|
60
60
|
export class CommentTool extends StateNode {
|
|
@@ -113,6 +113,44 @@ export class CommentTool extends StateNode {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
function pendingCommentAt(editor: Editor, point: VecLike): PendingComment {
|
|
117
|
+
const hit = commentTargetShapeAt(editor, point)
|
|
118
|
+
const anchor: TLCommentAnchor = hit
|
|
119
|
+
? shapeAnchorAt(
|
|
120
|
+
editor,
|
|
121
|
+
hit.id,
|
|
122
|
+
point,
|
|
123
|
+
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
124
|
+
shapeId: hit.id,
|
|
125
|
+
point,
|
|
126
|
+
altKey: editor.inputs.getAltKey(),
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
: { type: 'point', x: point.x, y: point.y }
|
|
130
|
+
return { anchor, point: { x: point.x, y: point.y } }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Open the comment composer at a page point, anchored as a comment tool click there would be. Use
|
|
135
|
+
* it to start a comment from outside the tool, such as a context menu item. Enters the comment tool
|
|
136
|
+
* (which must be registered), since the draft composer closes when the tool exits. It doesn't check
|
|
137
|
+
* `canComment`: a viewer who can't comment sees the `ComposerFallback` slot instead.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```tsx
|
|
141
|
+
* <TldrawUiMenuItem id="comment" label="Comment" onSelect={() => startCommentAt(editor, point)} />
|
|
142
|
+
* ```
|
|
143
|
+
* @public
|
|
144
|
+
*/
|
|
145
|
+
export function startCommentAt(editor: Editor, point: VecLike) {
|
|
146
|
+
editor.setCurrentTool('comment')
|
|
147
|
+
const pending = pendingCommentAt(editor, point)
|
|
148
|
+
pendingComment.set(editor, pending)
|
|
149
|
+
// Entering the tool hints the shape under the live pointer, which is wherever the caller's UI
|
|
150
|
+
// was (a menu item), not `point`.
|
|
151
|
+
editor.setHintingShapes(pending.anchor.type === 'shape' ? [pending.anchor.shapeId] : [])
|
|
152
|
+
}
|
|
153
|
+
|
|
116
154
|
/** Hint the shape a comment placed at the pointer would anchor to, using the same hit-test as the
|
|
117
155
|
* anchor resolution on release. Hinting shapes render an indicator ungated by the active tool, so
|
|
118
156
|
* this shows the select-style outline while the comment tool — not select — is active. */
|
|
@@ -142,6 +180,12 @@ class CommentIdle extends StateNode {
|
|
|
142
180
|
}
|
|
143
181
|
|
|
144
182
|
override onPointerDown() {
|
|
183
|
+
// A press away from an open composer cancels the placement rather than moving it. The
|
|
184
|
+
// tool's onExit drops the draft composer; its text survives in the draft store.
|
|
185
|
+
if (pendingComment.get(this.editor)) {
|
|
186
|
+
this.editor.setCurrentTool('select')
|
|
187
|
+
return
|
|
188
|
+
}
|
|
145
189
|
this.parent.transition('pointing')
|
|
146
190
|
}
|
|
147
191
|
}
|
|
@@ -183,20 +227,7 @@ class CommentPointing extends StateNode {
|
|
|
183
227
|
override onPointerUp() {
|
|
184
228
|
const { editor } = this
|
|
185
229
|
const point = editor.inputs.getCurrentPagePoint()
|
|
186
|
-
|
|
187
|
-
const anchor: TLCommentAnchor = hit
|
|
188
|
-
? shapeAnchorAt(
|
|
189
|
-
editor,
|
|
190
|
-
hit.id,
|
|
191
|
-
point,
|
|
192
|
-
getCommentingOptions(editor).shouldBePrecise(editor, {
|
|
193
|
-
shapeId: hit.id,
|
|
194
|
-
point,
|
|
195
|
-
altKey: editor.inputs.getAltKey(),
|
|
196
|
-
})
|
|
197
|
-
)
|
|
198
|
-
: { type: 'point', x: point.x, y: point.y }
|
|
199
|
-
pendingComment.set(editor, { anchor, point: { x: point.x, y: point.y } })
|
|
230
|
+
pendingComment.set(editor, pendingCommentAt(editor, point))
|
|
200
231
|
// Stay in the tool while the composer is open — the interaction isn't over until the
|
|
201
232
|
// comment is posted or dismissed, and staying keeps the surrounding UI (style panel,
|
|
202
233
|
// sidebar) from churning mid-placement.
|
|
@@ -80,6 +80,9 @@ export function PendingComposer({
|
|
|
80
80
|
// A click in the composer, or in the mention picker it spawns (portaled elsewhere), is
|
|
81
81
|
// not "outside" — keep the draft open so the pick can insert.
|
|
82
82
|
if (el.contains(target) || target.closest('.tlui-cmt-mention-popup')) return
|
|
83
|
+
// A press on the canvas is the comment tool's to handle: it ends the placement and returns
|
|
84
|
+
// to select. Clearing here first would let that press re-place the composer instead.
|
|
85
|
+
if (editor.getContainer().querySelector('.tl-canvas')?.contains(target)) return
|
|
83
86
|
pendingComment.set(editor, null)
|
|
84
87
|
}
|
|
85
88
|
document.addEventListener('pointerdown', onPointerDown, true)
|
package/src/index.ts
CHANGED
|
@@ -80,7 +80,12 @@ export {
|
|
|
80
80
|
toggleCommentReaction,
|
|
81
81
|
useCommentReactions,
|
|
82
82
|
} from './canvas/comment-reactions'
|
|
83
|
-
export {
|
|
83
|
+
export {
|
|
84
|
+
CommentTool,
|
|
85
|
+
commentToolOverrides,
|
|
86
|
+
commentTools,
|
|
87
|
+
startCommentAt,
|
|
88
|
+
} from './canvas/comment-tool'
|
|
84
89
|
export {
|
|
85
90
|
getCommentReactions,
|
|
86
91
|
getCommentRecord,
|