@tldraw/commenting 0.0.0-bootstrap → 5.3.0-internal.1640468db8fd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commenting.css +284 -153
- package/dist-cjs/canvas/comment-body.js +1 -1
- package/dist-cjs/canvas/comment-body.js.map +2 -2
- package/dist-cjs/canvas/comment-drafts.js +54 -0
- package/dist-cjs/canvas/comment-drafts.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +2 -0
- package/dist-cjs/canvas/comment-render.js.map +2 -2
- package/dist-cjs/canvas/comment-tool.js +65 -12
- package/dist-cjs/canvas/comment-tool.js.map +2 -2
- package/dist-cjs/canvas/comments-filter-menu.js +54 -63
- package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overflow-menu.js +21 -27
- package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
- package/dist-cjs/canvas/comments-overlay.js +304 -112
- package/dist-cjs/canvas/comments-overlay.js.map +2 -2
- package/dist-cjs/canvas/comments-sidebar.js +12 -18
- package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
- package/dist-cjs/canvas/comments-visibility-toggle.js +66 -0
- package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
- package/dist-cjs/canvas/options.js +1 -0
- package/dist-cjs/canvas/options.js.map +2 -2
- package/dist-cjs/canvas/sidebar-filters.js +2 -2
- package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
- package/dist-cjs/canvas/state.js +11 -0
- package/dist-cjs/canvas/state.js.map +2 -2
- package/dist-cjs/canvas/thread-state.js +8 -1
- package/dist-cjs/canvas/thread-state.js.map +2 -2
- package/dist-cjs/clustering/runtime.js +119 -7
- package/dist-cjs/clustering/runtime.js.map +2 -2
- package/dist-cjs/index.d.ts +126 -41
- package/dist-cjs/index.js +7 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/ui/avatar.js +14 -6
- package/dist-cjs/ui/avatar.js.map +2 -2
- package/dist-cjs/ui/byline.js +4 -4
- package/dist-cjs/ui/byline.js.map +2 -2
- package/dist-cjs/ui/comment-author.js +17 -0
- package/dist-cjs/ui/comment-author.js.map +7 -0
- package/dist-cjs/ui/comment-card.js +4 -4
- package/dist-cjs/ui/comment-card.js.map +2 -2
- package/dist-cjs/ui/comment-composer.js +92 -29
- package/dist-cjs/ui/comment-composer.js.map +2 -2
- package/dist-cjs/ui/comment-mention.js +1 -1
- package/dist-cjs/ui/comment-mention.js.map +2 -2
- package/dist-cjs/ui/comment-pin.js +23 -3
- package/dist-cjs/ui/comment-pin.js.map +2 -2
- package/dist-cjs/ui/comment-text.js +1 -1
- package/dist-cjs/ui/comment-text.js.map +2 -2
- package/dist-cjs/ui/comment-thread.js +6 -6
- package/dist-cjs/ui/comment-thread.js.map +2 -2
- package/dist-cjs/ui/comments-list.js +12 -12
- package/dist-cjs/ui/comments-list.js.map +2 -2
- package/dist-cjs/ui/count-badge.js +1 -1
- package/dist-cjs/ui/count-badge.js.map +2 -2
- package/dist-cjs/ui/empty-state.js +3 -3
- package/dist-cjs/ui/empty-state.js.map +2 -2
- package/dist-cjs/ui/format-time.js +3 -0
- package/dist-cjs/ui/format-time.js.map +2 -2
- package/dist-cjs/ui/mention-list.js +8 -8
- package/dist-cjs/ui/mention-list.js.map +2 -2
- package/dist-cjs/ui/mention-suggestion.js +3 -3
- package/dist-cjs/ui/mention-suggestion.js.map +2 -2
- package/dist-cjs/ui/mention.js +1 -1
- package/dist-cjs/ui/mention.js.map +2 -2
- package/dist-cjs/ui/reaction.js +11 -4
- package/dist-cjs/ui/reaction.js.map +2 -2
- package/dist-cjs/ui/reactions.js +10 -2
- package/dist-cjs/ui/reactions.js.map +2 -2
- package/dist-cjs/ui/send-button.js +1 -1
- package/dist-cjs/ui/send-button.js.map +2 -2
- package/dist-cjs/ui/tooltip-button.js +32 -0
- package/dist-cjs/ui/tooltip-button.js.map +7 -0
- package/dist-esm/canvas/comment-body.mjs +1 -1
- package/dist-esm/canvas/comment-body.mjs.map +2 -2
- package/dist-esm/canvas/comment-drafts.mjs +34 -0
- package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +2 -0
- package/dist-esm/canvas/comment-render.mjs.map +2 -2
- package/dist-esm/canvas/comment-tool.mjs +67 -14
- package/dist-esm/canvas/comment-tool.mjs.map +2 -2
- package/dist-esm/canvas/comments-filter-menu.mjs +54 -63
- package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overflow-menu.mjs +22 -27
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
- package/dist-esm/canvas/comments-overlay.mjs +322 -114
- package/dist-esm/canvas/comments-overlay.mjs.map +2 -2
- package/dist-esm/canvas/comments-sidebar.mjs +15 -21
- package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
- package/dist-esm/canvas/comments-visibility-toggle.mjs +46 -0
- package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
- package/dist-esm/canvas/options.mjs +1 -0
- package/dist-esm/canvas/options.mjs.map +2 -2
- package/dist-esm/canvas/sidebar-filters.mjs +2 -2
- package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
- package/dist-esm/canvas/state.mjs +11 -0
- package/dist-esm/canvas/state.mjs.map +2 -2
- package/dist-esm/canvas/thread-state.mjs +8 -1
- package/dist-esm/canvas/thread-state.mjs.map +2 -2
- package/dist-esm/clustering/runtime.mjs +119 -7
- package/dist-esm/clustering/runtime.mjs.map +2 -2
- package/dist-esm/index.d.mts +126 -41
- package/dist-esm/index.mjs +11 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/ui/avatar.mjs +14 -6
- package/dist-esm/ui/avatar.mjs.map +2 -2
- package/dist-esm/ui/byline.mjs +4 -4
- package/dist-esm/ui/byline.mjs.map +2 -2
- package/dist-esm/ui/comment-author.mjs +1 -0
- package/dist-esm/ui/comment-author.mjs.map +7 -0
- package/dist-esm/ui/comment-card.mjs +4 -4
- package/dist-esm/ui/comment-card.mjs.map +2 -2
- package/dist-esm/ui/comment-composer.mjs +93 -30
- package/dist-esm/ui/comment-composer.mjs.map +2 -2
- package/dist-esm/ui/comment-mention.mjs +1 -1
- package/dist-esm/ui/comment-mention.mjs.map +2 -2
- package/dist-esm/ui/comment-pin.mjs +23 -3
- package/dist-esm/ui/comment-pin.mjs.map +2 -2
- package/dist-esm/ui/comment-text.mjs +1 -1
- package/dist-esm/ui/comment-text.mjs.map +2 -2
- package/dist-esm/ui/comment-thread.mjs +6 -6
- package/dist-esm/ui/comment-thread.mjs.map +2 -2
- package/dist-esm/ui/comments-list.mjs +12 -12
- package/dist-esm/ui/comments-list.mjs.map +2 -2
- package/dist-esm/ui/count-badge.mjs +1 -1
- package/dist-esm/ui/count-badge.mjs.map +2 -2
- package/dist-esm/ui/empty-state.mjs +3 -3
- package/dist-esm/ui/empty-state.mjs.map +2 -2
- package/dist-esm/ui/format-time.mjs +3 -0
- package/dist-esm/ui/format-time.mjs.map +2 -2
- package/dist-esm/ui/mention-list.mjs +8 -8
- package/dist-esm/ui/mention-list.mjs.map +2 -2
- package/dist-esm/ui/mention-suggestion.mjs +3 -3
- package/dist-esm/ui/mention-suggestion.mjs.map +2 -2
- package/dist-esm/ui/mention.mjs +1 -1
- package/dist-esm/ui/mention.mjs.map +2 -2
- package/dist-esm/ui/reaction.mjs +11 -4
- package/dist-esm/ui/reaction.mjs.map +2 -2
- package/dist-esm/ui/reactions.mjs +10 -2
- package/dist-esm/ui/reactions.mjs.map +2 -2
- package/dist-esm/ui/send-button.mjs +1 -1
- package/dist-esm/ui/send-button.mjs.map +2 -2
- package/dist-esm/ui/tooltip-button.mjs +12 -0
- package/dist-esm/ui/tooltip-button.mjs.map +7 -0
- package/package.json +3 -3
- package/src/canvas/canvas.css +79 -36
- package/src/canvas/comment-body.tsx +1 -1
- package/src/canvas/comment-drafts.test.ts +55 -0
- package/src/canvas/comment-drafts.ts +46 -0
- package/src/canvas/comment-render.test.ts +1 -1
- package/src/canvas/comment-render.ts +7 -2
- package/src/canvas/comment-tool.tsx +98 -17
- package/src/canvas/comments-filter-menu.tsx +27 -33
- package/src/canvas/comments-overflow-menu.tsx +13 -21
- package/src/canvas/comments-overlay.tsx +441 -145
- package/src/canvas/comments-sidebar.tsx +27 -28
- package/src/canvas/comments-visibility-toggle.tsx +50 -0
- package/src/canvas/options.ts +25 -0
- package/src/canvas/sidebar-filters.ts +4 -3
- package/src/canvas/state.ts +22 -0
- package/src/canvas/thread-state.ts +16 -3
- package/src/clustering/runtime.test.ts +183 -7
- package/src/clustering/runtime.ts +164 -15
- package/src/index.ts +7 -0
- package/src/ui/avatar.tsx +15 -11
- package/src/ui/byline.tsx +6 -5
- package/src/ui/comment-author.ts +15 -0
- package/src/ui/comment-card.tsx +6 -5
- package/src/ui/comment-composer.tsx +122 -33
- package/src/ui/comment-mention.ts +2 -2
- package/src/ui/comment-pin.tsx +35 -4
- package/src/ui/comment-text.tsx +1 -1
- package/src/ui/comment-thread.tsx +8 -6
- package/src/ui/comments-list.tsx +19 -14
- package/src/ui/comments.css +205 -117
- package/src/ui/count-badge.tsx +1 -1
- package/src/ui/empty-state.tsx +3 -3
- package/src/ui/format-time.ts +5 -0
- package/src/ui/mention-list.tsx +13 -16
- package/src/ui/mention-suggestion.tsx +3 -3
- package/src/ui/mention.tsx +1 -1
- package/src/ui/reaction.tsx +6 -3
- package/src/ui/reactions.tsx +6 -2
- package/src/ui/send-button.tsx +1 -1
- package/src/ui/tooltip-button.tsx +20 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/comments-overlay.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tmemo,\n\ttype CSSProperties,\n\ttype PointerEvent as ReactPointerEvent,\n\tReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n\ttype BoxModel,\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tgetFirstCharacter,\n\treact,\n\tTLComment,\n\tTLCommentId,\n\tTLCommentThread,\n\tTLRichText,\n\tTldrawUiIcon,\n\tuseContainer,\n\tuseEditor,\n\tusePassThroughMouseOverEvents,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n\tVecLike,\n} from 'tldraw'\nimport { computeClusterTable } from '../clustering/computeClusterTable'\nimport { type ClusterRuntime, createClusterRuntime } from '../clustering/runtime'\nimport type { ClusterNode, ClusterTable, MergeEvent } from '../clustering/types'\nimport { CommentCard, CommentCardProps } from '../ui/comment-card'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport { CommentThread } from '../ui/comment-thread'\nimport { CountBadge } from '../ui/count-badge'\nimport { MentionMember } from '../ui/mention-list'\nimport { isMentionPickerOpen } from '../ui/mention-suggestion'\nimport { collectClusterLeaves } from './cluster-input'\nimport { CommentBody } from './comment-body'\nimport { UNKNOWN_AUTHOR } from './comment-render'\nimport { getCommentRecord, putCommentRecords, removeCommentRecords } from './comment-store'\nimport { PendingComment } from './comment-tool'\nimport { useCommentThreads, useThreadComments } from './hooks'\nimport { useCommentingEnabled } from './license'\nimport {\n\ttype CommentingComponents,\n\ttype CommentingOptions,\n\tgetCommentingOptions,\n\tuseCommentingOptions,\n} from './options'\nimport {\n\tDEFAULT_REGION_COMMENT_OPTIONS,\n\tRegionCommentOptions,\n\tsetRegionCommentOptions,\n} from './region-options'\nimport {\n\tcommentsHidden,\n\tcommitCommentMutation,\n\topenThreadId,\n\tpendingComment,\n\tregionDraft,\n\ttoggleCommentsHidden,\n\tusePendingComment,\n} from './state'\nimport { anchorPagePoint, regionPinPoint, shapeAnchorAt } from './thread-state'\n\n/**\n * A ready-to-use comments layer for a tldraw canvas: pins each thread at its anchor, opens a\n * thread popover (with a reply composer) on click, and shows a composer where the comment tool\n * placed a new thread. Reads/writes comment records straight from `editor.store`.\n *\n * It's meant as the batteries-included default \u2014 every visible piece is a lever (the `CommentBody`\n * and `PinContent` slots on `CommentTool.configure({ components })`), and the pieces it composes\n * (`CommentPin`, `CommentThread`, `CommentComposer`, the hooks, the tool) are all exported, so a\n * consumer can rebuild this from parts instead.\n * @public\n */\nexport interface CanvasCommentsProps {\n\t/** The signed-in user's id, or null for a read-only viewer. Only a signed-in user composes. */\n\tcurrentUserId: string | null\n\t/** Map an author id to a display name, or `undefined` when the id can't be named. */\n\tresolveName(id: string): string | undefined\n\t/** Called after any comment (a new thread's first comment, or a reply) is posted. */\n\tonPostComment?(comment: TLComment): void\n\t/** Whether a comment is unread for the current user (return true for unread). */\n\tisCommentUnread?(commentId: TLCommentId): boolean\n\t/**\n\t * Called for each unread comment shown to the user in an open thread popover, so hosts can\n\t * record a read receipt. Needs `isCommentUnread` to know what's unread.\n\t */\n\tonCommentRead?(commentId: TLCommentId): void\n\t/** Resolve the members matching an `@`-query in the composers (sync or async). */\n\tgetMentionSuggestions?(query: string): MentionMember[] | Promise<MentionMember[]>\n\t/** Override a mention-picker row's content. */\n\trenderMentionSuggestion?(member: MentionMember): ReactNode\n\t/** Where imprecise shape pins sit \u2014 a normalized (0\u20131) spot within the shape. Default top-right. */\n\timpreciseShapeAnchor?: { x: number; y: number }\n\t/** Region comment behaviour. Region is off by default \u2014 omit this for click-only point/shape\n\t * comments. Anything unset falls back to {@link DEFAULT_REGION_COMMENT_OPTIONS}. */\n\tregionOptions?: Partial<RegionCommentOptions>\n}\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\nconst initialOf = (name: string): string => (getFirstCharacter(name.trim()) || '?').toUpperCase()\nconst CLUSTER_FADE_MS = 150\n/** Duration of the click-a-badge zoom-to-split animation. */\nconst CLUSTER_EXPAND_ZOOM_MS = 450\n\n/** The leading element for the placement composer \u2014 the comment pin's shape, but a pencil\n * instead of an initial, marking an unsent draft. */\nconst draftAvatar = (\n\t<CommentPin>\n\t\t<svg\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\twidth=\"15\"\n\t\t\theight=\"15\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t<path d=\"M12 20h9\" />\n\t\t\t<path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\" />\n\t\t</svg>\n\t</CommentPin>\n)\n\nfunction toCardProps(\n\tcomment: TLComment,\n\tprops: CanvasCommentsProps,\n\tcomponents: CommentingComponents\n): CommentCardProps {\n\tconst Body = components.CommentBody\n\t// The `CommentBody` component slot overrides the built-in rich-text default (which resolves\n\t// mention ids to names).\n\tconst body = Body ? (\n\t\t<Body comment={comment} />\n\t) : (\n\t\t<CommentBody richText={comment.body} resolveName={props.resolveName} />\n\t)\n\treturn {\n\t\tauthor: props.resolveName(comment.authorId) ?? UNKNOWN_AUTHOR,\n\t\tbody,\n\t\tdate: new Date(comment.createdAt).toISOString(),\n\t\tyou: comment.authorId === props.currentUserId,\n\t\tedited: comment.editedAt != null,\n\t}\n}\n\n/** @public @react */\nexport function CanvasComments(props: CanvasCommentsProps) {\n\t// Gate the whole layer on the license before doing any work. The inner component holds all the\n\t// other hooks, so mounting/unmounting it as the license resolves keeps hook order stable here.\n\tconst commentingEnabled = useCommentingEnabled()\n\tif (!commentingEnabled) return null\n\treturn <CanvasCommentsLayer {...props} />\n}\n\nfunction CanvasCommentsLayer(props: CanvasCommentsProps) {\n\tconst editor = useEditor()\n\tconst options = useCommentingOptions()\n\tconst container = useContainer()\n\t// Merge the consumer's region options over the disabled defaults and publish them for this editor,\n\t// so the comment tool (which has no props) reads the same per-instance config.\n\tconst regionOptions = useMemo(\n\t\t() => ({ ...DEFAULT_REGION_COMMENT_OPTIONS, ...props.regionOptions }),\n\t\t[props.regionOptions]\n\t)\n\tuseEffect(() => setRegionCommentOptions(editor, regionOptions), [editor, regionOptions])\n\tconst layerRef = useRef<HTMLDivElement>(null)\n\t// Over the pins and cluster badges, wheel and hover pass through to the canvas beneath (these\n\t// events bubble up from the pointer-interactive markers to this layer root).\n\tusePassThroughWheelEvents(layerRef)\n\tusePassThroughMouseOverEvents(layerRef)\n\tconst deepLinkHandled = useRef(false)\n\tconst threads = useCommentThreads(editor)\n\tconst pending = usePendingComment()\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor\n\t// Threads whose anchor has moved (by any means \u2014 drag, nudge, align, undo, a collaborator)\n\t// since the rendered clustering was built. They pop out of clustering and render as live pins,\n\t// and only rejoin at the next zoom event, when everything re-clusters anyway.\n\tconst [movedThreadIds, setMovedThreadIds] = useState<ReadonlySet<string>>(EMPTY_SET)\n\tconst adoptOnRebuild = useRef(false)\n\tconst clusterLeaves = useValue(\n\t\t'comment cluster leaves',\n\t\t() =>\n\t\t\tcollectClusterLeaves(\n\t\t\t\teditor,\n\t\t\t\tthreads.filter((thread) => !movedThreadIds.has(thread.id)),\n\t\t\t\topenThreadId.get(editor),\n\t\t\t\timpreciseShapeAnchor\n\t\t\t),\n\t\t[editor, threads, impreciseShapeAnchor, movedThreadIds]\n\t)\n\tconst clusterZoomBounds = useValue(\n\t\t'comment cluster zoom bounds',\n\t\t() => getClusterZoomBounds(editor),\n\t\t[editor]\n\t)\n\tconst latestModel = useMemo(() => {\n\t\tconst table = computeClusterTable(clusterLeaves, clusterZoomBounds)\n\t\tconst runtime = createClusterRuntime(table)\n\t\truntime.seed(editor.getZoomLevel())\n\t\treturn { runtime, table }\n\t}, [clusterLeaves, clusterZoomBounds, editor])\n\t// Re-clustering only applies while zooming: a rebuilt model (thread added, moved, or closed)\n\t// is held as `latestModel` and adopted on the next zoom change, so pins never re-flow into\n\t// clusters under a static camera. Until adoption, threads the rendered model doesn't know\n\t// about show as plain unclustered pins (`orphanThreads`). Exception: a rebuild that *removed*\n\t// leaves (thread deleted or opened, page changed) is adopted immediately, so stale pins and\n\t// badge counts never linger.\n\tconst [renderedModel, setRenderedModel] = useState(latestModel)\n\tlet clusterModel = renderedModel\n\tif (\n\t\trenderedModel !== latestModel &&\n\t\t(adoptOnRebuild.current || hasRemovedLeaves(renderedModel.table, latestModel.table))\n\t) {\n\t\tadoptOnRebuild.current = false\n\t\t// Carryover seed: events inside their hysteresis band inherit the outgoing partition's\n\t\t// merged/unmerged state instead of the geometric-mean tiebreak, so untouched pins never\n\t\t// snap together (or apart) just because the model was swapped. Idempotent, so safe to\n\t\t// run during render.\n\t\tlatestModel.runtime.seedFrom(editor.getZoomLevel(), renderedModel.runtime.getVisible())\n\t\tsetRenderedModel(latestModel)\n\t\tclusterModel = latestModel\n\t}\n\t// Pop-out detection: a leaf folded inside a badge can't follow its anchor (the badge position\n\t// is baked into the model), so when its live position drifts from the baked one it ghosts.\n\t// Marking it moved excludes it from the cluster input, which reads as a removal above and\n\t// re-clusters the rest of its pile immediately; the pin itself renders live below.\n\tconst newlyMovedIds = findMovedClusteredLeafIds(clusterModel, latestModel)\n\tif (newlyMovedIds.length > 0) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.debug(`[comments] pins popped out of clustering: ${newlyMovedIds.join(', ')}`)\n\t\tconst next = new Set(movedThreadIds)\n\t\tfor (const id of newlyMovedIds) next.add(id)\n\t\tsetMovedThreadIds(next)\n\t}\n\t// Moved pins rejoin clustering on the next zoom-out motion: clear the set (so the rebuild\n\t// includes them again) and adopt that rebuild immediately instead of deferring it. Zooming in\n\t// never folds pins into clusters \u2014 merging is a zoom-out-only move, matching the runtime.\n\tuseEffect(() => {\n\t\tif (movedThreadIds.size === 0) return\n\t\tlet lastZoom = editor.getZoomLevel()\n\t\treturn react('rejoin moved comment pins on zoom out', () => {\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\tconst prevZoom = lastZoom\n\t\t\tlastZoom = zoom\n\t\t\tif (zoom >= prevZoom) return\n\t\t\tadoptOnRebuild.current = true\n\t\t\tsetMovedThreadIds(EMPTY_SET)\n\t\t})\n\t}, [movedThreadIds, editor])\n\t// Adopt a pending rebuild only on zoom-out motion: folding deferred additions into clusters is\n\t// a merge, and merging only happens while zooming out. While zooming in, the stale table still\n\t// splits correctly on its own (split thresholds are direction-safe by the hysteresis invariant).\n\tuseEffect(() => {\n\t\tif (clusterModel === latestModel) return\n\t\tlet lastZoom = editor.getZoomLevel()\n\t\treturn react('adopt pending cluster model on zoom out', () => {\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\tconst prevZoom = lastZoom\n\t\t\tlastZoom = zoom\n\t\t\tif (zoom >= prevZoom) return\n\t\t\tlatestModel.runtime.seedFrom(zoom, clusterModel.runtime.getVisible())\n\t\t\tsetRenderedModel(latestModel)\n\t\t})\n\t}, [clusterModel, latestModel, editor])\n\tconst orphanThreads = useMemo(() => {\n\t\tif (clusterModel === latestModel) return []\n\t\tconst renderedIds = new Set(clusterModel.table.leaves.map((leaf) => leaf.id))\n\t\tconst latestIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id))\n\t\treturn threads.filter((thread) => latestIds.has(thread.id) && !renderedIds.has(thread.id))\n\t}, [clusterModel, latestModel, threads])\n\tconst movedThreads = useMemo(\n\t\t() => threads.filter((thread) => movedThreadIds.has(thread.id) && thread.id !== openId),\n\t\t[threads, movedThreadIds, openId]\n\t)\n\t// Subscribe to the runtime cursor, not the raw zoom: onCamera runs on every zoom tick (two\n\t// O(1) threshold checks against the event table) but returns the same integer until a merge\n\t// or split event actually fires \u2014 so this component only re-renders on cluster changes, not\n\t// on every camera frame.\n\tconst clusterCursor = useValue(\n\t\t'comment cluster cursor',\n\t\t() => {\n\t\t\tclusterModel.runtime.onCamera(editor.getZoomLevel())\n\t\t\treturn clusterModel.runtime.k\n\t\t},\n\t\t[clusterModel, editor]\n\t)\n\tconst visibleNodes = useMemo(() => {\n\t\tconst nodes = Array.from(clusterModel.runtime.getVisible().values())\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.debug(\n\t\t\t`[comments] cluster cursor k=${clusterCursor} \u2192 re-rendering ${nodes.length} visible nodes`\n\t\t)\n\t\treturn nodes\n\t}, [clusterModel, clusterCursor])\n\tconst fadeNodes = useFadeVisibleNodes(visibleNodes, clusterModel)\n\tconst threadsById = useMemo(\n\t\t() => new Map<string, TLCommentThread>(threads.map((thread) => [thread.id, thread])),\n\t\t[threads]\n\t)\n\tconst openThread = openId ? threadsById.get(openId) : null\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\n\t// Reset the transient UI state (open thread, half-placed comment) when this unmounts.\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\topenThreadId.set(editor, null)\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t}, [editor])\n\n\t// Open the thread named by a deep link (?comment=<thread or comment id>). If the thread is\n\t// currently inside a cluster, zoom to the first split that reveals it before opening.\n\tuseEffect(() => {\n\t\tif (deepLinkHandled.current) return\n\t\tconst id = new URLSearchParams(window.location.search).get('comment')\n\t\tif (!id) {\n\t\t\tdeepLinkHandled.current = true\n\t\t\treturn\n\t\t}\n\n\t\tconst record = getCommentRecord(editor, id)\n\t\tif (!record) return\n\n\t\tlet thread: TLCommentThread | undefined\n\t\tif (record.typeName === 'comment') {\n\t\t\tthread = threadsById.get(record.threadId)\n\t\t} else {\n\t\t\tthread = record\n\t\t}\n\t\tif (!thread) return\n\n\t\tdeepLinkHandled.current = true\n\t\trevealDeepLinkedThread(\n\t\t\teditor,\n\t\t\tthread,\n\t\t\tclusterModel.table,\n\t\t\tclusterZoomBounds,\n\t\t\toptions,\n\t\t\timpreciseShapeAnchor\n\t\t)\n\t\topenThreadId.set(editor, thread.id)\n\t}, [clusterModel.table, clusterZoomBounds, editor, threadsById, impreciseShapeAnchor, options])\n\n\t// Clicking a badge zooms to just past the zoom at which that cluster first unclusters,\n\t// centered on its centroid. The event that created a visible cluster is the event that splits\n\t// it, and (by the table's sort + monotone thresholds) it has the smallest zSplit of everything\n\t// applied inside it \u2014 so its zSplit is exactly the first split within those comments. The\n\t// animated zoom-in then drives the runtime cursor like any manual zoom, so the badge splits\n\t// (and can be drilled into further) with no extra bookkeeping.\n\tconst zoomToClusterSplit = useCallback(\n\t\t(node: ClusterNode) => {\n\t\t\tconst event = clusterModel.table.events.find((e) => e.result.id === node.id)\n\t\t\tif (!event || !Number.isFinite(event.zSplit)) return\n\t\t\tconst zoom = clamp(\n\t\t\t\tevent.zSplit * options.clusterSplitZoomFactor,\n\t\t\t\tclusterZoomBounds.minZoom,\n\t\t\t\tclusterZoomBounds.maxZoom\n\t\t\t)\n\t\t\tcenterOnPointAtZoom(editor, node.centroid, zoom, CLUSTER_EXPAND_ZOOM_MS)\n\t\t},\n\t\t[clusterModel, clusterZoomBounds, editor, options]\n\t)\n\n\t// Escape collapses the open thread. Capture-phase + stopPropagation so it runs ahead of the\n\t// editor (which would otherwise cancel the current tool or clear the selection). If a comment is\n\t// being edited, let its own Escape handler exit edit mode first, keeping the thread open.\n\tuseEffect(() => {\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || openThreadId.get(editor) === null) return\n\t\t\t// The mention picker owns Escape while it's open \u2014 let it dismiss the roster alone.\n\t\t\tif (isMentionPickerOpen()) return\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (target && target.closest('.cmt-editing')) return\n\t\t\topenThreadId.set(editor, null)\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [editor])\n\n\t// Shift+C toggles comment-pin visibility on the canvas (matching Figma). Skipped while typing so\n\t// it never fires from inside a composer. Physical `KeyC` (layout-independent) with shift only.\n\tuseEffect(() => {\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.code !== 'KeyC' || !e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (target && target.closest('input, textarea, [contenteditable=\"true\"]')) return\n\t\t\ttoggleCommentsHidden(editor)\n\t\t\te.preventDefault()\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [editor])\n\n\t// Hidden: the whole canvas layer (pins, open popover, pending composer) is withheld. The signal\n\t// is read above so this component stays mounted and its shortcut/Escape effects keep running.\n\tif (hidden) return null\n\n\t// Render into the container (above the panels' stacking context) so the pins and popovers\n\t// live in the UI layer rather than being clipped by the canvas layer.\n\treturn createPortal(\n\t\t<div ref={layerRef} className=\"cmt-canvas-layer\">\n\t\t\t{options.enableClustering ? (\n\t\t\t\t<>\n\t\t\t\t\t{fadeNodes.map(({ node, phase }) => {\n\t\t\t\t\t\tlet content: ReactNode\n\t\t\t\t\t\tif (node.count === 1) {\n\t\t\t\t\t\t\tconst thread = threadsById.get(node.id)\n\t\t\t\t\t\t\tif (!thread) return null\n\t\t\t\t\t\t\tcontent = (\n\t\t\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontent = <ClusterBadge editor={editor} node={node} onExpand={zoomToClusterSplit} />\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={`cluster-fade:${node.id}`} className={clusterFadeClassName(phase)}>\n\t\t\t\t\t\t\t\t{content}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)\n\t\t\t\t\t})}\n\t\t\t\t\t{orphanThreads.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{movedThreads.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t// Clustering off: every thread renders as its own live pin (each returns null when it's\n\t\t\t\t// not on the current page or its anchor is missing). The open thread is excluded here and\n\t\t\t\t// rendered once below, mirroring how the clustering path keeps it out of the cluster leaves \u2014\n\t\t\t\t// otherwise it would mount a second, stacked pin.\n\t\t\t\tthreads\n\t\t\t\t\t.filter((thread) => thread.id !== openId)\n\t\t\t\t\t.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))\n\t\t\t)}\n\t\t\t{openThread && (\n\t\t\t\t<ThreadPin\n\t\t\t\t\tkey={`open:${openThread.id}`}\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthread={openThread}\n\t\t\t\t\t{...props}\n\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<RegionDraftBox editor={editor} />\n\t\t\t{/* Keep the region visible while composing \u2014 the drag draft is gone by now, and no thread\n\t\t\t exists yet, so the pending anchor is what shows the area under the open composer. */}\n\t\t\t{pending?.anchor.type === 'region' && <RegionBox editor={editor} box={pending.anchor} />}\n\t\t\t{pending && props.currentUserId && (\n\t\t\t\t<PendingComposer editor={editor} pending={pending} {...props} />\n\t\t\t)}\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\nconst MOVED_LEAF_EPSILON = 1e-6\ntype ClusterFadePhase = 'entering' | 'present' | 'exiting'\n\ninterface ClusterFadeNode {\n\tnode: ClusterNode\n\tphase: ClusterFadePhase\n}\n\nfunction useFadeVisibleNodes(\n\tnodes: readonly ClusterNode[],\n\tresetKey: { runtime: ClusterRuntime; table: ClusterTable }\n): ClusterFadeNode[] {\n\tconst resetKeyRef = useRef(resetKey)\n\tconst didReset = resetKeyRef.current !== resetKey\n\tif (didReset) {\n\t\tresetKeyRef.current = resetKey\n\t}\n\n\tconst [fadeNodes, setFadeNodes] = useState<ClusterFadeNode[]>(() => toPresentFadeNodes(nodes))\n\tconst renderedNodes = didReset ? toPresentFadeNodes(nodes) : fadeNodes\n\n\tuseEffect(() => {\n\t\tsetFadeNodes(toPresentFadeNodes(nodes))\n\t\t// Resets only on a new model (resetKey); node-list changes within the same model are\n\t\t// handled by the reconcile effect below, which fades entries in/out instead of snapping.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [resetKey])\n\n\tuseEffect(() => {\n\t\tif (didReset) return\n\t\tsetFadeNodes((previous) => reconcileFadeNodes(previous, nodes))\n\t}, [didReset, nodes])\n\n\tconst hasEntering = renderedNodes.some((item) => item.phase === 'entering')\n\tuseEffect(() => {\n\t\tif (!hasEntering) return\n\t\tconst frame = requestClusterFadeFrame(() => {\n\t\t\tsetFadeNodes((previous) =>\n\t\t\t\tprevious.map((item) => (item.phase === 'entering' ? { ...item, phase: 'present' } : item))\n\t\t\t)\n\t\t})\n\t\treturn () => cancelClusterFadeFrame(frame)\n\t}, [hasEntering, renderedNodes])\n\n\tconst hasExiting = renderedNodes.some((item) => item.phase === 'exiting')\n\tuseEffect(() => {\n\t\tif (!hasExiting) return\n\t\tconst timeout = window.setTimeout(() => {\n\t\t\tsetFadeNodes((previous) => previous.filter((item) => item.phase !== 'exiting'))\n\t\t}, CLUSTER_FADE_MS)\n\t\treturn () => window.clearTimeout(timeout)\n\t}, [hasExiting, renderedNodes])\n\n\treturn renderedNodes\n}\n\nfunction toPresentFadeNodes(nodes: readonly ClusterNode[]): ClusterFadeNode[] {\n\treturn nodes.map((node) => ({ node, phase: 'present' }))\n}\n\nfunction reconcileFadeNodes(\n\tprevious: readonly ClusterFadeNode[],\n\tnextNodes: readonly ClusterNode[]\n): ClusterFadeNode[] {\n\tconst previousById = new Map(previous.map((item) => [item.node.id, item]))\n\tconst nextIds = new Set(nextNodes.map((node) => node.id))\n\tconst next: ClusterFadeNode[] = []\n\n\tfor (const node of nextNodes) {\n\t\tconst previousItem = previousById.get(node.id)\n\t\tnext.push({\n\t\t\tnode,\n\t\t\tphase:\n\t\t\t\tpreviousItem && previousItem.phase !== 'exiting'\n\t\t\t\t\t? previousItem.phase\n\t\t\t\t\t: previousItem\n\t\t\t\t\t\t? 'present'\n\t\t\t\t\t\t: 'entering',\n\t\t})\n\t}\n\n\tfor (const item of previous) {\n\t\tif (nextIds.has(item.node.id)) continue\n\t\tnext.push(item.phase === 'exiting' ? item : { ...item, phase: 'exiting' })\n\t}\n\n\treturn next\n}\n\nfunction requestClusterFadeFrame(callback: FrameRequestCallback): number {\n\tif (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback)\n\treturn window.setTimeout(() => callback(0), 16)\n}\n\nfunction cancelClusterFadeFrame(frame: number) {\n\tif (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(frame)\n\telse window.clearTimeout(frame)\n}\n\nfunction clusterFadeClassName(phase: ClusterFadePhase): string {\n\treturn `cmt-cluster-fade cmt-cluster-fade--${phase}`\n}\n\n/**\n * Leaves folded inside a badge whose live anchor no longer matches the position the rendered\n * model was built with. Visible (unclustered) leaf pins track their anchor live, so they can\n * stay deferred; a badge can't follow a member, so these must pop out of clustering.\n */\nfunction findMovedClusteredLeafIds(\n\trendered: { runtime: ClusterRuntime; table: ClusterTable },\n\tlatest: { table: ClusterTable }\n): string[] {\n\tif (rendered.table === latest.table) return []\n\tconst visible = rendered.runtime.getVisible()\n\tconst latestById = new Map(latest.table.leaves.map((leaf) => [leaf.id, leaf]))\n\tconst moved: string[] = []\n\tfor (const leaf of rendered.table.leaves) {\n\t\tif (visible.has(leaf.id)) continue\n\t\tconst current = latestById.get(leaf.id)\n\t\tif (!current) continue\n\t\tif (\n\t\t\tMath.abs(current.centroid.x - leaf.centroid.x) > MOVED_LEAF_EPSILON ||\n\t\t\tMath.abs(current.centroid.y - leaf.centroid.y) > MOVED_LEAF_EPSILON\n\t\t) {\n\t\t\tmoved.push(leaf.id)\n\t\t}\n\t}\n\treturn moved\n}\n\nfunction hasRemovedLeaves(rendered: ClusterTable, latest: ClusterTable): boolean {\n\tif (rendered.leaves.length === 0) return false\n\tconst latestIds = new Set(latest.leaves.map((leaf) => leaf.id))\n\treturn rendered.leaves.some((leaf) => !latestIds.has(leaf.id))\n}\n\nfunction getClusterZoomBounds(editor: Editor): { minZoom: number; maxZoom: number } {\n\tconst cameraOptions = editor.getCameraOptions()\n\tconst baseZoom = cameraOptions.constraints ? editor.getBaseZoom() : 1\n\tconst zoomSteps = cameraOptions.zoomSteps\n\treturn {\n\t\tminZoom: zoomSteps[0] * baseZoom,\n\t\tmaxZoom: zoomSteps[zoomSteps.length - 1] * baseZoom,\n\t}\n}\n\nfunction revealDeepLinkedThread(\n\teditor: Editor,\n\tthread: TLCommentThread,\n\ttable: ClusterTable,\n\tzoomBounds: { minZoom: number; maxZoom: number },\n\toptions: CommentingOptions,\n\timpreciseShapeAnchor: { x: number; y: number }\n) {\n\tif (thread.pageId !== editor.getCurrentPageId()) {\n\t\teditor.setCurrentPage(thread.pageId as any)\n\t}\n\n\t// Match where the rendered pin sits (resolved prop-or-option), so the camera centers on the pin.\n\tconst point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\tif (!point) return\n\n\t// With clustering off the pin always renders individually, so skip the zoom-to-split (its cluster\n\t// badge never exists) and just center on the pin.\n\tif (options.enableClustering) {\n\t\tconst parentEvent = findDirectParentEvent(table, thread.id)\n\t\tif (\n\t\t\tparentEvent &&\n\t\t\tNumber.isFinite(parentEvent.zSplit) &&\n\t\t\tparentEvent.zSplit <= zoomBounds.maxZoom\n\t\t) {\n\t\t\tconst zoom = clamp(\n\t\t\t\tparentEvent.zSplit * options.clusterSplitZoomFactor,\n\t\t\t\tzoomBounds.minZoom,\n\t\t\t\tzoomBounds.maxZoom\n\t\t\t)\n\t\t\tcenterOnPointAtZoom(editor, point, zoom)\n\t\t\treturn\n\t\t}\n\t}\n\n\teditor.centerOnPoint(point, { animation: { duration: 200 } })\n}\n\nfunction findDirectParentEvent(table: ClusterTable, threadId: string): MergeEvent | undefined {\n\treturn table.events.find((event) => event.children.some((child) => child.id === threadId))\n}\n\nfunction centerOnPointAtZoom(\n\teditor: Editor,\n\tpoint: { x: number; y: number },\n\tzoom: number,\n\tduration = 200\n) {\n\tconst viewport = editor.getViewportScreenBounds()\n\teditor.setCamera(\n\t\t{\n\t\t\tx: viewport.w / (2 * zoom) - point.x,\n\t\t\ty: viewport.h / (2 * zoom) - point.y,\n\t\t\tz: zoom,\n\t\t},\n\t\t{ animation: { duration } }\n\t)\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value))\n}\n\n// Memoized: cluster nodes and thread records are identity-stable while unchanged, so pins and\n// badges skip re-rendering when the parent re-renders for reasons that don't concern them\n// (leaf recomputes during shape drags, partition changes elsewhere). Camera tracking still\n// works \u2014 each component subscribes to its own viewport position via signals, not via props.\nconst ClusterBadge = memo(function ClusterBadge({\n\teditor,\n\tnode,\n\tonExpand,\n}: {\n\teditor: Editor\n\tnode: ClusterNode\n\tonExpand(node: ClusterNode): void\n}) {\n\tconst point = useValue(\n\t\t'cluster badge point',\n\t\t() => {\n\t\t\tconst pagePoint = editor.pageToViewport(node.centroid)\n\t\t\tif (!isInInflatedViewport(editor, pagePoint)) return null\n\t\t\treturn pagePoint\n\t\t},\n\t\t[editor, node]\n\t)\n\n\tif (!point) return null\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"cmt-canvas-cluster\"\n\t\t\tstyle={{ left: point.x, top: point.y }}\n\t\t\tonPointerDown={stop}\n\t\t\tonClick={(e) => {\n\t\t\t\te.stopPropagation()\n\t\t\t\tonExpand(node)\n\t\t\t}}\n\t\t>\n\t\t\t<CountBadge count={node.count} />\n\t\t</div>\n\t)\n})\n\nfunction isInInflatedViewport(editor: Editor, point: { x: number; y: number }): boolean {\n\tconst viewport = editor.getViewportScreenBounds()\n\tconst margin = getCommentingOptions(editor).clusterCullMargin\n\treturn (\n\t\tpoint.x >= -margin &&\n\t\tpoint.y >= -margin &&\n\t\tpoint.x <= viewport.w + margin &&\n\t\tpoint.y <= viewport.h + margin\n\t)\n}\n\n/** The open thread's popover, portaled above the UI panels. Over it, wheel and hover events pass\n * through to the canvas (unless the popover is scrolling its own content), like tldraw's panels. */\nfunction ThreadPopover({\n\tcontainer,\n\tstyle,\n\tchildren,\n}: {\n\tcontainer: HTMLElement\n\tstyle: CSSProperties\n\tchildren: ReactNode\n}) {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\tusePassThroughMouseOverEvents(ref)\n\treturn createPortal(\n\t\t<div ref={ref} className=\"cmt-canvas-popover\" style={style} onPointerDown={stop}>\n\t\t\t{children}\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n\n/** A dashed rectangle over a region anchor's bounds, in viewport space. Sits in the canvas layer as\n * a sibling of the pins. `pointer-events` stays off (canvas interaction passes through) unless\n * `movable`, in which case dragging the body translates the region \u2014 previews live, commits on drop. */\nfunction RegionBox({\n\teditor,\n\tbox,\n\tmovable,\n\tonPreview,\n\tonCommit,\n}: {\n\teditor: Editor\n\tbox: BoxModel\n\tmovable?: boolean\n\tonPreview?(bounds: BoxModel | null): void\n\tonCommit?(bounds: BoxModel): void\n}) {\n\tconst rect = useValue(\n\t\t'region rect',\n\t\t() => {\n\t\t\t// Position from the page\u2192viewport top-left; screen size scales with zoom, page size doesn't.\n\t\t\tconst topLeft = editor.pageToViewport({ x: box.x, y: box.y })\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\treturn { left: topLeft.x, top: topLeft.y, width: box.w * zoom, height: box.h * zoom }\n\t\t},\n\t\t[editor, box.x, box.y, box.w, box.h]\n\t)\n\t// The grab point and the box at grab time, captured so the drag translates by a stable delta even\n\t// as the box prop reflows under the live preview.\n\tconst grabRef = useRef<{ page: VecLike; box: BoxModel } | null>(null)\n\tconst translated = (e: ReactPointerEvent<HTMLDivElement>): BoxModel => {\n\t\tconst g = grabRef.current!\n\t\tconst p = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\treturn { ...g.box, x: g.box.x + (p.x - g.page.x), y: g.box.y + (p.y - g.page.y) }\n\t}\n\tconst startMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\te.stopPropagation()\n\t\tgrabRef.current = { page: editor.screenToPage({ x: e.clientX, y: e.clientY }), box }\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst onMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (grabRef.current) onPreview?.(translated(e))\n\t}\n\tconst endMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (!grabRef.current) return\n\t\tconst bounds = translated(e)\n\t\tgrabRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId))\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\tonCommit?.(bounds)\n\t}\n\treturn (\n\t\t<div\n\t\t\tclassName={movable ? 'cmt-canvas-region cmt-canvas-region--movable' : 'cmt-canvas-region'}\n\t\t\tstyle={rect}\n\t\t\tonPointerDown={movable ? startMove : undefined}\n\t\t\tonPointerMove={movable ? onMove : undefined}\n\t\t\tonPointerUp={movable ? endMove : undefined}\n\t\t/>\n\t)\n}\n\n/** The live region being dragged out by the comment tool, or nothing when not dragging. */\nfunction RegionDraftBox({ editor }: { editor: Editor }) {\n\tconst box = useValue('region draft', () => regionDraft.get(editor), [editor])\n\tif (!box) return null\n\treturn <RegionBox editor={editor} box={box} />\n}\n\n// A resize handle's normalized 0\u20131 spot on the box, and its cursor. An axis at 0.5 (a side midpoint)\n// is *not* controlled by that handle: corners resize both axes, edges resize only their own.\ninterface RegionHandle {\n\tx: number\n\ty: number\n\tcursor: string\n}\n\n// The four corners (both axes) and the four side midpoints (one axis each).\nconst REGION_CORNERS: readonly RegionHandle[] = [\n\t{ x: 0, y: 0, cursor: 'nwse-resize' },\n\t{ x: 1, y: 0, cursor: 'nesw-resize' },\n\t{ x: 0, y: 1, cursor: 'nesw-resize' },\n\t{ x: 1, y: 1, cursor: 'nwse-resize' },\n]\nconst REGION_EDGES: readonly RegionHandle[] = [\n\t{ x: 0.5, y: 0, cursor: 'ns-resize' },\n\t{ x: 1, y: 0.5, cursor: 'ew-resize' },\n\t{ x: 0.5, y: 1, cursor: 'ns-resize' },\n\t{ x: 0, y: 0.5, cursor: 'ew-resize' },\n]\n\n// Screen-space slack around a region's bounds within which its box and handles stay revealed, so\n// the handles (which sit on the edge) are comfortably reachable.\nconst REGION_HANDLE_MARGIN_PX = 12\n\n/** Resize `box` by dragging `handle` to `cursor` (page coords). Each controlled axis spans from the\n * handle's fixed opposite edge to the cursor (normalized, so dragging past it flips); an axis the\n * handle doesn't control (a midpoint, at 0.5) keeps its original position and size. */\nfunction resizeRegion(box: BoxModel, handle: RegionHandle, cursor: VecLike): BoxModel {\n\tconst controlsX = handle.x !== 0.5\n\tconst controlsY = handle.y !== 0.5\n\tconst fixedX = box.x + (1 - handle.x) * box.w\n\tconst fixedY = box.y + (1 - handle.y) * box.h\n\treturn {\n\t\tx: controlsX ? Math.min(fixedX, cursor.x) : box.x,\n\t\ty: controlsY ? Math.min(fixedY, cursor.y) : box.y,\n\t\tw: controlsX ? Math.abs(cursor.x - fixedX) : box.w,\n\t\th: controlsY ? Math.abs(cursor.y - fixedY) : box.h,\n\t}\n}\n\n/** Draggable handles that resize a region \u2014 corners (both axes) or edges (one axis), per the resize\n * option. Previews live, commits on release. */\nfunction RegionResizeHandles({\n\teditor,\n\tbox,\n\thandles,\n\tonPreview,\n\tonCommit,\n}: {\n\teditor: Editor\n\tbox: BoxModel\n\thandles: readonly RegionHandle[]\n\tonPreview(bounds: BoxModel | null): void\n\tonCommit(bounds: BoxModel): void\n}) {\n\t// The box at pointer-down, captured so the box prop reflowing under the live preview doesn't move\n\t// the fixed edges mid-drag.\n\tconst boxRef = useRef<BoxModel | null>(null)\n\tconst points = useValue(\n\t\t'region handle points',\n\t\t() =>\n\t\t\thandles.map((h) => {\n\t\t\t\tconst p = editor.pageToViewport({ x: box.x + h.x * box.w, y: box.y + h.y * box.h })\n\t\t\t\treturn { ...h, key: `${h.x}-${h.y}`, left: p.x, top: p.y }\n\t\t\t}),\n\t\t[editor, box.x, box.y, box.w, box.h, handles]\n\t)\n\tconst startResize = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\te.stopPropagation()\n\t\tboxRef.current = box\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst resizedTo = (h: RegionHandle, e: ReactPointerEvent<HTMLDivElement>): BoxModel =>\n\t\tresizeRegion(boxRef.current!, h, editor.screenToPage({ x: e.clientX, y: e.clientY }))\n\tconst onResize = (h: RegionHandle) => (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (boxRef.current) onPreview(resizedTo(h, e))\n\t}\n\tconst endResize = (h: RegionHandle) => (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (!boxRef.current) return\n\t\tconst bounds = resizedTo(h, e)\n\t\tboxRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId))\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\tonCommit(bounds)\n\t}\n\treturn (\n\t\t<>\n\t\t\t{points.map((h) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={h.key}\n\t\t\t\t\tclassName=\"cmt-canvas-region-handle\"\n\t\t\t\t\tstyle={{ left: h.left, top: h.top, cursor: h.cursor }}\n\t\t\t\t\tonPointerDown={startResize}\n\t\t\t\t\tonPointerMove={onResize(h)}\n\t\t\t\t\tonPointerUp={endResize(h)}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t)\n}\n\nconst ThreadPin = memo(function ThreadPin({\n\teditor,\n\tthread,\n\tregionOptions,\n\t...props\n}: Omit<CanvasCommentsProps, 'regionOptions'> & {\n\teditor: Editor\n\tthread: TLCommentThread\n\tregionOptions: RegionCommentOptions\n}) {\n\tconst {\n\t\tcurrentUserId,\n\t\tresolveName,\n\t\tonPostComment,\n\t\tisCommentUnread,\n\t\tonCommentRead,\n\t\tgetMentionSuggestions,\n\t\trenderMentionSuggestion,\n\t} = props\n\tconst options = useCommentingOptions()\n\tconst impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor\n\tconst container = useContainer()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst msg = useTranslation()\n\t// Only one thread's popover is open at a time \u2014 shared across pins via the atom.\n\tconst open = useValue('thread open', () => openThreadId.get(editor) === thread.id, [\n\t\teditor,\n\t\tthread.id,\n\t])\n\tconst [reply, setReply] = useState<TLRichText>(EMPTY_COMMENT)\n\tconst [editingId, setEditingId] = useState<string | null>(null)\n\tconst [editText, setEditText] = useState<TLRichText>(EMPTY_COMMENT)\n\t// While dragging the marker, its page point overrides the anchor's; committed on drop.\n\tconst [dragPagePoint, setDragPagePoint] = useState<{ x: number; y: number } | null>(null)\n\t// The live bounds while a corner handle is resizing the region, else null.\n\tconst [resizeBounds, setResizeBounds] = useState<BoxModel | null>(null)\n\t// Whether the pin marker is hovered \u2014 only consulted by the 'pin-hover' reveal mode.\n\tconst [pinHovered, setPinHovered] = useState(false)\n\t// The 'pointer' reveal mode: is the pointer within the region's bounds (plus a grab margin)?\n\t// Driven by pointer position, not DOM hover, so moving from anywhere in the region out to a corner\n\t// handle never loses the affordance \u2014 the box stays `pointer-events: none`.\n\tconst pointerInRegion = useValue(\n\t\t'pointer in region',\n\t\t() => {\n\t\t\tif (thread.anchor.type !== 'region' || thread.pageId !== editor.getCurrentPageId())\n\t\t\t\treturn false\n\t\t\tconst m = REGION_HANDLE_MARGIN_PX / editor.getZoomLevel()\n\t\t\tconst p = editor.inputs.getCurrentPagePoint()\n\t\t\tconst a = thread.anchor\n\t\t\treturn p.x >= a.x - m && p.x <= a.x + a.w + m && p.y >= a.y - m && p.y <= a.y + a.h + m\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId]\n\t)\n\t// A region's box and handles are revealed while open or mid-resize, plus \u2014 per the reveal mode \u2014\n\t// while the pointer is within the region ('pointer') or the pin is hovered ('pin-hover').\n\tconst revealed =\n\t\topen ||\n\t\tresizeBounds != null ||\n\t\t(regionOptions.reveal === 'pointer' && pointerInRegion) ||\n\t\t(regionOptions.reveal === 'pin-hover' && pinHovered)\n\t// The resize handles: side midpoints ('edges'), or the corners other than the pin's ('corners').\n\tconst resizeHandles = useMemo(\n\t\t() =>\n\t\t\tregionOptions.resize === 'edges'\n\t\t\t\t? REGION_EDGES\n\t\t\t\t: REGION_CORNERS.filter(\n\t\t\t\t\t\t(c) => c.x !== regionOptions.pinCorner.x || c.y !== regionOptions.pinCorner.y\n\t\t\t\t\t),\n\t\t[regionOptions.resize, regionOptions.pinCorner]\n\t)\n\tconst dragRef = useRef<{ startX: number; startY: number; moved: boolean } | null>(null)\n\tconst markerRef = useRef<HTMLDivElement>(null)\n\n\t// Clicking outside the open popover (and off its own pin) closes the thread \u2014 mirrors the\n\t// pending composer's dismiss. Capture phase + a class check rather than stopPropagation, since the\n\t// popover portals elsewhere in the DOM. The pin marker is excluded so its own click-to-toggle\n\t// handles it instead of this closing then the toggle reopening.\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!target) return\n\t\t\tif (target.closest('.cmt-canvas-popover')) return\n\t\t\tconst marker = markerRef.current\n\t\t\tif (marker && marker.contains(target)) return\n\t\t\t// A press on a region's resize handle or movable body edits this thread \u2014 don't dismiss it.\n\t\t\tif (target.closest('.cmt-canvas-region-handle, .cmt-canvas-region--movable')) return\n\t\t\t// A click inside a menu/popover layered above us (the sidebar's filter or overflow\n\t\t\t// dropdown, or the composer's mention picker \u2014 all portaled elsewhere) belongs to that\n\t\t\t// layer; defer to its own dismissal instead of closing the thread out from under it.\n\t\t\tif (target.closest('.tlui-menu, [data-radix-popper-content-wrapper], .cmt-mention-popup'))\n\t\t\t\treturn\n\t\t\topenThreadId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [open, editor])\n\n\tconst point = useValue(\n\t\t'pin point',\n\t\t() => {\n\t\t\tif (thread.pageId !== editor.getCurrentPageId()) return null\n\t\t\tconst pagePoint = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\t\t\treturn pagePoint ? editor.pageToViewport(pagePoint) : null\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId, impreciseShapeAnchor]\n\t)\n\tconst visible = point !== null\n\n\t// While the popover is open, every unread comment on display gets reported read \u2014 including\n\t// replies that arrive while it stays open, since the effect re-runs as `comments` changes.\n\t// The host's receipt write flips isCommentUnread to false, so re-runs find nothing to report.\n\tuseEffect(() => {\n\t\tif (!open || !visible || !isCommentUnread || !onCommentRead) return\n\t\tfor (const comment of comments) {\n\t\t\tif (isCommentUnread(comment.id)) {\n\t\t\t\tonCommentRead(comment.id)\n\t\t\t}\n\t\t}\n\t}, [open, visible, comments, isCommentUnread, onCommentRead])\n\n\tif (!point) return null\n\n\tconst postReply = () => {\n\t\tif (isCommentEmpty(reply) || !currentUserId) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId: thread.pageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: reply,\n\t\t\t})\n\t\t\tputCommentRecords(editor, [comment])\n\t\t\tif (onPostComment) onPostComment(comment)\n\t\t})\n\t\tsetReply(EMPTY_COMMENT)\n\t}\n\n\tconst toggleResolve = () => {\n\t\tif (!currentUserId) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tputCommentRecords(editor, [\n\t\t\t\t{\n\t\t\t\t\t...thread,\n\t\t\t\t\tresolved: thread.resolved ? null : { at: Date.now(), by: currentUserId },\n\t\t\t\t},\n\t\t\t])\n\t\t})\n\t}\n\n\tconst deleteThread = () => {\n\t\topenThreadId.set(editor, null)\n\t\tcommitCommentMutation(editor, () =>\n\t\t\tremoveCommentRecords(editor, [thread.id, ...comments.map((c) => c.id)])\n\t\t)\n\t}\n\n\tconst startEdit = (comment: TLComment) => {\n\t\tsetEditingId(comment.id)\n\t\tsetEditText(comment.body)\n\t}\n\n\tconst saveEdit = () => {\n\t\tconst comment = comments.find((c) => c.id === editingId)\n\t\tif (!comment || isCommentEmpty(editText)) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tputCommentRecords(editor, [{ ...comment, body: editText, editedAt: Date.now() }])\n\t\t})\n\t\tsetEditingId(null)\n\t}\n\n\t// Swap a comment for a pre-filled composer while it's being edited; otherwise show the card,\n\t// with an edit affordance on your own comments.\n\tconst renderComment = (card: CommentCardProps, index: number): ReactNode => {\n\t\tconst comment = comments[index]\n\t\tif (editingId === comment.id) {\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"cmt-editing\"\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === 'Escape') {\n\t\t\t\t\t\t\tsetEditingId(null)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={card.author}\n\t\t\t\t\t\tplaceholder={msg('comments.edit-placeholder')}\n\t\t\t\t\t\tvalue={editText}\n\t\t\t\t\t\tonChange={setEditText}\n\t\t\t\t\t\tonSubmit={saveEdit}\n\t\t\t\t\t\tsendLabel={msg('comments.save')}\n\t\t\t\t\t\tdisabled={isCommentEmpty(editText)}\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/>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t\treturn (\n\t\t\t<CommentCard\n\t\t\t\t{...card}\n\t\t\t\tactions={\n\t\t\t\t\tcomment.authorId === currentUserId ? (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"cmt-thread__action\"\n\t\t\t\t\t\t\ttitle={msg('comments.edit')}\n\t\t\t\t\t\t\tonClick={() => startEdit(comment)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<TldrawUiIcon icon=\"dots-horizontal\" label={msg('comments.edit')} small />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t) : undefined\n\t\t\t\t}\n\t\t\t/>\n\t\t)\n\t}\n\n\tconst headerActions = (\n\t\t<>\n\t\t\t{currentUserId && (\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"cmt-thread__action\"\n\t\t\t\t\ttitle={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\tonClick={toggleResolve}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon\n\t\t\t\t\t\ticon=\"check\"\n\t\t\t\t\t\tlabel={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\t\tsmall\n\t\t\t\t\t/>\n\t\t\t\t</button>\n\t\t\t)}\n\t\t\t{currentUserId && (\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"cmt-thread__action\"\n\t\t\t\t\ttitle={msg('comments.delete')}\n\t\t\t\t\tonClick={deleteThread}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon icon=\"trash\" label={msg('comments.delete')} small />\n\t\t\t\t</button>\n\t\t\t)}\n\t\t\t<button\n\t\t\t\tclassName=\"cmt-thread__action\"\n\t\t\t\ttitle={msg('comments.dismiss')}\n\t\t\t\tonClick={() => openThreadId.set(editor, null)}\n\t\t\t>\n\t\t\t\t<TldrawUiIcon icon=\"cross-2\" label={msg('comments.dismiss')} small />\n\t\t\t</button>\n\t\t</>\n\t)\n\n\tconst PinContent = options.components.PinContent\n\t// The `PinContent` component slot overrides the built-in author-initial default.\n\tconst pinContent = PinContent ? (\n\t\t<PinContent thread={thread} comments={comments} />\n\t) : (\n\t\tinitialOf(resolveName(thread.createdBy) ?? UNKNOWN_AUTHOR)\n\t)\n\n\t// Drag the marker to move the thread: its position is overridden locally while dragging, then\n\t// re-anchored on drop. A point/shape thread re-anchors to whatever it's dropped on (a shape, else\n\t// a point); a region thread translates, keeping its size. A pointer that barely moves is a click \u2014\n\t// toggle the popover.\n\t// Which affordances move a region, per the option: 'pin' \u2192 pin only, 'body' \u2192 body only, 'both'.\n\tconst isRegion = thread.anchor.type === 'region'\n\tconst pinMovable = regionOptions.move !== 'body'\n\tconst bodyMovable = regionOptions.move !== 'pin'\n\tconst startDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\te.stopPropagation()\n\t\tdragRef.current = { startX: e.clientX, startY: e.clientY, moved: false }\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst onDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tconst drag = dragRef.current\n\t\tif (!drag) return\n\t\t// A region that moves by its body ignores pin drags (the pin only toggles the thread).\n\t\tif (isRegion && !pinMovable) return\n\t\tif (!drag.moved && Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < 4) return\n\t\tdrag.moved = true\n\t\tsetDragPagePoint(editor.screenToPage({ x: e.clientX, y: e.clientY }))\n\t}\n\tconst endDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tconst drag = dragRef.current\n\t\tdragRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId)) {\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\t}\n\t\tif (!drag) return\n\t\tif (!drag.moved) {\n\t\t\topenThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id)\n\t\t\treturn\n\t\t}\n\t\tconst pagePoint = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\tsetDragPagePoint(null)\n\t\tlet anchor: TLCommentThread['anchor']\n\t\tif (thread.anchor.type === 'region') {\n\t\t\t// Translate so the pin (the region's pin corner) lands at the drop; size unchanged.\n\t\t\tanchor = {\n\t\t\t\t...thread.anchor,\n\t\t\t\tx: pagePoint.x - regionOptions.pinCorner.x * thread.anchor.w,\n\t\t\t\ty: pagePoint.y - regionOptions.pinCorner.y * thread.anchor.h,\n\t\t\t}\n\t\t} else {\n\t\t\tconst hit = editor.getShapeAtPoint(pagePoint, { hitInside: true })\n\t\t\tanchor = hit\n\t\t\t\t? shapeAnchorAt(editor, hit.id, pagePoint, e.altKey)\n\t\t\t\t: { type: 'point', x: pagePoint.x, y: pagePoint.y }\n\t\t}\n\t\tcommitCommentMutation(editor, () => putCommentRecords(editor, [{ ...thread, anchor }]), 'drag')\n\t}\n\n\t// The pin (and its popover) track the live edit: a resize moves it to the region's pin corner, a\n\t// move to the drag point; otherwise it sits at the stored anchor's viewport point.\n\tconst livePinPage = resizeBounds\n\t\t? regionPinPoint(resizeBounds, regionOptions.pinCorner)\n\t\t: dragPagePoint\n\tconst renderPoint = livePinPage ? editor.pageToViewport(livePinPage) : point\n\n\t// A region's live box bounds, by priority: a corner resize, else a pin-drag translation (the pin\n\t// corner tracks the cursor), else the stored anchor. Undefined for non-region threads.\n\tconst regionAnchor = thread.anchor.type === 'region' ? thread.anchor : undefined\n\tconst movedRegion =\n\t\tregionAnchor && dragPagePoint\n\t\t\t? {\n\t\t\t\t\t...regionAnchor,\n\t\t\t\t\tx: dragPagePoint.x - regionOptions.pinCorner.x * regionAnchor.w,\n\t\t\t\t\ty: dragPagePoint.y - regionOptions.pinCorner.y * regionAnchor.h,\n\t\t\t\t}\n\t\t\t: regionAnchor\n\tconst regionBoxBounds = resizeBounds ?? movedRegion\n\tconst commitResize = (bounds: BoxModel) => {\n\t\tsetResizeBounds(null)\n\t\teditor.run(\n\t\t\t() => putCommentRecords(editor, [{ ...thread, anchor: { type: 'region', ...bounds } }]),\n\t\t\t{\n\t\t\t\thistory: 'ignore',\n\t\t\t}\n\t\t)\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{regionBoxBounds && (dragPagePoint || revealed) && (\n\t\t\t\t<RegionBox\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tbox={regionBoxBounds}\n\t\t\t\t\tmovable={bodyMovable && !dragPagePoint}\n\t\t\t\t\tonPreview={setResizeBounds}\n\t\t\t\t\tonCommit={commitResize}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{regionBoxBounds && revealed && !dragPagePoint && regionOptions.resize !== 'none' && (\n\t\t\t\t<RegionResizeHandles\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tbox={regionBoxBounds}\n\t\t\t\t\thandles={resizeHandles}\n\t\t\t\t\tonPreview={setResizeBounds}\n\t\t\t\t\tonCommit={commitResize}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={open ? 'cmt-canvas-pin cmt-canvas-pin--open' : 'cmt-canvas-pin'}\n\t\t\t\tstyle={{ left: renderPoint.x, top: renderPoint.y }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tref={markerRef}\n\t\t\t\t\tclassName=\"cmt-canvas-pin__marker\"\n\t\t\t\t\tonPointerDown={startDrag}\n\t\t\t\t\tonPointerMove={onDrag}\n\t\t\t\t\tonPointerUp={endDrag}\n\t\t\t\t\tonPointerEnter={() => setPinHovered(true)}\n\t\t\t\t\tonPointerLeave={() => setPinHovered(false)}\n\t\t\t\t>\n\t\t\t\t\t<CommentPin resolved={thread.resolved != null} open={open}>\n\t\t\t\t\t\t{pinContent}\n\t\t\t\t\t</CommentPin>\n\t\t\t\t</div>\n\t\t\t\t{/* The popover portals up to the menus layer (above the UI panels) so it isn't clipped;\n\t\t\t the pin itself stays in the canvas-in-front layer, beneath the UI. */}\n\t\t\t\t{open && (\n\t\t\t\t\t<ThreadPopover\n\t\t\t\t\t\tcontainer={container}\n\t\t\t\t\t\tstyle={{ left: renderPoint.x + 36, top: renderPoint.y - 28 }}\n\t\t\t\t\t>\n\t\t\t\t\t\t<CommentThread\n\t\t\t\t\t\t\theader={msg('comments.thread-title')}\n\t\t\t\t\t\t\theaderActions={headerActions}\n\t\t\t\t\t\t\trenderComment={renderComment}\n\t\t\t\t\t\t\tcomments={comments.map((c) => toCardProps(c, props, options.components))}\n\t\t\t\t\t\t\tresolvedBanner={\n\t\t\t\t\t\t\t\tthread.resolved\n\t\t\t\t\t\t\t\t\t? msg('comments.resolved-by').replace(\n\t\t\t\t\t\t\t\t\t\t\t'{name}',\n\t\t\t\t\t\t\t\t\t\t\tresolveName(thread.resolved.by) ?? UNKNOWN_AUTHOR\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcomposer={\n\t\t\t\t\t\t\t\tcurrentUserId && !thread.resolved\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tauthor: resolveName(currentUserId) ?? UNKNOWN_AUTHOR,\n\t\t\t\t\t\t\t\t\t\t\tplaceholder: msg('comments.reply-placeholder'),\n\t\t\t\t\t\t\t\t\t\t\tsendLabel: msg('comments.send'),\n\t\t\t\t\t\t\t\t\t\t\tvalue: reply,\n\t\t\t\t\t\t\t\t\t\t\tonChange: setReply,\n\t\t\t\t\t\t\t\t\t\t\tonSubmit: postReply,\n\t\t\t\t\t\t\t\t\t\t\tdisabled: isCommentEmpty(reply),\n\t\t\t\t\t\t\t\t\t\t\tgetMentionSuggestions,\n\t\t\t\t\t\t\t\t\t\t\trenderMentionSuggestion,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</ThreadPopover>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n})\n\nfunction PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveName,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CanvasCommentsProps & { editor: Editor; pending: PendingComment }) {\n\tconst [text, setText] = useState<TLRichText>(EMPTY_COMMENT)\n\tconst ref = useRef<HTMLDivElement>(null)\n\tconst msg = useTranslation()\n\tconst container = useContainer()\n\t// Over this floating panel, scroll and hover reach the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\tusePassThroughMouseOverEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\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('.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\tcommitCommentMutation(editor, () => {\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\tputCommentRecords(editor, [thread, comment])\n\t\t\tif (onPostComment) onPostComment(comment)\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tpendingComment.set(editor, null)\n\t}\n\n\treturn createPortal(\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName=\"cmt-canvas-composer\"\n\t\t\tstyle={{ left: point.x, top: point.y }}\n\t\t\tonPointerDown={stop}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t}}\n\t\t>\n\t\t\t<CommentComposer\n\t\t\t\tauthor={currentUserId ? (resolveName(currentUserId) ?? UNKNOWN_AUTHOR) : ''}\n\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\tvalue={text}\n\t\t\t\tonChange={setText}\n\t\t\t\tonSubmit={submit}\n\t\t\t\tdisabled={isCommentEmpty(text)}\n\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\tautoFocus\n\t\t\t\tleading={draftAvatar}\n\t\t\t/>\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import {\n\tmemo,\n\ttype CSSProperties,\n\ttype PointerEvent as ReactPointerEvent,\n\tReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n\ttype BoxModel,\n\tcreateComment,\n\tcreateCommentThread,\n\tEditor,\n\tgetFirstCharacter,\n\treact,\n\tTLComment,\n\tTLCommentId,\n\tTLCommentThread,\n\tTLRichText,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuGroup,\n\tTldrawUiDropdownMenuItem,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\tTldrawUiIcon,\n\tuseContainer,\n\tuseEditor,\n\tusePassThroughMouseOverEvents,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n\tVecLike,\n} from 'tldraw'\nimport { computeClusterTable } from '../clustering/computeClusterTable'\nimport { type ClusterRuntime, createClusterRuntime } from '../clustering/runtime'\nimport type { ClusterNode, ClusterTable, MergeEvent } from '../clustering/types'\nimport { CommentAuthor } from '../ui/comment-author'\nimport { CommentCard, CommentCardProps } from '../ui/comment-card'\nimport { CommentComposer } from '../ui/comment-composer'\nimport { EMPTY_COMMENT, isCommentEmpty } from '../ui/comment-extensions'\nimport { CommentPin } from '../ui/comment-pin'\nimport { CommentThread } from '../ui/comment-thread'\nimport { CountBadge } from '../ui/count-badge'\nimport { MentionMember } from '../ui/mention-list'\nimport { isMentionPickerOpen } from '../ui/mention-suggestion'\nimport { TooltipButton } from '../ui/tooltip-button'\nimport { collectClusterLeaves } from './cluster-input'\nimport { CommentBody } from './comment-body'\nimport {\n\tclearCommentDraft,\n\tgetCommentDraft,\n\tNEW_COMMENT_DRAFT,\n\treplyDraftSlot,\n\tsaveCommentDraft,\n} from './comment-drafts'\nimport { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { getCommentRecord, putCommentRecords, removeCommentRecords } from './comment-store'\nimport { PendingComment } from './comment-tool'\nimport { useCommentThreads, useThreadComments } from './hooks'\nimport { useCommentingEnabled } from './license'\nimport {\n\ttype CommentingComponents,\n\ttype CommentingOptions,\n\tgetCommentingOptions,\n\tuseCommentingOptions,\n} from './options'\nimport {\n\tDEFAULT_REGION_COMMENT_OPTIONS,\n\tRegionCommentOptions,\n\tsetRegionCommentOptions,\n} from './region-options'\nimport {\n\tcommentsHidden,\n\tcommitCommentMutation,\n\topenThreadId,\n\tpendingComment,\n\tregionDraft,\n\ttoggleCommentsHidden,\n\tusePendingComment,\n} from './state'\nimport {\n\tanchorPagePoint,\n\tregionAnchorPinCorner,\n\tregionPinPoint,\n\tshapeAnchorAt,\n} from './thread-state'\n\n/**\n * A ready-to-use comments layer for a tldraw canvas: pins each thread at its anchor, opens a\n * thread popover (with a reply composer) on click, and shows a composer where the comment tool\n * placed a new thread. Reads/writes comment records straight from `editor.store`.\n *\n * It's meant as the batteries-included default \u2014 every visible piece is a lever (the `CommentBody`\n * and `PinContent` slots on `CommentTool.configure({ components })`), and the pieces it composes\n * (`CommentPin`, `CommentThread`, `CommentComposer`, the hooks, the tool) are all exported, so a\n * consumer can rebuild this from parts instead.\n * @public\n */\nexport interface CanvasCommentsProps {\n\t/** The signed-in user's id, or null for a read-only viewer. Only a signed-in user composes. */\n\tcurrentUserId: string | null\n\t/** Map an author id to their display info, or `undefined` when the id can't be resolved. */\n\tresolveAuthor(id: string): CommentAuthor | undefined\n\t/** Called after any comment (a new thread's first comment, or a reply) is posted. */\n\tonPostComment?(comment: TLComment): void\n\t/** Whether a comment is unread for the current user (return true for unread). */\n\tisCommentUnread?(commentId: TLCommentId): boolean\n\t/**\n\t * Called for each unread comment shown to the user in an open thread popover, so hosts can\n\t * record a read receipt. Needs `isCommentUnread` to know what's unread.\n\t */\n\tonCommentRead?(commentId: TLCommentId): void\n\t/** Resolve the members matching an `@`-query in the composers (sync or async). */\n\tgetMentionSuggestions?(query: string): MentionMember[] | Promise<MentionMember[]>\n\t/** Override a mention-picker row's content. */\n\trenderMentionSuggestion?(member: MentionMember): ReactNode\n\t/** Where imprecise shape pins sit \u2014 a normalized (0\u20131) spot within the shape. Default top-right. */\n\timpreciseShapeAnchor?: { x: number; y: number }\n\t/** Region comment behaviour. Region is off by default \u2014 omit this for click-only point/shape\n\t * comments. Anything unset falls back to {@link DEFAULT_REGION_COMMENT_OPTIONS}. */\n\tregionOptions?: Partial<RegionCommentOptions>\n}\n\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation()\n\n/** How far an imprecise shape pin steps inside the shape from its anchor spot, in screen px \u2014\n * most of the marker sits within the shape, with a small overhang past the corner. */\nconst IMPRECISE_PIN_INSET_PX = 20\n\n/** Imprecise shape pins tuck inside the shape rather than hanging off its edge: the marker\n * extends up-right of its anchor point, so step it toward the shape's centre. Screen px \u2014 the\n * pin is screen-fixed while the shape scales with zoom. Null for anchors that need no inset. */\nfunction impreciseShapePinInset(\n\tanchor: TLCommentThread['anchor'],\n\tspot: { x: number; y: number }\n): { x: number; y: number } | null {\n\tif (anchor.type !== 'shape' || anchor.isPrecise) return null\n\treturn {\n\t\tx: Math.sign(0.5 - spot.x) * IMPRECISE_PIN_INSET_PX,\n\t\ty: Math.sign(0.5 - spot.y) * IMPRECISE_PIN_INSET_PX,\n\t}\n}\n\n/** A pointer-down that belongs to the camera, not the comment UI: any non-primary button\n * (middle/right-button pans), or a primary press with the spacebar pan key held. */\nconst isCanvasPanGesture = (editor: Editor, e: ReactPointerEvent) =>\n\te.button !== 0 || editor.inputs.keys.has('Space')\n\n/** Hand a pointer event to the canvas beneath the comments layer, marked the same way the\n * pass-through wheel/hover hooks mark their re-dispatched events. */\nfunction forwardPointerEventToCanvas(container: HTMLElement, e: ReactPointerEvent) {\n\tconst cvs = container.querySelector('.tl-canvas')\n\tif (!cvs) return\n\tconst newEvent = new PointerEvent(e.type, e.nativeEvent as any)\n\t;(newEvent as any).isSpecialRedispatchedEvent = true\n\tcvs.dispatchEvent(newEvent)\n}\n\nconst initialOf = (name: string): string => (getFirstCharacter(name.trim()) || '?').toUpperCase()\nconst CLUSTER_FADE_MS = 150\n/** Duration of the click-a-badge zoom-to-split animation. */\nconst CLUSTER_EXPAND_ZOOM_MS = 450\n\n/** The leading element for the placement composer \u2014 the comment pin's shape, but a pencil\n * instead of an initial, marking an unsent draft. */\nconst draftAvatar = (color?: string) => (\n\t<CommentPin color={color}>\n\t\t<svg\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\twidth=\"15\"\n\t\t\theight=\"15\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t<path d=\"M12 20h9\" />\n\t\t\t<path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\" />\n\t\t</svg>\n\t</CommentPin>\n)\n\nfunction toCardProps(\n\tcomment: TLComment,\n\tprops: CanvasCommentsProps,\n\tcomponents: CommentingComponents,\n\tresolveName: (id: string) => string | undefined\n): CommentCardProps {\n\tconst Body = components.CommentBody\n\t// The `CommentBody` component slot overrides the built-in rich-text default (which resolves\n\t// mention ids to names).\n\tconst body = Body ? (\n\t\t<Body comment={comment} />\n\t) : (\n\t\t<CommentBody richText={comment.body} resolveName={resolveName} />\n\t)\n\treturn {\n\t\tauthor: props.resolveAuthor(comment.authorId) ?? UNKNOWN_COMMENT_AUTHOR,\n\t\tbody,\n\t\tdate: new Date(comment.createdAt).toISOString(),\n\t\tyou: comment.authorId === props.currentUserId,\n\t\tedited: comment.editedAt != null,\n\t}\n}\n\n/** @public @react */\nexport function CanvasComments(props: CanvasCommentsProps) {\n\t// Gate the whole layer on the license before doing any work. The inner component holds all the\n\t// other hooks, so mounting/unmounting it as the license resolves keeps hook order stable here.\n\tconst commentingEnabled = useCommentingEnabled()\n\tif (!commentingEnabled) return null\n\treturn <CanvasCommentsLayer {...props} />\n}\n\nfunction CanvasCommentsLayer(props: CanvasCommentsProps) {\n\tconst editor = useEditor()\n\tconst options = useCommentingOptions()\n\tconst container = useContainer()\n\t// Merge the consumer's region options over the disabled defaults and publish them for this editor,\n\t// so the comment tool (which has no props) reads the same per-instance config.\n\tconst regionOptions = useMemo(\n\t\t() => ({ ...DEFAULT_REGION_COMMENT_OPTIONS, ...props.regionOptions }),\n\t\t[props.regionOptions]\n\t)\n\tuseEffect(() => setRegionCommentOptions(editor, regionOptions), [editor, regionOptions])\n\tconst layerRef = useRef<HTMLDivElement>(null)\n\t// Over the pins and cluster badges, hover passes through to the canvas beneath (these events\n\t// bubble up from the pointer-interactive markers to this layer root). Wheel pass-through is\n\t// NOT on this root: it lives on each interactive element instead. The root spans the whole\n\t// canvas, so any pin past its bottom/right edge inflates the root's scrollHeight \u2014 which the\n\t// wheel hook's is-this-scrollable guard reads as scrollable, silently disabling pass-through.\n\tusePassThroughMouseOverEvents(layerRef)\n\tconst deepLinkHandled = useRef(false)\n\tconst threads = useCommentThreads(editor)\n\tconst pending = usePendingComment()\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor\n\t// Threads held out of clustering because their anchor moved while folded inside a badge\n\t// (drag, nudge, align, undo, a collaborator \u2014 detected by position, not gesture). They render\n\t// as live pins riding their anchor and rejoin clustering on the next zoom-out.\n\tconst [heldThreadIds, setHeldThreadIds] = useState<ReadonlySet<string>>(EMPTY_SET)\n\tconst adoptOnRebuild = useRef(false)\n\tconst clusterLeaves = useValue(\n\t\t'comment cluster leaves',\n\t\t() =>\n\t\t\tcollectClusterLeaves(\n\t\t\t\teditor,\n\t\t\t\tthreads.filter((thread) => !heldThreadIds.has(thread.id)),\n\t\t\t\topenThreadId.get(editor),\n\t\t\t\timpreciseShapeAnchor\n\t\t\t),\n\t\t[editor, threads, impreciseShapeAnchor, heldThreadIds]\n\t)\n\tconst clusterZoomBounds = useValue(\n\t\t'comment cluster zoom bounds',\n\t\t() => getClusterZoomBounds(editor),\n\t\t[editor]\n\t)\n\tconst latestModel = useMemo(() => {\n\t\tconst table = computeClusterTable(clusterLeaves, clusterZoomBounds)\n\t\tconst runtime = createClusterRuntime(table)\n\t\truntime.seed(editor.getZoomLevel())\n\t\treturn { runtime, table }\n\t}, [clusterLeaves, clusterZoomBounds, editor])\n\t// The core invariant: the only thing that re-flows clustering doc-wide is zoom. Every rebuild\n\t// (add / move / delete / open / pop-out) is computed immediately as `latestModel` \u2014 the MST\n\t// stays correct \u2014 but the on-screen partition is `renderedModel`, and it only ever changes via\n\t// (a) the cursor walking on zoom, (b) adoption of the pending rebuild on zoom-out, or\n\t// (c) LOCAL detach patches: a leaf that left the input (deleted, opened, popped out) is\n\t// detached from its own badge in place \u2014 count and centroid update for that badge alone,\n\t// and nothing else on the canvas moves.\n\tconst [renderedModel, setRenderedModel] = useState(latestModel)\n\tlet clusterModel = renderedModel\n\t// A page switch replaces the whole scene: hard-reset rather than detach the world.\n\tconst pageId = useValue('comment cluster page', () => editor.getCurrentPageId(), [editor])\n\tconst pageRef = useRef(pageId)\n\tif (pageRef.current !== pageId) {\n\t\tpageRef.current = pageId\n\t\tadoptOnRebuild.current = false\n\t\tlatestModel.runtime.seed(editor.getZoomLevel())\n\t\tif (heldThreadIds.size > 0) setHeldThreadIds(EMPTY_SET)\n\t\tsetRenderedModel(latestModel)\n\t\tclusterModel = latestModel\n\t}\n\t// adoptOnRebuild is set by the rejoin reaction below, outside React's render cycle, paired\n\t// with clearing heldThreadIds. Only trust it once that pairing is actually visible here\n\t// (heldThreadIds confirmed empty) \u2014 an unrelated re-render can land in the gap between the\n\t// ref being set and the state update it was paired with being applied.\n\tconst rejoinPending = heldThreadIds.size === 0 && adoptOnRebuild.current\n\tif (renderedModel !== latestModel && rejoinPending) {\n\t\tadoptOnRebuild.current = false\n\t\t// Carryover seed: band events inherit the outgoing partition's merged/unmerged state, so\n\t\t// nothing changes state because of the swap alone. Idempotent, so safe during render.\n\t\tlatestModel.runtime.seedFrom(editor.getZoomLevel(), renderedModel.runtime.getVisible())\n\t\tsetRenderedModel(latestModel)\n\t\tclusterModel = latestModel\n\t} else if (heldThreadIds.size === 0 && renderedModel === latestModel) {\n\t\t// Nothing pending and nothing to adopt: clear any leftover force-adopt intent so it can't\n\t\t// survive to force-adopt a later, unrelated rebuild.\n\t\tadoptOnRebuild.current = false\n\t}\n\t// Pop-out detection: a leaf folded inside a badge can't follow its anchor (the badge position\n\t// is baked into the model), so when its live position drifts from the baked one, hold it out.\n\t// It renders as a live pin riding the anchor; the detach loop below shrinks its badge locally.\n\tconst newlyMovedIds = findMovedClusteredLeafIds(clusterModel, latestModel)\n\tif (newlyMovedIds.length > 0) {\n\t\tconst next = new Set(heldThreadIds)\n\t\tfor (const id of newlyMovedIds) next.add(id)\n\t\tsetHeldThreadIds(next)\n\t}\n\t// Local partition maintenance \u2014 the only non-zoom visual change, and it is local by\n\t// construction: any displayed leaf that has left the cluster input (deleted, thread opened,\n\t// popped out above) is detached from its badge in place. The corrected rebuild is already\n\t// sitting in latestModel awaiting the next zoom-out.\n\t{\n\t\tconst latestLeafIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id))\n\t\tfor (const leaf of clusterModel.table.leaves) {\n\t\t\tif (!latestLeafIds.has(leaf.id)) {\n\t\t\t\tclusterModel.runtime.detachLeaf(leaf.id)\n\t\t\t}\n\t\t}\n\t}\n\t// Moved pins rejoin clustering on the next zoom-out motion: clear the set (so the rebuild\n\t// includes them again) and adopt that rebuild immediately instead of deferring it. Zooming in\n\t// never folds pins into clusters \u2014 merging is a zoom-out-only move, matching the runtime.\n\tuseEffect(() => {\n\t\tif (heldThreadIds.size === 0) return\n\t\tlet lastZoom = editor.getZoomLevel()\n\t\treturn react('rejoin moved comment pins on zoom out', () => {\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\tconst prevZoom = lastZoom\n\t\t\tlastZoom = zoom\n\t\t\tif (zoom >= prevZoom) return\n\t\t\tadoptOnRebuild.current = true\n\t\t\tsetHeldThreadIds(EMPTY_SET)\n\t\t})\n\t}, [heldThreadIds, editor])\n\t// Adopt a pending rebuild only on zoom-out motion: folding deferred additions into clusters is\n\t// a merge, and merging only happens while zooming out. While zooming in, the stale table still\n\t// splits correctly on its own (split thresholds are direction-safe by the hysteresis invariant).\n\tuseEffect(() => {\n\t\tif (clusterModel === latestModel) return\n\t\tlet lastZoom = editor.getZoomLevel()\n\t\treturn react('adopt pending cluster model on zoom out', () => {\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\tconst prevZoom = lastZoom\n\t\t\tlastZoom = zoom\n\t\t\tif (zoom >= prevZoom) return\n\t\t\tlatestModel.runtime.seedFrom(zoom, clusterModel.runtime.getVisible())\n\t\t\tsetRenderedModel(latestModel)\n\t\t})\n\t}, [clusterModel, latestModel, editor])\n\t// Threads in the current input that the displayed partition doesn't show anywhere (new\n\t// comments, reopened threads, undone deletions): render as plain pins until the next\n\t// zoom-out folds them in. Membership is judged against the *displayed* partition (with\n\t// detaches applied), not the rendered table, so a detached-then-restored leaf reappears.\n\tconst partitionVersion = clusterModel.runtime.version\n\tconst orphanThreads = useMemo(() => {\n\t\tif (clusterModel === latestModel) return []\n\t\tconst displayed = new Set<string>()\n\t\tfor (const node of clusterModel.runtime.getVisible().values()) {\n\t\t\tfor (const member of node.members) displayed.add(member)\n\t\t}\n\t\tconst latestIds = new Set(latestModel.table.leaves.map((leaf) => leaf.id))\n\t\treturn threads.filter((thread) => latestIds.has(thread.id) && !displayed.has(thread.id))\n\t\t// The runtime mutates its partition in place; partitionVersion is its change stamp.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [clusterModel, latestModel, threads, partitionVersion])\n\tconst heldThreads = useMemo(\n\t\t() => threads.filter((thread) => heldThreadIds.has(thread.id) && thread.id !== openId),\n\t\t[threads, heldThreadIds, openId]\n\t)\n\t// Subscribe to the runtime's partition version, not the raw zoom: onCamera runs on every zoom\n\t// tick (O(1) threshold checks) but the version only moves when the partition actually changes\n\t// \u2014 so this component only re-renders on cluster changes, not on every camera frame. The memo\n\t// below keys on a fresh inline read of the version rather than the subscribed value, because\n\t// render-time detaches (above) bump it after the subscription's computed already evaluated.\n\tuseValue(\n\t\t'comment cluster version',\n\t\t() => {\n\t\t\tclusterModel.runtime.onCamera(editor.getZoomLevel())\n\t\t\treturn clusterModel.runtime.version\n\t\t},\n\t\t[clusterModel, editor]\n\t)\n\tconst visibleNodes = useMemo(() => {\n\t\treturn Array.from(clusterModel.runtime.getVisible().values())\n\t\t// The runtime mutates its partition in place; partitionVersion is its change stamp.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [clusterModel, partitionVersion])\n\tconst fadeNodes = useFadeVisibleNodes(visibleNodes, clusterModel)\n\tconst threadsById = useMemo(\n\t\t() => new Map<string, TLCommentThread>(threads.map((thread) => [thread.id, thread])),\n\t\t[threads]\n\t)\n\tconst openThread = openId ? threadsById.get(openId) : null\n\tconst hidden = useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n\n\t// Reset the transient UI state (open thread, half-placed comment) when this unmounts.\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\topenThreadId.set(editor, null)\n\t\t\tpendingComment.set(editor, null)\n\t\t}\n\t}, [editor])\n\n\t// Open the thread named by a deep link (?comment=<thread or comment id>). If the thread is\n\t// currently inside a cluster, zoom to the first split that reveals it before opening.\n\tuseEffect(() => {\n\t\tif (deepLinkHandled.current) return\n\t\tconst id = new URLSearchParams(window.location.search).get('comment')\n\t\tif (!id) {\n\t\t\tdeepLinkHandled.current = true\n\t\t\treturn\n\t\t}\n\n\t\tconst record = getCommentRecord(editor, id)\n\t\tif (!record) return\n\n\t\tlet thread: TLCommentThread | undefined\n\t\tif (record.typeName === 'comment') {\n\t\t\tthread = threadsById.get(record.threadId)\n\t\t} else {\n\t\t\tthread = record\n\t\t}\n\t\tif (!thread) return\n\n\t\tdeepLinkHandled.current = true\n\t\trevealDeepLinkedThread(\n\t\t\teditor,\n\t\t\tthread,\n\t\t\tclusterModel.table,\n\t\t\tclusterZoomBounds,\n\t\t\toptions,\n\t\t\timpreciseShapeAnchor\n\t\t)\n\t\topenThreadId.set(editor, thread.id)\n\t}, [clusterModel.table, clusterZoomBounds, editor, threadsById, impreciseShapeAnchor, options])\n\n\t// Clicking a badge zooms to just past the zoom at which that cluster first unclusters,\n\t// centered on its centroid. The event that created a visible cluster is the event that splits\n\t// it, and (by the table's sort + monotone thresholds) it has the smallest zSplit of everything\n\t// applied inside it \u2014 so its zSplit is exactly the first split within those comments. The\n\t// animated zoom-in then drives the runtime cursor like any manual zoom, so the badge splits\n\t// (and can be drilled into further) with no extra bookkeeping.\n\tconst zoomToClusterSplit = useCallback(\n\t\t(node: ClusterNode) => {\n\t\t\tconst event = clusterModel.table.events.find((e) => e.result.id === node.id)\n\t\t\tif (!event || !Number.isFinite(event.zSplit)) return\n\t\t\tconst zoom = clamp(\n\t\t\t\tevent.zSplit * options.clusterSplitZoomFactor,\n\t\t\t\tclusterZoomBounds.minZoom,\n\t\t\t\tclusterZoomBounds.maxZoom\n\t\t\t)\n\t\t\tcenterOnPointAtZoom(editor, node.centroid, zoom, CLUSTER_EXPAND_ZOOM_MS)\n\t\t},\n\t\t[clusterModel, clusterZoomBounds, editor, options]\n\t)\n\n\t// Escape collapses the open thread. Capture-phase + stopPropagation so it runs ahead of the\n\t// editor (which would otherwise cancel the current tool or clear the selection). If a comment is\n\t// being edited, let its own Escape handler exit edit mode first, keeping the thread open.\n\tuseEffect(() => {\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || openThreadId.get(editor) === null) return\n\t\t\t// The mention picker owns Escape while it's open \u2014 let it dismiss the roster alone.\n\t\t\tif (isMentionPickerOpen()) return\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (target && target.closest('.tlui-cmt-editing')) return\n\t\t\topenThreadId.set(editor, null)\n\t\t\te.preventDefault()\n\t\t\te.stopPropagation()\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [editor])\n\n\t// Shift+C toggles comment-pin visibility on the canvas. Skipped while typing so it never fires\n\t// from inside a composer. Physical `KeyC` (layout-independent) with shift only.\n\tuseEffect(() => {\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.code !== 'KeyC' || !e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (target && target.closest('input, textarea, [contenteditable=\"true\"]')) return\n\t\t\ttoggleCommentsHidden(editor)\n\t\t\te.preventDefault()\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [editor])\n\n\t// Hidden: the whole canvas layer (pins, open popover, pending composer) is withheld. The signal\n\t// is read above so this component stays mounted and its shortcut/Escape effects keep running.\n\tif (hidden) return null\n\n\t// Render into the container (above the panels' stacking context) so the pins and popovers\n\t// live in the UI layer rather than being clipped by the canvas layer.\n\treturn createPortal(\n\t\t<div ref={layerRef} className=\"tlui-cmt-canvas-layer\">\n\t\t\t{options.enableClustering ? (\n\t\t\t\t<>\n\t\t\t\t\t{fadeNodes.map(({ node, phase }) => {\n\t\t\t\t\t\tlet content: ReactNode\n\t\t\t\t\t\tif (node.count === 1) {\n\t\t\t\t\t\t\tconst thread = threadsById.get(node.id)\n\t\t\t\t\t\t\tif (!thread) return null\n\t\t\t\t\t\t\tcontent = (\n\t\t\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontent = <ClusterBadge editor={editor} node={node} onExpand={zoomToClusterSplit} />\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={`cluster-fade:${node.id}`} className={clusterFadeClassName(phase)}>\n\t\t\t\t\t\t\t\t{content}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)\n\t\t\t\t\t})}\n\t\t\t\t\t{orphanThreads.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{heldThreads.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t// Clustering off: every thread renders as its own live pin (each returns null when it's\n\t\t\t\t// not on the current page or its anchor is missing). The open thread is excluded here and\n\t\t\t\t// rendered once below, mirroring how the clustering path keeps it out of the cluster leaves \u2014\n\t\t\t\t// otherwise it would mount a second, stacked pin.\n\t\t\t\tthreads\n\t\t\t\t\t.filter((thread) => thread.id !== openId)\n\t\t\t\t\t.map((thread) => (\n\t\t\t\t\t\t<ThreadPin\n\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\t\tthread={thread}\n\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))\n\t\t\t)}\n\t\t\t{openThread && (\n\t\t\t\t<ThreadPin\n\t\t\t\t\tkey={`open:${openThread.id}`}\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthread={openThread}\n\t\t\t\t\t{...props}\n\t\t\t\t\tregionOptions={regionOptions}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<RegionDraftBox editor={editor} />\n\t\t\t{/* Keep the region visible while composing \u2014 the drag draft is gone by now, and no thread\n\t\t\t exists yet, so the pending anchor is what shows the area under the open composer. */}\n\t\t\t{pending?.anchor.type === 'region' && <RegionBox editor={editor} box={pending.anchor} />}\n\t\t\t{pending && props.currentUserId && (\n\t\t\t\t<PendingComposer editor={editor} pending={pending} {...props} />\n\t\t\t)}\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\nconst MOVED_LEAF_EPSILON = 1e-6\ntype ClusterFadePhase = 'entering' | 'present' | 'exiting'\n\ninterface ClusterFadeNode {\n\tnode: ClusterNode\n\tphase: ClusterFadePhase\n}\n\nfunction useFadeVisibleNodes(\n\tnodes: readonly ClusterNode[],\n\tresetKey: { runtime: ClusterRuntime; table: ClusterTable }\n): ClusterFadeNode[] {\n\tconst resetKeyRef = useRef(resetKey)\n\tconst didReset = resetKeyRef.current !== resetKey\n\tif (didReset) {\n\t\tresetKeyRef.current = resetKey\n\t}\n\n\tconst [fadeNodes, setFadeNodes] = useState<ClusterFadeNode[]>(() => toPresentFadeNodes(nodes))\n\tconst renderedNodes = didReset ? toPresentFadeNodes(nodes) : fadeNodes\n\n\tuseEffect(() => {\n\t\tsetFadeNodes(toPresentFadeNodes(nodes))\n\t\t// Resets only on a new model (resetKey); node-list changes within the same model are\n\t\t// handled by the reconcile effect below, which fades entries in/out instead of snapping.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [resetKey])\n\n\tuseEffect(() => {\n\t\tif (didReset) return\n\t\tsetFadeNodes((previous) => reconcileFadeNodes(previous, nodes))\n\t}, [didReset, nodes])\n\n\tconst hasEntering = renderedNodes.some((item) => item.phase === 'entering')\n\tuseEffect(() => {\n\t\tif (!hasEntering) return\n\t\tconst frame = requestClusterFadeFrame(() => {\n\t\t\tsetFadeNodes((previous) =>\n\t\t\t\tprevious.map((item) => (item.phase === 'entering' ? { ...item, phase: 'present' } : item))\n\t\t\t)\n\t\t})\n\t\treturn () => cancelClusterFadeFrame(frame)\n\t}, [hasEntering, renderedNodes])\n\n\tconst hasExiting = renderedNodes.some((item) => item.phase === 'exiting')\n\tuseEffect(() => {\n\t\tif (!hasExiting) return\n\t\tconst timeout = window.setTimeout(() => {\n\t\t\tsetFadeNodes((previous) => previous.filter((item) => item.phase !== 'exiting'))\n\t\t}, CLUSTER_FADE_MS)\n\t\treturn () => window.clearTimeout(timeout)\n\t}, [hasExiting, renderedNodes])\n\n\treturn renderedNodes\n}\n\nfunction toPresentFadeNodes(nodes: readonly ClusterNode[]): ClusterFadeNode[] {\n\treturn nodes.map((node) => ({ node, phase: 'present' }))\n}\n\nfunction reconcileFadeNodes(\n\tprevious: readonly ClusterFadeNode[],\n\tnextNodes: readonly ClusterNode[]\n): ClusterFadeNode[] {\n\tconst previousById = new Map(previous.map((item) => [item.node.id, item]))\n\tconst nextIds = new Set(nextNodes.map((node) => node.id))\n\tconst next: ClusterFadeNode[] = []\n\n\tfor (const node of nextNodes) {\n\t\tconst previousItem = previousById.get(node.id)\n\t\tnext.push({\n\t\t\tnode,\n\t\t\tphase:\n\t\t\t\tpreviousItem && previousItem.phase !== 'exiting'\n\t\t\t\t\t? previousItem.phase\n\t\t\t\t\t: previousItem\n\t\t\t\t\t\t? 'present'\n\t\t\t\t\t\t: 'entering',\n\t\t})\n\t}\n\n\tfor (const item of previous) {\n\t\tif (nextIds.has(item.node.id)) continue\n\t\tnext.push(item.phase === 'exiting' ? item : { ...item, phase: 'exiting' })\n\t}\n\n\treturn next\n}\n\nfunction requestClusterFadeFrame(callback: FrameRequestCallback): number {\n\tif (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback)\n\treturn window.setTimeout(() => callback(0), 16)\n}\n\nfunction cancelClusterFadeFrame(frame: number) {\n\tif (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(frame)\n\telse window.clearTimeout(frame)\n}\n\nfunction clusterFadeClassName(phase: ClusterFadePhase): string {\n\treturn `tlui-cmt-cluster-fade tlui-cmt-cluster-fade--${phase}`\n}\n\n/**\n * Leaves folded inside a badge whose live anchor no longer matches the position the rendered\n * model was built with. Visible (unclustered) leaf pins track their anchor live, so they can\n * stay deferred; a badge can't follow a member, so these must pop out of clustering.\n */\nfunction findMovedClusteredLeafIds(\n\trendered: { runtime: ClusterRuntime; table: ClusterTable },\n\tlatest: { table: ClusterTable }\n): string[] {\n\tif (rendered.table === latest.table) return []\n\tconst visible = rendered.runtime.getVisible()\n\tconst latestById = new Map(latest.table.leaves.map((leaf) => [leaf.id, leaf]))\n\tconst moved: string[] = []\n\tfor (const leaf of rendered.table.leaves) {\n\t\tif (visible.has(leaf.id)) continue\n\t\tconst current = latestById.get(leaf.id)\n\t\tif (!current) continue\n\t\tif (\n\t\t\tMath.abs(current.centroid.x - leaf.centroid.x) > MOVED_LEAF_EPSILON ||\n\t\t\tMath.abs(current.centroid.y - leaf.centroid.y) > MOVED_LEAF_EPSILON\n\t\t) {\n\t\t\tmoved.push(leaf.id)\n\t\t}\n\t}\n\treturn moved\n}\n\nfunction getClusterZoomBounds(editor: Editor): { minZoom: number; maxZoom: number } {\n\tconst cameraOptions = editor.getCameraOptions()\n\tconst baseZoom = cameraOptions.constraints ? editor.getBaseZoom() : 1\n\tconst zoomSteps = cameraOptions.zoomSteps\n\treturn {\n\t\tminZoom: zoomSteps[0] * baseZoom,\n\t\tmaxZoom: zoomSteps[zoomSteps.length - 1] * baseZoom,\n\t}\n}\n\nfunction revealDeepLinkedThread(\n\teditor: Editor,\n\tthread: TLCommentThread,\n\ttable: ClusterTable,\n\tzoomBounds: { minZoom: number; maxZoom: number },\n\toptions: CommentingOptions,\n\timpreciseShapeAnchor: { x: number; y: number }\n) {\n\tif (thread.pageId !== editor.getCurrentPageId()) {\n\t\teditor.setCurrentPage(thread.pageId as any)\n\t}\n\n\t// Match where the rendered pin sits (resolved prop-or-option), so the camera centers on the pin.\n\tconst point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\tif (!point) return\n\n\t// With clustering off the pin always renders individually, so skip the zoom-to-split (its cluster\n\t// badge never exists) and just center on the pin.\n\tif (options.enableClustering) {\n\t\tconst parentEvent = findDirectParentEvent(table, thread.id)\n\t\tif (\n\t\t\tparentEvent &&\n\t\t\tNumber.isFinite(parentEvent.zSplit) &&\n\t\t\tparentEvent.zSplit <= zoomBounds.maxZoom\n\t\t) {\n\t\t\tconst zoom = clamp(\n\t\t\t\tparentEvent.zSplit * options.clusterSplitZoomFactor,\n\t\t\t\tzoomBounds.minZoom,\n\t\t\t\tzoomBounds.maxZoom\n\t\t\t)\n\t\t\tcenterOnPointAtZoom(editor, point, zoom)\n\t\t\treturn\n\t\t}\n\t}\n\n\teditor.centerOnPoint(point, { animation: { duration: 200 } })\n}\n\nfunction findDirectParentEvent(table: ClusterTable, threadId: string): MergeEvent | undefined {\n\treturn table.events.find((event) => event.children.some((child) => child.id === threadId))\n}\n\nfunction centerOnPointAtZoom(\n\teditor: Editor,\n\tpoint: { x: number; y: number },\n\tzoom: number,\n\tduration = 200\n) {\n\tconst viewport = editor.getViewportScreenBounds()\n\teditor.setCamera(\n\t\t{\n\t\t\tx: viewport.w / (2 * zoom) - point.x,\n\t\t\ty: viewport.h / (2 * zoom) - point.y,\n\t\t\tz: zoom,\n\t\t},\n\t\t{ animation: { duration } }\n\t)\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value))\n}\n\n// Memoized: cluster nodes and thread records are identity-stable while unchanged, so pins and\n// badges skip re-rendering when the parent re-renders for reasons that don't concern them\n// (leaf recomputes during shape drags, partition changes elsewhere). Camera tracking still\n// works \u2014 each component subscribes to its own viewport position via signals, not via props.\nconst ClusterBadge = memo(function ClusterBadge({\n\teditor,\n\tnode,\n\tonExpand,\n}: {\n\teditor: Editor\n\tnode: ClusterNode\n\tonExpand(node: ClusterNode): void\n}) {\n\tconst container = useContainer()\n\tconst badgeRef = useRef<HTMLDivElement>(null)\n\t// Wheel pass-through sits on the badge (never scrollable), not the layer root \u2014 see the\n\t// note on the layer.\n\tusePassThroughWheelEvents(badgeRef)\n\tconst point = useValue(\n\t\t'cluster badge point',\n\t\t() => {\n\t\t\tconst pagePoint = editor.pageToViewport(node.centroid)\n\t\t\tif (!isInInflatedViewport(editor, pagePoint)) return null\n\t\t\treturn pagePoint\n\t\t},\n\t\t[editor, node]\n\t)\n\n\tif (!point) return null\n\n\treturn (\n\t\t<div\n\t\t\tref={badgeRef}\n\t\t\tclassName=\"tlui-cmt-canvas-cluster\"\n\t\t\tstyle={{ left: point.x, top: point.y }}\n\t\t\tonPointerDown={(e) => {\n\t\t\t\tif (isCanvasPanGesture(editor, e)) {\n\t\t\t\t\tforwardPointerEventToCanvas(container, e)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\te.stopPropagation()\n\t\t\t}}\n\t\t\tonClick={(e) => {\n\t\t\t\te.stopPropagation()\n\t\t\t\tonExpand(node)\n\t\t\t}}\n\t\t>\n\t\t\t<CountBadge count={node.count} />\n\t\t</div>\n\t)\n})\n\nfunction isInInflatedViewport(editor: Editor, point: { x: number; y: number }): boolean {\n\tconst viewport = editor.getViewportScreenBounds()\n\tconst margin = getCommentingOptions(editor).clusterCullMargin\n\treturn (\n\t\tpoint.x >= -margin &&\n\t\tpoint.y >= -margin &&\n\t\tpoint.x <= viewport.w + margin &&\n\t\tpoint.y <= viewport.h + margin\n\t)\n}\n\n/** The open thread's popover, portaled above the UI panels. Over it, wheel and hover events pass\n * through to the canvas (unless the popover is scrolling its own content), like tldraw's panels. */\nfunction ThreadPopover({\n\tcontainer,\n\tstyle,\n\tchildren,\n}: {\n\tcontainer: HTMLElement\n\tstyle: CSSProperties\n\tchildren: ReactNode\n}) {\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\tusePassThroughMouseOverEvents(ref)\n\treturn createPortal(\n\t\t// contextmenu also stops here: portals bubble React events to the canvas's context-menu\n\t\t// trigger (the layer mounts inside it), which would open the canvas menu over this panel.\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName=\"tlui-cmt-canvas-popover\"\n\t\t\tstyle={style}\n\t\t\tonPointerDown={stop}\n\t\t\tonContextMenu={stop}\n\t\t>\n\t\t\t{children}\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n\n/** A dashed rectangle over a region anchor's bounds, in viewport space. Sits in the canvas layer as\n * a sibling of the pins. `pointer-events` stays off (canvas interaction passes through) unless\n * `movable`, in which case dragging the body translates the region \u2014 previews live, commits on drop. */\nfunction RegionBox({\n\teditor,\n\tbox,\n\tmovable,\n\tonPreview,\n\tonCommit,\n}: {\n\teditor: Editor\n\tbox: BoxModel\n\tmovable?: boolean\n\tonPreview?(bounds: BoxModel | null): void\n\tonCommit?(bounds: BoxModel): void\n}) {\n\tconst rect = useValue(\n\t\t'region rect',\n\t\t() => {\n\t\t\t// Position from the page\u2192viewport top-left; screen size scales with zoom, page size doesn't.\n\t\t\tconst topLeft = editor.pageToViewport({ x: box.x, y: box.y })\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\treturn { left: topLeft.x, top: topLeft.y, width: box.w * zoom, height: box.h * zoom }\n\t\t},\n\t\t[editor, box.x, box.y, box.w, box.h]\n\t)\n\t// The grab point and the box at grab time, captured so the drag translates by a stable delta even\n\t// as the box prop reflows under the live preview.\n\tconst grabRef = useRef<{ page: VecLike; box: BoxModel } | null>(null)\n\tconst container = useContainer()\n\tconst boxRef = useRef<HTMLDivElement>(null)\n\t// A movable region body takes pointer events over its whole area \u2014 wheel pass-through sits\n\t// here for the same reason as on the pin markers (see the note on the layer).\n\tusePassThroughWheelEvents(boxRef)\n\tconst translated = (e: ReactPointerEvent<HTMLDivElement>): BoxModel => {\n\t\tconst g = grabRef.current!\n\t\tconst p = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\treturn { ...g.box, x: g.box.x + (p.x - g.page.x), y: g.box.y + (p.y - g.page.y) }\n\t}\n\tconst startMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (isCanvasPanGesture(editor, e)) {\n\t\t\tforwardPointerEventToCanvas(container, e)\n\t\t\treturn\n\t\t}\n\t\te.stopPropagation()\n\t\tgrabRef.current = { page: editor.screenToPage({ x: e.clientX, y: e.clientY }), box }\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst onMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (grabRef.current) onPreview?.(translated(e))\n\t}\n\tconst endMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (!grabRef.current) return\n\t\tconst bounds = translated(e)\n\t\tgrabRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId))\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\tonCommit?.(bounds)\n\t}\n\treturn (\n\t\t<div\n\t\t\tref={boxRef}\n\t\t\tclassName={\n\t\t\t\tmovable\n\t\t\t\t\t? 'tlui-cmt-canvas-region tlui-cmt-canvas-region--movable'\n\t\t\t\t\t: 'tlui-cmt-canvas-region'\n\t\t\t}\n\t\t\tstyle={rect}\n\t\t\tonPointerDown={movable ? startMove : undefined}\n\t\t\tonPointerMove={movable ? onMove : undefined}\n\t\t\tonPointerUp={movable ? endMove : undefined}\n\t\t/>\n\t)\n}\n\n/** The live region being dragged out by the comment tool, or nothing when not dragging. */\nfunction RegionDraftBox({ editor }: { editor: Editor }) {\n\tconst box = useValue('region draft', () => regionDraft.get(editor), [editor])\n\tif (!box) return null\n\treturn <RegionBox editor={editor} box={box} />\n}\n\n// A resize handle's normalized 0\u20131 spot on the box, and its cursor. An axis at 0.5 (a side midpoint)\n// is *not* controlled by that handle: corners resize both axes, edges resize only their own.\ninterface RegionHandle {\n\tx: number\n\ty: number\n\tcursor: string\n}\n\n// The four corners (both axes) and the four side midpoints (one axis each).\nconst REGION_CORNERS: readonly RegionHandle[] = [\n\t{ x: 0, y: 0, cursor: 'nwse-resize' },\n\t{ x: 1, y: 0, cursor: 'nesw-resize' },\n\t{ x: 0, y: 1, cursor: 'nesw-resize' },\n\t{ x: 1, y: 1, cursor: 'nwse-resize' },\n]\nconst REGION_EDGES: readonly RegionHandle[] = [\n\t{ x: 0.5, y: 0, cursor: 'ns-resize' },\n\t{ x: 1, y: 0.5, cursor: 'ew-resize' },\n\t{ x: 0.5, y: 1, cursor: 'ns-resize' },\n\t{ x: 0, y: 0.5, cursor: 'ew-resize' },\n]\n\n// Screen-space slack around a region's bounds within which its box and handles stay revealed, so\n// the handles (which sit on the edge) are comfortably reachable.\nconst REGION_HANDLE_MARGIN_PX = 12\n\n/** Resize `box` by dragging `handle` to `cursor` (page coords). Each controlled axis spans from the\n * handle's fixed opposite edge to the cursor (normalized, so dragging past it flips); an axis the\n * handle doesn't control (a midpoint, at 0.5) keeps its original position and size. */\nfunction resizeRegion(box: BoxModel, handle: RegionHandle, cursor: VecLike): BoxModel {\n\tconst controlsX = handle.x !== 0.5\n\tconst controlsY = handle.y !== 0.5\n\tconst fixedX = box.x + (1 - handle.x) * box.w\n\tconst fixedY = box.y + (1 - handle.y) * box.h\n\treturn {\n\t\tx: controlsX ? Math.min(fixedX, cursor.x) : box.x,\n\t\ty: controlsY ? Math.min(fixedY, cursor.y) : box.y,\n\t\tw: controlsX ? Math.abs(cursor.x - fixedX) : box.w,\n\t\th: controlsY ? Math.abs(cursor.y - fixedY) : box.h,\n\t}\n}\n\n/** Draggable handles that resize a region \u2014 corners (both axes) or edges (one axis), per the resize\n * option. Previews live, commits on release. */\nfunction RegionResizeHandles({\n\teditor,\n\tbox,\n\thandles,\n\tonPreview,\n\tonCommit,\n}: {\n\teditor: Editor\n\tbox: BoxModel\n\thandles: readonly RegionHandle[]\n\tonPreview(bounds: BoxModel | null): void\n\tonCommit(bounds: BoxModel): void\n}) {\n\t// The box at pointer-down, captured so the box prop reflowing under the live preview doesn't move\n\t// the fixed edges mid-drag.\n\tconst boxRef = useRef<BoxModel | null>(null)\n\tconst points = useValue(\n\t\t'region handle points',\n\t\t() =>\n\t\t\thandles.map((h) => {\n\t\t\t\tconst p = editor.pageToViewport({ x: box.x + h.x * box.w, y: box.y + h.y * box.h })\n\t\t\t\treturn { ...h, key: `${h.x}-${h.y}`, left: p.x, top: p.y }\n\t\t\t}),\n\t\t[editor, box.x, box.y, box.w, box.h, handles]\n\t)\n\tconst startResize = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\te.stopPropagation()\n\t\tboxRef.current = box\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst resizedTo = (h: RegionHandle, e: ReactPointerEvent<HTMLDivElement>): BoxModel =>\n\t\tresizeRegion(boxRef.current!, h, editor.screenToPage({ x: e.clientX, y: e.clientY }))\n\tconst onResize = (h: RegionHandle) => (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (boxRef.current) onPreview(resizedTo(h, e))\n\t}\n\tconst endResize = (h: RegionHandle) => (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tif (!boxRef.current) return\n\t\tconst bounds = resizedTo(h, e)\n\t\tboxRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId))\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\tonCommit(bounds)\n\t}\n\treturn (\n\t\t<>\n\t\t\t{points.map((h) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={h.key}\n\t\t\t\t\tclassName=\"tlui-cmt-canvas-region-handle\"\n\t\t\t\t\tstyle={{ left: h.left, top: h.top, cursor: h.cursor }}\n\t\t\t\t\tonPointerDown={startResize}\n\t\t\t\t\tonPointerMove={onResize(h)}\n\t\t\t\t\tonPointerUp={endResize(h)}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t)\n}\n\nconst ThreadPin = memo(function ThreadPin({\n\teditor,\n\tthread,\n\tregionOptions,\n\t...props\n}: Omit<CanvasCommentsProps, 'regionOptions'> & {\n\teditor: Editor\n\tthread: TLCommentThread\n\tregionOptions: RegionCommentOptions\n}) {\n\tconst {\n\t\tcurrentUserId,\n\t\tresolveAuthor,\n\t\tonPostComment,\n\t\tisCommentUnread,\n\t\tonCommentRead,\n\t\tgetMentionSuggestions,\n\t\trenderMentionSuggestion,\n\t} = props\n\t// Name-only view of the resolver, for the mention/rich-text paths (stable identity so\n\t// CommentBody's memoized render doesn't recompute every render).\n\tconst resolveName = useCallback((id: string) => resolveAuthor(id)?.name, [resolveAuthor])\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\n\tconst options = useCommentingOptions()\n\tconst impreciseShapeAnchor = props.impreciseShapeAnchor ?? options.impreciseShapeAnchor\n\tconst container = useContainer()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst msg = useTranslation()\n\t// Only one thread's popover is open at a time \u2014 shared across pins via the atom.\n\tconst open = useValue('thread open', () => openThreadId.get(editor) === thread.id, [\n\t\teditor,\n\t\tthread.id,\n\t])\n\t// An unsent reply survives closing the thread (saved on every change, keyed by thread id) \u2014\n\t// the flip side of dismissing without a discard warning.\n\tconst [reply, setReply] = useState<TLRichText>(\n\t\t() => getCommentDraft(replyDraftSlot(thread.id)) ?? EMPTY_COMMENT\n\t)\n\tconst [editingId, setEditingId] = useState<string | null>(null)\n\tconst [editText, setEditText] = useState<TLRichText>(EMPTY_COMMENT)\n\t// While dragging the marker, its page point overrides the anchor's; committed on drop.\n\tconst [dragPagePoint, setDragPagePoint] = useState<{ x: number; y: number } | null>(null)\n\t// The live bounds while a corner handle is resizing the region, else null.\n\tconst [resizeBounds, setResizeBounds] = useState<BoxModel | null>(null)\n\t// Whether the pin marker is hovered \u2014 only consulted by the 'pin-hover' reveal mode.\n\tconst [pinHovered, setPinHovered] = useState(false)\n\t// The 'pointer' reveal mode: is the pointer within the region's bounds (plus a grab margin)?\n\t// Driven by pointer position, not DOM hover, so moving from anywhere in the region out to a corner\n\t// handle never loses the affordance \u2014 the box stays `pointer-events: none`.\n\tconst pointerInRegion = useValue(\n\t\t'pointer in region',\n\t\t() => {\n\t\t\tif (thread.anchor.type !== 'region' || thread.pageId !== editor.getCurrentPageId())\n\t\t\t\treturn false\n\t\t\tconst m = REGION_HANDLE_MARGIN_PX / editor.getZoomLevel()\n\t\t\tconst p = editor.inputs.getCurrentPagePoint()\n\t\t\tconst a = thread.anchor\n\t\t\treturn p.x >= a.x - m && p.x <= a.x + a.w + m && p.y >= a.y - m && p.y <= a.y + a.h + m\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId]\n\t)\n\t// A region's box and handles are revealed while open or mid-resize, plus \u2014 per the reveal mode \u2014\n\t// while the pointer is within the region ('pointer') or the pin is hovered ('pin-hover').\n\tconst revealed =\n\t\topen ||\n\t\tresizeBounds != null ||\n\t\t(regionOptions.reveal === 'pointer' && pointerInRegion) ||\n\t\t(regionOptions.reveal === 'pin-hover' && pinHovered)\n\t// A region thread's pin corner is its own (the corner its creating drag released on), with\n\t// the configured default as the fallback for older records.\n\tconst pinCorner =\n\t\tthread.anchor.type === 'region'\n\t\t\t? regionAnchorPinCorner(editor, thread.anchor)\n\t\t\t: regionOptions.pinCorner\n\t// The resize handles: side midpoints ('edges'), or the corners other than the pin's ('corners').\n\tconst resizeHandles = useMemo(\n\t\t() =>\n\t\t\tregionOptions.resize === 'edges'\n\t\t\t\t? REGION_EDGES\n\t\t\t\t: REGION_CORNERS.filter((c) => c.x !== pinCorner.x || c.y !== pinCorner.y),\n\t\t[regionOptions.resize, pinCorner]\n\t)\n\tconst dragRef = useRef<{\n\t\tstartX: number\n\t\tstartY: number\n\t\tmoved: boolean\n\t\t// The anchor's page-space offset from the grab point, so a drag translates the pin by the\n\t\t// cursor's delta (like RegionBox's move) instead of snapping the anchor to the cursor.\n\t\toffsetX: number\n\t\toffsetY: number\n\t} | null>(null)\n\tconst markerRef = useRef<HTMLDivElement>(null)\n\t// Wheel pass-through sits on the marker (which is never scrollable), not the layer root \u2014\n\t// see the note on the layer.\n\tusePassThroughWheelEvents(markerRef)\n\n\t// The drop-target hint is editor-global state with no automatic reset. If the pin unmounts\n\t// mid-drag (e.g. Shift+C hides comments), no pointer event will ever reach the drag handlers \u2014\n\t// clear the hint here or it stays on the shape indefinitely.\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (dragRef.current) editor.setHintingShapes([])\n\t\t}\n\t}, [editor])\n\n\t// Clicking outside the open popover (and off its own pin) closes the thread \u2014 mirrors the\n\t// pending composer's dismiss. Capture phase + a class check rather than stopPropagation, since the\n\t// popover portals elsewhere in the DOM. The pin marker is excluded so its own click-to-toggle\n\t// handles it instead of this closing then the toggle reopening.\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst onPointerDown = (e: PointerEvent) => {\n\t\t\tconst target = e.target as HTMLElement | null\n\t\t\tif (!target) return\n\t\t\tif (target.closest('.tlui-cmt-canvas-popover')) return\n\t\t\tconst marker = markerRef.current\n\t\t\tif (marker && marker.contains(target)) return\n\t\t\t// A press on a region's resize handle or movable body edits this thread \u2014 don't dismiss it.\n\t\t\tif (target.closest('.tlui-cmt-canvas-region-handle, .tlui-cmt-canvas-region--movable')) return\n\t\t\t// A click inside a menu/popover layered above us (the sidebar's filter or overflow\n\t\t\t// dropdown, or the composer's mention picker \u2014 all portaled elsewhere) belongs to that\n\t\t\t// layer; defer to its own dismissal instead of closing the thread out from under it.\n\t\t\tif (\n\t\t\t\ttarget.closest('.tlui-menu, [data-radix-popper-content-wrapper], .tlui-cmt-mention-popup')\n\t\t\t)\n\t\t\t\treturn\n\t\t\topenThreadId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('pointerdown', onPointerDown, true)\n\t\treturn () => document.removeEventListener('pointerdown', onPointerDown, true)\n\t}, [open, editor])\n\n\tconst point = useValue(\n\t\t'pin point',\n\t\t() => {\n\t\t\tif (thread.pageId !== editor.getCurrentPageId()) return null\n\t\t\tconst pagePoint = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\t\t\tif (!pagePoint) return null\n\t\t\tconst viewportPoint = editor.pageToViewport(pagePoint)\n\t\t\tconst inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor)\n\t\t\treturn inset ? { x: viewportPoint.x + inset.x, y: viewportPoint.y + inset.y } : viewportPoint\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId, impreciseShapeAnchor]\n\t)\n\tconst visible = point !== null\n\n\t// While the popover is open, every unread comment on display gets reported read \u2014 including\n\t// replies that arrive while it stays open, since the effect re-runs as `comments` changes.\n\t// The host's receipt write flips isCommentUnread to false, so re-runs find nothing to report.\n\tuseEffect(() => {\n\t\tif (!open || !visible || !isCommentUnread || !onCommentRead) return\n\t\tfor (const comment of comments) {\n\t\t\tif (isCommentUnread(comment.id)) {\n\t\t\t\tonCommentRead(comment.id)\n\t\t\t}\n\t\t}\n\t}, [open, visible, comments, isCommentUnread, onCommentRead])\n\n\tif (!point) return null\n\n\tconst postReply = () => {\n\t\tif (isCommentEmpty(reply) || !currentUserId) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tconst comment = createComment({\n\t\t\t\tthreadId: thread.id,\n\t\t\t\tpageId: thread.pageId,\n\t\t\t\tauthorId: currentUserId,\n\t\t\t\tbody: reply,\n\t\t\t})\n\t\t\tputCommentRecords(editor, [comment])\n\t\t\tif (onPostComment) onPostComment(comment)\n\t\t})\n\t\tsetReply(EMPTY_COMMENT)\n\t\tclearCommentDraft(replyDraftSlot(thread.id))\n\t}\n\n\tconst toggleResolve = () => {\n\t\tif (!currentUserId) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tputCommentRecords(editor, [\n\t\t\t\t{\n\t\t\t\t\t...thread,\n\t\t\t\t\tresolved: thread.resolved ? null : { at: Date.now(), by: currentUserId },\n\t\t\t\t},\n\t\t\t])\n\t\t})\n\t}\n\n\tconst deleteThread = () => {\n\t\topenThreadId.set(editor, null)\n\t\tcommitCommentMutation(editor, () =>\n\t\t\tremoveCommentRecords(editor, [thread.id, ...comments.map((c) => c.id)])\n\t\t)\n\t}\n\n\tconst startEdit = (comment: TLComment) => {\n\t\tsetEditingId(comment.id)\n\t\tsetEditText(comment.body)\n\t}\n\n\tconst deleteComment = (comment: TLComment) => {\n\t\tcommitCommentMutation(editor, () => {\n\t\t\t// Deleting a thread's only comment deletes the thread \u2014 an empty thread has no surface.\n\t\t\tif (comments.length === 1) {\n\t\t\t\topenThreadId.set(editor, null)\n\t\t\t\tremoveCommentRecords(editor, [thread.id, comment.id])\n\t\t\t} else {\n\t\t\t\tremoveCommentRecords(editor, [comment.id])\n\t\t\t}\n\t\t})\n\t}\n\n\tconst saveEdit = () => {\n\t\tconst comment = comments.find((c) => c.id === editingId)\n\t\tif (!comment || isCommentEmpty(editText)) return\n\t\tcommitCommentMutation(editor, () => {\n\t\t\tputCommentRecords(editor, [{ ...comment, body: editText, editedAt: Date.now() }])\n\t\t})\n\t\tsetEditingId(null)\n\t}\n\n\t// Swap a comment for a pre-filled composer while it's being edited; otherwise show the card,\n\t// with an edit affordance on your own comments.\n\tconst renderComment = (card: CommentCardProps, index: number): ReactNode => {\n\t\tconst comment = comments[index]\n\t\tif (editingId === comment.id) {\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"tlui-cmt-editing\"\n\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\tif (e.key === 'Escape') {\n\t\t\t\t\t\t\tsetEditingId(null)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<CommentComposer\n\t\t\t\t\t\tauthor={card.author}\n\t\t\t\t\t\tplaceholder={msg('comments.edit-placeholder')}\n\t\t\t\t\t\tvalue={editText}\n\t\t\t\t\t\tonChange={setEditText}\n\t\t\t\t\t\tonSubmit={saveEdit}\n\t\t\t\t\t\tsendLabel={msg('comments.save')}\n\t\t\t\t\t\tdisabled={isCommentEmpty(editText)}\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/>\n\t\t\t\t</div>\n\t\t\t)\n\t\t}\n\t\treturn (\n\t\t\t<CommentCard\n\t\t\t\t{...card}\n\t\t\t\tactions={\n\t\t\t\t\tcomment.authorId === currentUserId ? (\n\t\t\t\t\t\t<TldrawUiDropdownMenuRoot id={`comment-actions-${comment.id}`}>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t\t<TooltipButton\n\t\t\t\t\t\t\t\t\ttooltip={msg('comments.more-options')}\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<TldrawUiIcon icon=\"dots-vertical\" label={msg('comments.more-options')} small />\n\t\t\t\t\t\t\t\t</TooltipButton>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\t\t\t\t\tside=\"bottom\"\n\t\t\t\t\t\t\t\talign=\"end\"\n\t\t\t\t\t\t\t\talignOffset={0}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => startEdit(comment)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span>{msg('comments.edit-comment')}</span>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item tlui-cmt-menu-item--danger\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => deleteComment(comment)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span>{msg('comments.delete-comment')}</span>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t</TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuContent>\n\t\t\t\t\t\t</TldrawUiDropdownMenuRoot>\n\t\t\t\t\t) : undefined\n\t\t\t\t}\n\t\t\t/>\n\t\t)\n\t}\n\n\tconst headerActions = (\n\t\t<>\n\t\t\t{currentUserId && (\n\t\t\t\t<TooltipButton\n\t\t\t\t\ttooltip={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\tonClick={toggleResolve}\n\t\t\t\t>\n\t\t\t\t\t<TldrawUiIcon\n\t\t\t\t\t\ticon=\"check\"\n\t\t\t\t\t\tlabel={msg(thread.resolved ? 'comments.reopen' : 'comments.resolve')}\n\t\t\t\t\t\tsmall\n\t\t\t\t\t/>\n\t\t\t\t</TooltipButton>\n\t\t\t)}\n\t\t\t{currentUserId && (\n\t\t\t\t<TldrawUiDropdownMenuRoot id={`comment-thread-actions-${thread.id}`}>\n\t\t\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t<TooltipButton\n\t\t\t\t\t\t\ttooltip={msg('comments.more-options')}\n\t\t\t\t\t\t\tclassName=\"tlui-cmt-thread__action\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<TldrawUiIcon icon=\"dots-vertical\" label={msg('comments.more-options')} small />\n\t\t\t\t\t\t</TooltipButton>\n\t\t\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t<TldrawUiDropdownMenuContent\n\t\t\t\t\t\tclassName=\"tlui-cmt-menu\"\n\t\t\t\t\t\tside=\"bottom\"\n\t\t\t\t\t\talign=\"end\"\n\t\t\t\t\t\talignOffset={0}\n\t\t\t\t\t>\n\t\t\t\t\t\t<TldrawUiDropdownMenuGroup>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-cmt-menu-item tlui-cmt-menu-item--danger\"\n\t\t\t\t\t\t\t\t\tonClick={deleteThread}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span>{msg('comments.delete')}</span>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuItem>\n\t\t\t\t\t\t</TldrawUiDropdownMenuGroup>\n\t\t\t\t\t</TldrawUiDropdownMenuContent>\n\t\t\t\t</TldrawUiDropdownMenuRoot>\n\t\t\t)}\n\t\t</>\n\t)\n\n\tconst PinContent = options.components.PinContent\n\t// The `PinContent` component slot overrides the built-in author-initial default.\n\tconst threadAuthor = resolveAuthor(thread.createdBy)\n\tconst pinContent = PinContent ? (\n\t\t<PinContent thread={thread} comments={comments} />\n\t) : (\n\t\tinitialOf(threadAuthor?.name ?? UNKNOWN_AUTHOR)\n\t)\n\n\t// Drag the marker to move the thread: its position is overridden locally while dragging, then\n\t// re-anchored on drop. A point/shape thread re-anchors to whatever it's dropped on (a shape, else\n\t// a point); a region thread translates, keeping its size. A pointer that barely moves is a click \u2014\n\t// toggle the popover.\n\t// Which affordances move a region, per the option: 'pin' \u2192 pin only, 'body' \u2192 body only, 'both'.\n\tconst isRegion = thread.anchor.type === 'region'\n\tconst pinMovable = regionOptions.move !== 'body'\n\tconst bodyMovable = regionOptions.move !== 'pin'\n\tconst startDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\t// A middle/right-button or space-held press over a pin is a camera pan, not a pin drag \u2014\n\t\t// hand it to the canvas untouched.\n\t\tif (isCanvasPanGesture(editor, e)) {\n\t\t\tforwardPointerEventToCanvas(container, e)\n\t\t\treturn\n\t\t}\n\t\te.stopPropagation()\n\t\tconst grabPage = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\tconst anchorPage = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\t\t// The drag delta is taken from where the pin is drawn, which for an imprecise shape pin\n\t\t// is inset from its anchor point \u2014 without this the pin jumps by the inset on drag start.\n\t\tconst inset = impreciseShapePinInset(thread.anchor, impreciseShapeAnchor)\n\t\tif (anchorPage && inset) {\n\t\t\tconst zoom = editor.getZoomLevel()\n\t\t\tanchorPage.x += inset.x / zoom\n\t\t\tanchorPage.y += inset.y / zoom\n\t\t}\n\t\tdragRef.current = {\n\t\t\tstartX: e.clientX,\n\t\t\tstartY: e.clientY,\n\t\t\tmoved: false,\n\t\t\toffsetX: anchorPage ? anchorPage.x - grabPage.x : 0,\n\t\t\toffsetY: anchorPage ? anchorPage.y - grabPage.y : 0,\n\t\t}\n\t\te.currentTarget.setPointerCapture(e.pointerId)\n\t}\n\tconst onDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tconst drag = dragRef.current\n\t\tif (!drag) return\n\t\t// A region that moves by its body ignores pin drags (the pin only toggles the thread).\n\t\tif (isRegion && !pinMovable) return\n\t\tif (!drag.moved && Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < 4) return\n\t\tdrag.moved = true\n\t\tconst cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\tconst pagePoint = { x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY }\n\t\tsetDragPagePoint(pagePoint)\n\t\t// Hint the shape the pin would re-anchor to on drop \u2014 the same hit-test endDrag resolves\n\t\t// with. Regions translate rather than re-anchor, so they never hint.\n\t\tif (!isRegion) {\n\t\t\tconst hit = editor.getShapeAtPoint(pagePoint, { hitInside: true })\n\t\t\teditor.setHintingShapes(hit ? [hit.id] : [])\n\t\t}\n\t}\n\t// A cancelled pointer (touch gesture takeover, browser interruption) aborts the drag outright:\n\t// no re-anchor commit, no click-toggle \u2014 the pin snaps back and the hint clears.\n\tconst cancelDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tconst drag = dragRef.current\n\t\tdragRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId)) {\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\t}\n\t\tif (!drag) return\n\t\tsetDragPagePoint(null)\n\t\teditor.setHintingShapes([])\n\t}\n\tconst endDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n\t\tconst drag = dragRef.current\n\t\tdragRef.current = null\n\t\tif (e.currentTarget.hasPointerCapture(e.pointerId)) {\n\t\t\te.currentTarget.releasePointerCapture(e.pointerId)\n\t\t}\n\t\tif (!drag) return\n\t\teditor.setHintingShapes([])\n\t\tif (!drag.moved) {\n\t\t\topenThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id)\n\t\t\treturn\n\t\t}\n\t\tconst cursorPage = editor.screenToPage({ x: e.clientX, y: e.clientY })\n\t\tconst pagePoint = { x: cursorPage.x + drag.offsetX, y: cursorPage.y + drag.offsetY }\n\t\tsetDragPagePoint(null)\n\t\tlet anchor: TLCommentThread['anchor']\n\t\tif (thread.anchor.type === 'region') {\n\t\t\t// Translate so the pin (the region's pin corner) lands at the drop; size unchanged.\n\t\t\tanchor = {\n\t\t\t\t...thread.anchor,\n\t\t\t\tx: pagePoint.x - pinCorner.x * thread.anchor.w,\n\t\t\t\ty: pagePoint.y - pinCorner.y * thread.anchor.h,\n\t\t\t}\n\t\t} else {\n\t\t\tconst hit = editor.getShapeAtPoint(pagePoint, { hitInside: true })\n\t\t\tanchor = hit\n\t\t\t\t? shapeAnchorAt(\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\thit.id,\n\t\t\t\t\t\tpagePoint,\n\t\t\t\t\t\tgetCommentingOptions(editor).shouldBePrecise(editor, {\n\t\t\t\t\t\t\tshapeId: hit.id,\n\t\t\t\t\t\t\tpoint: pagePoint,\n\t\t\t\t\t\t\taltKey: e.altKey,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t: { type: 'point', x: pagePoint.x, y: pagePoint.y }\n\t\t}\n\t\tcommitCommentMutation(editor, () => putCommentRecords(editor, [{ ...thread, anchor }]), 'drag')\n\t}\n\n\t// The pin (and its popover) track the live edit: a resize moves it to the region's pin corner, a\n\t// move to the drag point; otherwise it sits at the stored anchor's viewport point.\n\tconst livePinPage = resizeBounds ? regionPinPoint(resizeBounds, pinCorner) : dragPagePoint\n\tconst renderPointBase = livePinPage ? editor.pageToViewport(livePinPage) : point\n\t// A region's pin centres on its corner \u2014 overlapping the box \u2014 rather than hanging off it.\n\t// The marker anchors bottom-left, so step half its 34px size left and down (screen px).\n\tconst renderPoint = isRegion\n\t\t? { x: renderPointBase.x - 17, y: renderPointBase.y + 17 }\n\t\t: renderPointBase\n\n\t// A region's live box bounds, by priority: a corner resize, else a pin-drag translation (the pin\n\t// corner tracks the cursor), else the stored anchor. Undefined for non-region threads.\n\tconst regionAnchor = thread.anchor.type === 'region' ? thread.anchor : undefined\n\tconst movedRegion =\n\t\tregionAnchor && dragPagePoint\n\t\t\t? {\n\t\t\t\t\t...regionAnchor,\n\t\t\t\t\tx: dragPagePoint.x - pinCorner.x * regionAnchor.w,\n\t\t\t\t\ty: dragPagePoint.y - pinCorner.y * regionAnchor.h,\n\t\t\t\t}\n\t\t\t: regionAnchor\n\tconst regionBoxBounds = resizeBounds ?? movedRegion\n\tconst commitResize = (bounds: BoxModel) => {\n\t\tsetResizeBounds(null)\n\t\teditor.run(\n\t\t\t// Spread the existing anchor first so the region's pin corner survives a resize.\n\t\t\t() => putCommentRecords(editor, [{ ...thread, anchor: { ...regionAnchor!, ...bounds } }]),\n\t\t\t{\n\t\t\t\thistory: 'ignore',\n\t\t\t}\n\t\t)\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{regionBoxBounds && (dragPagePoint || revealed) && (\n\t\t\t\t<RegionBox\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tbox={regionBoxBounds}\n\t\t\t\t\tmovable={bodyMovable && !dragPagePoint}\n\t\t\t\t\tonPreview={setResizeBounds}\n\t\t\t\t\tonCommit={commitResize}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{regionBoxBounds && revealed && !dragPagePoint && regionOptions.resize !== 'none' && (\n\t\t\t\t<RegionResizeHandles\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tbox={regionBoxBounds}\n\t\t\t\t\thandles={resizeHandles}\n\t\t\t\t\tonPreview={setResizeBounds}\n\t\t\t\t\tonCommit={commitResize}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={[\n\t\t\t\t\t'tlui-cmt-canvas-pin',\n\t\t\t\t\topen && 'tlui-cmt-canvas-pin--open',\n\t\t\t\t\tdragPagePoint && 'tlui-cmt-canvas-pin--dragging',\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: renderPoint.x, top: renderPoint.y }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tref={markerRef}\n\t\t\t\t\tclassName=\"tlui-cmt-canvas-pin__marker\"\n\t\t\t\t\tonPointerDown={startDrag}\n\t\t\t\t\tonPointerMove={onDrag}\n\t\t\t\t\tonPointerUp={endDrag}\n\t\t\t\t\tonPointerCancel={cancelDrag}\n\t\t\t\t\tonPointerEnter={() => setPinHovered(true)}\n\t\t\t\t\tonPointerLeave={() => setPinHovered(false)}\n\t\t\t\t>\n\t\t\t\t\t<CommentPin resolved={thread.resolved != null} open={open} color={threadAuthor?.color}>\n\t\t\t\t\t\t{pinContent}\n\t\t\t\t\t</CommentPin>\n\t\t\t\t</div>\n\t\t\t\t{/* The popover portals up to the menus layer (above the UI panels) so it isn't clipped;\n\t\t\t the pin itself stays in the canvas-in-front layer, beneath the UI. */}\n\t\t\t\t{open && (\n\t\t\t\t\t<ThreadPopover\n\t\t\t\t\t\tcontainer={container}\n\t\t\t\t\t\t// Clear the bottom-left-anchored pin: it spans 34px right of and above the\n\t\t\t\t\t\t// anchor, plus the open ring's 5px \u2014 the popover starts past that, opening\n\t\t\t\t\t\t// above the pin's top.\n\t\t\t\t\t\tstyle={{ left: renderPoint.x + 48, top: renderPoint.y - 54 }}\n\t\t\t\t\t>\n\t\t\t\t\t\t<CommentThread\n\t\t\t\t\t\t\theader={msg('comments.thread-title')}\n\t\t\t\t\t\t\theaderActions={headerActions}\n\t\t\t\t\t\t\trenderComment={renderComment}\n\t\t\t\t\t\t\tcomments={comments.map((c) => toCardProps(c, props, options.components, resolveName))}\n\t\t\t\t\t\t\tresolvedBanner={\n\t\t\t\t\t\t\t\tthread.resolved\n\t\t\t\t\t\t\t\t\t? msg('comments.resolved-by').replace(\n\t\t\t\t\t\t\t\t\t\t\t'{name}',\n\t\t\t\t\t\t\t\t\t\t\tresolveAuthor(thread.resolved.by)?.name ?? UNKNOWN_AUTHOR\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcomposer={\n\t\t\t\t\t\t\t\tcurrentUserId && !thread.resolved\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tauthor: me ?? UNKNOWN_COMMENT_AUTHOR,\n\t\t\t\t\t\t\t\t\t\t\tplaceholder: msg('comments.reply-placeholder'),\n\t\t\t\t\t\t\t\t\t\t\tsendLabel: msg('comments.send'),\n\t\t\t\t\t\t\t\t\t\t\tvalue: reply,\n\t\t\t\t\t\t\t\t\t\t\tonChange: (value: TLRichText) => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetReply(value)\n\t\t\t\t\t\t\t\t\t\t\t\tsaveCommentDraft(replyDraftSlot(thread.id), value)\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tonSubmit: postReply,\n\t\t\t\t\t\t\t\t\t\t\tdisabled: isCommentEmpty(reply),\n\t\t\t\t\t\t\t\t\t\t\tgetMentionSuggestions,\n\t\t\t\t\t\t\t\t\t\t\trenderMentionSuggestion,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</ThreadPopover>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n})\n\nfunction PendingComposer({\n\teditor,\n\tpending,\n\tcurrentUserId,\n\tresolveAuthor,\n\tonPostComment,\n\tgetMentionSuggestions,\n\trenderMentionSuggestion,\n}: CanvasCommentsProps & { editor: Editor; pending: PendingComment }) {\n\tconst me = currentUserId ? resolveAuthor(currentUserId) : undefined\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\tconst container = useContainer()\n\t// Over this floating panel, scroll and hover reach the canvas (except where it scrolls itself).\n\tusePassThroughWheelEvents(ref)\n\tusePassThroughMouseOverEvents(ref)\n\n\tconst point = useValue('composer point', () => editor.pageToViewport(pending.point), [\n\t\teditor,\n\t\tpending.point,\n\t])\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\tcommitCommentMutation(editor, () => {\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\tputCommentRecords(editor, [thread, comment])\n\t\t\tif (onPostComment) onPostComment(comment)\n\t\t})\n\t\tsetText(EMPTY_COMMENT)\n\t\tclearCommentDraft(NEW_COMMENT_DRAFT)\n\t\tpendingComment.set(editor, null)\n\t}\n\n\treturn createPortal(\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={[\n\t\t\t\t'tlui-cmt-canvas-composer',\n\t\t\t\tpending.anchor.type === 'region' && 'tlui-cmt-canvas-composer--region',\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' ')}\n\t\t\tstyle={{ left: point.x, top: point.y }}\n\t\t\tonPointerDown={stop}\n\t\t\tonContextMenu={stop}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Escape' && !isMentionPickerOpen()) pendingComment.set(editor, null)\n\t\t\t}}\n\t\t>\n\t\t\t<CommentComposer\n\t\t\t\tauthor={me ?? UNKNOWN_COMMENT_AUTHOR}\n\t\t\t\tplaceholder={msg('comments.add-placeholder')}\n\t\t\t\tsendLabel={msg('comments.send')}\n\t\t\t\tvalue={text}\n\t\t\t\tonChange={(value) => {\n\t\t\t\t\tsetText(value)\n\t\t\t\t\tsaveCommentDraft(NEW_COMMENT_DRAFT, value)\n\t\t\t\t}}\n\t\t\t\tonSubmit={submit}\n\t\t\t\tdisabled={isCommentEmpty(text)}\n\t\t\t\tgetMentionSuggestions={getMentionSuggestions}\n\t\t\t\trenderMentionSuggestion={renderMentionSuggestion}\n\t\t\t\tautoFocus\n\t\t\t\tleading={draftAvatar(me?.color)}\n\t\t\t/>\n\t\t</div>,\n\t\tcontainer\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ClusterBadge", "ThreadPin"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRef } from "react";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import {
|
|
5
5
|
useContainer,
|
|
@@ -9,35 +9,27 @@ import {
|
|
|
9
9
|
useValue
|
|
10
10
|
} from "tldraw";
|
|
11
11
|
import { CommentsList } from "../ui/comments-list.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
|
|
13
13
|
import { CommentsFilterMenu } from "./comments-filter-menu.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import { CommentsVisibilityToggle } from "./comments-visibility-toggle.mjs";
|
|
15
15
|
import { useComments, useCommentThreads } from "./hooks.mjs";
|
|
16
16
|
import { useCommentingEnabled } from "./license.mjs";
|
|
17
17
|
import { useCommentingOptions } from "./options.mjs";
|
|
18
18
|
import { richTextToPlaintext } from "./rich-text.mjs";
|
|
19
|
-
import { openThreadId, sidebarFilters } from "./state.mjs";
|
|
19
|
+
import { commentsSidebarOpen, openThreadId, sidebarFilters } from "./state.mjs";
|
|
20
20
|
import { focusThread } from "./thread-state.mjs";
|
|
21
21
|
function CanvasCommentsSidebar(props) {
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
currentUserId,
|
|
25
|
-
isCommentUnread,
|
|
26
|
-
tools,
|
|
27
|
-
header,
|
|
28
|
-
empty,
|
|
29
|
-
impreciseShapeAnchor
|
|
30
|
-
} = props;
|
|
22
|
+
const { resolveAuthor, currentUserId, isCommentUnread, header, empty, impreciseShapeAnchor } = props;
|
|
23
|
+
const resolveName = useCallback((id) => resolveAuthor(id)?.name, [resolveAuthor]);
|
|
31
24
|
const editor = useEditor();
|
|
32
25
|
const options = useCommentingOptions();
|
|
33
|
-
const sidebarTools = tools ?? ["comment"];
|
|
34
26
|
const container = useContainer();
|
|
35
27
|
const commentingEnabled = useCommentingEnabled();
|
|
36
28
|
const msg = useTranslation();
|
|
37
29
|
const threads = useCommentThreads(editor);
|
|
38
30
|
const comments = useComments(editor);
|
|
39
31
|
const currentPageId = useValue("page id", () => editor.getCurrentPageId(), [editor]);
|
|
40
|
-
const
|
|
32
|
+
const open = useValue("sidebar open", () => commentsSidebarOpen.get(editor), [editor]);
|
|
41
33
|
const openId = useValue("open thread", () => openThreadId.get(editor), [editor]);
|
|
42
34
|
const filters = useValue("sidebar filters", () => sidebarFilters.get(editor), [editor]);
|
|
43
35
|
const pageNames = useValue(
|
|
@@ -45,7 +37,7 @@ function CanvasCommentsSidebar(props) {
|
|
|
45
37
|
() => new Map(editor.getPages().map((page) => [page.id, page.name])),
|
|
46
38
|
[editor]
|
|
47
39
|
);
|
|
48
|
-
if (!commentingEnabled || !
|
|
40
|
+
if (!commentingEnabled || !open) return null;
|
|
49
41
|
const byThread = /* @__PURE__ */ new Map();
|
|
50
42
|
for (const comment of comments) {
|
|
51
43
|
const list = byThread.get(comment.threadId) ?? [];
|
|
@@ -69,11 +61,13 @@ function CanvasCommentsSidebar(props) {
|
|
|
69
61
|
}
|
|
70
62
|
return {
|
|
71
63
|
id: thread.id,
|
|
72
|
-
author:
|
|
64
|
+
author: resolveAuthor(thread.createdBy) ?? UNKNOWN_COMMENT_AUTHOR,
|
|
73
65
|
preview,
|
|
74
66
|
date: new Date((first ?? thread).createdAt).toISOString(),
|
|
75
67
|
resolved: thread.resolved != null,
|
|
76
|
-
page:
|
|
68
|
+
// The page label only earns its place when it adds information: multiple pages
|
|
69
|
+
// exist, and the thread is somewhere other than where you already are.
|
|
70
|
+
page: pageNames.size > 1 && thread.pageId !== currentPageId ? pageNames.get(thread.pageId) : void 0,
|
|
77
71
|
count: threadComments.length,
|
|
78
72
|
selected: openId === thread.id
|
|
79
73
|
};
|
|
@@ -90,7 +84,7 @@ function CanvasCommentsSidebar(props) {
|
|
|
90
84
|
{
|
|
91
85
|
items,
|
|
92
86
|
header: header ?? msg("comments.title"),
|
|
93
|
-
headerAction: /* @__PURE__ */ jsxs("div", { className: "cmt-list__header-actions", children: [
|
|
87
|
+
headerAction: /* @__PURE__ */ jsxs("div", { className: "tlui-cmt-list__header-actions", children: [
|
|
94
88
|
/* @__PURE__ */ jsx(
|
|
95
89
|
CommentsFilterMenu,
|
|
96
90
|
{
|
|
@@ -98,7 +92,7 @@ function CanvasCommentsSidebar(props) {
|
|
|
98
92
|
canFilterByUnread: isCommentUnread !== void 0
|
|
99
93
|
}
|
|
100
94
|
),
|
|
101
|
-
/* @__PURE__ */ jsx(
|
|
95
|
+
/* @__PURE__ */ jsx(CommentsVisibilityToggle, {})
|
|
102
96
|
] }),
|
|
103
97
|
empty: items.length === 0 && pageThreads.length > 0 ? msg("comments.empty-filtered") : empty ?? msg("comments.empty"),
|
|
104
98
|
resolvedLabel: msg("comments.resolved"),
|
|
@@ -110,7 +104,7 @@ function SidebarPanel({ container, children }) {
|
|
|
110
104
|
const ref = useRef(null);
|
|
111
105
|
usePassThroughMouseOverEvents(ref);
|
|
112
106
|
return createPortal(
|
|
113
|
-
/* @__PURE__ */ jsx("div", { ref, className: "cmt-canvas-sidebar", children }),
|
|
107
|
+
/* @__PURE__ */ jsx("div", { ref, className: "tlui-cmt-canvas-sidebar", onContextMenu: (e) => e.stopPropagation(), children }),
|
|
114
108
|
container
|
|
115
109
|
);
|
|
116
110
|
}
|