@tldraw/commenting 5.6.0-next.133b55acee83 → 5.6.0-next.568da2160ccd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist-cjs/canvas/cluster-badge.js +7 -1
  2. package/dist-cjs/canvas/cluster-badge.js.map +2 -2
  3. package/dist-cjs/canvas/comment-reactions.js +10 -4
  4. package/dist-cjs/canvas/comment-reactions.js.map +2 -2
  5. package/dist-cjs/canvas/comment-render.js.map +2 -2
  6. package/dist-cjs/canvas/comment-tool.js +4 -1
  7. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  8. package/dist-cjs/canvas/comments-filter-menu.js +13 -6
  9. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  10. package/dist-cjs/canvas/comments-menu-item.js +4 -1
  11. package/dist-cjs/canvas/comments-menu-item.js.map +2 -2
  12. package/dist-cjs/canvas/comments-overflow-menu.js +9 -4
  13. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  14. package/dist-cjs/canvas/comments-sidebar.js +14 -5
  15. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  16. package/dist-cjs/canvas/comments-visibility-toggle.js +5 -1
  17. package/dist-cjs/canvas/comments-visibility-toggle.js.map +2 -2
  18. package/dist-cjs/canvas/options.js.map +1 -1
  19. package/dist-cjs/canvas/pending-composer.js +6 -2
  20. package/dist-cjs/canvas/pending-composer.js.map +2 -2
  21. package/dist-cjs/canvas/thread-pin.js +13 -2
  22. package/dist-cjs/canvas/thread-pin.js.map +2 -2
  23. package/dist-cjs/canvas/thread-preview.js +4 -1
  24. package/dist-cjs/canvas/thread-preview.js.map +2 -2
  25. package/dist-cjs/canvas/thread-stack.js +18 -4
  26. package/dist-cjs/canvas/thread-stack.js.map +2 -2
  27. package/dist-cjs/canvas/thread-view.js +40 -20
  28. package/dist-cjs/canvas/thread-view.js.map +2 -2
  29. package/dist-cjs/index.d.ts +9 -5
  30. package/dist-cjs/index.js +1 -1
  31. package/dist-cjs/ui/comment-composer.js +5 -0
  32. package/dist-cjs/ui/comment-composer.js.map +2 -2
  33. package/dist-cjs/ui/gesture-focus.js +37 -0
  34. package/dist-cjs/ui/gesture-focus.js.map +7 -0
  35. package/dist-cjs/ui/reaction-picker.js +5 -2
  36. package/dist-cjs/ui/reaction-picker.js.map +2 -2
  37. package/dist-cjs/ui/reaction.js +15 -5
  38. package/dist-cjs/ui/reaction.js.map +2 -2
  39. package/dist-cjs/ui/reply-count.js +8 -2
  40. package/dist-cjs/ui/reply-count.js.map +2 -2
  41. package/dist-esm/canvas/cluster-badge.mjs +8 -1
  42. package/dist-esm/canvas/cluster-badge.mjs.map +2 -2
  43. package/dist-esm/canvas/comment-reactions.mjs +12 -4
  44. package/dist-esm/canvas/comment-reactions.mjs.map +2 -2
  45. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  46. package/dist-esm/canvas/comment-tool.mjs +6 -2
  47. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  48. package/dist-esm/canvas/comments-filter-menu.mjs +14 -6
  49. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  50. package/dist-esm/canvas/comments-menu-item.mjs +5 -2
  51. package/dist-esm/canvas/comments-menu-item.mjs.map +2 -2
  52. package/dist-esm/canvas/comments-overflow-menu.mjs +10 -4
  53. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  54. package/dist-esm/canvas/comments-sidebar.mjs +21 -6
  55. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  56. package/dist-esm/canvas/comments-visibility-toggle.mjs +6 -2
  57. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +2 -2
  58. package/dist-esm/canvas/options.mjs.map +1 -1
  59. package/dist-esm/canvas/pending-composer.mjs +7 -2
  60. package/dist-esm/canvas/pending-composer.mjs.map +2 -2
  61. package/dist-esm/canvas/thread-pin.mjs +15 -3
  62. package/dist-esm/canvas/thread-pin.mjs.map +2 -2
  63. package/dist-esm/canvas/thread-preview.mjs +5 -1
  64. package/dist-esm/canvas/thread-preview.mjs.map +2 -2
  65. package/dist-esm/canvas/thread-stack.mjs +19 -4
  66. package/dist-esm/canvas/thread-stack.mjs.map +2 -2
  67. package/dist-esm/canvas/thread-view.mjs +42 -21
  68. package/dist-esm/canvas/thread-view.mjs.map +2 -2
  69. package/dist-esm/index.d.mts +9 -5
  70. package/dist-esm/index.mjs +1 -1
  71. package/dist-esm/ui/comment-composer.mjs +5 -0
  72. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  73. package/dist-esm/ui/gesture-focus.mjs +17 -0
  74. package/dist-esm/ui/gesture-focus.mjs.map +7 -0
  75. package/dist-esm/ui/reaction-picker.mjs +6 -2
  76. package/dist-esm/ui/reaction-picker.mjs.map +2 -2
  77. package/dist-esm/ui/reaction.mjs +16 -8
  78. package/dist-esm/ui/reaction.mjs.map +2 -2
  79. package/dist-esm/ui/reply-count.mjs +8 -2
  80. package/dist-esm/ui/reply-count.mjs.map +2 -2
  81. package/package.json +5 -5
  82. package/src/canvas/cluster-badge.tsx +9 -1
  83. package/src/canvas/comment-reactions.tsx +18 -7
  84. package/src/canvas/comment-render.ts +6 -4
  85. package/src/canvas/comment-tool.test.ts +11 -0
  86. package/src/canvas/comment-tool.tsx +6 -1
  87. package/src/canvas/comments-filter-menu.tsx +15 -6
  88. package/src/canvas/comments-menu-item.tsx +6 -2
  89. package/src/canvas/comments-overflow-menu.tsx +11 -4
  90. package/src/canvas/comments-sidebar.tsx +23 -6
  91. package/src/canvas/comments-visibility-toggle.tsx +7 -2
  92. package/src/canvas/options.ts +1 -1
  93. package/src/canvas/pending-composer.tsx +8 -2
  94. package/src/canvas/thread-pin.tsx +17 -4
  95. package/src/canvas/thread-preview.tsx +6 -1
  96. package/src/canvas/thread-stack.tsx +21 -4
  97. package/src/canvas/thread-view.tsx +44 -22
  98. package/src/ui/comment-composer.tsx +9 -0
  99. package/src/ui/gesture-focus.test.ts +45 -0
  100. package/src/ui/gesture-focus.ts +29 -0
  101. package/src/ui/reaction-picker.tsx +7 -2
  102. package/src/ui/reaction.test.tsx +22 -0
  103. package/src/ui/reaction.tsx +25 -14
  104. package/src/ui/reply-count.ts +9 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/canvas/thread-pin.tsx"],
4
- "sourcesContent": ["import { Avatar } from '@tldraw/mentions'\nimport {\n\tmemo,\n\ttype PointerEvent as ReactPointerEvent,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport {\n\ttype BoxModel,\n\tEditor,\n\tTLCommentThread,\n\tuseContainer,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentPin } from '../ui/comment-pin'\nimport { forwardPointerEventToCanvas, isCanvasPanGesture } from './canvas-events'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { getCommentingOptions, useCanComment, useCommentingOptions } from './options'\nimport {\n\tREGION_CORNERS,\n\tREGION_HANDLE_MARGIN_PX,\n\tRegionBox,\n\tRegionResizeHandles,\n} from './region-box'\nimport { openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport {\n\tanchorPagePoint,\n\tanchorViewportPoint,\n\tcommentTargetShapeAt,\n\timpreciseShapePinInset,\n\tisBoxInInflatedViewport,\n\tisInInflatedViewport,\n\tREGION_PIN_CORNER,\n\tregionAnchorPinCorner,\n\tregionPinPoint,\n\tregionWithPinAt,\n\tshapeAnchorAt,\n} from './thread-state'\nimport { POPOVER_OFFSET, ThreadPopover, ThreadView } from './thread-view'\n\n/** The opened popover has a header row the hover preview lacks, so it opens this much higher \u2014 the\n * first comment then lands where the preview's sat. Re-measure if the header height or the preview\n * panel padding changes. */\nconst THREAD_HEADER_BLOCK = 36\n\n/**\n * A single thread's pin: the marker at its anchor, the thread popover when it's open, the hover\n * preview, and \u2014 for a region anchor \u2014 the dashed box and its resize handles. Dragging the marker\n * re-anchors the thread.\n *\n * Memoized so a pin skips re-rendering when the layer re-renders for unrelated reasons; camera\n * tracking still works, since the pin subscribes to its own viewport position via signals.\n */\nexport const ThreadPin = memo(function ThreadPin({\n\teditor,\n\tthread,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\tthread: TLCommentThread\n}) {\n\tconst { resolveAuthor } = props\n\tconst options = useCommentingOptions()\n\tconst canComment = useCanComment(props.currentUserId)\n\tconst container = useContainer()\n\tconst msg = useTranslation()\n\tconst comments = useThreadComments(editor, thread.id)\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// 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\tconst [resizeBounds, setResizeBounds] = useState<BoxModel | null>(null)\n\t// Hovering the marker previews the thread's opening comment, on the delay every marker uses.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `pin:${thread.id}`)\n\tconst previewThreads = useMemo(() => [thread], [thread])\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, mid-resize, or while the pointer is\n\t// within the region.\n\tconst revealed = open || resizeBounds != null || pointerInRegion\n\t// A region thread's pin corner is its own (the corner its creating drag released on), with\n\t// the default as the fallback for older records.\n\tconst pinCorner =\n\t\tthread.anchor.type === 'region' ? regionAnchorPinCorner(thread.anchor) : REGION_PIN_CORNER\n\t// A region resizes from its corners \u2014 every corner but the pin's own, which the pin owns.\n\tconst resizeHandles = useMemo(\n\t\t() => REGION_CORNERS.filter((c) => c.x !== pinCorner.x || c.y !== pinCorner.y),\n\t\t[pinCorner.x, pinCorner.y]\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<HTMLButtonElement>(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 closes the thread. Capture phase + a class check rather than\n\t// stopPropagation, since the popover portals elsewhere in the DOM. The marker is excluded so its own\n\t// click-to-toggle handles it instead of this closing and 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 edits this thread \u2014 don't dismiss it.\n\t\t\tif (target.closest('.tlui-cmt-canvas-region-handle')) 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\t// The pin must not unmount mid-interaction: an open thread's popover hangs off it, and a drag\n\t// or resize holds pointer capture on it \u2014 so those states are exempt from the viewport cull.\n\tconst exemptFromCull = open || dragPagePoint != null || resizeBounds != null\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 point = anchorViewportPoint(editor, thread.anchor)\n\t\t\tif (!point) return null\n\t\t\t// Off-screen pins (plus a pre-mount margin) unmount rather than re-render every camera frame. A\n\t\t\t// region thread stays mounted while any part of its box is on screen, since the box renders here too.\n\t\t\tif (!exemptFromCull) {\n\t\t\t\tconst visible =\n\t\t\t\t\tthread.anchor.type === 'region'\n\t\t\t\t\t\t? isBoxInInflatedViewport(editor, thread.anchor)\n\t\t\t\t\t\t: isInInflatedViewport(editor, point)\n\t\t\t\tif (!visible) return null\n\t\t\t}\n\t\t\treturn point\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId, exemptFromCull]\n\t)\n\tif (!point) return null\n\n\tconst toggleOpen = () =>\n\t\topenThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id)\n\n\tconst PinContent = options.components.PinContent\n\tconst threadAuthor = resolveAuthor(thread.createdBy)\n\tconst pinContent = PinContent ? (\n\t\t<PinContent thread={thread} comments={comments} />\n\t) : (\n\t\t<Avatar author={threadAuthor ?? UNKNOWN_COMMENT_AUTHOR} />\n\t)\n\tconst pinLabel = msg(\n\t\tthread.resolved ? 'comments.pin-label-resolved' : 'comments.pin-label'\n\t).replace('{name}', threadAuthor?.name ?? UNKNOWN_AUTHOR)\n\n\t// Drag the marker to move the thread: position is overridden locally while dragging, then re-anchored\n\t// on drop. A region translates keeping its size; a barely-moved pointer is a click.\n\tconst isRegion = thread.anchor.type === 'region'\n\t// The marker is a button (so it's keyboard-reachable), so the drag handlers are typed to it.\n\tconst startDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\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)\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(editor, thread.anchor)\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<HTMLButtonElement>) => {\n\t\tconst drag = dragRef.current\n\t\tif (!drag) return\n\t\t// Moving a pin re-anchors the thread record \u2014 a commenting write. Without the permission the\n\t\t// press stays a click (`moved` never sets, so release toggles the popover and never commits).\n\t\tif (!canComment) 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 = commentTargetShapeAt(editor, pagePoint)\n\t\t\teditor.setHintingShapes(hit ? [hit.id] : [])\n\t\t}\n\t}\n\t// Release capture and take the drag record; null when no drag was in progress.\n\tconst takeDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\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\treturn drag\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<HTMLButtonElement>) => {\n\t\tif (!takeDrag(e)) return\n\t\tsetDragPagePoint(null)\n\t\teditor.setHintingShapes([])\n\t}\n\tconst endDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\n\t\tconst drag = takeDrag(e)\n\t\tif (!drag) return\n\t\teditor.setHintingShapes([])\n\t\tif (!drag.moved) {\n\t\t\ttoggleOpen()\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\tanchor = regionWithPinAt(thread.anchor, pinCorner, pagePoint)\n\t\t} else {\n\t\t\tconst hit = commentTargetShapeAt(editor, pagePoint)\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, ({ put }) => put([{ ...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 28px size (--tlui-cmt-pin-size) left and\n\t// down (screen px), matching the draft composer's --region centring on the same corner.\n\tconst renderPoint = isRegion\n\t\t? { x: renderPointBase.x - 14, y: renderPointBase.y + 14 }\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? regionWithPinAt(regionAnchor, pinCorner, dragPagePoint)\n\t\t\t: regionAnchor\n\tconst regionBoxBounds = resizeBounds ?? movedRegion\n\tconst commitResize = (bounds: BoxModel) => {\n\t\tsetResizeBounds(null)\n\t\tif (!canComment) return\n\t\t// Same commit path as a pin drag, so the configured `dragHistory` governs both \u2014 going\n\t\t// straight to `editor.run` here would make region resizes silently ignore the option.\n\t\tcommitCommentMutation(\n\t\t\teditor,\n\t\t\t// Spread the existing anchor first so the region's pin corner survives a resize.\n\t\t\t({ put }) => put([{ ...thread, anchor: { ...regionAnchor!, ...bounds } }]),\n\t\t\t'drag'\n\t\t)\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{regionBoxBounds && (dragPagePoint || revealed) && (\n\t\t\t\t<RegionBox editor={editor} box={regionBoxBounds} />\n\t\t\t)}\n\t\t\t{regionBoxBounds && revealed && !dragPagePoint && canComment && (\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<button\n\t\t\t\t\tref={markerRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-pin__marker\"\n\t\t\t\t\taria-label={pinLabel}\n\t\t\t\t\taria-expanded={open}\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\t// Pointer activation is already handled by endDrag (which distinguishes a click\n\t\t\t\t\t// from a drag), so only take keyboard-synthesised clicks here \u2014 those report\n\t\t\t\t\t// `detail === 0` \u2014 or the thread would toggle twice per mouse click.\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\tif (e.detail === 0) toggleOpen()\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerEnter={previewHandlers.onPointerEnter}\n\t\t\t\t\tonPointerLeave={previewHandlers.onPointerLeave}\n\t\t\t\t\t// Focus stands in for hover, so tabbing to a marker gets the same preview.\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\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</button>\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\tbase={{\n\t\t\t\t\t\t\tx: renderPoint.x + POPOVER_OFFSET.thread.x,\n\t\t\t\t\t\t\ty: renderPoint.y + POPOVER_OFFSET.thread.y - THREAD_HEADER_BLOCK,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t</ThreadPopover>\n\t\t\t\t)}\n\t\t\t\t{/* Not while dragging: the pin is being moved, not read, and a panel trailing the\n\t\t\t\t cursor would obscure the drop target. */}\n\t\t\t\t{previewShown && !dragPagePoint && (\n\t\t\t\t\t<ThreadPreview\n\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\tthreads={previewThreads}\n\t\t\t\t\t\tvariant=\"thread\"\n\t\t\t\t\t\tpoint={renderPoint}\n\t\t\t\t\t\tonSelectThread={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\t\tresolveAuthor={resolveAuthor}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Avatar } from '@tldraw/mentions'\nimport {\n\tmemo,\n\ttype PointerEvent as ReactPointerEvent,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport {\n\tdefineMessages,\n\tEditor,\n\tTLCommentThread,\n\ttype BoxModel,\n\tuseContainer,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentPin } from '../ui/comment-pin'\nimport { forwardPointerEventToCanvas, isCanvasPanGesture } from './canvas-events'\nimport { commitCommentMutation } from './comment-mutations'\nimport { UNKNOWN_COMMENT_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { getCommentingOptions, useCanComment, useCommentingOptions } from './options'\nimport {\n\tREGION_CORNERS,\n\tREGION_HANDLE_MARGIN_PX,\n\tRegionBox,\n\tRegionResizeHandles,\n} from './region-box'\nimport { openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport {\n\tanchorPagePoint,\n\tanchorViewportPoint,\n\tcommentTargetShapeAt,\n\timpreciseShapePinInset,\n\tisBoxInInflatedViewport,\n\tisInInflatedViewport,\n\tREGION_PIN_CORNER,\n\tregionAnchorPinCorner,\n\tregionPinPoint,\n\tregionWithPinAt,\n\tshapeAnchorAt,\n} from './thread-state'\nimport { POPOVER_OFFSET, ThreadPopover, ThreadView } from './thread-view'\n\nconst messages = defineMessages({\n\tcommentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },\n\tcommentsPinLabel: { id: 'comments.pin-label', defaultMessage: 'Comment by {name}' },\n\tcommentsPinLabelResolved: {\n\t\tid: 'comments.pin-label-resolved',\n\t\tdefaultMessage: 'Resolved comment by {name}',\n\t},\n})\n\n/** The opened popover has a header row the hover preview lacks, so it opens this much higher \u2014 the\n * first comment then lands where the preview's sat. Re-measure if the header height or the preview\n * panel padding changes. */\nconst THREAD_HEADER_BLOCK = 36\n\n/**\n * A single thread's pin: the marker at its anchor, the thread popover when it's open, the hover\n * preview, and \u2014 for a region anchor \u2014 the dashed box and its resize handles. Dragging the marker\n * re-anchors the thread.\n *\n * Memoized so a pin skips re-rendering when the layer re-renders for unrelated reasons; camera\n * tracking still works, since the pin subscribes to its own viewport position via signals.\n */\nexport const ThreadPin = memo(function ThreadPin({\n\teditor,\n\tthread,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\tthread: TLCommentThread\n}) {\n\tconst { resolveAuthor } = props\n\tconst options = useCommentingOptions()\n\tconst canComment = useCanComment(props.currentUserId)\n\tconst container = useContainer()\n\tconst msg = useTranslation()\n\tconst comments = useThreadComments(editor, thread.id)\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// 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\tconst [resizeBounds, setResizeBounds] = useState<BoxModel | null>(null)\n\t// Hovering the marker previews the thread's opening comment, on the delay every marker uses.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `pin:${thread.id}`)\n\tconst previewThreads = useMemo(() => [thread], [thread])\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, mid-resize, or while the pointer is\n\t// within the region.\n\tconst revealed = open || resizeBounds != null || pointerInRegion\n\t// A region thread's pin corner is its own (the corner its creating drag released on), with\n\t// the default as the fallback for older records.\n\tconst pinCorner =\n\t\tthread.anchor.type === 'region' ? regionAnchorPinCorner(thread.anchor) : REGION_PIN_CORNER\n\t// A region resizes from its corners \u2014 every corner but the pin's own, which the pin owns.\n\tconst resizeHandles = useMemo(\n\t\t() => REGION_CORNERS.filter((c) => c.x !== pinCorner.x || c.y !== pinCorner.y),\n\t\t[pinCorner.x, pinCorner.y]\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<HTMLButtonElement>(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 closes the thread. Capture phase + a class check rather than\n\t// stopPropagation, since the popover portals elsewhere in the DOM. The marker is excluded so its own\n\t// click-to-toggle handles it instead of this closing and 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 edits this thread \u2014 don't dismiss it.\n\t\t\tif (target.closest('.tlui-cmt-canvas-region-handle')) 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\t// The pin must not unmount mid-interaction: an open thread's popover hangs off it, and a drag\n\t// or resize holds pointer capture on it \u2014 so those states are exempt from the viewport cull.\n\tconst exemptFromCull = open || dragPagePoint != null || resizeBounds != null\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 point = anchorViewportPoint(editor, thread.anchor)\n\t\t\tif (!point) return null\n\t\t\t// Off-screen pins (plus a pre-mount margin) unmount rather than re-render every camera frame. A\n\t\t\t// region thread stays mounted while any part of its box is on screen, since the box renders here too.\n\t\t\tif (!exemptFromCull) {\n\t\t\t\tconst visible =\n\t\t\t\t\tthread.anchor.type === 'region'\n\t\t\t\t\t\t? isBoxInInflatedViewport(editor, thread.anchor)\n\t\t\t\t\t\t: isInInflatedViewport(editor, point)\n\t\t\t\tif (!visible) return null\n\t\t\t}\n\t\t\treturn point\n\t\t},\n\t\t[editor, thread.anchor, thread.pageId, exemptFromCull]\n\t)\n\tif (!point) return null\n\n\tconst toggleOpen = () =>\n\t\topenThreadId.set(editor, openThreadId.get(editor) === thread.id ? null : thread.id)\n\n\tconst PinContent = options.components.PinContent\n\tconst threadAuthor = resolveAuthor(thread.createdBy)\n\tconst pinContent = PinContent ? (\n\t\t<PinContent thread={thread} comments={comments} />\n\t) : (\n\t\t<Avatar author={threadAuthor ?? UNKNOWN_COMMENT_AUTHOR} />\n\t)\n\tconst pinLabel = msg(\n\t\tthread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,\n\t\t{\n\t\t\tname: threadAuthor?.name ?? msg(messages.commentsUnknownAuthor),\n\t\t}\n\t)\n\n\t// Drag the marker to move the thread: position is overridden locally while dragging, then re-anchored\n\t// on drop. A region translates keeping its size; a barely-moved pointer is a click.\n\tconst isRegion = thread.anchor.type === 'region'\n\t// The marker is a button (so it's keyboard-reachable), so the drag handlers are typed to it.\n\tconst startDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\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)\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(editor, thread.anchor)\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<HTMLButtonElement>) => {\n\t\tconst drag = dragRef.current\n\t\tif (!drag) return\n\t\t// Moving a pin re-anchors the thread record \u2014 a commenting write. Without the permission the\n\t\t// press stays a click (`moved` never sets, so release toggles the popover and never commits).\n\t\tif (!canComment) 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 = commentTargetShapeAt(editor, pagePoint)\n\t\t\teditor.setHintingShapes(hit ? [hit.id] : [])\n\t\t}\n\t}\n\t// Release capture and take the drag record; null when no drag was in progress.\n\tconst takeDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\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\treturn drag\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<HTMLButtonElement>) => {\n\t\tif (!takeDrag(e)) return\n\t\tsetDragPagePoint(null)\n\t\teditor.setHintingShapes([])\n\t}\n\tconst endDrag = (e: ReactPointerEvent<HTMLButtonElement>) => {\n\t\tconst drag = takeDrag(e)\n\t\tif (!drag) return\n\t\teditor.setHintingShapes([])\n\t\tif (!drag.moved) {\n\t\t\ttoggleOpen()\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\tanchor = regionWithPinAt(thread.anchor, pinCorner, pagePoint)\n\t\t} else {\n\t\t\tconst hit = commentTargetShapeAt(editor, pagePoint)\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, ({ put }) => put([{ ...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 28px size (--tlui-cmt-pin-size) left and\n\t// down (screen px), matching the draft composer's --region centring on the same corner.\n\tconst renderPoint = isRegion\n\t\t? { x: renderPointBase.x - 14, y: renderPointBase.y + 14 }\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? regionWithPinAt(regionAnchor, pinCorner, dragPagePoint)\n\t\t\t: regionAnchor\n\tconst regionBoxBounds = resizeBounds ?? movedRegion\n\tconst commitResize = (bounds: BoxModel) => {\n\t\tsetResizeBounds(null)\n\t\tif (!canComment) return\n\t\t// Same commit path as a pin drag, so the configured `dragHistory` governs both \u2014 going\n\t\t// straight to `editor.run` here would make region resizes silently ignore the option.\n\t\tcommitCommentMutation(\n\t\t\teditor,\n\t\t\t// Spread the existing anchor first so the region's pin corner survives a resize.\n\t\t\t({ put }) => put([{ ...thread, anchor: { ...regionAnchor!, ...bounds } }]),\n\t\t\t'drag'\n\t\t)\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{regionBoxBounds && (dragPagePoint || revealed) && (\n\t\t\t\t<RegionBox editor={editor} box={regionBoxBounds} />\n\t\t\t)}\n\t\t\t{regionBoxBounds && revealed && !dragPagePoint && canComment && (\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<button\n\t\t\t\t\tref={markerRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-pin__marker\"\n\t\t\t\t\taria-label={pinLabel}\n\t\t\t\t\taria-expanded={open}\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\t// Pointer activation is already handled by endDrag (which distinguishes a click\n\t\t\t\t\t// from a drag), so only take keyboard-synthesised clicks here \u2014 those report\n\t\t\t\t\t// `detail === 0` \u2014 or the thread would toggle twice per mouse click.\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\tif (e.detail === 0) toggleOpen()\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerEnter={previewHandlers.onPointerEnter}\n\t\t\t\t\tonPointerLeave={previewHandlers.onPointerLeave}\n\t\t\t\t\t// Focus stands in for hover, so tabbing to a marker gets the same preview.\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\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</button>\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\tbase={{\n\t\t\t\t\t\t\tx: renderPoint.x + POPOVER_OFFSET.thread.x,\n\t\t\t\t\t\t\ty: renderPoint.y + POPOVER_OFFSET.thread.y - THREAD_HEADER_BLOCK,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t</ThreadPopover>\n\t\t\t\t)}\n\t\t\t\t{/* Not while dragging: the pin is being moved, not read, and a panel trailing the\n\t\t\t\t cursor would obscure the drop target. */}\n\t\t\t\t{previewShown && !dragPagePoint && (\n\t\t\t\t\t<ThreadPreview\n\t\t\t\t\t\teditor={editor}\n\t\t\t\t\t\tthreads={previewThreads}\n\t\t\t\t\t\tvariant=\"thread\"\n\t\t\t\t\t\tpoint={renderPoint}\n\t\t\t\t\t\tonSelectThread={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\t\tresolveAuthor={resolveAuthor}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["ThreadPin", "point"]
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef } from "react";
3
3
  import {
4
+ defineMessages,
4
5
  EditorAtom,
5
6
  EditorPortal,
6
7
  usePassThroughWheelEvents,
@@ -13,6 +14,9 @@ import { useComments } from "./hooks.mjs";
13
14
  import { useCommentingOptions } from "./options.mjs";
14
15
  import { openStackId, openThreadId } from "./state.mjs";
15
16
  import { POPOVER_OFFSET, toCardProps, useResolveName } from "./thread-view.mjs";
17
+ const messages = defineMessages({
18
+ commentsPreviewMore: { id: "comments.preview-more", defaultMessage: "{count} more" }
19
+ });
16
20
  const PREVIEW_OPEN_DELAY_MS = 180;
17
21
  const PREVIEW_CLOSE_DELAY_MS = 220;
18
22
  const PREVIEW_MAX_THREADS = 5;
@@ -146,7 +150,7 @@ function ThreadPreview({
146
150
  thread.id
147
151
  );
148
152
  }),
149
- overflow > 0 && /* @__PURE__ */ jsx("div", { className: "tlui-cmt-preview-more", children: msg("comments.preview-more").replace("{count}", String(overflow)) })
153
+ overflow > 0 && /* @__PURE__ */ jsx("div", { className: "tlui-cmt-preview-more", children: msg(messages.commentsPreviewMore, { count: overflow }) })
150
154
  ] })
151
155
  }
152
156
  ) });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/canvas/thread-preview.tsx"],
4
- "sourcesContent": ["import { useEffect, useMemo, useRef, type CSSProperties } from 'react'\nimport {\n\tEditor,\n\tEditorAtom,\n\tEditorPortal,\n\tTLComment,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { replyCountLabel } from '../ui/reply-count'\nimport { type CommentingContext } from './context'\nimport { useComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { openStackId, openThreadId } from './state'\nimport { POPOVER_OFFSET, toCardProps, useResolveName } from './thread-view'\n\n/**\n * Hover previews for every canvas marker \u2014 a single pin, a coincident stack, or a cluster badge.\n * Hovering shows the thread(s) behind the marker as cards; the marker's own click is unchanged.\n *\n * The panel is live, not a passive tooltip: the pointer can travel into it and click a card to open\n * that thread. Two pieces make that work \u2014 the close delay survives the trip across the gap, and\n * the panel carries an invisible bridge over it (see `.tlui-cmt-canvas-preview::before`).\n *\n * Opening a thread from a card needs nothing more than setting `openThreadId`, even for one folded\n * inside a badge: `collectClusterLeaves` skips the open thread, so it drops out and renders itself.\n */\n\n/** How long the pointer must rest on a marker before its preview appears. */\nconst PREVIEW_OPEN_DELAY_MS = 180\n/**\n * Grace period after the pointer leaves the marker *or* the panel \u2014 long enough to cross the gap\n * between them, which the bridge element covers geometrically.\n */\nconst PREVIEW_CLOSE_DELAY_MS = 220\n/** Cards shown before the panel falls back to a \"+N more\" line. */\nconst PREVIEW_MAX_THREADS = 5\n\n/** A thread paired with the comment that opens it \u2014 one card of a preview. */\nexport interface ThreadPreviewCard {\n\tthread: TLCommentThread\n\tfirst: TLComment\n}\n\n/**\n * The cards a marker's preview will show, and how many threads it will summarise as \"+N more\".\n *\n * A thread can exist before its opening comment does \u2014 a collaborator's, mid-sync. Those are\n * dropped rather than rendered blank, and don't count toward the overflow tally.\n */\nexport function selectPreviewCards(\n\tthreads: readonly TLCommentThread[],\n\tfirstCommentOf: (thread: TLCommentThread) => TLComment | undefined,\n\tmax = PREVIEW_MAX_THREADS\n): { cards: ThreadPreviewCard[]; overflow: number } {\n\tconst readable: ThreadPreviewCard[] = []\n\tfor (const thread of threads) {\n\t\tconst first = firstCommentOf(thread)\n\t\tif (first) readable.push({ thread, first })\n\t}\n\treturn { cards: readable.slice(0, max), overflow: Math.max(0, readable.length - max) }\n}\n\n/**\n * What a marker's click opens, which is what its preview imitates \u2014 the panel's surface, its\n * width, and how it lays a comment out.\n *\n * `'thread'` previews a single pin: a thread panel without the header. `'list'` previews a\n * coincident stack or a cluster: one card per thread, like the stack list.\n */\nexport type ThreadPreviewVariant = 'thread' | 'list'\n\n/**\n * Which marker's preview is showing, or null. One atom for the whole layer, so previews are\n * mutually exclusive: the close delay can leave an outgoing marker's timer pending when the next\n * opens, and per-component state would briefly show both.\n */\nconst hoveredMarkerId = new EditorAtom<string | null>('commentHoveredMarkerId', () => null)\n\n/**\n * Hover state for one marker. Returns whether its preview should render, plus the pointer handlers\n * to spread onto the marker element. `markerId` must be stable and unique per marker across the\n * layer \u2014 prefix by kind, or a stack collides with its oldest member's own pin.\n */\nexport function useMarkerPreview(editor: Editor, markerId: string) {\n\tconst openTimer = useRef(0)\n\tconst closeTimer = useRef(0)\n\n\t// Previews are a resting-state affordance. While a thread or stack list is open, that view is\n\t// the thing being read \u2014 a preview floating over it would just compete with it.\n\tconst suppressed = useValue(\n\t\t'marker preview suppressed',\n\t\t() => openThreadId.get(editor) !== null || openStackId.get(editor) !== null,\n\t\t[editor]\n\t)\n\tconst shown = useValue('marker preview shown', () => hoveredMarkerId.get(editor) === markerId, [\n\t\teditor,\n\t\tmarkerId,\n\t])\n\n\tuseEffect(() => {\n\t\tconst open = openTimer\n\t\tconst close = closeTimer\n\t\treturn () => {\n\t\t\twindow.clearTimeout(open.current)\n\t\t\twindow.clearTimeout(close.current)\n\t\t\t// Don't strand the atom on a marker that has unmounted \u2014 zoomed into a cluster, deleted,\n\t\t\t// or folded away \u2014 or no other marker's preview could ever show.\n\t\t\tif (hoveredMarkerId.get(editor) === markerId) hoveredMarkerId.set(editor, null)\n\t\t}\n\t}, [editor, markerId])\n\n\t// Suppression can start *while* a preview is up: clicking the hovered pin opens its thread.\n\t// Retract the preview rather than leaving it stranded behind the popover that just opened.\n\tuseEffect(() => {\n\t\tif (suppressed && hoveredMarkerId.get(editor) === markerId) {\n\t\t\thoveredMarkerId.set(editor, null)\n\t\t}\n\t}, [suppressed, editor, markerId])\n\n\tconst onPointerEnter = () => {\n\t\twindow.clearTimeout(openTimer.current)\n\t\twindow.clearTimeout(closeTimer.current)\n\t\tif (suppressed) return\n\t\topenTimer.current = window.setTimeout(\n\t\t\t() => hoveredMarkerId.set(editor, markerId),\n\t\t\tPREVIEW_OPEN_DELAY_MS\n\t\t)\n\t}\n\n\tconst onPointerLeave = () => {\n\t\twindow.clearTimeout(openTimer.current)\n\t\twindow.clearTimeout(closeTimer.current)\n\t\tcloseTimer.current = window.setTimeout(() => {\n\t\t\tif (hoveredMarkerId.get(editor) === markerId) hoveredMarkerId.set(editor, null)\n\t\t}, PREVIEW_CLOSE_DELAY_MS)\n\t}\n\n\treturn {\n\t\tpreviewShown: shown && !suppressed,\n\t\tpreviewHandlers: { onPointerEnter, onPointerLeave },\n\t}\n}\n\n/**\n * The hover panel: each thread's opening comment as a read-only card, capped with a \"+N more\" line.\n * Mounted only while hovering, so its store subscription costs nothing at rest.\n */\nexport function ThreadPreview({\n\teditor,\n\tthreads,\n\tvariant,\n\tpoint,\n\tonSelectThread,\n\tonPointerEnter,\n\tonPointerLeave,\n\t...props\n}: Pick<CommentingContext, 'currentUserId' | 'resolveAuthor'> & {\n\teditor: Editor\n\t/** The marker's threads, in the order they should read (oldest first). */\n\tthreads: readonly TLCommentThread[]\n\t/** What the marker's click opens, which this panel imitates. */\n\tvariant: ThreadPreviewVariant\n\t/** The marker's anchor point in viewport space \u2014 the same origin its popover is placed from. */\n\tpoint: { x: number; y: number }\n\t/** Open a thread from its card. Omit to leave the cards inert. */\n\tonSelectThread?(thread: TLCommentThread): void\n\t/** The owning marker's hover handlers, so the panel counts as part of its hover region. */\n\tonPointerEnter?(): void\n\tonPointerLeave?(): void\n}) {\n\tconst options = useCommentingOptions()\n\tconst msg = useTranslation()\n\tconst comments = useComments(editor)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\n\t// The panel floats over the canvas and scrolls nothing of its own, so a wheel on it should zoom\n\t// and pan the canvas underneath \u2014 like every other tldraw panel, and like the popover it\n\t// previews. Without this the canvas would freeze wherever the preview happened to be.\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\n\t// Each thread's opening comment and its total comment count. `useComments` is oldest-first, so\n\t// the first hit per thread is that thread's first comment. One pass over every comment beats a\n\t// per-thread hook \u2014 the thread count here is driven by cluster size, which has no fixed bound.\n\tconst { firstByThread, countByThread } = useMemo(() => {\n\t\tconst first = new Map<string, TLComment>()\n\t\tconst count = new Map<string, number>()\n\t\tfor (const comment of comments) {\n\t\t\tif (!first.has(comment.threadId)) first.set(comment.threadId, comment)\n\t\t\tcount.set(comment.threadId, (count.get(comment.threadId) ?? 0) + 1)\n\t\t}\n\t\treturn { firstByThread: first, countByThread: count }\n\t}, [comments])\n\n\tconst { cards, overflow } = useMemo(\n\t\t() => selectPreviewCards(threads, (thread) => firstByThread.get(thread.id)),\n\t\t[threads, firstByThread]\n\t)\n\n\t// Nothing readable yet \u2014 render no panel rather than an empty one. It matters most for the\n\t// thread variant, where the panel *is* the card's surface: an empty one would paint as a blank\n\t// box floating on the canvas.\n\tif (cards.length === 0) return null\n\n\t// Placed at the same origin as the popover this previews, so the two only differ by what their\n\t// stylesheets do \u2014 see `.tlui-cmt-canvas-preview__panel--thread` for the header compensation.\n\tconst offset = POPOVER_OFFSET[variant]\n\t// A thread preview is one comment on the panel itself, so the panel carries the hover and the\n\t// click. A list preview puts each thread on its own card, as the stack list does.\n\tconst isThread = variant === 'thread'\n\tconst panelClass = [\n\t\t'tlui-cmt-canvas-preview__panel',\n\t\t`tlui-cmt-canvas-preview__panel--${variant}`,\n\t\tisThread && onSelectThread ? 'tlui-cmt-canvas-preview__panel--selectable' : '',\n\t]\n\t\t.filter(Boolean)\n\t\t.join(' ')\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t{/* The root is the hover region \u2014 it carries the bridge back to the marker \u2014 while the panel\n\t\t inside it is the visible surface. Keeping them apart is what lets the surface light up on\n\t\t its own hover without the bridge (which reaches back over the marker) lighting it up too. */}\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={`tlui-cmt-canvas-preview tlui-cmt-canvas-preview--${variant}`}\n\t\t\t\tstyle={\n\t\t\t\t\t{\n\t\t\t\t\t\tleft: point.x + offset.x,\n\t\t\t\t\t\ttop: point.y + offset.y,\n\t\t\t\t\t\t// The stylesheet sizes the hover bridge against this, so the gap it spans follows\n\t\t\t\t\t\t// the offset rather than being restated as a second magic number.\n\t\t\t\t\t\t'--tlui-cmt-preview-offset': `${offset.x}px`,\n\t\t\t\t\t} as CSSProperties\n\t\t\t\t}\n\t\t\t\tonPointerEnter={onPointerEnter}\n\t\t\t\tonPointerLeave={onPointerLeave}\n\t\t\t\t// The panel sits over the canvas; a press on it is not a canvas press.\n\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t>\n\t\t\t\t<div className={panelClass}>\n\t\t\t\t\t{cards.map(({ thread, first }) => {\n\t\t\t\t\t\t// The preview shows only the opening comment, so its reply count is what tells the\n\t\t\t\t\t\t// reader the thread continues past what they see.\n\t\t\t\t\t\tconst replies = replyCountLabel(msg, (countByThread.get(thread.id) ?? 1) - 1)\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\tisThread\n\t\t\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t\t\t: onSelectThread\n\t\t\t\t\t\t\t\t\t\t\t? 'tlui-cmt-preview-card tlui-cmt-preview-card--selectable'\n\t\t\t\t\t\t\t\t\t\t\t: 'tlui-cmt-preview-card'\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tonClick={\n\t\t\t\t\t\t\t\t\tonSelectThread\n\t\t\t\t\t\t\t\t\t\t? (e) => {\n\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\t\tonSelectThread(thread)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<CommentCard\n\t\t\t\t\t\t\t\t\t{...toCardProps(first, props, options.components, resolveName)}\n\t\t\t\t\t\t\t\t\tfooter={\n\t\t\t\t\t\t\t\t\t\treplies ? <span className=\"tlui-cmt-card__replies\">{replies}</span> : undefined\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t/>\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{overflow > 0 && (\n\t\t\t\t\t\t<div className=\"tlui-cmt-preview-more\">\n\t\t\t\t\t\t\t{msg('comments.preview-more').replace('{count}', String(overflow))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n\n/** Order a marker's threads for reading: oldest first, id as the tiebreak so it's stable. */\nexport function sortThreadsForPreview(\n\tthreads: readonly TLCommentThread[]\n): readonly TLCommentThread[] {\n\treturn [...threads].sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useEffect, useMemo, useRef, type CSSProperties } from 'react'\nimport {\n\tdefineMessages,\n\tEditor,\n\tEditorAtom,\n\tEditorPortal,\n\tTLComment,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { replyCountLabel } from '../ui/reply-count'\nimport { type CommentingContext } from './context'\nimport { useComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { openStackId, openThreadId } from './state'\nimport { POPOVER_OFFSET, toCardProps, useResolveName } from './thread-view'\n\nconst messages = defineMessages({\n\tcommentsPreviewMore: { id: 'comments.preview-more', defaultMessage: '{count} more' },\n})\n\n/**\n * Hover previews for every canvas marker \u2014 a single pin, a coincident stack, or a cluster badge.\n * Hovering shows the thread(s) behind the marker as cards; the marker's own click is unchanged.\n *\n * The panel is live, not a passive tooltip: the pointer can travel into it and click a card to open\n * that thread. Two pieces make that work \u2014 the close delay survives the trip across the gap, and\n * the panel carries an invisible bridge over it (see `.tlui-cmt-canvas-preview::before`).\n *\n * Opening a thread from a card needs nothing more than setting `openThreadId`, even for one folded\n * inside a badge: `collectClusterLeaves` skips the open thread, so it drops out and renders itself.\n */\n\n/** How long the pointer must rest on a marker before its preview appears. */\nconst PREVIEW_OPEN_DELAY_MS = 180\n/**\n * Grace period after the pointer leaves the marker *or* the panel \u2014 long enough to cross the gap\n * between them, which the bridge element covers geometrically.\n */\nconst PREVIEW_CLOSE_DELAY_MS = 220\n/** Cards shown before the panel falls back to a \"+N more\" line. */\nconst PREVIEW_MAX_THREADS = 5\n\n/** A thread paired with the comment that opens it \u2014 one card of a preview. */\nexport interface ThreadPreviewCard {\n\tthread: TLCommentThread\n\tfirst: TLComment\n}\n\n/**\n * The cards a marker's preview will show, and how many threads it will summarise as \"+N more\".\n *\n * A thread can exist before its opening comment does \u2014 a collaborator's, mid-sync. Those are\n * dropped rather than rendered blank, and don't count toward the overflow tally.\n */\nexport function selectPreviewCards(\n\tthreads: readonly TLCommentThread[],\n\tfirstCommentOf: (thread: TLCommentThread) => TLComment | undefined,\n\tmax = PREVIEW_MAX_THREADS\n): { cards: ThreadPreviewCard[]; overflow: number } {\n\tconst readable: ThreadPreviewCard[] = []\n\tfor (const thread of threads) {\n\t\tconst first = firstCommentOf(thread)\n\t\tif (first) readable.push({ thread, first })\n\t}\n\treturn { cards: readable.slice(0, max), overflow: Math.max(0, readable.length - max) }\n}\n\n/**\n * What a marker's click opens, which is what its preview imitates \u2014 the panel's surface, its\n * width, and how it lays a comment out.\n *\n * `'thread'` previews a single pin: a thread panel without the header. `'list'` previews a\n * coincident stack or a cluster: one card per thread, like the stack list.\n */\nexport type ThreadPreviewVariant = 'thread' | 'list'\n\n/**\n * Which marker's preview is showing, or null. One atom for the whole layer, so previews are\n * mutually exclusive: the close delay can leave an outgoing marker's timer pending when the next\n * opens, and per-component state would briefly show both.\n */\nconst hoveredMarkerId = new EditorAtom<string | null>('commentHoveredMarkerId', () => null)\n\n/**\n * Hover state for one marker. Returns whether its preview should render, plus the pointer handlers\n * to spread onto the marker element. `markerId` must be stable and unique per marker across the\n * layer \u2014 prefix by kind, or a stack collides with its oldest member's own pin.\n */\nexport function useMarkerPreview(editor: Editor, markerId: string) {\n\tconst openTimer = useRef(0)\n\tconst closeTimer = useRef(0)\n\n\t// Previews are a resting-state affordance. While a thread or stack list is open, that view is\n\t// the thing being read \u2014 a preview floating over it would just compete with it.\n\tconst suppressed = useValue(\n\t\t'marker preview suppressed',\n\t\t() => openThreadId.get(editor) !== null || openStackId.get(editor) !== null,\n\t\t[editor]\n\t)\n\tconst shown = useValue('marker preview shown', () => hoveredMarkerId.get(editor) === markerId, [\n\t\teditor,\n\t\tmarkerId,\n\t])\n\n\tuseEffect(() => {\n\t\tconst open = openTimer\n\t\tconst close = closeTimer\n\t\treturn () => {\n\t\t\twindow.clearTimeout(open.current)\n\t\t\twindow.clearTimeout(close.current)\n\t\t\t// Don't strand the atom on a marker that has unmounted \u2014 zoomed into a cluster, deleted,\n\t\t\t// or folded away \u2014 or no other marker's preview could ever show.\n\t\t\tif (hoveredMarkerId.get(editor) === markerId) hoveredMarkerId.set(editor, null)\n\t\t}\n\t}, [editor, markerId])\n\n\t// Suppression can start *while* a preview is up: clicking the hovered pin opens its thread.\n\t// Retract the preview rather than leaving it stranded behind the popover that just opened.\n\tuseEffect(() => {\n\t\tif (suppressed && hoveredMarkerId.get(editor) === markerId) {\n\t\t\thoveredMarkerId.set(editor, null)\n\t\t}\n\t}, [suppressed, editor, markerId])\n\n\tconst onPointerEnter = () => {\n\t\twindow.clearTimeout(openTimer.current)\n\t\twindow.clearTimeout(closeTimer.current)\n\t\tif (suppressed) return\n\t\topenTimer.current = window.setTimeout(\n\t\t\t() => hoveredMarkerId.set(editor, markerId),\n\t\t\tPREVIEW_OPEN_DELAY_MS\n\t\t)\n\t}\n\n\tconst onPointerLeave = () => {\n\t\twindow.clearTimeout(openTimer.current)\n\t\twindow.clearTimeout(closeTimer.current)\n\t\tcloseTimer.current = window.setTimeout(() => {\n\t\t\tif (hoveredMarkerId.get(editor) === markerId) hoveredMarkerId.set(editor, null)\n\t\t}, PREVIEW_CLOSE_DELAY_MS)\n\t}\n\n\treturn {\n\t\tpreviewShown: shown && !suppressed,\n\t\tpreviewHandlers: { onPointerEnter, onPointerLeave },\n\t}\n}\n\n/**\n * The hover panel: each thread's opening comment as a read-only card, capped with a \"+N more\" line.\n * Mounted only while hovering, so its store subscription costs nothing at rest.\n */\nexport function ThreadPreview({\n\teditor,\n\tthreads,\n\tvariant,\n\tpoint,\n\tonSelectThread,\n\tonPointerEnter,\n\tonPointerLeave,\n\t...props\n}: Pick<CommentingContext, 'currentUserId' | 'resolveAuthor'> & {\n\teditor: Editor\n\t/** The marker's threads, in the order they should read (oldest first). */\n\tthreads: readonly TLCommentThread[]\n\t/** What the marker's click opens, which this panel imitates. */\n\tvariant: ThreadPreviewVariant\n\t/** The marker's anchor point in viewport space \u2014 the same origin its popover is placed from. */\n\tpoint: { x: number; y: number }\n\t/** Open a thread from its card. Omit to leave the cards inert. */\n\tonSelectThread?(thread: TLCommentThread): void\n\t/** The owning marker's hover handlers, so the panel counts as part of its hover region. */\n\tonPointerEnter?(): void\n\tonPointerLeave?(): void\n}) {\n\tconst options = useCommentingOptions()\n\tconst msg = useTranslation()\n\tconst comments = useComments(editor)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\n\t// The panel floats over the canvas and scrolls nothing of its own, so a wheel on it should zoom\n\t// and pan the canvas underneath \u2014 like every other tldraw panel, and like the popover it\n\t// previews. Without this the canvas would freeze wherever the preview happened to be.\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\n\t// Each thread's opening comment and its total comment count. `useComments` is oldest-first, so\n\t// the first hit per thread is that thread's first comment. One pass over every comment beats a\n\t// per-thread hook \u2014 the thread count here is driven by cluster size, which has no fixed bound.\n\tconst { firstByThread, countByThread } = useMemo(() => {\n\t\tconst first = new Map<string, TLComment>()\n\t\tconst count = new Map<string, number>()\n\t\tfor (const comment of comments) {\n\t\t\tif (!first.has(comment.threadId)) first.set(comment.threadId, comment)\n\t\t\tcount.set(comment.threadId, (count.get(comment.threadId) ?? 0) + 1)\n\t\t}\n\t\treturn { firstByThread: first, countByThread: count }\n\t}, [comments])\n\n\tconst { cards, overflow } = useMemo(\n\t\t() => selectPreviewCards(threads, (thread) => firstByThread.get(thread.id)),\n\t\t[threads, firstByThread]\n\t)\n\n\t// Nothing readable yet \u2014 render no panel rather than an empty one. It matters most for the\n\t// thread variant, where the panel *is* the card's surface: an empty one would paint as a blank\n\t// box floating on the canvas.\n\tif (cards.length === 0) return null\n\n\t// Placed at the same origin as the popover this previews, so the two only differ by what their\n\t// stylesheets do \u2014 see `.tlui-cmt-canvas-preview__panel--thread` for the header compensation.\n\tconst offset = POPOVER_OFFSET[variant]\n\t// A thread preview is one comment on the panel itself, so the panel carries the hover and the\n\t// click. A list preview puts each thread on its own card, as the stack list does.\n\tconst isThread = variant === 'thread'\n\tconst panelClass = [\n\t\t'tlui-cmt-canvas-preview__panel',\n\t\t`tlui-cmt-canvas-preview__panel--${variant}`,\n\t\tisThread && onSelectThread ? 'tlui-cmt-canvas-preview__panel--selectable' : '',\n\t]\n\t\t.filter(Boolean)\n\t\t.join(' ')\n\n\treturn (\n\t\t<EditorPortal>\n\t\t\t{/* The root is the hover region \u2014 it carries the bridge back to the marker \u2014 while the panel\n\t\t inside it is the visible surface. Keeping them apart is what lets the surface light up on\n\t\t its own hover without the bridge (which reaches back over the marker) lighting it up too. */}\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={`tlui-cmt-canvas-preview tlui-cmt-canvas-preview--${variant}`}\n\t\t\t\tstyle={\n\t\t\t\t\t{\n\t\t\t\t\t\tleft: point.x + offset.x,\n\t\t\t\t\t\ttop: point.y + offset.y,\n\t\t\t\t\t\t// The stylesheet sizes the hover bridge against this, so the gap it spans follows\n\t\t\t\t\t\t// the offset rather than being restated as a second magic number.\n\t\t\t\t\t\t'--tlui-cmt-preview-offset': `${offset.x}px`,\n\t\t\t\t\t} as CSSProperties\n\t\t\t\t}\n\t\t\t\tonPointerEnter={onPointerEnter}\n\t\t\t\tonPointerLeave={onPointerLeave}\n\t\t\t\t// The panel sits over the canvas; a press on it is not a canvas press.\n\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t>\n\t\t\t\t<div className={panelClass}>\n\t\t\t\t\t{cards.map(({ thread, first }) => {\n\t\t\t\t\t\t// The preview shows only the opening comment, so its reply count is what tells the\n\t\t\t\t\t\t// reader the thread continues past what they see.\n\t\t\t\t\t\tconst replies = replyCountLabel(msg, (countByThread.get(thread.id) ?? 1) - 1)\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={thread.id}\n\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\tisThread\n\t\t\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t\t\t: onSelectThread\n\t\t\t\t\t\t\t\t\t\t\t? 'tlui-cmt-preview-card tlui-cmt-preview-card--selectable'\n\t\t\t\t\t\t\t\t\t\t\t: 'tlui-cmt-preview-card'\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tonClick={\n\t\t\t\t\t\t\t\t\tonSelectThread\n\t\t\t\t\t\t\t\t\t\t? (e) => {\n\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\t\tonSelectThread(thread)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<CommentCard\n\t\t\t\t\t\t\t\t\t{...toCardProps(first, props, options.components, resolveName)}\n\t\t\t\t\t\t\t\t\tfooter={\n\t\t\t\t\t\t\t\t\t\treplies ? <span className=\"tlui-cmt-card__replies\">{replies}</span> : undefined\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t/>\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{overflow > 0 && (\n\t\t\t\t\t\t<div className=\"tlui-cmt-preview-more\">\n\t\t\t\t\t\t\t{msg(messages.commentsPreviewMore, { count: overflow })}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</EditorPortal>\n\t)\n}\n\n/** Order a marker's threads for reading: oldest first, id as the tiebreak so it's stable. */\nexport function sortThreadsForPreview(\n\tthreads: readonly TLCommentThread[]\n): readonly TLCommentThread[] {\n\treturn [...threads].sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,13 +1,13 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { memo, useEffect, useRef } from "react";
3
3
  import {
4
+ defineMessages,
4
5
  usePassThroughWheelEvents,
5
6
  useTranslation,
6
7
  useValue
7
8
  } from "tldraw";
8
9
  import { CommentCard } from "../ui/comment-card.mjs";
9
10
  import { CountBadge } from "../ui/count-badge.mjs";
10
- import { UNKNOWN_AUTHOR } from "./comment-render.mjs";
11
11
  import { useThreadComments } from "./hooks.mjs";
12
12
  import { useCommentingOptions } from "./options.mjs";
13
13
  import { pinStackKey } from "./pin-stacking.mjs";
@@ -21,6 +21,18 @@ import {
21
21
  toCardProps,
22
22
  useResolveName
23
23
  } from "./thread-view.mjs";
24
+ const messages = defineMessages({
25
+ commentsUnknownAuthor: { id: "comments.unknown-author", defaultMessage: "Someone" },
26
+ commentsPinLabel: { id: "comments.pin-label", defaultMessage: "Comment by {name}" },
27
+ commentsPinLabelResolved: {
28
+ id: "comments.pin-label-resolved",
29
+ defaultMessage: "Resolved comment by {name}"
30
+ },
31
+ commentsStackLabel: {
32
+ id: "comments.stack-label",
33
+ defaultMessage: "{count, plural, one {# comment at this spot} other {# comments at this spot}}"
34
+ }
35
+ });
24
36
  const ThreadStackPin = memo(function ThreadStackPin2({
25
37
  editor,
26
38
  threads,
@@ -96,7 +108,7 @@ const ThreadStackPin = memo(function ThreadStackPin2({
96
108
  ref: badgeRef,
97
109
  type: "button",
98
110
  className: "tlui-cmt-button tlui-cmt-canvas-stack-badge",
99
- "aria-label": msg("comments.stack-label").replace("{count}", String(threads.length)),
111
+ "aria-label": msg(messages.commentsStackLabel, { count: threads.length }),
100
112
  "aria-expanded": open,
101
113
  onPointerDown: (e) => e.stopPropagation(),
102
114
  onClick: (e) => {
@@ -171,8 +183,11 @@ function StackThreadCard({
171
183
  type: "button",
172
184
  className: "tlui-cmt-button tlui-cmt-stack-list__card-action",
173
185
  "aria-label": msg(
174
- thread.resolved ? "comments.pin-label-resolved" : "comments.pin-label"
175
- ).replace("{name}", props.resolveAuthor(thread.createdBy)?.name ?? UNKNOWN_AUTHOR),
186
+ thread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,
187
+ {
188
+ name: props.resolveAuthor(thread.createdBy)?.name ?? msg(messages.commentsUnknownAuthor)
189
+ }
190
+ ),
176
191
  onClick: open
177
192
  }
178
193
  ),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/canvas/thread-stack.tsx"],
4
- "sourcesContent": ["import { type MouseEvent as ReactMouseEvent, memo, useEffect, useRef } from 'react'\nimport {\n\tEditor,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { CountBadge } from '../ui/count-badge'\nimport { UNKNOWN_AUTHOR } from './comment-render'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { pinStackKey } from './pin-stacking'\nimport { openStackId, openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport { anchorPagePoint, anchorViewportPoint } from './thread-state'\nimport {\n\tPOPOVER_OFFSET,\n\tThreadPopover,\n\tThreadView,\n\ttoCardProps,\n\tuseResolveName,\n} from './thread-view'\n\n/**\n * The pin for threads whose anchors resolve to the same page point \u2014 pins zooming can never\n * separate, so they share one count badge. Clicking it lists each thread as a card; clicking a card\n * expands that thread in place, via the single open-thread state.\n */\nexport const ThreadStackPin = memo(function ThreadStackPin({\n\teditor,\n\tthreads,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\t/** The stack's threads, oldest first. All resolve to the same anchor point. */\n\tthreads: readonly TLCommentThread[]\n}) {\n\tconst msg = useTranslation()\n\tconst badgeRef = useRef<HTMLButtonElement>(null)\n\t// The badge takes pointer events (to open on click), so wheel input over it would otherwise be\n\t// swallowed instead of zooming \u2014 pass it through to the canvas, as the pin and cluster badge do.\n\tusePassThroughWheelEvents(badgeRef)\n\t// Hovering the badge previews its threads; clicking still opens them as the interactive list.\n\t// The preview is what makes the badge legible before you commit to opening it.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `stack:${threads[0].id}`)\n\t// The list stays open while a member thread is expanded, so Escape steps back: expanded thread ->\n\t// card list -> closed. Held in editor state because this pin remounts as its owning render path\n\t// changes, and keyed by the coincident page point so the open state survives losing a member.\n\tconst stackId = useValue(\n\t\t'stack id',\n\t\t() => {\n\t\t\tconst pagePoint = anchorPagePoint(editor, threads[0].anchor)\n\t\t\treturn pagePoint ? pinStackKey(pagePoint) : threads[0].id\n\t\t},\n\t\t[editor, threads]\n\t)\n\tconst listOpen = useValue('stack list open', () => openStackId.get(editor) === stackId, [\n\t\teditor,\n\t\tstackId,\n\t])\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst openMember = threads.find((thread) => thread.id === openId)\n\tconst open = listOpen || openMember !== undefined\n\n\tconst point = useValue(\n\t\t'stack point',\n\t\t() => {\n\t\t\tconst first = threads[0]\n\t\t\tif (first.pageId !== editor.getCurrentPageId()) return null\n\t\t\t// The badge hangs off its anchor point bottom-left like a pin, and applies the same imprecise-shape\n\t\t\t// inset the pins it stands in for would (see ThreadPin) \u2014 otherwise the marker would snap from tucked\n\t\t\t// inside the shape to the raw corner the moment a second comment turns a pin into a stack.\n\t\t\treturn anchorViewportPoint(editor, first.anchor)\n\t\t},\n\t\t[editor, threads]\n\t)\n\n\t// Clicking outside the popover (and off the badge) closes the whole stack \u2014 mirrors the\n\t// single pin's dismiss. Capture phase + class checks, since the popover portals elsewhere.\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 badge = badgeRef.current\n\t\t\tif (badge && badge.contains(target)) return\n\t\t\t// A click inside a menu/popover layered above us belongs to that layer; defer to its\n\t\t\t// own dismissal instead of closing the stack 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\topenStackId.set(editor, null)\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\t// Escape with only the card list showing closes it. When a member thread is expanded, the\n\t// layer's Escape handler collapses that first and marks the event consumed \u2014 stepping back\n\t// to the list rather than closing everything at once.\n\tuseEffect(() => {\n\t\tif (!listOpen) return\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || e.defaultPrevented) return\n\t\t\topenStackId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [listOpen, editor])\n\n\tif (!point) return null\n\n\tconst toggle = () => {\n\t\tif (open) {\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t} else {\n\t\t\topenStackId.set(editor, stackId)\n\t\t}\n\t}\n\n\t// The expanded thread gains a header that pushes its first comment down from where the hover preview\n\t// showed it. When that thread is the list's first, lift the whole list so its \"You\" holds position\n\t// across hover -> open. Only the first entry \u2014 lifting can't also hold a lower thread's neighbours.\n\tconst liftForHeader = threads[0].id === openId ? 42 : 0\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"tlui-cmt-canvas-pin\" style={{ left: point.x, top: point.y }}>\n\t\t\t\t<button\n\t\t\t\t\tref={badgeRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-stack-badge\"\n\t\t\t\t\taria-label={msg('comments.stack-label').replace('{count}', String(threads.length))}\n\t\t\t\t\taria-expanded={open}\n\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\ttoggle()\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\n\t\t\t\t>\n\t\t\t\t\t<CountBadge count={threads.length} open={open} />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{previewShown && !open && (\n\t\t\t\t<ThreadPreview\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthreads={threads}\n\t\t\t\t\t// Lines the preview up with the stack list itself, so opening it leaves the cards\n\t\t\t\t\t// exactly where the preview had them.\n\t\t\t\t\tvariant=\"list\"\n\t\t\t\t\tpoint={point}\n\t\t\t\t\t// Picking a card from the preview lands in the same place clicking the badge and\n\t\t\t\t\t// then the card would: the list open, that thread expanded within it.\n\t\t\t\t\tonSelectThread={(thread) => {\n\t\t\t\t\t\topenStackId.set(editor, stackId)\n\t\t\t\t\t\topenThreadId.set(editor, thread.id)\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\tresolveAuthor={props.resolveAuthor}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{open && (\n\t\t\t\t<ThreadPopover\n\t\t\t\t\tbase={{\n\t\t\t\t\t\tx: point.x + POPOVER_OFFSET.list.x,\n\t\t\t\t\t\ty: point.y + POPOVER_OFFSET.list.y - liftForHeader,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tlui-cmt-stack-list\">\n\t\t\t\t\t\t{threads.map((thread) =>\n\t\t\t\t\t\t\tthread.id === openId ? (\n\t\t\t\t\t\t\t\t<div key={thread.id} className=\"tlui-cmt-stack-list__thread\">\n\t\t\t\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<StackThreadCard\n\t\t\t\t\t\t\t\t\tkey={thread.id}\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\tonOpen={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ThreadPopover>\n\t\t\t)}\n\t\t</>\n\t)\n})\n\n/** A collapsed stack entry: the thread's first comment as a card; clicking expands the thread. */\nfunction StackThreadCard({\n\teditor,\n\tthread,\n\tonOpen,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread; onOpen(): void }) {\n\tconst msg = useTranslation()\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\tconst first = comments[0]\n\tif (!first) return null\n\tconst open = (e: ReactMouseEvent) => {\n\t\te.stopPropagation()\n\t\tonOpen()\n\t}\n\treturn (\n\t\t<div className=\"tlui-cmt-stack-list__card\" onClick={open}>\n\t\t\t{/* The card's keyboard affordance. A button *wrapping* the card would put the comment\n\t\t\t body inside it, and a body renders its links as real anchors \u2014 interactive content\n\t\t\t nested in a button, which is an invalid content model and reads to assistive tech as a\n\t\t\t broken control. So the button covers the card as a sibling instead, leaving the body's\n\t\t\t own links above it and still reachable. */}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-stack-list__card-action\"\n\t\t\t\taria-label={msg(\n\t\t\t\t\tthread.resolved ? 'comments.pin-label-resolved' : 'comments.pin-label'\n\t\t\t\t).replace('{name}', props.resolveAuthor(thread.createdBy)?.name ?? UNKNOWN_AUTHOR)}\n\t\t\t\tonClick={open}\n\t\t\t/>\n\t\t\t<CommentCard {...toCardProps(first, props, options.components, resolveName)} />\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { type MouseEvent as ReactMouseEvent, memo, useEffect, useRef } from 'react'\nimport {\n\tdefineMessages,\n\tEditor,\n\tTLCommentThread,\n\tusePassThroughWheelEvents,\n\tuseTranslation,\n\tuseValue,\n} from 'tldraw'\nimport { CommentCard } from '../ui/comment-card'\nimport { CountBadge } from '../ui/count-badge'\nimport { type CommentingContext } from './context'\nimport { useThreadComments } from './hooks'\nimport { useCommentingOptions } from './options'\nimport { pinStackKey } from './pin-stacking'\nimport { openStackId, openThreadId } from './state'\nimport { ThreadPreview, useMarkerPreview } from './thread-preview'\nimport { anchorPagePoint, anchorViewportPoint } from './thread-state'\nimport {\n\tPOPOVER_OFFSET,\n\tThreadPopover,\n\tThreadView,\n\ttoCardProps,\n\tuseResolveName,\n} from './thread-view'\n\nconst messages = defineMessages({\n\tcommentsUnknownAuthor: { id: 'comments.unknown-author', defaultMessage: 'Someone' },\n\tcommentsPinLabel: { id: 'comments.pin-label', defaultMessage: 'Comment by {name}' },\n\tcommentsPinLabelResolved: {\n\t\tid: 'comments.pin-label-resolved',\n\t\tdefaultMessage: 'Resolved comment by {name}',\n\t},\n\tcommentsStackLabel: {\n\t\tid: 'comments.stack-label',\n\t\tdefaultMessage: '{count, plural, one {# comment at this spot} other {# comments at this spot}}',\n\t},\n})\n\n/**\n * The pin for threads whose anchors resolve to the same page point \u2014 pins zooming can never\n * separate, so they share one count badge. Clicking it lists each thread as a card; clicking a card\n * expands that thread in place, via the single open-thread state.\n */\nexport const ThreadStackPin = memo(function ThreadStackPin({\n\teditor,\n\tthreads,\n\t...props\n}: CommentingContext & {\n\teditor: Editor\n\t/** The stack's threads, oldest first. All resolve to the same anchor point. */\n\tthreads: readonly TLCommentThread[]\n}) {\n\tconst msg = useTranslation()\n\tconst badgeRef = useRef<HTMLButtonElement>(null)\n\t// The badge takes pointer events (to open on click), so wheel input over it would otherwise be\n\t// swallowed instead of zooming \u2014 pass it through to the canvas, as the pin and cluster badge do.\n\tusePassThroughWheelEvents(badgeRef)\n\t// Hovering the badge previews its threads; clicking still opens them as the interactive list.\n\t// The preview is what makes the badge legible before you commit to opening it.\n\tconst { previewShown, previewHandlers } = useMarkerPreview(editor, `stack:${threads[0].id}`)\n\t// The list stays open while a member thread is expanded, so Escape steps back: expanded thread ->\n\t// card list -> closed. Held in editor state because this pin remounts as its owning render path\n\t// changes, and keyed by the coincident page point so the open state survives losing a member.\n\tconst stackId = useValue(\n\t\t'stack id',\n\t\t() => {\n\t\t\tconst pagePoint = anchorPagePoint(editor, threads[0].anchor)\n\t\t\treturn pagePoint ? pinStackKey(pagePoint) : threads[0].id\n\t\t},\n\t\t[editor, threads]\n\t)\n\tconst listOpen = useValue('stack list open', () => openStackId.get(editor) === stackId, [\n\t\teditor,\n\t\tstackId,\n\t])\n\tconst openId = useValue('open thread id', () => openThreadId.get(editor), [editor])\n\tconst openMember = threads.find((thread) => thread.id === openId)\n\tconst open = listOpen || openMember !== undefined\n\n\tconst point = useValue(\n\t\t'stack point',\n\t\t() => {\n\t\t\tconst first = threads[0]\n\t\t\tif (first.pageId !== editor.getCurrentPageId()) return null\n\t\t\t// The badge hangs off its anchor point bottom-left like a pin, and applies the same imprecise-shape\n\t\t\t// inset the pins it stands in for would (see ThreadPin) \u2014 otherwise the marker would snap from tucked\n\t\t\t// inside the shape to the raw corner the moment a second comment turns a pin into a stack.\n\t\t\treturn anchorViewportPoint(editor, first.anchor)\n\t\t},\n\t\t[editor, threads]\n\t)\n\n\t// Clicking outside the popover (and off the badge) closes the whole stack \u2014 mirrors the\n\t// single pin's dismiss. Capture phase + class checks, since the popover portals elsewhere.\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 badge = badgeRef.current\n\t\t\tif (badge && badge.contains(target)) return\n\t\t\t// A click inside a menu/popover layered above us belongs to that layer; defer to its\n\t\t\t// own dismissal instead of closing the stack 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\topenStackId.set(editor, null)\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\t// Escape with only the card list showing closes it. When a member thread is expanded, the\n\t// layer's Escape handler collapses that first and marks the event consumed \u2014 stepping back\n\t// to the list rather than closing everything at once.\n\tuseEffect(() => {\n\t\tif (!listOpen) return\n\t\tconst onKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key !== 'Escape' || e.defaultPrevented) return\n\t\t\topenStackId.set(editor, null)\n\t\t}\n\t\tdocument.addEventListener('keydown', onKeyDown, true)\n\t\treturn () => document.removeEventListener('keydown', onKeyDown, true)\n\t}, [listOpen, editor])\n\n\tif (!point) return null\n\n\tconst toggle = () => {\n\t\tif (open) {\n\t\t\topenStackId.set(editor, null)\n\t\t\topenThreadId.set(editor, null)\n\t\t} else {\n\t\t\topenStackId.set(editor, stackId)\n\t\t}\n\t}\n\n\t// The expanded thread gains a header that pushes its first comment down from where the hover preview\n\t// showed it. When that thread is the list's first, lift the whole list so its \"You\" holds position\n\t// across hover -> open. Only the first entry \u2014 lifting can't also hold a lower thread's neighbours.\n\tconst liftForHeader = threads[0].id === openId ? 42 : 0\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"tlui-cmt-canvas-pin\" style={{ left: point.x, top: point.y }}>\n\t\t\t\t<button\n\t\t\t\t\tref={badgeRef}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-canvas-stack-badge\"\n\t\t\t\t\taria-label={msg(messages.commentsStackLabel, { count: threads.length })}\n\t\t\t\t\taria-expanded={open}\n\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\ttoggle()\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tonFocus={previewHandlers.onPointerEnter}\n\t\t\t\t\tonBlur={previewHandlers.onPointerLeave}\n\t\t\t\t>\n\t\t\t\t\t<CountBadge count={threads.length} open={open} />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{previewShown && !open && (\n\t\t\t\t<ThreadPreview\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tthreads={threads}\n\t\t\t\t\t// Lines the preview up with the stack list itself, so opening it leaves the cards\n\t\t\t\t\t// exactly where the preview had them.\n\t\t\t\t\tvariant=\"list\"\n\t\t\t\t\tpoint={point}\n\t\t\t\t\t// Picking a card from the preview lands in the same place clicking the badge and\n\t\t\t\t\t// then the card would: the list open, that thread expanded within it.\n\t\t\t\t\tonSelectThread={(thread) => {\n\t\t\t\t\t\topenStackId.set(editor, stackId)\n\t\t\t\t\t\topenThreadId.set(editor, thread.id)\n\t\t\t\t\t}}\n\t\t\t\t\t{...previewHandlers}\n\t\t\t\t\tcurrentUserId={props.currentUserId}\n\t\t\t\t\tresolveAuthor={props.resolveAuthor}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t{open && (\n\t\t\t\t<ThreadPopover\n\t\t\t\t\tbase={{\n\t\t\t\t\t\tx: point.x + POPOVER_OFFSET.list.x,\n\t\t\t\t\t\ty: point.y + POPOVER_OFFSET.list.y - liftForHeader,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tlui-cmt-stack-list\">\n\t\t\t\t\t\t{threads.map((thread) =>\n\t\t\t\t\t\t\tthread.id === openId ? (\n\t\t\t\t\t\t\t\t<div key={thread.id} className=\"tlui-cmt-stack-list__thread\">\n\t\t\t\t\t\t\t\t\t<ThreadView editor={editor} thread={thread} {...props} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<StackThreadCard\n\t\t\t\t\t\t\t\t\tkey={thread.id}\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\tonOpen={() => openThreadId.set(editor, thread.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ThreadPopover>\n\t\t\t)}\n\t\t</>\n\t)\n})\n\n/** A collapsed stack entry: the thread's first comment as a card; clicking expands the thread. */\nfunction StackThreadCard({\n\teditor,\n\tthread,\n\tonOpen,\n\t...props\n}: CommentingContext & { editor: Editor; thread: TLCommentThread; onOpen(): void }) {\n\tconst msg = useTranslation()\n\tconst options = useCommentingOptions()\n\tconst comments = useThreadComments(editor, thread.id)\n\tconst resolveName = useResolveName(props.resolveAuthor)\n\tconst first = comments[0]\n\tif (!first) return null\n\tconst open = (e: ReactMouseEvent) => {\n\t\te.stopPropagation()\n\t\tonOpen()\n\t}\n\treturn (\n\t\t<div className=\"tlui-cmt-stack-list__card\" onClick={open}>\n\t\t\t{/* The card's keyboard affordance. A button *wrapping* the card would put the comment\n\t\t\t body inside it, and a body renders its links as real anchors \u2014 interactive content\n\t\t\t nested in a button, which is an invalid content model and reads to assistive tech as a\n\t\t\t broken control. So the button covers the card as a sibling instead, leaving the body's\n\t\t\t own links above it and still reachable. */}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"tlui-cmt-button tlui-cmt-stack-list__card-action\"\n\t\t\t\taria-label={msg(\n\t\t\t\t\tthread.resolved ? messages.commentsPinLabelResolved : messages.commentsPinLabel,\n\t\t\t\t\t{\n\t\t\t\t\t\tname:\n\t\t\t\t\t\t\tprops.resolveAuthor(thread.createdBy)?.name ?? msg(messages.commentsUnknownAuthor),\n\t\t\t\t\t}\n\t\t\t\t)}\n\t\t\t\tonClick={open}\n\t\t\t/>\n\t\t\t<CommentCard {...toCardProps(first, props, options.components, resolveName)} />\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["ThreadStackPin"]
7
7
  }
@@ -2,6 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import {
4
4
  createComment,
5
+ defineMessages,
5
6
  EditorPortal,
6
7
  TldrawUiButton,
7
8
  TldrawUiDropdownMenuContent,
@@ -35,7 +36,7 @@ import {
35
36
  resolveThread
36
37
  } from "./comment-mutations.mjs";
37
38
  import { CommentReactionPicker, CommentReactions } from "./comment-reactions.mjs";
38
- import { UNKNOWN_AUTHOR, UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
39
+ import { UNKNOWN_COMMENT_AUTHOR } from "./comment-render.mjs";
39
40
  import { useThreadComments } from "./hooks.mjs";
40
41
  import { useIsMobileCommenting, useMobilePlacement } from "./mobile-placement.mjs";
41
42
  import {
@@ -45,6 +46,27 @@ import {
45
46
  useCommentingOptions
46
47
  } from "./options.mjs";
47
48
  import { openThreadId } from "./state.mjs";
49
+ const messages = defineMessages({
50
+ actionDelete: { id: "action.delete", defaultMessage: "Delete" },
51
+ commentsCopyLink: { id: "comments.copy-link", defaultMessage: "Copy link" },
52
+ commentsDelete: { id: "comments.delete", defaultMessage: "Delete thread" },
53
+ commentsDismiss: { id: "comments.dismiss", defaultMessage: "Dismiss" },
54
+ commentsEdit: { id: "comments.edit", defaultMessage: "Edit" },
55
+ commentsEditPlaceholder: {
56
+ id: "comments.edit-placeholder",
57
+ defaultMessage: "Edit comment\u2026"
58
+ },
59
+ commentsLinkCopied: { id: "comments.link-copied", defaultMessage: "Link copied" },
60
+ commentsMoreOptions: { id: "comments.more-options", defaultMessage: "More options" },
61
+ commentsReopen: { id: "comments.reopen", defaultMessage: "Reopen" },
62
+ commentsReplyPlaceholder: { id: "comments.reply-placeholder", defaultMessage: "Reply\u2026" },
63
+ commentsResolve: { id: "comments.resolve", defaultMessage: "Resolve" },
64
+ commentsResolvedBy: { id: "comments.resolved-by", defaultMessage: "Resolved by {name}" },
65
+ commentsSave: { id: "comments.save", defaultMessage: "Save" },
66
+ commentsSend: { id: "comments.send", defaultMessage: "Send" },
67
+ commentsThreadTitle: { id: "comments.thread-title", defaultMessage: "Comment" },
68
+ commentsUnknownAuthor: { id: "comments.unknown-author", defaultMessage: "Someone" }
69
+ });
48
70
  const stop = (e) => e.stopPropagation();
49
71
  function MoreOptionsMenu({
50
72
  id,
@@ -57,11 +79,11 @@ function MoreOptionsMenu({
57
79
  TldrawUiButton,
58
80
  {
59
81
  type: "icon",
60
- tooltip: msg("comments.more-options"),
61
- title: msg("comments.more-options"),
82
+ tooltip: msg(messages.commentsMoreOptions),
83
+ title: msg(messages.commentsMoreOptions),
62
84
  className: "tlui-cmt-thread__action",
63
85
  "data-cmt-more-for": moreFor,
64
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg("comments.more-options"), small: true })
86
+ children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "dots-vertical", label: msg(messages.commentsMoreOptions), small: true })
65
87
  }
66
88
  ) }),
67
89
  /* @__PURE__ */ jsx(
@@ -314,11 +336,11 @@ const ThreadView = memo(function ThreadView2({
314
336
  CommentComposer,
315
337
  {
316
338
  author: card.author,
317
- placeholder: msg("comments.edit-placeholder"),
339
+ placeholder: msg(messages.commentsEditPlaceholder),
318
340
  value: editText,
319
341
  onChange: setEditText,
320
342
  onSubmit: saveEdit,
321
- sendLabel: msg("comments.save"),
343
+ sendLabel: msg(messages.commentsSave),
322
344
  disabled: isCommentEmpty(editText),
323
345
  getMentionSuggestions,
324
346
  renderMentionSuggestion,
@@ -345,14 +367,14 @@ const ThreadView = memo(function ThreadView2({
345
367
  permissions?.edit && /* @__PURE__ */ jsx(
346
368
  MenuItem,
347
369
  {
348
- label: msg("comments.edit"),
370
+ label: msg(messages.commentsEdit),
349
371
  onClick: () => startEdit(comment, { fromMoreMenu: true })
350
372
  }
351
373
  ),
352
374
  permissions?.delete && /* @__PURE__ */ jsx(
353
375
  MenuItem,
354
376
  {
355
- label: msg("action.delete"),
377
+ label: msg(messages.actionDelete),
356
378
  danger: true,
357
379
  onClick: () => deleteComment(editor, comment)
358
380
  }
@@ -363,19 +385,19 @@ const ThreadView = memo(function ThreadView2({
363
385
  }
364
386
  );
365
387
  };
366
- const resolveLabel = msg(thread.resolved ? "comments.reopen" : "comments.resolve");
388
+ const resolveLabel = msg(thread.resolved ? messages.commentsReopen : messages.commentsResolve);
367
389
  const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
368
390
  ThreadActions && /* @__PURE__ */ jsx(ThreadActions, { thread, comments }),
369
391
  (threadHref !== void 0 || canDeleteThread) && /* @__PURE__ */ jsxs(MoreOptionsMenu, { id: `comment-thread-actions-${thread.id}`, children: [
370
392
  threadHref !== void 0 && /* @__PURE__ */ jsx(
371
393
  MenuItem,
372
394
  {
373
- label: msg(linkCopied ? "comments.link-copied" : "comments.copy-link"),
395
+ label: msg(linkCopied ? messages.commentsLinkCopied : messages.commentsCopyLink),
374
396
  noClose: true,
375
397
  onClick: copyThreadLink
376
398
  }
377
399
  ),
378
- canDeleteThread && /* @__PURE__ */ jsx(MenuItem, { label: msg("comments.delete"), danger: true, onClick: removeThread })
400
+ canDeleteThread && /* @__PURE__ */ jsx(MenuItem, { label: msg(messages.commentsDelete), danger: true, onClick: removeThread })
379
401
  ] }),
380
402
  canComment && currentUserId && /* @__PURE__ */ jsx(
381
403
  TldrawUiButton,
@@ -392,29 +414,28 @@ const ThreadView = memo(function ThreadView2({
392
414
  TldrawUiButton,
393
415
  {
394
416
  type: "icon",
395
- tooltip: msg("comments.dismiss"),
396
- title: msg("comments.dismiss"),
417
+ tooltip: msg(messages.commentsDismiss),
418
+ title: msg(messages.commentsDismiss),
397
419
  className: "tlui-cmt-thread__action",
398
420
  onClick: () => openThreadId.set(editor, null),
399
- children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "cross-2", label: msg("comments.dismiss"), small: true })
421
+ children: /* @__PURE__ */ jsx(TldrawUiIcon, { icon: "cross-2", label: msg(messages.commentsDismiss), small: true })
400
422
  }
401
423
  )
402
424
  ] });
403
425
  return /* @__PURE__ */ jsx(
404
426
  CommentThread,
405
427
  {
406
- header: msg("comments.thread-title"),
428
+ header: msg(messages.commentsThreadTitle),
407
429
  headerActions,
408
430
  renderComment,
409
431
  comments: cards,
410
- resolvedBanner: thread.resolved ? msg("comments.resolved-by").replace(
411
- "{name}",
412
- resolveAuthor(thread.resolved.by)?.name ?? UNKNOWN_AUTHOR
413
- ) : void 0,
432
+ resolvedBanner: thread.resolved ? msg(messages.commentsResolvedBy, {
433
+ name: resolveAuthor(thread.resolved.by)?.name ?? msg(messages.commentsUnknownAuthor)
434
+ }) : void 0,
414
435
  composer: canReply ? {
415
436
  author: me ?? UNKNOWN_COMMENT_AUTHOR,
416
- placeholder: msg("comments.reply-placeholder"),
417
- sendLabel: msg("comments.send"),
437
+ placeholder: msg(messages.commentsReplyPlaceholder),
438
+ sendLabel: msg(messages.commentsSend),
418
439
  value: reply,
419
440
  onChange: (value) => {
420
441
  setReply(value);