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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/commenting.css +284 -153
  2. package/dist-cjs/canvas/comment-body.js +1 -1
  3. package/dist-cjs/canvas/comment-body.js.map +2 -2
  4. package/dist-cjs/canvas/comment-drafts.js +54 -0
  5. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  6. package/dist-cjs/canvas/comment-render.js +2 -0
  7. package/dist-cjs/canvas/comment-render.js.map +2 -2
  8. package/dist-cjs/canvas/comment-tool.js +65 -12
  9. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  10. package/dist-cjs/canvas/comments-filter-menu.js +54 -63
  11. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  12. package/dist-cjs/canvas/comments-overflow-menu.js +21 -27
  13. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  14. package/dist-cjs/canvas/comments-overlay.js +304 -112
  15. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  16. package/dist-cjs/canvas/comments-sidebar.js +12 -18
  17. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  18. package/dist-cjs/canvas/comments-visibility-toggle.js +66 -0
  19. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  20. package/dist-cjs/canvas/options.js +1 -0
  21. package/dist-cjs/canvas/options.js.map +2 -2
  22. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  23. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  24. package/dist-cjs/canvas/state.js +11 -0
  25. package/dist-cjs/canvas/state.js.map +2 -2
  26. package/dist-cjs/canvas/thread-state.js +8 -1
  27. package/dist-cjs/canvas/thread-state.js.map +2 -2
  28. package/dist-cjs/clustering/runtime.js +119 -7
  29. package/dist-cjs/clustering/runtime.js.map +2 -2
  30. package/dist-cjs/index.d.ts +126 -41
  31. package/dist-cjs/index.js +7 -1
  32. package/dist-cjs/index.js.map +2 -2
  33. package/dist-cjs/ui/avatar.js +14 -6
  34. package/dist-cjs/ui/avatar.js.map +2 -2
  35. package/dist-cjs/ui/byline.js +4 -4
  36. package/dist-cjs/ui/byline.js.map +2 -2
  37. package/dist-cjs/ui/comment-author.js +17 -0
  38. package/dist-cjs/ui/comment-author.js.map +7 -0
  39. package/dist-cjs/ui/comment-card.js +4 -4
  40. package/dist-cjs/ui/comment-card.js.map +2 -2
  41. package/dist-cjs/ui/comment-composer.js +92 -29
  42. package/dist-cjs/ui/comment-composer.js.map +2 -2
  43. package/dist-cjs/ui/comment-mention.js +1 -1
  44. package/dist-cjs/ui/comment-mention.js.map +2 -2
  45. package/dist-cjs/ui/comment-pin.js +23 -3
  46. package/dist-cjs/ui/comment-pin.js.map +2 -2
  47. package/dist-cjs/ui/comment-text.js +1 -1
  48. package/dist-cjs/ui/comment-text.js.map +2 -2
  49. package/dist-cjs/ui/comment-thread.js +6 -6
  50. package/dist-cjs/ui/comment-thread.js.map +2 -2
  51. package/dist-cjs/ui/comments-list.js +12 -12
  52. package/dist-cjs/ui/comments-list.js.map +2 -2
  53. package/dist-cjs/ui/count-badge.js +1 -1
  54. package/dist-cjs/ui/count-badge.js.map +2 -2
  55. package/dist-cjs/ui/empty-state.js +3 -3
  56. package/dist-cjs/ui/empty-state.js.map +2 -2
  57. package/dist-cjs/ui/format-time.js +3 -0
  58. package/dist-cjs/ui/format-time.js.map +2 -2
  59. package/dist-cjs/ui/mention-list.js +8 -8
  60. package/dist-cjs/ui/mention-list.js.map +2 -2
  61. package/dist-cjs/ui/mention-suggestion.js +3 -3
  62. package/dist-cjs/ui/mention-suggestion.js.map +2 -2
  63. package/dist-cjs/ui/mention.js +1 -1
  64. package/dist-cjs/ui/mention.js.map +2 -2
  65. package/dist-cjs/ui/reaction.js +11 -4
  66. package/dist-cjs/ui/reaction.js.map +2 -2
  67. package/dist-cjs/ui/reactions.js +10 -2
  68. package/dist-cjs/ui/reactions.js.map +2 -2
  69. package/dist-cjs/ui/send-button.js +1 -1
  70. package/dist-cjs/ui/send-button.js.map +2 -2
  71. package/dist-cjs/ui/tooltip-button.js +32 -0
  72. package/dist-cjs/ui/tooltip-button.js.map +7 -0
  73. package/dist-esm/canvas/comment-body.mjs +1 -1
  74. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  75. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  76. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  77. package/dist-esm/canvas/comment-render.mjs +2 -0
  78. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  79. package/dist-esm/canvas/comment-tool.mjs +67 -14
  80. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  81. package/dist-esm/canvas/comments-filter-menu.mjs +54 -63
  82. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  83. package/dist-esm/canvas/comments-overflow-menu.mjs +22 -27
  84. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  85. package/dist-esm/canvas/comments-overlay.mjs +322 -114
  86. package/dist-esm/canvas/comments-overlay.mjs.map +2 -2
  87. package/dist-esm/canvas/comments-sidebar.mjs +15 -21
  88. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  89. package/dist-esm/canvas/comments-visibility-toggle.mjs +46 -0
  90. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  91. package/dist-esm/canvas/options.mjs +1 -0
  92. package/dist-esm/canvas/options.mjs.map +2 -2
  93. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  94. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  95. package/dist-esm/canvas/state.mjs +11 -0
  96. package/dist-esm/canvas/state.mjs.map +2 -2
  97. package/dist-esm/canvas/thread-state.mjs +8 -1
  98. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  99. package/dist-esm/clustering/runtime.mjs +119 -7
  100. package/dist-esm/clustering/runtime.mjs.map +2 -2
  101. package/dist-esm/index.d.mts +126 -41
  102. package/dist-esm/index.mjs +11 -1
  103. package/dist-esm/index.mjs.map +2 -2
  104. package/dist-esm/ui/avatar.mjs +14 -6
  105. package/dist-esm/ui/avatar.mjs.map +2 -2
  106. package/dist-esm/ui/byline.mjs +4 -4
  107. package/dist-esm/ui/byline.mjs.map +2 -2
  108. package/dist-esm/ui/comment-author.mjs +1 -0
  109. package/dist-esm/ui/comment-author.mjs.map +7 -0
  110. package/dist-esm/ui/comment-card.mjs +4 -4
  111. package/dist-esm/ui/comment-card.mjs.map +2 -2
  112. package/dist-esm/ui/comment-composer.mjs +93 -30
  113. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  114. package/dist-esm/ui/comment-mention.mjs +1 -1
  115. package/dist-esm/ui/comment-mention.mjs.map +2 -2
  116. package/dist-esm/ui/comment-pin.mjs +23 -3
  117. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  118. package/dist-esm/ui/comment-text.mjs +1 -1
  119. package/dist-esm/ui/comment-text.mjs.map +2 -2
  120. package/dist-esm/ui/comment-thread.mjs +6 -6
  121. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  122. package/dist-esm/ui/comments-list.mjs +12 -12
  123. package/dist-esm/ui/comments-list.mjs.map +2 -2
  124. package/dist-esm/ui/count-badge.mjs +1 -1
  125. package/dist-esm/ui/count-badge.mjs.map +2 -2
  126. package/dist-esm/ui/empty-state.mjs +3 -3
  127. package/dist-esm/ui/empty-state.mjs.map +2 -2
  128. package/dist-esm/ui/format-time.mjs +3 -0
  129. package/dist-esm/ui/format-time.mjs.map +2 -2
  130. package/dist-esm/ui/mention-list.mjs +8 -8
  131. package/dist-esm/ui/mention-list.mjs.map +2 -2
  132. package/dist-esm/ui/mention-suggestion.mjs +3 -3
  133. package/dist-esm/ui/mention-suggestion.mjs.map +2 -2
  134. package/dist-esm/ui/mention.mjs +1 -1
  135. package/dist-esm/ui/mention.mjs.map +2 -2
  136. package/dist-esm/ui/reaction.mjs +11 -4
  137. package/dist-esm/ui/reaction.mjs.map +2 -2
  138. package/dist-esm/ui/reactions.mjs +10 -2
  139. package/dist-esm/ui/reactions.mjs.map +2 -2
  140. package/dist-esm/ui/send-button.mjs +1 -1
  141. package/dist-esm/ui/send-button.mjs.map +2 -2
  142. package/dist-esm/ui/tooltip-button.mjs +12 -0
  143. package/dist-esm/ui/tooltip-button.mjs.map +7 -0
  144. package/package.json +3 -3
  145. package/src/canvas/canvas.css +79 -36
  146. package/src/canvas/comment-body.tsx +1 -1
  147. package/src/canvas/comment-drafts.test.ts +55 -0
  148. package/src/canvas/comment-drafts.ts +46 -0
  149. package/src/canvas/comment-render.test.ts +1 -1
  150. package/src/canvas/comment-render.ts +7 -2
  151. package/src/canvas/comment-tool.tsx +98 -17
  152. package/src/canvas/comments-filter-menu.tsx +27 -33
  153. package/src/canvas/comments-overflow-menu.tsx +13 -21
  154. package/src/canvas/comments-overlay.tsx +441 -145
  155. package/src/canvas/comments-sidebar.tsx +27 -28
  156. package/src/canvas/comments-visibility-toggle.tsx +50 -0
  157. package/src/canvas/options.ts +25 -0
  158. package/src/canvas/sidebar-filters.ts +4 -3
  159. package/src/canvas/state.ts +22 -0
  160. package/src/canvas/thread-state.ts +16 -3
  161. package/src/clustering/runtime.test.ts +183 -7
  162. package/src/clustering/runtime.ts +164 -15
  163. package/src/index.ts +7 -0
  164. package/src/ui/avatar.tsx +15 -11
  165. package/src/ui/byline.tsx +6 -5
  166. package/src/ui/comment-author.ts +15 -0
  167. package/src/ui/comment-card.tsx +6 -5
  168. package/src/ui/comment-composer.tsx +122 -33
  169. package/src/ui/comment-mention.ts +2 -2
  170. package/src/ui/comment-pin.tsx +35 -4
  171. package/src/ui/comment-text.tsx +1 -1
  172. package/src/ui/comment-thread.tsx +8 -6
  173. package/src/ui/comments-list.tsx +19 -14
  174. package/src/ui/comments.css +205 -117
  175. package/src/ui/count-badge.tsx +1 -1
  176. package/src/ui/empty-state.tsx +3 -3
  177. package/src/ui/format-time.ts +5 -0
  178. package/src/ui/mention-list.tsx +13 -16
  179. package/src/ui/mention-suggestion.tsx +3 -3
  180. package/src/ui/mention.tsx +1 -1
  181. package/src/ui/reaction.tsx +6 -3
  182. package/src/ui/reactions.tsx +6 -2
  183. package/src/ui/send-button.tsx +1 -1
  184. package/src/ui/tooltip-button.tsx +20 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/mention-list.tsx"],
4
- "sourcesContent": ["import { ReactNode } from 'react'\nimport { useTranslation } from 'tldraw'\nimport { Avatar } from './avatar'\n\n/** A person the composer's \\@-mention picker can offer. @public */\nexport interface MentionMember {\n\tid: string\n\tname: string\n\t/** Avatar image URL. Falls back to a coloured initial when omitted. */\n\tavatar?: string\n\t/** Avatar background colour, used when there's no `avatar` image. */\n\tcolor?: string\n\t/** A secondary line under the name \u2014 e.g. an email or handle. Omit for a single-line row. */\n\tsecondary?: string\n\t/** Marks the current user; shown as a \"(You)\" suffix after the name. */\n\tyou?: boolean\n\t/** Hosts may carry extra fields; a custom `renderMember` can read them. */\n\t[key: string]: unknown\n}\n\n/** @public */\nexport interface MentionListProps {\n\t/** Members to choose from \u2014 already resolved for the current query by the host. */\n\tmembers: MentionMember[]\n\t/** Index of the highlighted row, driven by the composer's keyboard navigation. */\n\tactiveIndex?: number\n\t/** Called when a member is chosen (click, or Enter on the active row). */\n\tonSelect?(member: MentionMember): void\n\t/** Shown when no member matches the query. Defaults to a translated \"No matches\". */\n\temptyLabel?: string\n\t/** Override a row's content (inside the selectable button). Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The default row content: avatar, name (with a \"(You)\" marker), and an optional secondary line. */\nfunction DefaultMemberRow({ member }: { member: MentionMember }) {\n\tconst msg = useTranslation()\n\treturn (\n\t\t<>\n\t\t\t<Avatar name={member.name} color={member.color} image={member.avatar} />\n\t\t\t<span className=\"cmt-mention-list__text\">\n\t\t\t\t<span className=\"cmt-mention-list__name\">\n\t\t\t\t\t{member.name}\n\t\t\t\t\t{member.you && (\n\t\t\t\t\t\t<span className=\"cmt-mention-list__you\">{`(${msg('comments.mention-you')})`}</span>\n\t\t\t\t\t)}\n\t\t\t\t</span>\n\t\t\t\t{member.secondary && (\n\t\t\t\t\t<span className=\"cmt-mention-list__secondary\">{member.secondary}</span>\n\t\t\t\t)}\n\t\t\t</span>\n\t\t</>\n\t)\n}\n\n/**\n * The \\@-mention member picker: a popover list of people to mention, shown as the user types after\n * `@`. Presentational \u2014 the composer's suggestion plugin owns resolution, keyboard navigation, and\n * placement, and drives this with the host-resolved `members` and the highlighted `activeIndex`.\n * @public @react\n */\nexport function MentionList({\n\tmembers,\n\tactiveIndex = 0,\n\tonSelect,\n\temptyLabel,\n\trenderMember,\n}: MentionListProps) {\n\tconst msg = useTranslation()\n\tif (members.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"cmt-mention-list cmt-mention-list--empty\">\n\t\t\t\t{emptyLabel ?? msg('comments.mention-no-matches')}\n\t\t\t</div>\n\t\t)\n\t}\n\treturn (\n\t\t<div className=\"cmt-mention-list\" role=\"listbox\">\n\t\t\t{members.map((member, i) => {\n\t\t\t\tconst active = i === activeIndex\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={member.id}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\taria-selected={active}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tactive\n\t\t\t\t\t\t\t\t? 'cmt-mention-list__item cmt-mention-list__item--active'\n\t\t\t\t\t\t\t\t: 'cmt-mention-list__item'\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Keep the composer focused on a row press: it's portaled outside the editor, so a\n\t\t\t\t\t\t// bare mousedown would blur the composer \u2014 which now dismisses the picker \u2014 before\n\t\t\t\t\t\t// the click could select. Selection runs on click; focus stays put.\n\t\t\t\t\t\tonMouseDown={(e) => e.preventDefault()}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tif (onSelect) onSelect(member)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{renderMember ? renderMember(member) : <DefaultMemberRow member={member} />}\n\t\t\t\t\t</button>\n\t\t\t\t)\n\t\t\t})}\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "AAsCE,mBACC,KAEC,YAHF;AArCF,SAAS,sBAAsB;AAC/B,SAAS,cAAc;AAiCvB,SAAS,iBAAiB,EAAE,OAAO,GAA8B;AAChE,QAAM,MAAM,eAAe;AAC3B,SACC,iCACC;AAAA,wBAAC,UAAO,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,QAAQ;AAAA,IACtE,qBAAC,UAAK,WAAU,0BACf;AAAA,2BAAC,UAAK,WAAU,0BACd;AAAA,eAAO;AAAA,QACP,OAAO,OACP,oBAAC,UAAK,WAAU,yBAAyB,cAAI,IAAI,sBAAsB,CAAC,KAAI;AAAA,SAE9E;AAAA,MACC,OAAO,aACP,oBAAC,UAAK,WAAU,+BAA+B,iBAAO,WAAU;AAAA,OAElE;AAAA,KACD;AAEF;AAQO,SAAS,YAAY;AAAA,EAC3B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACD,GAAqB;AACpB,QAAM,MAAM,eAAe;AAC3B,MAAI,QAAQ,WAAW,GAAG;AACzB,WACC,oBAAC,SAAI,WAAU,4CACb,wBAAc,IAAI,6BAA6B,GACjD;AAAA,EAEF;AACA,SACC,oBAAC,SAAI,WAAU,oBAAmB,MAAK,WACrC,kBAAQ,IAAI,CAAC,QAAQ,MAAM;AAC3B,UAAM,SAAS,MAAM;AACrB,WACC;AAAA,MAAC;AAAA;AAAA,QAEA,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,WACC,SACG,0DACA;AAAA,QAKJ,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM;AACd,cAAI,SAAU,UAAS,MAAM;AAAA,QAC9B;AAAA,QAEC,yBAAe,aAAa,MAAM,IAAI,oBAAC,oBAAiB,QAAgB;AAAA;AAAA,MAjBpE,OAAO;AAAA,IAkBb;AAAA,EAEF,CAAC,GACF;AAEF;",
4
+ "sourcesContent": ["import { ReactNode } from 'react'\nimport { useTranslation } from 'tldraw'\nimport { Avatar } from './avatar'\nimport { CommentAuthor } from './comment-author'\n\n/** A person the composer's \\@-mention picker can offer: a {@link CommentAuthor} plus its id and\n * picker-only display fields. @public */\nexport interface MentionMember extends CommentAuthor {\n\tid: string\n\t/** A secondary line under the name \u2014 e.g. an email or handle. Omit for a single-line row. */\n\tsecondary?: string\n\t/** Marks the current user; shown as a \"(You)\" suffix after the name. */\n\tyou?: boolean\n\t/** Hosts may carry extra fields; a custom `renderMember` can read them. */\n\t[key: string]: unknown\n}\n\n/** @public */\nexport interface MentionListProps {\n\t/** Members to choose from \u2014 already resolved for the current query by the host. */\n\tmembers: MentionMember[]\n\t/** Index of the highlighted row, driven by the composer's keyboard navigation. */\n\tactiveIndex?: number\n\t/** Called when a member is chosen (click, or Enter on the active row). */\n\tonSelect?(member: MentionMember): void\n\t/** Shown when no member matches the query. Defaults to a translated \"No matches\". */\n\temptyLabel?: string\n\t/** Override a row's content (inside the selectable button). Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The default row content: avatar, name (with a \"(You)\" marker), and an optional secondary line. */\nfunction DefaultMemberRow({ member }: { member: MentionMember }) {\n\tconst msg = useTranslation()\n\treturn (\n\t\t<>\n\t\t\t<Avatar author={member} />\n\t\t\t<span className=\"tlui-cmt-mention-list__text\">\n\t\t\t\t<span className=\"tlui-cmt-mention-list__name\">\n\t\t\t\t\t{member.name}\n\t\t\t\t\t{member.you && (\n\t\t\t\t\t\t<span className=\"tlui-cmt-mention-list__you\">{`(${msg('comments.mention-you')})`}</span>\n\t\t\t\t\t)}\n\t\t\t\t</span>\n\t\t\t\t{member.secondary && (\n\t\t\t\t\t<span className=\"tlui-cmt-mention-list__secondary\">{member.secondary}</span>\n\t\t\t\t)}\n\t\t\t</span>\n\t\t</>\n\t)\n}\n\n/**\n * The \\@-mention member picker: a popover list of people to mention, shown as the user types after\n * `@`. Presentational \u2014 the composer's suggestion plugin owns resolution, keyboard navigation, and\n * placement, and drives this with the host-resolved `members` and the highlighted `activeIndex`.\n * @public @react\n */\nexport function MentionList({\n\tmembers,\n\tactiveIndex = 0,\n\tonSelect,\n\temptyLabel,\n\trenderMember,\n}: MentionListProps) {\n\tconst msg = useTranslation()\n\tif (members.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"tlui-cmt-mention-list tlui-cmt-mention-list--empty\">\n\t\t\t\t{emptyLabel ?? msg('comments.mention-no-matches')}\n\t\t\t</div>\n\t\t)\n\t}\n\treturn (\n\t\t<div className=\"tlui-cmt-mention-list\" role=\"listbox\">\n\t\t\t{members.map((member, i) => {\n\t\t\t\tconst active = i === activeIndex\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={member.id}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"option\"\n\t\t\t\t\t\taria-selected={active}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tactive\n\t\t\t\t\t\t\t\t? 'tlui-cmt-mention-list__item tlui-cmt-mention-list__item--active'\n\t\t\t\t\t\t\t\t: 'tlui-cmt-mention-list__item'\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Keep the composer focused on a row press: it's portaled outside the editor, so a\n\t\t\t\t\t\t// bare mousedown would blur the composer \u2014 which now dismisses the picker \u2014 before\n\t\t\t\t\t\t// the click could select. Selection runs on click; focus stays put.\n\t\t\t\t\t\tonMouseDown={(e) => e.preventDefault()}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tif (onSelect) onSelect(member)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t{renderMember ? renderMember(member) : <DefaultMemberRow member={member} />}\n\t\t\t\t\t</button>\n\t\t\t\t)\n\t\t\t})}\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "AAmCE,mBACC,KAEC,YAHF;AAlCF,SAAS,sBAAsB;AAC/B,SAAS,cAAc;AA8BvB,SAAS,iBAAiB,EAAE,OAAO,GAA8B;AAChE,QAAM,MAAM,eAAe;AAC3B,SACC,iCACC;AAAA,wBAAC,UAAO,QAAQ,QAAQ;AAAA,IACxB,qBAAC,UAAK,WAAU,+BACf;AAAA,2BAAC,UAAK,WAAU,+BACd;AAAA,eAAO;AAAA,QACP,OAAO,OACP,oBAAC,UAAK,WAAU,8BAA8B,cAAI,IAAI,sBAAsB,CAAC,KAAI;AAAA,SAEnF;AAAA,MACC,OAAO,aACP,oBAAC,UAAK,WAAU,oCAAoC,iBAAO,WAAU;AAAA,OAEvE;AAAA,KACD;AAEF;AAQO,SAAS,YAAY;AAAA,EAC3B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACD,GAAqB;AACpB,QAAM,MAAM,eAAe;AAC3B,MAAI,QAAQ,WAAW,GAAG;AACzB,WACC,oBAAC,SAAI,WAAU,sDACb,wBAAc,IAAI,6BAA6B,GACjD;AAAA,EAEF;AACA,SACC,oBAAC,SAAI,WAAU,yBAAwB,MAAK,WAC1C,kBAAQ,IAAI,CAAC,QAAQ,MAAM;AAC3B,UAAM,SAAS,MAAM;AACrB,WACC;AAAA,MAAC;AAAA;AAAA,QAEA,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,WACC,SACG,oEACA;AAAA,QAKJ,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM;AACd,cAAI,SAAU,UAAS,MAAM;AAAA,QAC9B;AAAA,QAEC,yBAAe,aAAa,MAAM,IAAI,oBAAC,oBAAiB,QAAgB;AAAA;AAAA,MAjBpE,OAAO;AAAA,IAkBb;AAAA,EAEF,CAAC,GACF;AAEF;",
6
6
  "names": []
7
7
  }
@@ -70,7 +70,7 @@ function createMentionSuggestion(getSuggestions, options = {}) {
70
70
  };
71
71
  const place = () => {
72
72
  if (!container || !editorEl || container.style.display === "none") return;
73
- const field = editorEl.closest(".cmt-composer__field") ?? editorEl;
73
+ const field = editorEl.closest(".tlui-cmt-composer__field") ?? editorEl;
74
74
  const rect = field.getBoundingClientRect();
75
75
  popupWidth = rect.width;
76
76
  anchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null;
@@ -104,7 +104,7 @@ function createMentionSuggestion(getSuggestions, options = {}) {
104
104
  };
105
105
  const onWheel = (e) => {
106
106
  if (e.isSpecialRedispatchedEvent || !canvasEl) return;
107
- const list = container?.querySelector(".cmt-mention-list");
107
+ const list = container?.querySelector(".tlui-cmt-mention-list");
108
108
  if (list && list.scrollHeight > list.clientHeight) return;
109
109
  e.preventDefault();
110
110
  const redispatched = new WheelEvent("wheel", e);
@@ -124,7 +124,7 @@ function createMentionSuggestion(getSuggestions, options = {}) {
124
124
  editorEl = props.editor.view.dom;
125
125
  editorEl.addEventListener("blur", hide);
126
126
  container = document.createElement("div");
127
- container.className = "cmt-mention-popup";
127
+ container.className = "tlui-cmt-mention-popup";
128
128
  container.appendChild(renderer.element);
129
129
  const themed = editorEl.closest(".tl-container");
130
130
  (themed ?? document.body).appendChild(container);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/mention-suggestion.tsx"],
4
- "sourcesContent": ["import type { MentionNodeAttrs } from '@tiptap/extension-mention'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionKeyDownProps, SuggestionOptions } from '@tiptap/suggestion'\nimport { type ReactNode, forwardRef, useImperativeHandle, useState } from 'react'\nimport { type Editor as TldrawEditor, atom, react } from 'tldraw'\nimport { MentionList, MentionMember } from './mention-list'\n\n/** The handle the suggestion plugin drives \u2014 it forwards navigation keys into the popup. */\nexport interface MentionPopupHandle {\n\tonKeyDown(props: SuggestionKeyDownProps): boolean\n}\n\ninterface MentionPopupProps {\n\titems: MentionMember[]\n\tcommand(attrs: MentionNodeAttrs): void\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The live @-picker popup: owns the highlighted index and keyboard, renders the presentational list. */\nconst MentionPopup = forwardRef<MentionPopupHandle, MentionPopupProps>(function MentionPopup(\n\t{ items, command, renderMember },\n\tref\n) {\n\tconst [activeIndex, setActiveIndex] = useState(0)\n\t// A new query yields new items; reset the highlight to the top during render \u2014 not in an effect,\n\t// which would leave a frame where `activeIndex` still points past a shrunk list and Enter selects\n\t// its (now out-of-range, undefined) item, swallowing the key without inserting a mention.\n\tconst [prevItems, setPrevItems] = useState(items)\n\tif (items !== prevItems) {\n\t\tsetPrevItems(items)\n\t\tsetActiveIndex(0)\n\t}\n\n\tconst select = (member: MentionMember | undefined) => {\n\t\tif (member) command({ id: member.id, label: member.name })\n\t}\n\n\tuseImperativeHandle(ref, () => ({\n\t\tonKeyDown: ({ event }) => {\n\t\t\tif (items.length === 0) return false\n\t\t\tif (event.key === 'ArrowUp') {\n\t\t\t\tsetActiveIndex((i) => (i + items.length - 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (event.key === 'ArrowDown') {\n\t\t\t\tsetActiveIndex((i) => (i + 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Enter and Tab both complete the highlighted member (falling back to the top match so a\n\t\t\t// stale index never selects `undefined`). The empty-roster case is handled a level up, in\n\t\t\t// the suggestion's onKeyDown, which cancels the picker.\n\t\t\tif (event.key === 'Enter' || event.key === 'Tab') {\n\t\t\t\tselect(items[activeIndex] ?? items[0])\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t}))\n\n\treturn (\n\t\t<MentionList\n\t\t\tmembers={items}\n\t\t\tactiveIndex={activeIndex}\n\t\t\tonSelect={select}\n\t\t\trenderMember={renderMember}\n\t\t/>\n\t)\n})\n\nconst MAX_SUGGESTIONS = 8\n\n/** Members whose name contains the query (case-insensitive), capped to the popup's length.\n * @public */\nexport function filterMentionMembers(members: MentionMember[], query: string): MentionMember[] {\n\tconst q = query.toLowerCase()\n\treturn members.filter((m) => m.name.toLowerCase().includes(q)).slice(0, MAX_SUGGESTIONS)\n}\n\n// A reactive flag for whether the @-picker is showing. Deliberately NOT tldraw's open-menu registry:\n// registering there mounts MenuClickCapture, which covers the canvas with a click-capture overlay to\n// make it inert while a menu is open. But the picker is an inline autocomplete, not a modal \u2014 the\n// canvas must stay pannable/zoomable beneath it \u2014 so we track \"open\" ourselves instead.\nconst mentionPickerOpen = atom('isMentionPickerOpen', false)\n\n/**\n * Whether the @-mention picker is currently showing. Host dismissal (Escape, outside-click) checks\n * this so it can defer to the picker instead of tearing down the composer or thread beneath it.\n */\nexport function isMentionPickerOpen(): boolean {\n\treturn mentionPickerOpen.get()\n}\n\n/** @public */\nexport interface MentionSuggestionOptions {\n\t/** Override a member row's content in the picker. Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n\t/**\n\t * The tldraw editor the composer lives in. When provided, the popup re-anchors reactively as the\n\t * canvas camera moves (the composer rides it) instead of polling every frame. Omit off-canvas.\n\t */\n\teditor?: TldrawEditor | null\n}\n\n/**\n * Build the TipTap `suggestion` config for the comment \\@-picker. `getSuggestions(query)` is the\n * host's resolver \u2014 it returns the members matching the query (sync or async); the SDK owns neither\n * the roster nor the filtering. The plugin runs outside React, so `render` mounts `MentionPopup` via\n * a `ReactRenderer`, forwards navigation keys through the popup's imperative handle, and lets it call\n * `command` to insert.\n * @public\n */\nexport function createMentionSuggestion(\n\tgetSuggestions: (query: string) => MentionMember[] | Promise<MentionMember[]>,\n\toptions: MentionSuggestionOptions = {}\n): Omit<SuggestionOptions<MentionMember, MentionNodeAttrs>, 'editor'> {\n\treturn {\n\t\tchar: '@',\n\t\titems: ({ query }) => getSuggestions(query),\n\t\trender: () => {\n\t\t\tlet renderer: ReactRenderer<MentionPopupHandle, MentionPopupProps> | null = null\n\t\t\tlet container: HTMLElement | null = null\n\t\t\tlet editorEl: HTMLElement | null = null\n\t\t\tlet canvasEl: Element | null = null\n\t\t\tlet stopCameraReaction: (() => void) | null = null\n\t\t\t// The composer field's top-left in page space, plus the popup's screen width. Captured on a\n\t\t\t// fresh read so camera moves can re-derive the popup's screen position from the page anchor\n\t\t\t// (see reposition) rather than the field's DOM rect.\n\t\t\tlet anchorPage: { x: number; y: number } | null = null\n\t\t\tlet popupWidth = 0\n\n\t\t\tconst applyScreen = (left: number, top: number, width: number) => {\n\t\t\t\tif (!container) return\n\t\t\t\tcontainer.style.left = `${left}px`\n\t\t\t\tcontainer.style.top = `${top + 4}px`\n\t\t\t\tcontainer.style.width = `${width}px`\n\t\t\t}\n\n\t\t\t// Fresh placement: read the field's real screen rect, position the popup flush under it (not\n\t\t\t// the caret, matching its width), and remember the field's page-space anchor for reposition.\n\t\t\tconst place = () => {\n\t\t\t\tif (!container || !editorEl || container.style.display === 'none') return\n\t\t\t\tconst field = editorEl.closest('.cmt-composer__field') ?? editorEl\n\t\t\t\tconst rect = field.getBoundingClientRect()\n\t\t\t\tpopupWidth = rect.width\n\t\t\t\tanchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null\n\t\t\t\tapplyScreen(rect.left, rect.bottom, rect.width)\n\t\t\t}\n\n\t\t\t// Re-derive the popup's screen position from the remembered page anchor \u2014 pure camera math,\n\t\t\t// always current. Reading the field's DOM rect here instead would lag by a frame: the canvas\n\t\t\t// composer re-positions itself on a React commit (a `useValue(pageToViewport(...))`), which\n\t\t\t// lands after a camera reaction runs, so the rect is still last frame's during a pan.\n\t\t\tconst reposition = () => {\n\t\t\t\tif (!anchorPage || !options.editor) return\n\t\t\t\tconst s = options.editor.pageToScreen(anchorPage)\n\t\t\t\tapplyScreen(s.x, s.y, popupWidth)\n\t\t\t}\n\n\t\t\t// The popup is `position: fixed`, but its anchor \u2014 a canvas composer \u2014 rides the camera:\n\t\t\t// panning or zooming moves the composer with no scroll/resize event to hook. Re-anchor when\n\t\t\t// the camera actually changes (via a tldraw reaction) rather than polling every frame, plus\n\t\t\t// on window scroll/resize for the off-canvas case (which re-read the field directly).\n\t\t\tconst startFollowing = () => {\n\t\t\t\twindow.addEventListener('scroll', place, true)\n\t\t\t\twindow.addEventListener('resize', place)\n\t\t\t\tif (options.editor) {\n\t\t\t\t\tstopCameraReaction = react('anchor mention popup to camera', () => {\n\t\t\t\t\t\toptions.editor!.getCamera() // track the camera so this re-runs as it moves\n\t\t\t\t\t\treposition()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst stopFollowing = () => {\n\t\t\t\twindow.removeEventListener('scroll', place, true)\n\t\t\t\twindow.removeEventListener('resize', place)\n\t\t\t\tstopCameraReaction?.()\n\t\t\t\tstopCameraReaction = null\n\t\t\t\tanchorPage = null\n\t\t\t}\n\n\t\t\t// Dismiss the roster \u2014 on Escape, or when the composer loses focus \u2014 by hiding it and\n\t\t\t// clearing the open flag, so isMentionPickerOpen() stays accurate and the thread's own\n\t\t\t// Escape/outside-click dismissal can take over. The TipTap suggestion stays active, so typing\n\t\t\t// re-shows the roster via onUpdate.\n\t\t\tconst hide = () => {\n\t\t\t\tif (container) container.style.display = 'none'\n\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t}\n\n\t\t\t// Wheel/panning over the popup drives the canvas beneath it, so scrolling to pan or zoom\n\t\t\t// isn't swallowed by the roster \u2014 the same passthrough the rest of the comments UI gets from\n\t\t\t// `usePassThroughWheelEvents`. The list still scrolls itself when the roster overflows (we\n\t\t\t// only redispatch when it can't). Done imperatively because the popup lives outside React.\n\t\t\tconst onWheel = (e: WheelEvent) => {\n\t\t\t\tif ((e as any).isSpecialRedispatchedEvent || !canvasEl) return\n\t\t\t\tconst list = container?.querySelector('.cmt-mention-list')\n\t\t\t\tif (list && list.scrollHeight > list.clientHeight) return\n\t\t\t\te.preventDefault()\n\t\t\t\tconst redispatched = new WheelEvent('wheel', e)\n\t\t\t\t;(redispatched as any).isSpecialRedispatchedEvent = true\n\t\t\t\tcanvasEl.dispatchEvent(redispatched)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonStart: (props) => {\n\t\t\t\t\trenderer = new ReactRenderer(MentionPopup, {\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t},\n\t\t\t\t\t\teditor: props.editor,\n\t\t\t\t\t})\n\t\t\t\t\teditorEl = props.editor.view.dom as HTMLElement\n\t\t\t\t\t// The TipTap suggestion has no blur handling, so without this the picker would stay\n\t\t\t\t\t// \"open\" (and the thread would defer its Escape to it) after focus moved away from the\n\t\t\t\t\t// composer \u2014 leaving Escape a no-op and the roster stuck on screen.\n\t\t\t\t\teditorEl.addEventListener('blur', hide)\n\t\t\t\t\tcontainer = document.createElement('div')\n\t\t\t\t\tcontainer.className = 'cmt-mention-popup'\n\t\t\t\t\tcontainer.appendChild(renderer.element)\n\t\t\t\t\t// Mount inside the tldraw container so the popup inherits the theme variables\n\t\t\t\t\t// (--tl-color-*); portaling to document.body would strip them and lose the panel.\n\t\t\t\t\tconst themed = editorEl.closest('.tl-container')\n\t\t\t\t\t;(themed ?? document.body).appendChild(container)\n\t\t\t\t\tcanvasEl = themed?.querySelector('.tl-canvas') ?? null\n\t\t\t\t\tcontainer.addEventListener('wheel', onWheel, { passive: false })\n\t\t\t\t\tplace()\n\t\t\t\t\tstartFollowing()\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t},\n\t\t\t\tonUpdate: (props) => {\n\t\t\t\t\tif (renderer)\n\t\t\t\t\t\trenderer.updateProps({\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t})\n\t\t\t\t\t// Typing after an Escape re-shows the roster.\n\t\t\t\t\tif (container) container.style.display = ''\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t\tplace()\n\t\t\t\t},\n\t\t\t\tonKeyDown: (props) => {\n\t\t\t\t\t// Once the roster is hidden (a prior Escape), the TipTap suggestion is still active but\n\t\t\t\t\t// this handler goes inert \u2014 every key passes through to the composer/thread (so a second\n\t\t\t\t\t// Escape closes them, and Arrow/Enter don't select from an invisible list). Typing\n\t\t\t\t\t// re-shows the roster via onUpdate.\n\t\t\t\t\tif (!isMentionPickerOpen()) return false\n\t\t\t\t\tif (props.event.key === 'Escape') {\n\t\t\t\t\t\t// Dismiss only the roster: hide it and stop the key so the composer/thread beneath\n\t\t\t\t\t\t// doesn't also treat Escape as \"abandon\".\n\t\t\t\t\t\thide()\n\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (props.event.key === 'Enter' || props.event.key === 'Tab') {\n\t\t\t\t\t\t// Complete the highlighted member if there is one to complete; if the roster is empty\n\t\t\t\t\t\t// there's nothing to pick, so cancel the picker and swallow the key \u2014 the composer\n\t\t\t\t\t\t// beneath neither submits (Enter) nor moves focus / indents (Tab).\n\t\t\t\t\t\tconst completed = renderer?.ref?.onKeyDown(props) ?? false\n\t\t\t\t\t\tif (!completed) {\n\t\t\t\t\t\t\thide()\n\t\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (renderer && renderer.ref) return renderer.ref.onKeyDown(props)\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t\tonExit: () => {\n\t\t\t\t\tstopFollowing()\n\t\t\t\t\tif (editorEl) editorEl.removeEventListener('blur', hide)\n\t\t\t\t\tif (container) container.removeEventListener('wheel', onWheel)\n\t\t\t\t\tif (container) container.remove()\n\t\t\t\t\tif (renderer) renderer.destroy()\n\t\t\t\t\trenderer = null\n\t\t\t\t\tcontainer = null\n\t\t\t\t\tcanvasEl = null\n\t\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { MentionNodeAttrs } from '@tiptap/extension-mention'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionKeyDownProps, SuggestionOptions } from '@tiptap/suggestion'\nimport { type ReactNode, forwardRef, useImperativeHandle, useState } from 'react'\nimport { type Editor as TldrawEditor, atom, react } from 'tldraw'\nimport { MentionList, MentionMember } from './mention-list'\n\n/** The handle the suggestion plugin drives \u2014 it forwards navigation keys into the popup. */\nexport interface MentionPopupHandle {\n\tonKeyDown(props: SuggestionKeyDownProps): boolean\n}\n\ninterface MentionPopupProps {\n\titems: MentionMember[]\n\tcommand(attrs: MentionNodeAttrs): void\n\trenderMember?(member: MentionMember): ReactNode\n}\n\n/** The live @-picker popup: owns the highlighted index and keyboard, renders the presentational list. */\nconst MentionPopup = forwardRef<MentionPopupHandle, MentionPopupProps>(function MentionPopup(\n\t{ items, command, renderMember },\n\tref\n) {\n\tconst [activeIndex, setActiveIndex] = useState(0)\n\t// A new query yields new items; reset the highlight to the top during render \u2014 not in an effect,\n\t// which would leave a frame where `activeIndex` still points past a shrunk list and Enter selects\n\t// its (now out-of-range, undefined) item, swallowing the key without inserting a mention.\n\tconst [prevItems, setPrevItems] = useState(items)\n\tif (items !== prevItems) {\n\t\tsetPrevItems(items)\n\t\tsetActiveIndex(0)\n\t}\n\n\tconst select = (member: MentionMember | undefined) => {\n\t\tif (member) command({ id: member.id, label: member.name })\n\t}\n\n\tuseImperativeHandle(ref, () => ({\n\t\tonKeyDown: ({ event }) => {\n\t\t\tif (items.length === 0) return false\n\t\t\tif (event.key === 'ArrowUp') {\n\t\t\t\tsetActiveIndex((i) => (i + items.length - 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (event.key === 'ArrowDown') {\n\t\t\t\tsetActiveIndex((i) => (i + 1) % items.length)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// Enter and Tab both complete the highlighted member (falling back to the top match so a\n\t\t\t// stale index never selects `undefined`). The empty-roster case is handled a level up, in\n\t\t\t// the suggestion's onKeyDown, which cancels the picker.\n\t\t\tif (event.key === 'Enter' || event.key === 'Tab') {\n\t\t\t\tselect(items[activeIndex] ?? items[0])\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t}))\n\n\treturn (\n\t\t<MentionList\n\t\t\tmembers={items}\n\t\t\tactiveIndex={activeIndex}\n\t\t\tonSelect={select}\n\t\t\trenderMember={renderMember}\n\t\t/>\n\t)\n})\n\nconst MAX_SUGGESTIONS = 8\n\n/** Members whose name contains the query (case-insensitive), capped to the popup's length.\n * @public */\nexport function filterMentionMembers(members: MentionMember[], query: string): MentionMember[] {\n\tconst q = query.toLowerCase()\n\treturn members.filter((m) => m.name.toLowerCase().includes(q)).slice(0, MAX_SUGGESTIONS)\n}\n\n// A reactive flag for whether the @-picker is showing. Deliberately NOT tldraw's open-menu registry:\n// registering there mounts MenuClickCapture, which covers the canvas with a click-capture overlay to\n// make it inert while a menu is open. But the picker is an inline autocomplete, not a modal \u2014 the\n// canvas must stay pannable/zoomable beneath it \u2014 so we track \"open\" ourselves instead.\nconst mentionPickerOpen = atom('isMentionPickerOpen', false)\n\n/**\n * Whether the @-mention picker is currently showing. Host dismissal (Escape, outside-click) checks\n * this so it can defer to the picker instead of tearing down the composer or thread beneath it.\n */\nexport function isMentionPickerOpen(): boolean {\n\treturn mentionPickerOpen.get()\n}\n\n/** @public */\nexport interface MentionSuggestionOptions {\n\t/** Override a member row's content in the picker. Defaults to avatar + name (+ secondary). */\n\trenderMember?(member: MentionMember): ReactNode\n\t/**\n\t * The tldraw editor the composer lives in. When provided, the popup re-anchors reactively as the\n\t * canvas camera moves (the composer rides it) instead of polling every frame. Omit off-canvas.\n\t */\n\teditor?: TldrawEditor | null\n}\n\n/**\n * Build the TipTap `suggestion` config for the comment \\@-picker. `getSuggestions(query)` is the\n * host's resolver \u2014 it returns the members matching the query (sync or async); the SDK owns neither\n * the roster nor the filtering. The plugin runs outside React, so `render` mounts `MentionPopup` via\n * a `ReactRenderer`, forwards navigation keys through the popup's imperative handle, and lets it call\n * `command` to insert.\n * @public\n */\nexport function createMentionSuggestion(\n\tgetSuggestions: (query: string) => MentionMember[] | Promise<MentionMember[]>,\n\toptions: MentionSuggestionOptions = {}\n): Omit<SuggestionOptions<MentionMember, MentionNodeAttrs>, 'editor'> {\n\treturn {\n\t\tchar: '@',\n\t\titems: ({ query }) => getSuggestions(query),\n\t\trender: () => {\n\t\t\tlet renderer: ReactRenderer<MentionPopupHandle, MentionPopupProps> | null = null\n\t\t\tlet container: HTMLElement | null = null\n\t\t\tlet editorEl: HTMLElement | null = null\n\t\t\tlet canvasEl: Element | null = null\n\t\t\tlet stopCameraReaction: (() => void) | null = null\n\t\t\t// The composer field's top-left in page space, plus the popup's screen width. Captured on a\n\t\t\t// fresh read so camera moves can re-derive the popup's screen position from the page anchor\n\t\t\t// (see reposition) rather than the field's DOM rect.\n\t\t\tlet anchorPage: { x: number; y: number } | null = null\n\t\t\tlet popupWidth = 0\n\n\t\t\tconst applyScreen = (left: number, top: number, width: number) => {\n\t\t\t\tif (!container) return\n\t\t\t\tcontainer.style.left = `${left}px`\n\t\t\t\tcontainer.style.top = `${top + 4}px`\n\t\t\t\tcontainer.style.width = `${width}px`\n\t\t\t}\n\n\t\t\t// Fresh placement: read the field's real screen rect, position the popup flush under it (not\n\t\t\t// the caret, matching its width), and remember the field's page-space anchor for reposition.\n\t\t\tconst place = () => {\n\t\t\t\tif (!container || !editorEl || container.style.display === 'none') return\n\t\t\t\tconst field = editorEl.closest('.tlui-cmt-composer__field') ?? editorEl\n\t\t\t\tconst rect = field.getBoundingClientRect()\n\t\t\t\tpopupWidth = rect.width\n\t\t\t\tanchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null\n\t\t\t\tapplyScreen(rect.left, rect.bottom, rect.width)\n\t\t\t}\n\n\t\t\t// Re-derive the popup's screen position from the remembered page anchor \u2014 pure camera math,\n\t\t\t// always current. Reading the field's DOM rect here instead would lag by a frame: the canvas\n\t\t\t// composer re-positions itself on a React commit (a `useValue(pageToViewport(...))`), which\n\t\t\t// lands after a camera reaction runs, so the rect is still last frame's during a pan.\n\t\t\tconst reposition = () => {\n\t\t\t\tif (!anchorPage || !options.editor) return\n\t\t\t\tconst s = options.editor.pageToScreen(anchorPage)\n\t\t\t\tapplyScreen(s.x, s.y, popupWidth)\n\t\t\t}\n\n\t\t\t// The popup is `position: fixed`, but its anchor \u2014 a canvas composer \u2014 rides the camera:\n\t\t\t// panning or zooming moves the composer with no scroll/resize event to hook. Re-anchor when\n\t\t\t// the camera actually changes (via a tldraw reaction) rather than polling every frame, plus\n\t\t\t// on window scroll/resize for the off-canvas case (which re-read the field directly).\n\t\t\tconst startFollowing = () => {\n\t\t\t\twindow.addEventListener('scroll', place, true)\n\t\t\t\twindow.addEventListener('resize', place)\n\t\t\t\tif (options.editor) {\n\t\t\t\t\tstopCameraReaction = react('anchor mention popup to camera', () => {\n\t\t\t\t\t\toptions.editor!.getCamera() // track the camera so this re-runs as it moves\n\t\t\t\t\t\treposition()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst stopFollowing = () => {\n\t\t\t\twindow.removeEventListener('scroll', place, true)\n\t\t\t\twindow.removeEventListener('resize', place)\n\t\t\t\tstopCameraReaction?.()\n\t\t\t\tstopCameraReaction = null\n\t\t\t\tanchorPage = null\n\t\t\t}\n\n\t\t\t// Dismiss the roster \u2014 on Escape, or when the composer loses focus \u2014 by hiding it and\n\t\t\t// clearing the open flag, so isMentionPickerOpen() stays accurate and the thread's own\n\t\t\t// Escape/outside-click dismissal can take over. The TipTap suggestion stays active, so typing\n\t\t\t// re-shows the roster via onUpdate.\n\t\t\tconst hide = () => {\n\t\t\t\tif (container) container.style.display = 'none'\n\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t}\n\n\t\t\t// Wheel/panning over the popup drives the canvas beneath it, so scrolling to pan or zoom\n\t\t\t// isn't swallowed by the roster \u2014 the same passthrough the rest of the comments UI gets from\n\t\t\t// `usePassThroughWheelEvents`. The list still scrolls itself when the roster overflows (we\n\t\t\t// only redispatch when it can't). Done imperatively because the popup lives outside React.\n\t\t\tconst onWheel = (e: WheelEvent) => {\n\t\t\t\tif ((e as any).isSpecialRedispatchedEvent || !canvasEl) return\n\t\t\t\tconst list = container?.querySelector('.tlui-cmt-mention-list')\n\t\t\t\tif (list && list.scrollHeight > list.clientHeight) return\n\t\t\t\te.preventDefault()\n\t\t\t\tconst redispatched = new WheelEvent('wheel', e)\n\t\t\t\t;(redispatched as any).isSpecialRedispatchedEvent = true\n\t\t\t\tcanvasEl.dispatchEvent(redispatched)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonStart: (props) => {\n\t\t\t\t\trenderer = new ReactRenderer(MentionPopup, {\n\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t},\n\t\t\t\t\t\teditor: props.editor,\n\t\t\t\t\t})\n\t\t\t\t\teditorEl = props.editor.view.dom as HTMLElement\n\t\t\t\t\t// The TipTap suggestion has no blur handling, so without this the picker would stay\n\t\t\t\t\t// \"open\" (and the thread would defer its Escape to it) after focus moved away from the\n\t\t\t\t\t// composer \u2014 leaving Escape a no-op and the roster stuck on screen.\n\t\t\t\t\teditorEl.addEventListener('blur', hide)\n\t\t\t\t\tcontainer = document.createElement('div')\n\t\t\t\t\tcontainer.className = 'tlui-cmt-mention-popup'\n\t\t\t\t\tcontainer.appendChild(renderer.element)\n\t\t\t\t\t// Mount inside the tldraw container so the popup inherits the theme variables\n\t\t\t\t\t// (--tl-color-*); portaling to document.body would strip them and lose the panel.\n\t\t\t\t\tconst themed = editorEl.closest('.tl-container')\n\t\t\t\t\t;(themed ?? document.body).appendChild(container)\n\t\t\t\t\tcanvasEl = themed?.querySelector('.tl-canvas') ?? null\n\t\t\t\t\tcontainer.addEventListener('wheel', onWheel, { passive: false })\n\t\t\t\t\tplace()\n\t\t\t\t\tstartFollowing()\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t},\n\t\t\t\tonUpdate: (props) => {\n\t\t\t\t\tif (renderer)\n\t\t\t\t\t\trenderer.updateProps({\n\t\t\t\t\t\t\titems: props.items,\n\t\t\t\t\t\t\tcommand: props.command,\n\t\t\t\t\t\t\trenderMember: options.renderMember,\n\t\t\t\t\t\t})\n\t\t\t\t\t// Typing after an Escape re-shows the roster.\n\t\t\t\t\tif (container) container.style.display = ''\n\t\t\t\t\tmentionPickerOpen.set(true)\n\t\t\t\t\tplace()\n\t\t\t\t},\n\t\t\t\tonKeyDown: (props) => {\n\t\t\t\t\t// Once the roster is hidden (a prior Escape), the TipTap suggestion is still active but\n\t\t\t\t\t// this handler goes inert \u2014 every key passes through to the composer/thread (so a second\n\t\t\t\t\t// Escape closes them, and Arrow/Enter don't select from an invisible list). Typing\n\t\t\t\t\t// re-shows the roster via onUpdate.\n\t\t\t\t\tif (!isMentionPickerOpen()) return false\n\t\t\t\t\tif (props.event.key === 'Escape') {\n\t\t\t\t\t\t// Dismiss only the roster: hide it and stop the key so the composer/thread beneath\n\t\t\t\t\t\t// doesn't also treat Escape as \"abandon\".\n\t\t\t\t\t\thide()\n\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (props.event.key === 'Enter' || props.event.key === 'Tab') {\n\t\t\t\t\t\t// Complete the highlighted member if there is one to complete; if the roster is empty\n\t\t\t\t\t\t// there's nothing to pick, so cancel the picker and swallow the key \u2014 the composer\n\t\t\t\t\t\t// beneath neither submits (Enter) nor moves focus / indents (Tab).\n\t\t\t\t\t\tconst completed = renderer?.ref?.onKeyDown(props) ?? false\n\t\t\t\t\t\tif (!completed) {\n\t\t\t\t\t\t\thide()\n\t\t\t\t\t\t\tprops.event.stopPropagation()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tif (renderer && renderer.ref) return renderer.ref.onKeyDown(props)\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t\tonExit: () => {\n\t\t\t\t\tstopFollowing()\n\t\t\t\t\tif (editorEl) editorEl.removeEventListener('blur', hide)\n\t\t\t\t\tif (container) container.removeEventListener('wheel', onWheel)\n\t\t\t\t\tif (container) container.remove()\n\t\t\t\t\tif (renderer) renderer.destroy()\n\t\t\t\t\trenderer = null\n\t\t\t\t\tcontainer = null\n\t\t\t\t\tcanvasEl = null\n\t\t\t\t\tmentionPickerOpen.set(false)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n}\n"],
5
+ "mappings": "AA4DE;AA3DF,SAAS,qBAAqB;AAE9B,SAAyB,YAAY,qBAAqB,gBAAgB;AAC1E,SAAsC,MAAM,aAAa;AACzD,SAAS,mBAAkC;AAc3C,MAAM,eAAe,WAAkD,SAASA,cAC/E,EAAE,OAAO,SAAS,aAAa,GAC/B,KACC;AACD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,CAAC;AAIhD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,MAAI,UAAU,WAAW;AACxB,iBAAa,KAAK;AAClB,mBAAe,CAAC;AAAA,EACjB;AAEA,QAAM,SAAS,CAAC,WAAsC;AACrD,QAAI,OAAQ,SAAQ,EAAE,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,EAC1D;AAEA,sBAAoB,KAAK,OAAO;AAAA,IAC/B,WAAW,CAAC,EAAE,MAAM,MAAM;AACzB,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAI,MAAM,QAAQ,WAAW;AAC5B,uBAAe,CAAC,OAAO,IAAI,MAAM,SAAS,KAAK,MAAM,MAAM;AAC3D,eAAO;AAAA,MACR;AACA,UAAI,MAAM,QAAQ,aAAa;AAC9B,uBAAe,CAAC,OAAO,IAAI,KAAK,MAAM,MAAM;AAC5C,eAAO;AAAA,MACR;AAIA,UAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,OAAO;AACjD,eAAO,MAAM,WAAW,KAAK,MAAM,CAAC,CAAC;AACrC,eAAO;AAAA,MACR;AACA,aAAO;AAAA,IACR;AAAA,EACD,EAAE;AAEF,SACC;AAAA,IAAC;AAAA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,UAAU;AAAA,MACV;AAAA;AAAA,EACD;AAEF,CAAC;AAED,MAAM,kBAAkB;AAIjB,SAAS,qBAAqB,SAA0B,OAAgC;AAC9F,QAAM,IAAI,MAAM,YAAY;AAC5B,SAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe;AACxF;AAMA,MAAM,oBAAoB,KAAK,uBAAuB,KAAK;AAMpD,SAAS,sBAA+B;AAC9C,SAAO,kBAAkB,IAAI;AAC9B;AAqBO,SAAS,wBACf,gBACA,UAAoC,CAAC,GACgC;AACrE,SAAO;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,EAAE,MAAM,MAAM,eAAe,KAAK;AAAA,IAC1C,QAAQ,MAAM;AACb,UAAI,WAAwE;AAC5E,UAAI,YAAgC;AACpC,UAAI,WAA+B;AACnC,UAAI,WAA2B;AAC/B,UAAI,qBAA0C;AAI9C,UAAI,aAA8C;AAClD,UAAI,aAAa;AAEjB,YAAM,cAAc,CAAC,MAAc,KAAa,UAAkB;AACjE,YAAI,CAAC,UAAW;AAChB,kBAAU,MAAM,OAAO,GAAG,IAAI;AAC9B,kBAAU,MAAM,MAAM,GAAG,MAAM,CAAC;AAChC,kBAAU,MAAM,QAAQ,GAAG,KAAK;AAAA,MACjC;AAIA,YAAM,QAAQ,MAAM;AACnB,YAAI,CAAC,aAAa,CAAC,YAAY,UAAU,MAAM,YAAY,OAAQ;AACnE,cAAM,QAAQ,SAAS,QAAQ,2BAA2B,KAAK;AAC/D,cAAM,OAAO,MAAM,sBAAsB;AACzC,qBAAa,KAAK;AAClB,qBAAa,QAAQ,QAAQ,aAAa,EAAE,GAAG,KAAK,MAAM,GAAG,KAAK,OAAO,CAAC,KAAK;AAC/E,oBAAY,KAAK,MAAM,KAAK,QAAQ,KAAK,KAAK;AAAA,MAC/C;AAMA,YAAM,aAAa,MAAM;AACxB,YAAI,CAAC,cAAc,CAAC,QAAQ,OAAQ;AACpC,cAAM,IAAI,QAAQ,OAAO,aAAa,UAAU;AAChD,oBAAY,EAAE,GAAG,EAAE,GAAG,UAAU;AAAA,MACjC;AAMA,YAAM,iBAAiB,MAAM;AAC5B,eAAO,iBAAiB,UAAU,OAAO,IAAI;AAC7C,eAAO,iBAAiB,UAAU,KAAK;AACvC,YAAI,QAAQ,QAAQ;AACnB,+BAAqB,MAAM,kCAAkC,MAAM;AAClE,oBAAQ,OAAQ,UAAU;AAC1B,uBAAW;AAAA,UACZ,CAAC;AAAA,QACF;AAAA,MACD;AACA,YAAM,gBAAgB,MAAM;AAC3B,eAAO,oBAAoB,UAAU,OAAO,IAAI;AAChD,eAAO,oBAAoB,UAAU,KAAK;AAC1C,6BAAqB;AACrB,6BAAqB;AACrB,qBAAa;AAAA,MACd;AAMA,YAAM,OAAO,MAAM;AAClB,YAAI,UAAW,WAAU,MAAM,UAAU;AACzC,0BAAkB,IAAI,KAAK;AAAA,MAC5B;AAMA,YAAM,UAAU,CAAC,MAAkB;AAClC,YAAK,EAAU,8BAA8B,CAAC,SAAU;AACxD,cAAM,OAAO,WAAW,cAAc,wBAAwB;AAC9D,YAAI,QAAQ,KAAK,eAAe,KAAK,aAAc;AACnD,UAAE,eAAe;AACjB,cAAM,eAAe,IAAI,WAAW,SAAS,CAAC;AAC7C,QAAC,aAAqB,6BAA6B;AACpD,iBAAS,cAAc,YAAY;AAAA,MACpC;AAEA,aAAO;AAAA,QACN,SAAS,CAAC,UAAU;AACnB,qBAAW,IAAI,cAAc,cAAc;AAAA,YAC1C,OAAO;AAAA,cACN,OAAO,MAAM;AAAA,cACb,SAAS,MAAM;AAAA,cACf,cAAc,QAAQ;AAAA,YACvB;AAAA,YACA,QAAQ,MAAM;AAAA,UACf,CAAC;AACD,qBAAW,MAAM,OAAO,KAAK;AAI7B,mBAAS,iBAAiB,QAAQ,IAAI;AACtC,sBAAY,SAAS,cAAc,KAAK;AACxC,oBAAU,YAAY;AACtB,oBAAU,YAAY,SAAS,OAAO;AAGtC,gBAAM,SAAS,SAAS,QAAQ,eAAe;AAC9C,WAAC,UAAU,SAAS,MAAM,YAAY,SAAS;AAChD,qBAAW,QAAQ,cAAc,YAAY,KAAK;AAClD,oBAAU,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAC/D,gBAAM;AACN,yBAAe;AACf,4BAAkB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA,UAAU,CAAC,UAAU;AACpB,cAAI;AACH,qBAAS,YAAY;AAAA,cACpB,OAAO,MAAM;AAAA,cACb,SAAS,MAAM;AAAA,cACf,cAAc,QAAQ;AAAA,YACvB,CAAC;AAEF,cAAI,UAAW,WAAU,MAAM,UAAU;AACzC,4BAAkB,IAAI,IAAI;AAC1B,gBAAM;AAAA,QACP;AAAA,QACA,WAAW,CAAC,UAAU;AAKrB,cAAI,CAAC,oBAAoB,EAAG,QAAO;AACnC,cAAI,MAAM,MAAM,QAAQ,UAAU;AAGjC,iBAAK;AACL,kBAAM,MAAM,gBAAgB;AAC5B,mBAAO;AAAA,UACR;AACA,cAAI,MAAM,MAAM,QAAQ,WAAW,MAAM,MAAM,QAAQ,OAAO;AAI7D,kBAAM,YAAY,UAAU,KAAK,UAAU,KAAK,KAAK;AACrD,gBAAI,CAAC,WAAW;AACf,mBAAK;AACL,oBAAM,MAAM,gBAAgB;AAAA,YAC7B;AACA,mBAAO;AAAA,UACR;AACA,cAAI,YAAY,SAAS,IAAK,QAAO,SAAS,IAAI,UAAU,KAAK;AACjE,iBAAO;AAAA,QACR;AAAA,QACA,QAAQ,MAAM;AACb,wBAAc;AACd,cAAI,SAAU,UAAS,oBAAoB,QAAQ,IAAI;AACvD,cAAI,UAAW,WAAU,oBAAoB,SAAS,OAAO;AAC7D,cAAI,UAAW,WAAU,OAAO;AAChC,cAAI,SAAU,UAAS,QAAQ;AAC/B,qBAAW;AACX,sBAAY;AACZ,qBAAW;AACX,4BAAkB,IAAI,KAAK;AAAA,QAC5B;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;",
6
6
  "names": ["MentionPopup"]
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import { jsxs } from "react/jsx-runtime";
2
2
  function Mention({ name }) {
3
- return /* @__PURE__ */ jsxs("span", { className: "cmt-mention", children: [
3
+ return /* @__PURE__ */ jsxs("span", { className: "tlui-cmt-mention", children: [
4
4
  "@",
5
5
  name
6
6
  ] });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/mention.tsx"],
4
- "sourcesContent": ["/** @public */\nexport interface MentionProps {\n\tname: string\n}\n\n/** An \\@-mention pill for referencing a person in a comment. @public @react */\nexport function Mention({ name }: MentionProps) {\n\treturn <span className=\"cmt-mention\">@{name}</span>\n}\n"],
5
- "mappings": "AAOQ;AADD,SAAS,QAAQ,EAAE,KAAK,GAAiB;AAC/C,SAAO,qBAAC,UAAK,WAAU,eAAc;AAAA;AAAA,IAAE;AAAA,KAAK;AAC7C;",
4
+ "sourcesContent": ["/** @public */\nexport interface MentionProps {\n\tname: string\n}\n\n/** An \\@-mention pill for referencing a person in a comment. @public @react */\nexport function Mention({ name }: MentionProps) {\n\treturn <span className=\"tlui-cmt-mention\">@{name}</span>\n}\n"],
5
+ "mappings": "AAOQ;AADD,SAAS,QAAQ,EAAE,KAAK,GAAiB;AAC/C,SAAO,qBAAC,UAAK,WAAU,oBAAmB;AAAA;AAAA,IAAE;AAAA,KAAK;AAClD;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,16 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  function Reaction({ emoji, count, active }) {
3
- return /* @__PURE__ */ jsxs("button", { className: active ? "cmt-reaction cmt-reaction--active" : "cmt-reaction", type: "button", children: [
4
- /* @__PURE__ */ jsx("span", { className: "cmt-reaction__emoji", children: emoji }),
5
- /* @__PURE__ */ jsx("span", { className: "cmt-reaction__count", children: count })
6
- ] });
3
+ return /* @__PURE__ */ jsxs(
4
+ "button",
5
+ {
6
+ className: active ? "tlui-cmt-reaction tlui-cmt-reaction--active" : "tlui-cmt-reaction",
7
+ type: "button",
8
+ children: [
9
+ /* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__emoji", children: emoji }),
10
+ /* @__PURE__ */ jsx("span", { className: "tlui-cmt-reaction__count", children: count })
11
+ ]
12
+ }
13
+ );
7
14
  }
8
15
  export {
9
16
  Reaction
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/reaction.tsx"],
4
- "sourcesContent": ["/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n}\n\n/** A single emoji reaction pill with a count, highlighted when the user reacted. @public @react */\nexport function Reaction({ emoji, count, active }: ReactionProps) {\n\treturn (\n\t\t<button className={active ? 'cmt-reaction cmt-reaction--active' : 'cmt-reaction'} type=\"button\">\n\t\t\t<span className=\"cmt-reaction__emoji\">{emoji}</span>\n\t\t\t<span className=\"cmt-reaction__count\">{count}</span>\n\t\t</button>\n\t)\n}\n"],
5
- "mappings": "AAUE,SACC,KADD;AAFK,SAAS,SAAS,EAAE,OAAO,OAAO,OAAO,GAAkB;AACjE,SACC,qBAAC,YAAO,WAAW,SAAS,sCAAsC,gBAAgB,MAAK,UACtF;AAAA,wBAAC,UAAK,WAAU,uBAAuB,iBAAM;AAAA,IAC7C,oBAAC,UAAK,WAAU,uBAAuB,iBAAM;AAAA,KAC9C;AAEF;",
4
+ "sourcesContent": ["/** @public */\nexport interface ReactionProps {\n\temoji: string\n\tcount: number\n\tactive: boolean\n}\n\n/** A single emoji reaction pill with a count, highlighted when the user reacted. @public @react */\nexport function Reaction({ emoji, count, active }: ReactionProps) {\n\treturn (\n\t\t<button\n\t\t\tclassName={active ? 'tlui-cmt-reaction tlui-cmt-reaction--active' : 'tlui-cmt-reaction'}\n\t\t\ttype=\"button\"\n\t\t>\n\t\t\t<span className=\"tlui-cmt-reaction__emoji\">{emoji}</span>\n\t\t\t<span className=\"tlui-cmt-reaction__count\">{count}</span>\n\t\t</button>\n\t)\n}\n"],
5
+ "mappings": "AAUE,SAIC,KAJD;AAFK,SAAS,SAAS,EAAE,OAAO,OAAO,OAAO,GAAkB;AACjE,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAW,SAAS,gDAAgD;AAAA,MACpE,MAAK;AAAA,MAEL;AAAA,4BAAC,UAAK,WAAU,4BAA4B,iBAAM;AAAA,QAClD,oBAAC,UAAK,WAAU,4BAA4B,iBAAM;AAAA;AAAA;AAAA,EACnD;AAEF;",
6
6
  "names": []
7
7
  }
@@ -1,11 +1,19 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Reaction } from "./reaction.mjs";
3
3
  function Reactions() {
4
- return /* @__PURE__ */ jsxs("div", { className: "cmt-reactions", children: [
4
+ return /* @__PURE__ */ jsxs("div", { className: "tlui-cmt-reactions", children: [
5
5
  /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F44D}", count: 3, active: true }),
6
6
  /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F389}", count: 1, active: false }),
7
7
  /* @__PURE__ */ jsx(Reaction, { emoji: "\u{1F440}", count: 2, active: false }),
8
- /* @__PURE__ */ jsx("button", { className: "cmt-reaction cmt-reaction--add", type: "button", "aria-label": "Add reaction", children: "\uFF0B" })
8
+ /* @__PURE__ */ jsx(
9
+ "button",
10
+ {
11
+ className: "tlui-cmt-reaction tlui-cmt-reaction--add",
12
+ type: "button",
13
+ "aria-label": "Add reaction",
14
+ children: "\uFF0B"
15
+ }
16
+ )
9
17
  ] });
10
18
  }
11
19
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/reactions.tsx"],
4
- "sourcesContent": ["import { Reaction } from './reaction'\n\n/** The row of reactions under a comment, plus an add-reaction button. @public @react */\nexport function Reactions() {\n\treturn (\n\t\t<div className=\"cmt-reactions\">\n\t\t\t<Reaction emoji=\"\uD83D\uDC4D\" count={3} active={true} />\n\t\t\t<Reaction emoji=\"\uD83C\uDF89\" count={1} active={false} />\n\t\t\t<Reaction emoji=\"\uD83D\uDC40\" count={2} active={false} />\n\t\t\t<button className=\"cmt-reaction cmt-reaction--add\" type=\"button\" aria-label=\"Add reaction\">\n\t\t\t\t\uFF0B\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "AAKE,SACC,KADD;AALF,SAAS,gBAAgB;AAGlB,SAAS,YAAY;AAC3B,SACC,qBAAC,SAAI,WAAU,iBACd;AAAA,wBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,MAAM;AAAA,IAC7C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C,oBAAC,YAAO,WAAU,kCAAiC,MAAK,UAAS,cAAW,gBAAe,oBAE3F;AAAA,KACD;AAEF;",
4
+ "sourcesContent": ["import { Reaction } from './reaction'\n\n/** The row of reactions under a comment, plus an add-reaction button. @public @react */\nexport function Reactions() {\n\treturn (\n\t\t<div className=\"tlui-cmt-reactions\">\n\t\t\t<Reaction emoji=\"\uD83D\uDC4D\" count={3} active={true} />\n\t\t\t<Reaction emoji=\"\uD83C\uDF89\" count={1} active={false} />\n\t\t\t<Reaction emoji=\"\uD83D\uDC40\" count={2} active={false} />\n\t\t\t<button\n\t\t\t\tclassName=\"tlui-cmt-reaction tlui-cmt-reaction--add\"\n\t\t\t\ttype=\"button\"\n\t\t\t\taria-label=\"Add reaction\"\n\t\t\t>\n\t\t\t\t\uFF0B\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "AAKE,SACC,KADD;AALF,SAAS,gBAAgB;AAGlB,SAAS,YAAY;AAC3B,SACC,qBAAC,SAAI,WAAU,sBACd;AAAA,wBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,MAAM;AAAA,IAC7C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C,oBAAC,YAAS,OAAM,aAAK,OAAO,GAAG,QAAQ,OAAO;AAAA,IAC9C;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAW;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KACD;AAEF;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  function SendButton({ label, disabled, onClick }) {
3
- return /* @__PURE__ */ jsx("button", { className: "cmt-send", type: "button", disabled, onClick, children: label });
3
+ return /* @__PURE__ */ jsx("button", { className: "tlui-cmt-send", type: "button", disabled, onClick, children: label });
4
4
  }
5
5
  export {
6
6
  SendButton
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ui/send-button.tsx"],
4
- "sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button className=\"cmt-send\" type=\"button\" disabled={disabled} onClick={onClick}>\n\t\t\t{label}\n\t\t</button>\n\t)\n}\n"],
5
- "mappings": "AAUE;AAFK,SAAS,WAAW,EAAE,OAAO,UAAU,QAAQ,GAAoB;AACzE,SACC,oBAAC,YAAO,WAAU,YAAW,MAAK,UAAS,UAAoB,SAC7D,iBACF;AAEF;",
4
+ "sourcesContent": ["/** @public */\nexport interface SendButtonProps {\n\tlabel: string\n\tdisabled?: boolean\n\tonClick?(): void\n}\n\n/** The button that posts a comment. @public @react */\nexport function SendButton({ label, disabled, onClick }: SendButtonProps) {\n\treturn (\n\t\t<button className=\"tlui-cmt-send\" type=\"button\" disabled={disabled} onClick={onClick}>\n\t\t\t{label}\n\t\t</button>\n\t)\n}\n"],
5
+ "mappings": "AAUE;AAFK,SAAS,WAAW,EAAE,OAAO,UAAU,QAAQ,GAAoB;AACzE,SACC,oBAAC,YAAO,WAAU,iBAAgB,MAAK,UAAS,UAAoB,SAClE,iBACF;AAEF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,12 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { TldrawUiTooltip } from "tldraw";
4
+ const TooltipButton = forwardRef(
5
+ function TooltipButton2({ tooltip, children, ...props }, ref) {
6
+ return /* @__PURE__ */ jsx(TldrawUiTooltip, { content: tooltip, children: /* @__PURE__ */ jsx("button", { ref, type: "button", "aria-label": tooltip, ...props, children }) });
7
+ }
8
+ );
9
+ export {
10
+ TooltipButton
11
+ };
12
+ //# sourceMappingURL=tooltip-button.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/tooltip-button.tsx"],
4
+ "sourcesContent": ["import { type ButtonHTMLAttributes, forwardRef } from 'react'\nimport { TldrawUiTooltip } from 'tldraw'\n\ninterface TooltipButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n\ttooltip: string\n}\n\n/** An icon button labelled by a styled tooltip. Forwards ref and props to the underlying button\n * so `asChild` triggers (dropdown, tooltip) both compose onto the real element. */\nexport const TooltipButton = forwardRef<HTMLButtonElement, TooltipButtonProps>(\n\tfunction TooltipButton({ tooltip, children, ...props }, ref) {\n\t\treturn (\n\t\t\t<TldrawUiTooltip content={tooltip}>\n\t\t\t\t<button ref={ref} type=\"button\" aria-label={tooltip} {...props}>\n\t\t\t\t\t{children}\n\t\t\t\t</button>\n\t\t\t</TldrawUiTooltip>\n\t\t)\n\t}\n)\n"],
5
+ "mappings": "AAaI;AAbJ,SAAoC,kBAAkB;AACtD,SAAS,uBAAuB;AAQzB,MAAM,gBAAgB;AAAA,EAC5B,SAASA,eAAc,EAAE,SAAS,UAAU,GAAG,MAAM,GAAG,KAAK;AAC5D,WACC,oBAAC,mBAAgB,SAAS,SACzB,8BAAC,YAAO,KAAU,MAAK,UAAS,cAAY,SAAU,GAAG,OACvD,UACF,GACD;AAAA,EAEF;AACD;",
6
+ "names": ["TooltipButton"]
7
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/commenting",
3
3
  "description": "tldraw commenting UI components.",
4
- "version": "0.0.0-bootstrap",
4
+ "version": "5.3.0-internal.1640468db8fd",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -56,8 +56,8 @@
56
56
  "@tiptap/pm": "^3.12.1",
57
57
  "@tiptap/react": "^3.12.1",
58
58
  "@tiptap/suggestion": "3.13.0",
59
- "@tldraw/utils": "5.2.5",
60
- "tldraw": "5.2.5"
59
+ "@tldraw/utils": "5.3.0-internal.1640468db8fd",
60
+ "tldraw": "5.3.0-internal.1640468db8fd"
61
61
  },
62
62
  "module": "dist-esm/index.mjs",
63
63
  "source": "src/index.ts",
@@ -1,69 +1,75 @@
1
1
  /* The comments layer is portaled into the editor container. Pins live in the canvas-in-front layer
2
2
  (beneath the UI panels at z-index 300), so the toolbar and style panel draw over them; the open
3
- thread's popover portals up to the menus layer separately (`.cmt-canvas-popover`) so it isn't
3
+ thread's popover portals up to the menus layer separately (`.tlui-cmt-canvas-popover`) so it isn't
4
4
  clipped. Click-through except on its own children. */
5
- .cmt-canvas-layer {
5
+ .tlui-cmt-canvas-layer {
6
6
  position: absolute;
7
7
  inset: 0;
8
8
  z-index: var(--tl-layer-canvas-in-front);
9
9
  pointer-events: none;
10
10
  }
11
11
 
12
- .cmt-canvas-pin,
13
- .cmt-canvas-cluster,
14
- .cmt-canvas-composer {
12
+ .tlui-cmt-canvas-pin,
13
+ .tlui-cmt-canvas-cluster,
14
+ .tlui-cmt-canvas-composer {
15
15
  position: absolute;
16
16
  }
17
17
 
18
- .cmt-canvas-pin,
19
- .cmt-canvas-composer {
18
+ .tlui-cmt-canvas-composer {
20
19
  pointer-events: auto;
21
20
  }
22
21
 
22
+ /* The pin's only interactive surface is the square hit layer below — the wrapper itself is
23
+ inert. Its box sits at the anchor point, not over the (translated) visual, so letting it take
24
+ events would put a phantom hit zone beside the pin. */
25
+ .tlui-cmt-canvas-pin {
26
+ pointer-events: none;
27
+ }
28
+
23
29
  /* Badges are clickable (zoom to the cluster's first split). */
24
- .cmt-canvas-cluster {
30
+ .tlui-cmt-canvas-cluster {
25
31
  transform: translate(-50%, -50%);
26
32
  pointer-events: auto;
27
33
  cursor: pointer;
28
34
  }
29
35
 
30
- .cmt-cluster-fade {
36
+ .tlui-cmt-cluster-fade {
31
37
  opacity: 1;
32
38
  transition: opacity 150ms ease;
33
39
  }
34
40
 
35
- .cmt-cluster-fade--entering {
41
+ .tlui-cmt-cluster-fade--entering {
36
42
  opacity: 0;
37
43
  }
38
44
 
39
- .cmt-cluster-fade--present {
45
+ .tlui-cmt-cluster-fade--present {
40
46
  opacity: 1;
41
47
  }
42
48
 
43
- .cmt-cluster-fade--exiting {
49
+ .tlui-cmt-cluster-fade--exiting {
44
50
  opacity: 0;
45
51
  }
46
52
 
47
- .cmt-cluster-fade--entering *,
48
- .cmt-cluster-fade--exiting * {
53
+ .tlui-cmt-cluster-fade--entering *,
54
+ .tlui-cmt-cluster-fade--exiting * {
49
55
  pointer-events: none !important;
50
56
  }
51
57
 
52
58
  @media (prefers-reduced-motion: reduce) {
53
- .cmt-cluster-fade {
59
+ .tlui-cmt-cluster-fade {
54
60
  transition: none;
55
61
  }
56
62
  }
57
63
 
58
64
  /* The open thread (its pin + popover) sits above the other pins, so nearby markers don't
59
65
  overlap its popover — they tuck behind it. */
60
- .cmt-canvas-pin--open {
66
+ .tlui-cmt-canvas-pin--open {
61
67
  z-index: 1;
62
68
  }
63
69
 
64
70
  /* A region anchor's area: a dashed box under the pins, non-interactive so canvas events pass
65
71
  through. Drawn for the live drag draft and for a region thread while hovered or open. */
66
- .cmt-canvas-region {
72
+ .tlui-cmt-canvas-region {
67
73
  position: absolute;
68
74
  pointer-events: none;
69
75
  border: 2px dashed var(--tl-color-selected);
@@ -73,13 +79,13 @@
73
79
 
74
80
  /* A movable region body (the 'body'/'both' move modes): draggable to translate the whole region.
75
81
  Interactive, so it captures the interior while shown — the tradeoff of dragging the body. */
76
- .cmt-canvas-region--movable {
82
+ .tlui-cmt-canvas-region--movable {
77
83
  pointer-events: auto;
78
84
  cursor: move;
79
85
  }
80
86
 
81
87
  /* Corner resize handles on a region box (three corners; the pin corner is the move handle). */
82
- .cmt-canvas-region-handle {
88
+ .tlui-cmt-canvas-region-handle {
83
89
  position: absolute;
84
90
  width: 9px;
85
91
  height: 9px;
@@ -91,19 +97,47 @@
91
97
  touch-action: none;
92
98
  }
93
99
 
94
- /* Centre the pin marker on its anchor point. */
95
- .cmt-canvas-pin__marker {
100
+ /* Anchor the pin by its bottom-left corner, so the marker's one sharp corner (the pin shape's
101
+ 3px bottom-left) points at the exact anchor point. */
102
+ .tlui-cmt-canvas-pin__marker {
103
+ position: relative;
96
104
  width: max-content;
97
- transform: translate(-50%, -50%);
98
- cursor: grab;
105
+ transform: translate(0, -100%);
106
+ /* Default at rest, like hovering a shape; the editor's move cursor while dragging (below). */
107
+ cursor: var(--tl-cursor-default);
99
108
  touch-action: none;
109
+ pointer-events: none;
110
+ }
111
+
112
+ /* Dragging a pin shows the same move cursor as dragging a shape. */
113
+ .tlui-cmt-canvas-pin--dragging .tlui-cmt-canvas-pin__marker {
114
+ cursor: var(--tl-cursor-move);
115
+ }
116
+
117
+ /* The predictable hit area: a fixed square over the marker's box, and the only part of the pin
118
+ that takes pointer events. Events over it target the marker (a pseudo-element's events report
119
+ its originating element), so the marker's handlers and :hover keep working — but the hit area
120
+ no longer stretches with pin content or scales with the hover transform. */
121
+ .tlui-cmt-canvas-pin__marker::after {
122
+ content: '';
123
+ position: absolute;
124
+ inset: 0;
125
+ pointer-events: auto;
100
126
  }
101
- .cmt-canvas-pin__marker:active {
102
- cursor: grabbing;
127
+
128
+ /* With the hit layer targeting the marker, the pin visual is never the hovered element itself
129
+ (`:hover` matches the target and its ancestors, and the pin is the marker's child) — so the
130
+ canvas pin's hover styling keys off the marker. The hover shadow is for closed pins only: an
131
+ open pin keeps its selection ring, which the shadow would otherwise override. */
132
+ .tlui-cmt-canvas-pin__marker:hover .tlui-cmt-pin {
133
+ transform: scale(1.08);
134
+ }
135
+ .tlui-cmt-canvas-pin__marker:hover .tlui-cmt-pin:not(.tlui-cmt-pin--open) {
136
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.32);
103
137
  }
104
138
 
105
139
  /* The open thread's popover — portaled to the menus layer (above the UI), positioned at the pin. */
106
- .cmt-canvas-popover {
140
+ .tlui-cmt-canvas-popover {
107
141
  position: absolute;
108
142
  z-index: var(--tl-layer-menus);
109
143
  pointer-events: auto;
@@ -112,32 +146,41 @@
112
146
  /* The placement composer — a distinct floating view (portaled to the menus layer, below the click
113
147
  point): a pencil "draft" avatar beside a pill field that is itself the surface (no wrapping
114
148
  panel). Scoped here so the in-thread reply composer keeps its own squarer look. */
115
- .cmt-canvas-composer {
149
+ .tlui-cmt-canvas-composer {
116
150
  position: absolute;
117
151
  z-index: var(--tl-layer-menus);
118
152
  pointer-events: auto;
119
- transform: translateY(8px);
153
+ /* Anchor the draft avatar bottom-left like the pins it becomes: it is 34px wide and sits 4px
154
+ into the row, so its bottom-left corner is 4px right of and 4 + 34 = 38px below the
155
+ composer's top-left corner. */
156
+ transform: translate(-4px, -38px);
120
157
  /* The rich-text editor has no intrinsic width (unlike the old <input>), so give the floating
121
158
  placement composer a definite width or it collapses to nothing. */
122
159
  width: 280px;
123
160
  }
124
- .cmt-canvas-composer .cmt-composer__field {
161
+
162
+ /* A region placement centres the draft avatar on the region's pin corner — the pin it becomes
163
+ straddles that corner. The avatar's centre sits 4 + 34/2 = 21px into the composer. */
164
+ .tlui-cmt-canvas-composer--region {
165
+ transform: translate(-21px, -21px);
166
+ }
167
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__field {
125
168
  background: var(--tl-color-panel);
126
169
  border-color: transparent;
127
170
  border-radius: var(--tl-radius-4);
128
171
  box-shadow: var(--tl-shadow-2);
129
172
  }
130
173
 
131
- /* The comments list panel — a right-side surface shown while the comment tool is active. Below
132
- the popover layer, so an open thread still draws over it. Sits under the top-right share panel
133
- (top offset clears its ~48px height) and sizes to its content, scrolling past max-height rather
134
- than stretching the full canvas height. */
135
- .cmt-canvas-sidebar {
174
+ /* The comments list panel — a right-side surface shown while the comments sidebar is open. Below
175
+ the popover layer, so an open thread still draws over it. Its top aligns flush with the style
176
+ panel (48px from the top, clearing the share panel) and it sizes to its content, scrolling past
177
+ max-height rather than stretching the full canvas height. */
178
+ .tlui-cmt-canvas-sidebar {
136
179
  position: absolute;
137
- top: 56px;
180
+ top: 48px;
138
181
  right: 8px;
139
182
  width: 280px;
140
- max-height: calc(100% - 64px);
183
+ max-height: calc(100% - 56px);
141
184
  z-index: var(--tl-layer-panels);
142
185
  pointer-events: auto;
143
186
  display: flex;
@@ -17,5 +17,5 @@ export interface CommentBodyProps {
17
17
  */
18
18
  export function CommentBody({ richText, resolveName }: CommentBodyProps) {
19
19
  const html = useMemo(() => renderCommentHtml(richText, resolveName), [richText, resolveName])
20
- return <div className="cmt-text" dangerouslySetInnerHTML={{ __html: html }} />
20
+ return <div className="tlui-cmt-text" dangerouslySetInnerHTML={{ __html: html }} />
21
21
  }
@@ -0,0 +1,55 @@
1
+ /* eslint-disable tldraw/no-direct-storage */
2
+ import type { TLRichText } from 'tldraw'
3
+ import { beforeEach, describe, expect, it } from 'vitest'
4
+ import {
5
+ clearCommentDraft,
6
+ getCommentDraft,
7
+ NEW_COMMENT_DRAFT,
8
+ replyDraftSlot,
9
+ saveCommentDraft,
10
+ } from './comment-drafts'
11
+
12
+ function richText(text: string): TLRichText {
13
+ return {
14
+ type: 'doc',
15
+ content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
16
+ } as unknown as TLRichText
17
+ }
18
+
19
+ const EMPTY = { type: 'doc', content: [{ type: 'paragraph' }] } as unknown as TLRichText
20
+
21
+ beforeEach(() => {
22
+ localStorage.clear()
23
+ })
24
+
25
+ describe('comment drafts', () => {
26
+ it('round-trips a draft through its slot', () => {
27
+ saveCommentDraft(NEW_COMMENT_DRAFT, richText('hello'))
28
+ expect(getCommentDraft(NEW_COMMENT_DRAFT)).toEqual(richText('hello'))
29
+ })
30
+
31
+ it('keeps reply drafts apart by thread id', () => {
32
+ saveCommentDraft(replyDraftSlot('comment-thread:a'), richText('for a'))
33
+ saveCommentDraft(replyDraftSlot('comment-thread:b'), richText('for b'))
34
+ expect(getCommentDraft(replyDraftSlot('comment-thread:a'))).toEqual(richText('for a'))
35
+ expect(getCommentDraft(replyDraftSlot('comment-thread:b'))).toEqual(richText('for b'))
36
+ expect(getCommentDraft(NEW_COMMENT_DRAFT)).toBeUndefined()
37
+ })
38
+
39
+ it('saving empty content clears the slot instead', () => {
40
+ saveCommentDraft(NEW_COMMENT_DRAFT, richText('typed then deleted'))
41
+ saveCommentDraft(NEW_COMMENT_DRAFT, EMPTY)
42
+ expect(getCommentDraft(NEW_COMMENT_DRAFT)).toBeUndefined()
43
+ })
44
+
45
+ it('clears a slot explicitly', () => {
46
+ saveCommentDraft(NEW_COMMENT_DRAFT, richText('posted'))
47
+ clearCommentDraft(NEW_COMMENT_DRAFT)
48
+ expect(getCommentDraft(NEW_COMMENT_DRAFT)).toBeUndefined()
49
+ })
50
+
51
+ it('returns undefined for a corrupt stored value', () => {
52
+ localStorage.setItem('tldraw-comment-draft:new', '{not json')
53
+ expect(getCommentDraft(NEW_COMMENT_DRAFT)).toBeUndefined()
54
+ })
55
+ })