@thebuoyant-tsdev/mui-ts-library 3.19.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.de.md CHANGED
@@ -475,6 +475,13 @@ Dieses Projekt folgt [Semantic Versioning](https://semver.org/). In der Praxis b
475
475
 
476
476
  ## Changelog
477
477
 
478
+ ### [3.20.0] — 2026-07-09
479
+
480
+ **Hinzugefügt**
481
+ - `RichTextEditor`: neue Prop `onMentionInserted?: (item: MentionItem) => void` — feuert wenn der Nutzer ein Element aus dem `@`-Mention-Dropdown auswählt. Das vollständige `MentionItem` (`{ id, label }`) wird übergeben — kein HTML-Parsen nötig. Rein additiv. Siehe [Full Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.md) für Details.
482
+
483
+ ---
484
+
478
485
  ### [3.19.0] — 2026-07-08
479
486
 
480
487
  **Hinzugefügt**
package/README.md CHANGED
@@ -475,6 +475,13 @@ This project follows [Semantic Versioning](https://semver.org/). In practice:
475
475
 
476
476
  ## Changelog
477
477
 
478
+ ### [3.20.0] — 2026-07-09
479
+
480
+ **Added**
481
+ - `RichTextEditor`: new `onMentionInserted?: (item: MentionItem) => void` prop — fires when the user selects an item from the `@` mention dropdown. The full `MentionItem` (`{ id, label }`) is passed to the callback, so no HTML parsing is needed to track who was mentioned. Purely additive. See [Full Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.md) for details.
482
+
483
+ ---
484
+
478
485
  ### [3.19.0] — 2026-07-08
479
486
 
480
487
  **Added**
@@ -5,4 +5,4 @@ declare module "@tiptap/core" {
5
5
  }
6
6
  }
7
7
  import { type RichTextEditorProps } from "./RichTextEditor.types";
8
- export declare function RichTextEditor({ defaultValue, disabled, error, height, helperText, maxCharacters, mentionItems, mentionTriggerChar, name, onMentionSearch, placeholder, readonly, showCharacterCount, showToolbar, showWordCount, toolbarConfig, translation, value, width, onBlur, onChange, onFocus, onMarkdownChange, onSave, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function RichTextEditor({ defaultValue, disabled, error, height, helperText, maxCharacters, mentionItems, mentionTriggerChar, name, onMentionInserted, onMentionSearch, placeholder, readonly, showCharacterCount, showToolbar, showWordCount, toolbarConfig, translation, value, width, onBlur, onChange, onFocus, onMarkdownChange, onSave, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -1,40 +1,44 @@
1
1
  import { normalizeSize as e } from "../shared/normalizeSize.js";
2
2
  import { DEFAULT_RICH_TEXT_EDITOR_TOOLBAR_CONFIG as t, DEFAULT_RICH_TEXT_EDITOR_TRANSLATION as n } from "./RichTextEditor.types.js";
3
3
  import { buildMentionSuggestion as r } from "./RichTextEditorMentionSuggestion.js";
4
- import { RichTextEditorContent as i } from "./RichTextEditorContent.js";
5
- import { RichTextEditorToolbar as ee } from "./RichTextEditorToolbar.js";
4
+ import { RichTextEditorContent as ee } from "./RichTextEditorContent.js";
5
+ import { RichTextEditorToolbar as i } from "./RichTextEditorToolbar.js";
6
6
  import { RichTextEditorFooter as a } from "./RichTextEditorFooter.js";
7
7
  import { useEffect as o, useMemo as te, useRef as s, useState as c } from "react";
8
- import { Box as ne, Divider as re, Paper as ie } from "@mui/material";
9
- import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
10
- import { useEditor as f } from "@tiptap/react";
8
+ import { Box as l, Divider as u, Paper as ne } from "@mui/material";
9
+ import { Fragment as re, jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { useEditor as ie } from "@tiptap/react";
11
11
  import { Mention as p } from "@tiptap/extension-mention";
12
- import { StarterKit as m } from "@tiptap/starter-kit";
13
- import { TextStyle as ae } from "@tiptap/extension-text-style";
14
- import { Color as h } from "@tiptap/extension-color";
15
- import { Highlight as oe } from "@tiptap/extension-highlight";
16
- import { Placeholder as se } from "@tiptap/extension-placeholder";
17
- import { CharacterCount as g } from "@tiptap/extension-character-count";
18
- import { TableKit as _ } from "@tiptap/extension-table";
19
- import { Image as v } from "@tiptap/extension-image";
20
- import { Markdown as ce } from "tiptap-markdown";
12
+ import { StarterKit as ae } from "@tiptap/starter-kit";
13
+ import { TextStyle as oe } from "@tiptap/extension-text-style";
14
+ import { Color as se } from "@tiptap/extension-color";
15
+ import { Highlight as ce } from "@tiptap/extension-highlight";
16
+ import { Placeholder as le } from "@tiptap/extension-placeholder";
17
+ import { CharacterCount as m } from "@tiptap/extension-character-count";
18
+ import { TableKit as h } from "@tiptap/extension-table";
19
+ import { Image as g } from "@tiptap/extension-image";
20
+ import { Markdown as _ } from "tiptap-markdown";
21
21
  //#region src/components/rich-text-editor/RichTextEditor.tsx
22
- function y({ defaultValue: y, disabled: b = !1, error: x = !1, height: le, helperText: S, maxCharacters: C, mentionItems: w, mentionTriggerChar: T = "@", name: E, onMentionSearch: D, placeholder: O, readonly: k = !1, showCharacterCount: A = !1, showToolbar: j = !0, showWordCount: M = !1, toolbarConfig: N, translation: P, value: F, width: I, onBlur: L, onChange: R, onFocus: z, onMarkdownChange: B, onSave: V }) {
23
- let H = {
22
+ function v({ defaultValue: v, disabled: y = !1, error: b = !1, height: ue, helperText: x, maxCharacters: S, mentionItems: C, mentionTriggerChar: w = "@", name: T, onMentionInserted: E, onMentionSearch: D, placeholder: de, readonly: O = !1, showCharacterCount: k = !1, showToolbar: A = !0, showWordCount: j = !1, toolbarConfig: M, translation: N, value: P, width: F, onBlur: I, onChange: L, onFocus: R, onMarkdownChange: z, onSave: B }) {
23
+ let V = {
24
24
  ...n,
25
- ...P
26
- }, U = {
27
- ...t,
28
25
  ...N
29
- }, [W, ue] = c(!1), [G, de] = c(!1), K = s(G), q = s(V), J = s(w ?? []);
26
+ }, H = {
27
+ ...t,
28
+ ...M
29
+ }, [U, fe] = c(!1), [W, pe] = c(!1), G = s(W), K = s(B), q = s(C ?? []);
30
+ o(() => {
31
+ q.current = C ?? [];
32
+ }, [C]);
33
+ let J = s(E);
30
34
  o(() => {
31
- J.current = w ?? [];
32
- }, [w]), o(() => {
33
- K.current = G;
34
- }, [G]), o(() => {
35
- q.current = V;
36
- }, [V]);
37
- let Y = e(le), fe = e(I), X = Y === "auto", pe = X ? void 0 : Y ?? 200, Z = w !== void 0 || D !== void 0, Q = te(() => Z ? p.configure({
35
+ J.current = E;
36
+ }, [E]), o(() => {
37
+ G.current = W;
38
+ }, [W]), o(() => {
39
+ K.current = B;
40
+ }, [B]);
41
+ let Y = e(ue), me = e(F), X = Y === "auto", he = X ? void 0 : Y ?? 200, Z = C !== void 0 || D !== void 0, Q = te(() => Z ? p.configure({
38
42
  HTMLAttributes: { class: "rte-mention" },
39
43
  renderHTML({ options: e, node: t }) {
40
44
  return [
@@ -44,43 +48,44 @@ function y({ defaultValue: y, disabled: b = !1, error: x = !1, height: le, helpe
44
48
  "data-id": t.attrs.id,
45
49
  "data-label": t.attrs.label
46
50
  },
47
- `${T}${t.attrs.label}`
51
+ `${w}${t.attrs.label}`
48
52
  ];
49
53
  },
50
54
  suggestion: r({
51
- getItems: () => J.current,
55
+ getItems: () => q.current,
52
56
  onMentionSearch: D,
53
- noResultsLabel: H.mentionNoResults ?? "No results"
57
+ onMentionInserted: (e) => J.current?.(e),
58
+ noResultsLabel: V.mentionNoResults ?? "No results"
54
59
  })
55
60
  }) : null, [
56
61
  Z,
57
- T,
62
+ w,
58
63
  D
59
- ]), me = C !== void 0 && C > 0 || A || M, $ = f({
64
+ ]), ge = S !== void 0 && S > 0 || k || j, $ = ie({
60
65
  shouldRerenderOnTransaction: !0,
61
66
  extensions: [
62
- m.configure({ link: { openOnClick: !1 } }),
63
- ae,
64
- h,
65
- oe.configure({ multicolor: !0 }),
66
- ce.configure({
67
+ ae.configure({ link: { openOnClick: !1 } }),
68
+ oe,
69
+ se,
70
+ ce.configure({ multicolor: !0 }),
71
+ _.configure({
67
72
  transformPastedText: !0,
68
73
  transformCopiedText: !1
69
74
  }),
70
- se.configure({ placeholder: O ?? "" }),
71
- ...U.showTableButton ? [_] : [],
72
- ...U.showImageButton ? [v.configure({
75
+ le.configure({ placeholder: de ?? "" }),
76
+ ...H.showTableButton ? [h] : [],
77
+ ...H.showImageButton ? [g.configure({
73
78
  inline: !1,
74
79
  allowBase64: !0
75
80
  })] : [],
76
81
  ...Q ? [Q] : [],
77
- ...me ? C !== void 0 && C > 0 ? [g.configure({ limit: C })] : [g] : []
82
+ ...ge ? S !== void 0 && S > 0 ? [m.configure({ limit: S })] : [m] : []
78
83
  ],
79
- content: F ?? y ?? "",
80
- editable: !b && !k,
84
+ content: P ?? v ?? "",
85
+ editable: !y && !O,
81
86
  editorProps: {
82
87
  handlePaste(e, t) {
83
- if (!K.current) return !1;
88
+ if (!G.current) return !1;
84
89
  let n = t.clipboardData?.getData("text/plain");
85
90
  if (!n) return !1;
86
91
  t.preventDefault();
@@ -88,31 +93,31 @@ function y({ defaultValue: y, disabled: b = !1, error: x = !1, height: le, helpe
88
93
  return e.dispatch(r.tr.insertText(n, r.selection.from, r.selection.to)), !0;
89
94
  },
90
95
  handleKeyDown(e, t) {
91
- return (t.ctrlKey || t.metaKey) && t.key === "s" ? (t.preventDefault(), q.current?.(), !0) : !1;
96
+ return (t.ctrlKey || t.metaKey) && t.key === "s" ? (t.preventDefault(), K.current?.(), !0) : !1;
92
97
  }
93
98
  },
94
99
  onUpdate({ editor: e }) {
95
- R?.(e.getHTML()), B?.(e.storage.markdown.getMarkdown());
100
+ L?.(e.getHTML()), z?.(e.storage.markdown.getMarkdown());
96
101
  },
97
102
  onBlur() {
98
- L?.();
103
+ I?.();
99
104
  },
100
105
  onFocus() {
101
- z?.();
106
+ R?.();
102
107
  }
103
108
  });
104
109
  o(() => {
105
- !$ || F === void 0 || $.getHTML() !== F && $.commands.setContent(F, { emitUpdate: !1 });
106
- }, [$, F]), o(() => {
107
- $ && $.setEditable(!b && !k);
110
+ !$ || P === void 0 || $.getHTML() !== P && $.commands.setContent(P, { emitUpdate: !1 });
111
+ }, [$, P]), o(() => {
112
+ $ && $.setEditable(!y && !O);
108
113
  }, [
109
114
  $,
110
- b,
111
- k
115
+ y,
116
+ O
112
117
  ]);
113
- let he = $?.storage.characterCount?.characters?.() ?? 0, ge = $?.storage.characterCount?.words?.() ?? 0, _e = A || C !== void 0 && C > 0 || M || !!S;
114
- return /* @__PURE__ */ d(ne, {
115
- sx: W ? {
118
+ let _e = $?.storage.characterCount?.characters?.() ?? 0, ve = $?.storage.characterCount?.words?.() ?? 0, ye = k || S !== void 0 && S > 0 || j || !!x;
119
+ return /* @__PURE__ */ f(l, {
120
+ sx: U ? {
116
121
  position: "fixed",
117
122
  top: 0,
118
123
  left: 0,
@@ -124,7 +129,7 @@ function y({ defaultValue: y, disabled: b = !1, error: x = !1, height: le, helpe
124
129
  bgcolor: "background.default",
125
130
  p: 1
126
131
  } : {
127
- width: fe ?? "100%",
132
+ width: me ?? "100%",
128
133
  ...X ? {
129
134
  display: "flex",
130
135
  flexDirection: "column",
@@ -132,52 +137,52 @@ function y({ defaultValue: y, disabled: b = !1, error: x = !1, height: le, helpe
132
137
  } : {}
133
138
  },
134
139
  children: [
135
- /* @__PURE__ */ d(ie, {
140
+ /* @__PURE__ */ f(ne, {
136
141
  variant: "outlined",
137
142
  sx: {
138
143
  display: "flex",
139
144
  flexDirection: "column",
140
145
  overflow: "hidden",
141
- ...W || X ? { flex: 1 } : { height: pe },
142
- borderColor: x ? "error.main" : void 0,
146
+ ...U || X ? { flex: 1 } : { height: he },
147
+ borderColor: b ? "error.main" : void 0,
143
148
  "&:focus-within": {
144
- borderColor: x ? "error.main" : "primary.main",
149
+ borderColor: b ? "error.main" : "primary.main",
145
150
  borderWidth: 2
146
151
  }
147
152
  },
148
- children: [j && !k && /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ u(ee, {
153
+ children: [A && !O && /* @__PURE__ */ f(re, { children: [/* @__PURE__ */ d(i, {
149
154
  editor: $,
150
- toolbarConfig: U,
151
- translation: H,
152
- disabled: b,
153
- isFullscreen: W,
154
- onToggleFullscreen: () => ue((e) => !e),
155
- pasteAsPlainText: G,
156
- onTogglePasteAsPlainText: () => de((e) => !e)
157
- }), /* @__PURE__ */ u(re, {})] }), /* @__PURE__ */ u(i, {
155
+ toolbarConfig: H,
156
+ translation: V,
157
+ disabled: y,
158
+ isFullscreen: U,
159
+ onToggleFullscreen: () => fe((e) => !e),
160
+ pasteAsPlainText: W,
161
+ onTogglePasteAsPlainText: () => pe((e) => !e)
162
+ }), /* @__PURE__ */ d(u, {})] }), /* @__PURE__ */ d(ee, {
158
163
  editor: $,
159
- error: x,
160
- disabled: b,
161
- readonly: k
164
+ error: b,
165
+ disabled: y,
166
+ readonly: O
162
167
  })]
163
168
  }),
164
- _e && /* @__PURE__ */ u(a, {
165
- helperText: S,
166
- error: x,
167
- showCharacterCount: A || C !== void 0 && C > 0,
168
- charCount: he,
169
- maxCharacters: C && C > 0 ? C : void 0,
170
- showWordCount: M,
171
- wordCount: ge,
172
- translation: H
169
+ ye && /* @__PURE__ */ d(a, {
170
+ helperText: x,
171
+ error: b,
172
+ showCharacterCount: k || S !== void 0 && S > 0,
173
+ charCount: _e,
174
+ maxCharacters: S && S > 0 ? S : void 0,
175
+ showWordCount: j,
176
+ wordCount: ve,
177
+ translation: V
173
178
  }),
174
- E && /* @__PURE__ */ u("input", {
179
+ T && /* @__PURE__ */ d("input", {
175
180
  type: "hidden",
176
- name: E,
181
+ name: T,
177
182
  value: $ ? $.getHTML() : ""
178
183
  })
179
184
  ]
180
185
  });
181
186
  }
182
187
  //#endregion
183
- export { y as RichTextEditor };
188
+ export { v as RichTextEditor };
@@ -147,6 +147,13 @@ export type RichTextEditorProps = {
147
147
  * @since 3.14.0
148
148
  */
149
149
  onMentionSearch?: (query: string) => MentionItem[] | Promise<MentionItem[]>;
150
+ /**
151
+ * Called when the user selects a mention item from the autocomplete dropdown.
152
+ * Fires immediately after the mention node is inserted into the editor.
153
+ * Useful for tracking which people are mentioned without parsing the HTML output.
154
+ * @since 3.20.0
155
+ */
156
+ onMentionInserted?: (item: MentionItem) => void;
150
157
  /**
151
158
  * Character that triggers the mention autocomplete popup. Default: "@"
152
159
  * @since 3.14.0
@@ -3,7 +3,13 @@ import type { MentionItem } from "./RichTextEditor.types";
3
3
  type BuildSuggestionOptions = {
4
4
  getItems: () => MentionItem[];
5
5
  onMentionSearch?: (query: string) => MentionItem[] | Promise<MentionItem[]>;
6
+ onMentionInserted?: (item: MentionItem) => void;
6
7
  noResultsLabel: string;
7
8
  };
8
- export declare function buildMentionSuggestion({ getItems, onMentionSearch, noResultsLabel, }: BuildSuggestionOptions): Partial<SuggestionOptions<MentionItem>>;
9
+ /** Pure handler factory extracted for testability without mocking ReactRenderer. */
10
+ export declare function createMentionSelectHandler(command: (attrs: {
11
+ id: string;
12
+ label: string;
13
+ }) => void, onMentionInserted?: (item: MentionItem) => void): (item: MentionItem) => void;
14
+ export declare function buildMentionSuggestion({ getItems, onMentionSearch, onMentionInserted, noResultsLabel, }: BuildSuggestionOptions): Partial<SuggestionOptions<MentionItem>>;
9
15
  export {};
@@ -1,45 +1,50 @@
1
1
  import { RichTextEditorMentionList as e } from "./RichTextEditorMentionList.js";
2
2
  import { ReactRenderer as t } from "@tiptap/react";
3
3
  //#region src/components/rich-text-editor/RichTextEditorMentionSuggestion.ts
4
- function n({ getItems: n, onMentionSearch: r, noResultsLabel: i }) {
4
+ function n(e, t) {
5
+ return (n) => {
6
+ e({
7
+ id: n.id,
8
+ label: n.label
9
+ }), t?.(n);
10
+ };
11
+ }
12
+ function r({ getItems: r, onMentionSearch: i, onMentionInserted: a, noResultsLabel: o }) {
5
13
  return {
6
14
  items: async ({ query: e }) => {
7
- if (r) return r(e);
8
- let t = n(), i = e.toLowerCase();
9
- return t.filter((e) => e.label.toLowerCase().includes(i));
15
+ if (i) return i(e);
16
+ let t = r(), n = e.toLowerCase();
17
+ return t.filter((e) => e.label.toLowerCase().includes(n));
10
18
  },
11
19
  render() {
12
- let n = null;
20
+ let r = null;
13
21
  return {
14
- onStart(r) {
15
- n = new t(e, {
22
+ onStart(i) {
23
+ r = new t(e, {
16
24
  props: {
17
- items: r.items,
18
- noResultsLabel: i,
19
- clientRect: r.clientRect,
20
- onSelect: (e) => r.command({
21
- id: e.id,
22
- label: e.label
23
- })
25
+ items: i.items,
26
+ noResultsLabel: o,
27
+ clientRect: i.clientRect,
28
+ onSelect: n(i.command, a)
24
29
  },
25
- editor: r.editor
26
- }), document.body.appendChild(n.element);
30
+ editor: i.editor
31
+ }), document.body.appendChild(r.element);
27
32
  },
28
33
  onUpdate(e) {
29
- n?.updateProps({
34
+ r?.updateProps({
30
35
  items: e.items,
31
36
  clientRect: e.clientRect
32
37
  });
33
38
  },
34
39
  onKeyDown(e) {
35
- return e.event.key === "Escape" ? (n?.destroy(), n = null, !0) : n?.ref?.onKeyDown(e.event) ?? !1;
40
+ return e.event.key === "Escape" ? (r?.destroy(), r = null, !0) : r?.ref?.onKeyDown(e.event) ?? !1;
36
41
  },
37
42
  onExit() {
38
- n?.element.remove(), n?.destroy(), n = null;
43
+ r?.element.remove(), r?.destroy(), r = null;
39
44
  }
40
45
  };
41
46
  }
42
47
  };
43
48
  }
44
49
  //#endregion
45
- export { n as buildMentionSuggestion };
50
+ export { r as buildMentionSuggestion };