@tldraw/mentions 0.0.0-bootstrap

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 (42) hide show
  1. package/README.md +16 -0
  2. package/dist-cjs/avatar.js +51 -0
  3. package/dist-cjs/avatar.js.map +7 -0
  4. package/dist-cjs/comment-author.js +17 -0
  5. package/dist-cjs/comment-author.js.map +7 -0
  6. package/dist-cjs/index.d.ts +131 -0
  7. package/dist-cjs/index.js +41 -0
  8. package/dist-cjs/index.js.map +7 -0
  9. package/dist-cjs/mention-extension.js +42 -0
  10. package/dist-cjs/mention-extension.js.map +7 -0
  11. package/dist-cjs/mention-list.js +70 -0
  12. package/dist-cjs/mention-list.js.map +7 -0
  13. package/dist-cjs/mention-suggestion.js +206 -0
  14. package/dist-cjs/mention-suggestion.js.map +7 -0
  15. package/dist-cjs/mention.js +31 -0
  16. package/dist-cjs/mention.js.map +7 -0
  17. package/dist-esm/avatar.mjs +31 -0
  18. package/dist-esm/avatar.mjs.map +7 -0
  19. package/dist-esm/comment-author.mjs +1 -0
  20. package/dist-esm/comment-author.mjs.map +7 -0
  21. package/dist-esm/index.d.mts +131 -0
  22. package/dist-esm/index.mjs +25 -0
  23. package/dist-esm/index.mjs.map +7 -0
  24. package/dist-esm/mention-extension.mjs +22 -0
  25. package/dist-esm/mention-extension.mjs.map +7 -0
  26. package/dist-esm/mention-list.mjs +50 -0
  27. package/dist-esm/mention-list.mjs.map +7 -0
  28. package/dist-esm/mention-suggestion.mjs +186 -0
  29. package/dist-esm/mention-suggestion.mjs.map +7 -0
  30. package/dist-esm/mention.mjs +11 -0
  31. package/dist-esm/mention.mjs.map +7 -0
  32. package/mentions.css +108 -0
  33. package/package.json +68 -0
  34. package/src/avatar.tsx +35 -0
  35. package/src/comment-author.ts +15 -0
  36. package/src/index.ts +25 -0
  37. package/src/mention-extension.test.ts +41 -0
  38. package/src/mention-extension.ts +51 -0
  39. package/src/mention-list.tsx +103 -0
  40. package/src/mention-suggestion.test.ts +18 -0
  41. package/src/mention-suggestion.tsx +286 -0
  42. package/src/mention.tsx +9 -0
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @tldraw/mentions
2
+
3
+ The tldraw `@`-mention picker and TipTap mention node. Shared by `@tldraw/commenting` (the comment
4
+ composer) and tldraw shape rich text, so mentioning works the same wherever rich text is edited.
5
+
6
+ This package owns:
7
+
8
+ - `createMentionExtension` — the TipTap mention node, configured to render as a pill and (optionally)
9
+ drive the `@` picker.
10
+ - `createMentionSuggestion` — the TipTap `suggestion` config that shows the live member picker as you
11
+ type after `@`, anchored to the editor and (on canvas) re-anchored as the camera moves.
12
+ - `MentionList` / `Mention` / `Avatar` — the presentational picker and pill components.
13
+ - `filterMentionMembers`, `isMentionPickerOpen` — helpers for hosts.
14
+
15
+ The host owns the roster: pass a resolver that returns the members matching an `@`-query, and a
16
+ `resolveName` that maps a stored member id to its current display name.
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var avatar_exports = {};
20
+ __export(avatar_exports, {
21
+ Avatar: () => Avatar
22
+ });
23
+ module.exports = __toCommonJS(avatar_exports);
24
+ var import_jsx_runtime = require("react/jsx-runtime");
25
+ var import_tldraw = require("tldraw");
26
+ function initial(name) {
27
+ return ((0, import_tldraw.getFirstCharacter)(name.trim()) || "?").toUpperCase();
28
+ }
29
+ function Avatar({ author }) {
30
+ if (author.image) {
31
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
32
+ "img",
33
+ {
34
+ className: "tlui-cmt-avatar tlui-cmt-avatar--image",
35
+ src: author.image,
36
+ alt: "",
37
+ "aria-hidden": "true"
38
+ }
39
+ );
40
+ }
41
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
42
+ "div",
43
+ {
44
+ className: "tlui-cmt-avatar",
45
+ "aria-hidden": "true",
46
+ style: author.color ? { backgroundColor: author.color } : void 0,
47
+ children: initial(author.name)
48
+ }
49
+ );
50
+ }
51
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/avatar.tsx"],
4
+ "sourcesContent": ["import { getFirstCharacter } from 'tldraw'\nimport { CommentAuthor } from './comment-author'\n\n/** @public */\nexport interface AvatarProps {\n\tauthor: CommentAuthor\n}\n\nfunction initial(name: string) {\n\treturn (getFirstCharacter(name.trim()) || '?').toUpperCase()\n}\n\n/** A person's avatar \u2014 their image if provided, otherwise a single-initial coloured circle.\n * @public @react */\nexport function Avatar({ author }: AvatarProps) {\n\tif (author.image) {\n\t\treturn (\n\t\t\t<img\n\t\t\t\tclassName=\"tlui-cmt-avatar tlui-cmt-avatar--image\"\n\t\t\t\tsrc={author.image}\n\t\t\t\talt=\"\"\n\t\t\t\taria-hidden=\"true\"\n\t\t\t/>\n\t\t)\n\t}\n\treturn (\n\t\t<div\n\t\t\tclassName=\"tlui-cmt-avatar\"\n\t\t\taria-hidden=\"true\"\n\t\t\tstyle={author.color ? { backgroundColor: author.color } : undefined}\n\t\t>\n\t\t\t{initial(author.name)}\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBG;AAjBH,oBAAkC;AAQlC,SAAS,QAAQ,MAAc;AAC9B,cAAQ,iCAAkB,KAAK,KAAK,CAAC,KAAK,KAAK,YAAY;AAC5D;AAIO,SAAS,OAAO,EAAE,OAAO,GAAgB;AAC/C,MAAI,OAAO,OAAO;AACjB,WACC;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QACV,KAAK,OAAO;AAAA,QACZ,KAAI;AAAA,QACJ,eAAY;AAAA;AAAA,IACb;AAAA,EAEF;AACA,SACC;AAAA,IAAC;AAAA;AAAA,MACA,WAAU;AAAA,MACV,eAAY;AAAA,MACZ,OAAO,OAAO,QAAQ,EAAE,iBAAiB,OAAO,MAAM,IAAI;AAAA,MAEzD,kBAAQ,OAAO,IAAI;AAAA;AAAA,EACrB;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var comment_author_exports = {};
16
+ module.exports = __toCommonJS(comment_author_exports);
17
+ //# sourceMappingURL=comment-author.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/comment-author.ts"],
4
+ "sourcesContent": ["import { JsonObject } from 'tldraw'\n\n/** A comment author's display identity, resolved from an author id.\n * @public */\nexport interface CommentAuthor {\n\tname: string\n\t/** The author's color (any CSS color) \u2014 colors their avatar and pins.\n\t * Omit for the default tint. */\n\tcolor?: string\n\t/** Avatar image URL. When set, avatars show the image instead of the colored initial;\n\t * the canvas pin keeps the initial-on-color. */\n\timage?: string\n\t/** User-defined metadata for the author, never read by the toolkit (like a shape's `meta`). */\n\tmeta?: JsonObject\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -0,0 +1,131 @@
1
+ import { Editor } from 'tldraw';
2
+ import { JsonObject } from 'tldraw';
3
+ import { JSX } from 'react/jsx-runtime';
4
+ import { MentionNodeAttrs } from '@tiptap/extension-mention';
5
+ import { MentionOptions } from '@tiptap/extension-mention';
6
+ import { Node as Node_2 } from '@tiptap/core';
7
+ import { ReactNode } from 'react';
8
+ import type { SuggestionOptions } from '@tiptap/suggestion';
9
+
10
+ /** A person's avatar — their image if provided, otherwise a single-initial coloured circle.
11
+ * @public @react */
12
+ export declare function Avatar({ author }: AvatarProps): JSX.Element;
13
+
14
+ /** @public */
15
+ export declare interface AvatarProps {
16
+ author: CommentAuthor;
17
+ }
18
+
19
+ /** A comment author's display identity, resolved from an author id.
20
+ * @public */
21
+ export declare interface CommentAuthor {
22
+ name: string;
23
+ /** The author's color (any CSS color) — colors their avatar and pins.
24
+ * Omit for the default tint. */
25
+ color?: string;
26
+ /** Avatar image URL. When set, avatars show the image instead of the colored initial;
27
+ * the canvas pin keeps the initial-on-color. */
28
+ image?: string;
29
+ /** User-defined metadata for the author, never read by the toolkit (like a shape's `meta`). */
30
+ meta?: JsonObject;
31
+ }
32
+
33
+ /**
34
+ * The \@-mention node — TipTap's `Mention` configured to render as a `.tlui-cmt-mention` pill.
35
+ *
36
+ * A factory rather than a shared constant because it's configured differently per context: the
37
+ * read-only render paths pass `resolveName` (so the stored `{ id }` node always shows the member's
38
+ * current name, not a copy frozen at insert time), while an editor passes a `suggestion` (the `@`
39
+ * picker). Shape rich text passes both — the same extension both edits and renders. The node schema
40
+ * is identical either way — only these two levers differ.
41
+ * @public
42
+ */
43
+ export declare function createMentionExtension({ resolveName, suggestion }?: MentionExtensionOptions): Node_2<MentionOptions<any, MentionNodeAttrs>, any>;
44
+
45
+ /**
46
+ * Build the TipTap `suggestion` config for the \@-picker. `getSuggestions(query)` is the host's
47
+ * resolver — it returns the members matching the query (sync or async); the SDK owns neither the
48
+ * roster nor the filtering. The plugin runs outside React, so `render` mounts `MentionPopup` via a
49
+ * `ReactRenderer`, forwards navigation keys through the popup's imperative handle, and lets it call
50
+ * `command` to insert.
51
+ * @public
52
+ */
53
+ export declare function createMentionSuggestion(getSuggestions: (query: string) => MentionMember[] | Promise<MentionMember[]>, options?: MentionSuggestionOptions): Omit<SuggestionOptions<MentionMember, MentionNodeAttrs>, 'editor'>;
54
+
55
+ /** Members whose name contains the query (case-insensitive), capped to the popup's length.
56
+ * @public */
57
+ export declare function filterMentionMembers(members: MentionMember[], query: string): MentionMember[];
58
+
59
+ /**
60
+ * Whether the \@-mention picker is currently showing. Host dismissal (Escape, outside-click) checks
61
+ * this so it can defer to the picker instead of tearing down the composer or thread beneath it.
62
+ * @public
63
+ */
64
+ export declare function isMentionPickerOpen(): boolean;
65
+
66
+ /** An \@-mention pill for referencing a person. @public @react */
67
+ export declare function Mention({ name }: MentionProps): JSX.Element;
68
+
69
+ /** @public */
70
+ export declare interface MentionExtensionOptions {
71
+ /**
72
+ * Resolve a member id to its current display name — for the read-only render paths, and for the
73
+ * editor while a mention is displayed. Returns `undefined` when the id can't be resolved, so the
74
+ * render falls back to the mention's stored label.
75
+ */
76
+ resolveName?(id: string): string | undefined;
77
+ /** The `@`-picker suggestion config — for editing (omit on read-only render paths). */
78
+ suggestion?: MentionOptions['suggestion'];
79
+ }
80
+
81
+ /**
82
+ * The \@-mention member picker: a popover list of people to mention, shown as the user types after
83
+ * `@`. Presentational — the composer's suggestion plugin owns resolution, keyboard navigation, and
84
+ * placement, and drives this with the host-resolved `members` and the highlighted `activeIndex`.
85
+ * @public @react
86
+ */
87
+ export declare function MentionList({ members, activeIndex, onSelect, emptyLabel, renderMember }: MentionListProps): JSX.Element;
88
+
89
+ /** @public */
90
+ export declare interface MentionListProps {
91
+ /** Members to choose from — already resolved for the current query by the host. */
92
+ members: MentionMember[];
93
+ /** Index of the highlighted row, driven by the composer's keyboard navigation. */
94
+ activeIndex?: number;
95
+ /** Called when a member is chosen (click, or Enter on the active row). */
96
+ onSelect?(member: MentionMember): void;
97
+ /** Shown when no member matches the query. Defaults to a translated "No matches". */
98
+ emptyLabel?: string;
99
+ /** Override a row's content (inside the selectable button). Defaults to avatar + name (+ secondary). */
100
+ renderMember?(member: MentionMember): ReactNode;
101
+ }
102
+
103
+ /** A person the composer's \@-mention picker can offer: a {@link CommentAuthor} plus its id and
104
+ * picker-only display fields. @public */
105
+ export declare interface MentionMember extends CommentAuthor {
106
+ id: string;
107
+ /** A secondary line under the name — e.g. an email or handle. Omit for a single-line row. */
108
+ secondary?: string;
109
+ /** Marks the current user; shown as a "(You)" suffix after the name. */
110
+ you?: boolean;
111
+ /** Hosts may carry extra fields; a custom `renderMember` can read them. */
112
+ [key: string]: unknown;
113
+ }
114
+
115
+ /** @public */
116
+ export declare interface MentionProps {
117
+ name: string;
118
+ }
119
+
120
+ /** @public */
121
+ export declare interface MentionSuggestionOptions {
122
+ /** Override a member row's content in the picker. Defaults to avatar + name (+ secondary). */
123
+ renderMember?(member: MentionMember): ReactNode;
124
+ /**
125
+ * The tldraw editor the composer lives in. When provided, the popup re-anchors reactively as the
126
+ * canvas camera moves (the composer rides it) instead of polling every frame. Omit off-canvas.
127
+ */
128
+ editor?: null | Editor;
129
+ }
130
+
131
+ export { }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var index_exports = {};
20
+ __export(index_exports, {
21
+ Avatar: () => import_avatar.Avatar,
22
+ Mention: () => import_mention.Mention,
23
+ MentionList: () => import_mention_list.MentionList,
24
+ createMentionExtension: () => import_mention_extension.createMentionExtension,
25
+ createMentionSuggestion: () => import_mention_suggestion.createMentionSuggestion,
26
+ filterMentionMembers: () => import_mention_suggestion.filterMentionMembers,
27
+ isMentionPickerOpen: () => import_mention_suggestion.isMentionPickerOpen
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var import_utils = require("@tldraw/utils");
31
+ var import_avatar = require("./avatar");
32
+ var import_mention = require("./mention");
33
+ var import_mention_list = require("./mention-list");
34
+ var import_mention_extension = require("./mention-extension");
35
+ var import_mention_suggestion = require("./mention-suggestion");
36
+ (0, import_utils.registerTldrawLibraryVersion)(
37
+ "@tldraw/mentions",
38
+ "0.0.0-bootstrap",
39
+ "cjs"
40
+ );
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\n\n// Presentational mention components. These are tldraw-independent and can be used to build custom\n// mention UI.\nexport { Avatar, type AvatarProps } from './avatar'\nexport { type CommentAuthor } from './comment-author'\nexport { Mention, type MentionProps } from './mention'\nexport { MentionList, type MentionListProps, type MentionMember } from './mention-list'\n\n// The TipTap mention node + `@`-picker suggestion. Add the node to a rich-text editor's extensions\n// (comments composer, shape rich text) to render mention pills and \u2014 when a suggestion is provided \u2014\n// drive the picker.\nexport { createMentionExtension, type MentionExtensionOptions } from './mention-extension'\nexport {\n\tcreateMentionSuggestion,\n\tfilterMentionMembers,\n\tisMentionPickerOpen,\n\ttype MentionSuggestionOptions,\n} from './mention-suggestion'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAI7C,oBAAyC;AAEzC,qBAA2C;AAC3C,0BAAuE;AAKvE,+BAAqE;AACrE,gCAKO;AAAA,IAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var mention_extension_exports = {};
20
+ __export(mention_extension_exports, {
21
+ createMentionExtension: () => createMentionExtension
22
+ });
23
+ module.exports = __toCommonJS(mention_extension_exports);
24
+ var import_core = require("@tiptap/core");
25
+ var import_extension_mention = require("@tiptap/extension-mention");
26
+ function mentionName(attrs, resolveName) {
27
+ const resolved = attrs.id && resolveName ? resolveName(attrs.id) : void 0;
28
+ return resolved ?? attrs.label ?? attrs.id ?? "";
29
+ }
30
+ function createMentionExtension({ resolveName, suggestion } = {}) {
31
+ return import_extension_mention.Mention.configure({
32
+ HTMLAttributes: { class: "tlui-cmt-mention" },
33
+ renderText: ({ node }) => `@${mentionName(node.attrs, resolveName)}`,
34
+ renderHTML: ({ node, options }) => [
35
+ "span",
36
+ (0, import_core.mergeAttributes)(options.HTMLAttributes, { "data-mention-id": node.attrs.id }),
37
+ `@${mentionName(node.attrs, resolveName)}`
38
+ ],
39
+ ...suggestion ? { suggestion } : {}
40
+ });
41
+ }
42
+ //# sourceMappingURL=mention-extension.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/mention-extension.ts"],
4
+ "sourcesContent": ["import { mergeAttributes } from '@tiptap/core'\nimport { Mention, type MentionNodeAttrs, type MentionOptions } from '@tiptap/extension-mention'\n\n/**\n * A mention node's display name: the member's current name, resolved live from its id when a\n * resolver is given (so a rename shows through), falling back to the label captured at insert time\n * when no source can name the id \u2014 e.g. a deleted account. The stored id is always the source of\n * truth; live resolution only freshens the displayed name over that captured label.\n */\nfunction mentionName(\n\tattrs: MentionNodeAttrs,\n\tresolveName?: (id: string) => string | undefined\n): string {\n\tconst resolved = attrs.id && resolveName ? resolveName(attrs.id) : undefined\n\treturn resolved ?? attrs.label ?? attrs.id ?? ''\n}\n\n/** @public */\nexport interface MentionExtensionOptions {\n\t/**\n\t * Resolve a member id to its current display name \u2014 for the read-only render paths, and for the\n\t * editor while a mention is displayed. Returns `undefined` when the id can't be resolved, so the\n\t * render falls back to the mention's stored label.\n\t */\n\tresolveName?(id: string): string | undefined\n\t/** The `@`-picker suggestion config \u2014 for editing (omit on read-only render paths). */\n\tsuggestion?: MentionOptions['suggestion']\n}\n\n/**\n * The \\@-mention node \u2014 TipTap's `Mention` configured to render as a `.tlui-cmt-mention` pill.\n *\n * A factory rather than a shared constant because it's configured differently per context: the\n * read-only render paths pass `resolveName` (so the stored `{ id }` node always shows the member's\n * current name, not a copy frozen at insert time), while an editor passes a `suggestion` (the `@`\n * picker). Shape rich text passes both \u2014 the same extension both edits and renders. The node schema\n * is identical either way \u2014 only these two levers differ.\n * @public\n */\nexport function createMentionExtension({ resolveName, suggestion }: MentionExtensionOptions = {}) {\n\treturn Mention.configure({\n\t\tHTMLAttributes: { class: 'tlui-cmt-mention' },\n\t\trenderText: ({ node }) => `@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,\n\t\trenderHTML: ({ node, options }) => [\n\t\t\t'span',\n\t\t\tmergeAttributes(options.HTMLAttributes, { 'data-mention-id': node.attrs.id }),\n\t\t\t`@${mentionName(node.attrs as MentionNodeAttrs, resolveName)}`,\n\t\t],\n\t\t...(suggestion ? { suggestion } : {}),\n\t})\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAgC;AAChC,+BAAoE;AAQpE,SAAS,YACR,OACA,aACS;AACT,QAAM,WAAW,MAAM,MAAM,cAAc,YAAY,MAAM,EAAE,IAAI;AACnE,SAAO,YAAY,MAAM,SAAS,MAAM,MAAM;AAC/C;AAwBO,SAAS,uBAAuB,EAAE,aAAa,WAAW,IAA6B,CAAC,GAAG;AACjG,SAAO,iCAAQ,UAAU;AAAA,IACxB,gBAAgB,EAAE,OAAO,mBAAmB;AAAA,IAC5C,YAAY,CAAC,EAAE,KAAK,MAAM,IAAI,YAAY,KAAK,OAA2B,WAAW,CAAC;AAAA,IACtF,YAAY,CAAC,EAAE,MAAM,QAAQ,MAAM;AAAA,MAClC;AAAA,UACA,6BAAgB,QAAQ,gBAAgB,EAAE,mBAAmB,KAAK,MAAM,GAAG,CAAC;AAAA,MAC5E,IAAI,YAAY,KAAK,OAA2B,WAAW,CAAC;AAAA,IAC7D;AAAA,IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACpC,CAAC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var mention_list_exports = {};
20
+ __export(mention_list_exports, {
21
+ MentionList: () => MentionList
22
+ });
23
+ module.exports = __toCommonJS(mention_list_exports);
24
+ var import_jsx_runtime = require("react/jsx-runtime");
25
+ var import_tldraw = require("tldraw");
26
+ var import_avatar = require("./avatar");
27
+ function DefaultMemberRow({ member }) {
28
+ const msg = (0, import_tldraw.useTranslation)();
29
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
30
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_avatar.Avatar, { author: member }),
31
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tlui-cmt-mention-list__text", children: [
32
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tlui-cmt-mention-list__name", children: [
33
+ member.name,
34
+ member.you && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tlui-cmt-mention-list__you", children: `(${msg("comments.mention-you")})` })
35
+ ] }),
36
+ member.secondary && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tlui-cmt-mention-list__secondary", children: member.secondary })
37
+ ] })
38
+ ] });
39
+ }
40
+ function MentionList({
41
+ members,
42
+ activeIndex = 0,
43
+ onSelect,
44
+ emptyLabel,
45
+ renderMember
46
+ }) {
47
+ const msg = (0, import_tldraw.useTranslation)();
48
+ if (members.length === 0) {
49
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tlui-cmt-mention-list tlui-cmt-mention-list--empty", children: emptyLabel ?? msg("comments.mention-no-matches") });
50
+ }
51
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tlui-cmt-mention-list", role: "listbox", children: members.map((member, i) => {
52
+ const active = i === activeIndex;
53
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ role: "option",
58
+ "aria-selected": active,
59
+ className: active ? "tlui-cmt-mention-list__item tlui-cmt-mention-list__item--active" : "tlui-cmt-mention-list__item",
60
+ onMouseDown: (e) => e.preventDefault(),
61
+ onClick: () => {
62
+ if (onSelect) onSelect(member);
63
+ },
64
+ children: renderMember ? renderMember(member) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultMemberRow, { member })
65
+ },
66
+ member.id
67
+ );
68
+ }) });
69
+ }
70
+ //# sourceMappingURL=mention-list.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/mention-list.tsx"],
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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCE;AAlCF,oBAA+B;AAC/B,oBAAuB;AA8BvB,SAAS,iBAAiB,EAAE,OAAO,GAA8B;AAChE,QAAM,UAAM,8BAAe;AAC3B,SACC,4EACC;AAAA,gDAAC,wBAAO,QAAQ,QAAQ;AAAA,IACxB,6CAAC,UAAK,WAAU,+BACf;AAAA,mDAAC,UAAK,WAAU,+BACd;AAAA,eAAO;AAAA,QACP,OAAO,OACP,4CAAC,UAAK,WAAU,8BAA8B,cAAI,IAAI,sBAAsB,CAAC,KAAI;AAAA,SAEnF;AAAA,MACC,OAAO,aACP,4CAAC,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,UAAM,8BAAe;AAC3B,MAAI,QAAQ,WAAW,GAAG;AACzB,WACC,4CAAC,SAAI,WAAU,sDACb,wBAAc,IAAI,6BAA6B,GACjD;AAAA,EAEF;AACA,SACC,4CAAC,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,4CAAC,oBAAiB,QAAgB;AAAA;AAAA,MAjBpE,OAAO;AAAA,IAkBb;AAAA,EAEF,CAAC,GACF;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var mention_suggestion_exports = {};
20
+ __export(mention_suggestion_exports, {
21
+ createMentionSuggestion: () => createMentionSuggestion,
22
+ filterMentionMembers: () => filterMentionMembers,
23
+ isMentionPickerOpen: () => isMentionPickerOpen
24
+ });
25
+ module.exports = __toCommonJS(mention_suggestion_exports);
26
+ var import_jsx_runtime = require("react/jsx-runtime");
27
+ var import_react = require("@tiptap/react");
28
+ var import_react2 = require("react");
29
+ var import_tldraw = require("tldraw");
30
+ var import_mention_list = require("./mention-list");
31
+ const MentionPopup = (0, import_react2.forwardRef)(function MentionPopup2({ items, command, renderMember }, ref) {
32
+ const [activeIndex, setActiveIndex] = (0, import_react2.useState)(0);
33
+ const [prevItems, setPrevItems] = (0, import_react2.useState)(items);
34
+ if (items !== prevItems) {
35
+ setPrevItems(items);
36
+ setActiveIndex(0);
37
+ }
38
+ const select = (member) => {
39
+ if (member) command({ id: member.id, label: member.name });
40
+ };
41
+ (0, import_react2.useImperativeHandle)(ref, () => ({
42
+ onKeyDown: ({ event }) => {
43
+ if (items.length === 0) return false;
44
+ if (event.key === "ArrowUp") {
45
+ setActiveIndex((i) => (i + items.length - 1) % items.length);
46
+ return true;
47
+ }
48
+ if (event.key === "ArrowDown") {
49
+ setActiveIndex((i) => (i + 1) % items.length);
50
+ return true;
51
+ }
52
+ if (event.key === "Enter" || event.key === "Tab") {
53
+ select(items[activeIndex] ?? items[0]);
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+ }));
59
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
60
+ import_mention_list.MentionList,
61
+ {
62
+ members: items,
63
+ activeIndex,
64
+ onSelect: select,
65
+ renderMember
66
+ }
67
+ );
68
+ });
69
+ const MAX_SUGGESTIONS = 8;
70
+ function filterMentionMembers(members, query) {
71
+ const q = query.toLowerCase();
72
+ return members.filter((m) => m.name.toLowerCase().includes(q)).slice(0, MAX_SUGGESTIONS);
73
+ }
74
+ const mentionPickerOpen = (0, import_tldraw.atom)("isMentionPickerOpen", false);
75
+ function isMentionPickerOpen() {
76
+ return mentionPickerOpen.get();
77
+ }
78
+ function createMentionSuggestion(getSuggestions, options = {}) {
79
+ return {
80
+ char: "@",
81
+ items: ({ query }) => getSuggestions(query),
82
+ render: () => {
83
+ let renderer = null;
84
+ let container = null;
85
+ let editorEl = null;
86
+ let canvasEl = null;
87
+ let stopCameraReaction = null;
88
+ let anchorPage = null;
89
+ let popupWidth = 0;
90
+ const applyScreen = (left, top, width) => {
91
+ if (!container) return;
92
+ container.style.left = `${left}px`;
93
+ container.style.top = `${top + 4}px`;
94
+ container.style.width = `${width}px`;
95
+ };
96
+ const place = () => {
97
+ if (!container || !editorEl || container.style.display === "none") return;
98
+ const field = editorEl.closest(".tlui-cmt-composer__field") ?? editorEl;
99
+ const rect = field.getBoundingClientRect();
100
+ popupWidth = rect.width;
101
+ anchorPage = options.editor?.screenToPage({ x: rect.left, y: rect.bottom }) ?? null;
102
+ applyScreen(rect.left, rect.bottom, rect.width);
103
+ };
104
+ const reposition = () => {
105
+ if (!anchorPage || !options.editor) return;
106
+ const s = options.editor.pageToScreen(anchorPage);
107
+ applyScreen(s.x, s.y, popupWidth);
108
+ };
109
+ const startFollowing = () => {
110
+ window.addEventListener("scroll", place, true);
111
+ window.addEventListener("resize", place);
112
+ if (options.editor) {
113
+ stopCameraReaction = (0, import_tldraw.react)("anchor mention popup to camera", () => {
114
+ options.editor.getCamera();
115
+ reposition();
116
+ });
117
+ }
118
+ };
119
+ const stopFollowing = () => {
120
+ window.removeEventListener("scroll", place, true);
121
+ window.removeEventListener("resize", place);
122
+ stopCameraReaction?.();
123
+ stopCameraReaction = null;
124
+ anchorPage = null;
125
+ };
126
+ const hide = () => {
127
+ if (container) container.style.display = "none";
128
+ mentionPickerOpen.set(false);
129
+ };
130
+ const onWheel = (e) => {
131
+ if (e.isSpecialRedispatchedEvent || !canvasEl) return;
132
+ const list = container?.querySelector(".tlui-cmt-mention-list");
133
+ if (list && list.scrollHeight > list.clientHeight) return;
134
+ e.preventDefault();
135
+ const redispatched = new WheelEvent("wheel", e);
136
+ redispatched.isSpecialRedispatchedEvent = true;
137
+ canvasEl.dispatchEvent(redispatched);
138
+ };
139
+ return {
140
+ onStart: (props) => {
141
+ renderer = new import_react.ReactRenderer(MentionPopup, {
142
+ props: {
143
+ items: props.items,
144
+ command: props.command,
145
+ renderMember: options.renderMember
146
+ },
147
+ editor: props.editor
148
+ });
149
+ editorEl = props.editor.view.dom;
150
+ editorEl.addEventListener("blur", hide);
151
+ container = document.createElement("div");
152
+ container.className = "tlui-cmt-mention-popup";
153
+ container.appendChild(renderer.element);
154
+ const themed = editorEl.closest(".tl-container");
155
+ (themed ?? document.body).appendChild(container);
156
+ canvasEl = themed?.querySelector(".tl-canvas") ?? null;
157
+ container.addEventListener("wheel", onWheel, { passive: false });
158
+ place();
159
+ startFollowing();
160
+ mentionPickerOpen.set(true);
161
+ },
162
+ onUpdate: (props) => {
163
+ if (renderer)
164
+ renderer.updateProps({
165
+ items: props.items,
166
+ command: props.command,
167
+ renderMember: options.renderMember
168
+ });
169
+ if (container) container.style.display = "";
170
+ mentionPickerOpen.set(true);
171
+ place();
172
+ },
173
+ onKeyDown: (props) => {
174
+ if (!isMentionPickerOpen()) return false;
175
+ if (props.event.key === "Escape") {
176
+ hide();
177
+ props.event.stopPropagation();
178
+ return true;
179
+ }
180
+ if (props.event.key === "Enter" || props.event.key === "Tab") {
181
+ const completed = renderer?.ref?.onKeyDown(props) ?? false;
182
+ if (!completed) {
183
+ hide();
184
+ props.event.stopPropagation();
185
+ }
186
+ return true;
187
+ }
188
+ if (renderer && renderer.ref) return renderer.ref.onKeyDown(props);
189
+ return false;
190
+ },
191
+ onExit: () => {
192
+ stopFollowing();
193
+ if (editorEl) editorEl.removeEventListener("blur", hide);
194
+ if (container) container.removeEventListener("wheel", onWheel);
195
+ if (container) container.remove();
196
+ if (renderer) renderer.destroy();
197
+ renderer = null;
198
+ container = null;
199
+ canvasEl = null;
200
+ mentionPickerOpen.set(false);
201
+ }
202
+ };
203
+ }
204
+ };
205
+ }
206
+ //# sourceMappingURL=mention-suggestion.js.map