@thebuoyant-tsdev/mui-ts-library 3.17.0 → 3.18.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,20 @@ Dieses Projekt folgt [Semantic Versioning](https://semver.org/). In der Praxis b
475
475
 
476
476
  ## Changelog
477
477
 
478
+ ### [3.18.0] — 2026-07-08
479
+
480
+ **Hinzugefügt**
481
+ - `RichTextEditor`: neue Prop `onSave?: () => void` — feuert wenn der Nutzer **Ctrl+S** (Windows/Linux) oder **Cmd+S** (macOS) im Editor drückt. Der native „Seite speichern"-Dialog des Browsers wird im Editor immer unterdrückt. Rein additiv — kein bestehendes Verhalten ändert sich. Siehe [Full Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.md) für Details.
482
+
483
+ ---
484
+
485
+ ### [3.17.1] — 2026-07-05
486
+
487
+ **Behoben**
488
+ - `GanttChart`: Reset-View-Button (`showResetView`) aktiviert sich jetzt wenn ein Assignee-Filter gesetzt ist — bisher blieb der Button inaktiv obwohl ein Klick den Filter korrekt zurückgesetzt hätte. `isViewChanged` berücksichtigt nun korrekt den Assignee-Filter-Zustand.
489
+
490
+ ---
491
+
478
492
  ### [3.17.0] — 2026-07-05
479
493
 
480
494
  **Hinzugefügt**
package/README.md CHANGED
@@ -475,6 +475,20 @@ This project follows [Semantic Versioning](https://semver.org/). In practice:
475
475
 
476
476
  ## Changelog
477
477
 
478
+ ### [3.18.0] — 2026-07-08
479
+
480
+ **Added**
481
+ - `RichTextEditor`: new `onSave?: () => void` prop — fires when the user presses **Ctrl+S** (Windows/Linux) or **Cmd+S** (macOS) inside the editor. The browser's native "Save Page" dialog is always suppressed within the editor. Purely additive — no existing behaviour changes. See [Full Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.md) for details.
482
+
483
+ ---
484
+
485
+ ### [3.17.1] — 2026-07-05
486
+
487
+ **Fixed**
488
+ - `GanttChart`: Reset View button (`showResetView`) now activates when an assignee filter is set — previously the button stayed disabled even though clicking it would have cleared the filter. `isViewChanged` now correctly includes the assignee filter state.
489
+
490
+ ---
491
+
478
492
  ### [3.17.0] — 2026-07-05
479
493
 
480
494
  **Added**
@@ -17,7 +17,7 @@ function C(e) {
17
17
  return !t || !n || !r ? null : new Date(t, n - 1, r, 0, 0, 0, 0);
18
18
  }
19
19
  function w({ onScrollToToday: w, config: T, onExportCSV: E }) {
20
- let D = t(), O = e((e) => e.timeScale), k = e((e) => e.defaultTimeScale), A = e((e) => e.setTimeScale), j = e((e) => e.timelineRange), M = e((e) => e.isRangeCustomized), N = e((e) => e.isExpandedCustomized), P = e((e) => e.setTimelineRange), F = e((e) => e.resetTimelineRange), I = e((e) => e.resetView), L = e((e) => e.tasks), R = e((e) => e.expandedIds), z = e((e) => e.expandAll), B = e((e) => e.collapseAll), V = e((e) => e.assigneeFilter), H = e((e) => e.setAssigneeFilter), U = n(() => [...new Set(L.flatMap((e) => e.assignee ? [e.assignee] : []))].sort(), [L]), W = L.length > 0 && L.every((e) => R.has(e.id)), G = O !== k || M || N, K = n(() => Date.now(), []), q = K >= j.start.getTime() && K <= j.end.getTime(), J = {
20
+ let D = t(), O = e((e) => e.timeScale), k = e((e) => e.defaultTimeScale), A = e((e) => e.setTimeScale), j = e((e) => e.timelineRange), M = e((e) => e.isRangeCustomized), N = e((e) => e.isExpandedCustomized), P = e((e) => e.setTimelineRange), F = e((e) => e.resetTimelineRange), I = e((e) => e.resetView), L = e((e) => e.tasks), R = e((e) => e.expandedIds), z = e((e) => e.expandAll), B = e((e) => e.collapseAll), V = e((e) => e.assigneeFilter), H = e((e) => e.setAssigneeFilter), U = n(() => [...new Set(L.flatMap((e) => e.assignee ? [e.assignee] : []))].sort(), [L]), W = L.length > 0 && L.every((e) => R.has(e.id)), G = O !== k || M || N || V !== "", K = n(() => Date.now(), []), q = K >= j.start.getTime() && K <= j.end.getTime(), J = {
21
21
  days: D.scaleDays,
22
22
  weeks: D.scaleWeeks,
23
23
  months: D.scaleMonths,
@@ -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({ disabled, error, height, helperText, maxCharacters, mentionItems, mentionTriggerChar, name, onMentionSearch, placeholder, readonly, showCharacterCount, showToolbar, showWordCount, toolbarConfig, translation, value, width, onBlur, onChange, onFocus, onMarkdownChange, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function RichTextEditor({ 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;
@@ -2,37 +2,39 @@ 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
4
  import { RichTextEditorContent as i } from "./RichTextEditorContent.js";
5
- import { RichTextEditorToolbar as a } from "./RichTextEditorToolbar.js";
6
- import { RichTextEditorFooter as ee } from "./RichTextEditorFooter.js";
5
+ import { RichTextEditorToolbar as ee } from "./RichTextEditorToolbar.js";
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 l, Divider as u, Paper as ne } from "@mui/material";
9
- import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
10
- import { useEditor as re } from "@tiptap/react";
11
- import { Mention as m } from "@tiptap/extension-mention";
12
- import { StarterKit as h } from "@tiptap/starter-kit";
13
- import { TextStyle as ie } from "@tiptap/extension-text-style";
14
- import { Color as g } from "@tiptap/extension-color";
15
- import { Highlight as _ } from "@tiptap/extension-highlight";
16
- import { Placeholder as ae } from "@tiptap/extension-placeholder";
17
- import { CharacterCount as v } from "@tiptap/extension-character-count";
18
- import { TableKit as oe } from "@tiptap/extension-table";
19
- import { Image as y } from "@tiptap/extension-image";
20
- import { Markdown as b } from "tiptap-markdown";
8
+ import { Box as l, Divider as ne, Paper as re } from "@mui/material";
9
+ import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { useEditor as p } from "@tiptap/react";
11
+ import { Mention as ie } from "@tiptap/extension-mention";
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 m } from "@tiptap/extension-placeholder";
17
+ import { CharacterCount as h } from "@tiptap/extension-character-count";
18
+ import { TableKit as g } from "@tiptap/extension-table";
19
+ import { Image as _ } from "@tiptap/extension-image";
20
+ import { Markdown as v } from "tiptap-markdown";
21
21
  //#region src/components/rich-text-editor/RichTextEditor.tsx
22
- function x({ disabled: x = !1, error: S = !1, height: se, helperText: C, maxCharacters: w, mentionItems: T, mentionTriggerChar: E = "@", name: D, onMentionSearch: O, placeholder: k, readonly: A = !1, showCharacterCount: j = !1, showToolbar: M = !0, showWordCount: N = !1, toolbarConfig: P, translation: F, value: I, width: L, onBlur: R, onChange: z, onFocus: B, onMarkdownChange: V }) {
23
- let H = {
22
+ function y({ disabled: y = !1, error: b = !1, height: le, helperText: x, maxCharacters: S, mentionItems: C, mentionTriggerChar: w = "@", name: T, onMentionSearch: E, placeholder: D, 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
- ...F
26
- }, U = {
25
+ ...N
26
+ }, H = {
27
27
  ...t,
28
- ...P
29
- }, [W, G] = c(!1), [K, ce] = c(!1), q = s(K), J = s(T ?? []);
28
+ ...M
29
+ }, [U, W] = c(!1), [G, ue] = c(!1), K = s(G), q = s(B), J = s(C ?? []);
30
30
  o(() => {
31
- J.current = T ?? [];
32
- }, [T]), o(() => {
33
- q.current = K;
34
- }, [K]);
35
- let Y = e(se), le = e(L), X = Y === "auto", ue = X ? void 0 : Y ?? 200, Z = T !== void 0 || O !== void 0, Q = te(() => Z ? m.configure({
31
+ J.current = C ?? [];
32
+ }, [C]), o(() => {
33
+ K.current = G;
34
+ }, [G]), o(() => {
35
+ q.current = B;
36
+ }, [B]);
37
+ let Y = e(le), de = e(F), X = Y === "auto", fe = X ? void 0 : Y ?? 200, Z = C !== void 0 || E !== void 0, Q = te(() => Z ? ie.configure({
36
38
  HTMLAttributes: { class: "rte-mention" },
37
39
  renderHTML({ options: e, node: t }) {
38
40
  return [
@@ -42,70 +44,75 @@ function x({ disabled: x = !1, error: S = !1, height: se, helperText: C, maxChar
42
44
  "data-id": t.attrs.id,
43
45
  "data-label": t.attrs.label
44
46
  },
45
- `${E}${t.attrs.label}`
47
+ `${w}${t.attrs.label}`
46
48
  ];
47
49
  },
48
50
  suggestion: r({
49
51
  getItems: () => J.current,
50
- onMentionSearch: O,
51
- noResultsLabel: H.mentionNoResults ?? "No results"
52
+ onMentionSearch: E,
53
+ noResultsLabel: V.mentionNoResults ?? "No results"
52
54
  })
53
55
  }) : null, [
54
56
  Z,
55
- E,
56
- O
57
- ]), de = w !== void 0 && w > 0 || j || N, $ = re({
57
+ w,
58
+ E
59
+ ]), pe = S !== void 0 && S > 0 || k || j, $ = p({
58
60
  shouldRerenderOnTransaction: !0,
59
61
  extensions: [
60
- h.configure({ link: { openOnClick: !1 } }),
61
- ie,
62
- g,
63
- _.configure({ multicolor: !0 }),
64
- b.configure({
62
+ ae.configure({ link: { openOnClick: !1 } }),
63
+ oe,
64
+ se,
65
+ ce.configure({ multicolor: !0 }),
66
+ v.configure({
65
67
  transformPastedText: !0,
66
68
  transformCopiedText: !1
67
69
  }),
68
- ae.configure({ placeholder: k ?? "" }),
69
- ...U.showTableButton ? [oe] : [],
70
- ...U.showImageButton ? [y.configure({
70
+ m.configure({ placeholder: D ?? "" }),
71
+ ...H.showTableButton ? [g] : [],
72
+ ...H.showImageButton ? [_.configure({
71
73
  inline: !1,
72
74
  allowBase64: !0
73
75
  })] : [],
74
76
  ...Q ? [Q] : [],
75
- ...de ? w !== void 0 && w > 0 ? [v.configure({ limit: w })] : [v] : []
77
+ ...pe ? S !== void 0 && S > 0 ? [h.configure({ limit: S })] : [h] : []
76
78
  ],
77
- content: I ?? "",
78
- editable: !x && !A,
79
- editorProps: { handlePaste(e, t) {
80
- if (!q.current) return !1;
81
- let n = t.clipboardData?.getData("text/plain");
82
- if (!n) return !1;
83
- t.preventDefault();
84
- let { state: r } = e;
85
- return e.dispatch(r.tr.insertText(n, r.selection.from, r.selection.to)), !0;
86
- } },
79
+ content: P ?? "",
80
+ editable: !y && !O,
81
+ editorProps: {
82
+ handlePaste(e, t) {
83
+ if (!K.current) return !1;
84
+ let n = t.clipboardData?.getData("text/plain");
85
+ if (!n) return !1;
86
+ t.preventDefault();
87
+ let { state: r } = e;
88
+ return e.dispatch(r.tr.insertText(n, r.selection.from, r.selection.to)), !0;
89
+ },
90
+ handleKeyDown(e, t) {
91
+ return (t.ctrlKey || t.metaKey) && t.key === "s" ? (t.preventDefault(), q.current?.(), !0) : !1;
92
+ }
93
+ },
87
94
  onUpdate({ editor: e }) {
88
- z?.(e.getHTML()), V?.(e.storage.markdown.getMarkdown());
95
+ L?.(e.getHTML()), z?.(e.storage.markdown.getMarkdown());
89
96
  },
90
97
  onBlur() {
91
- R?.();
98
+ I?.();
92
99
  },
93
100
  onFocus() {
94
- B?.();
101
+ R?.();
95
102
  }
96
103
  });
97
104
  o(() => {
98
- !$ || I === void 0 || $.getHTML() !== I && $.commands.setContent(I, { emitUpdate: !1 });
99
- }, [$, I]), o(() => {
100
- $ && $.setEditable(!x && !A);
105
+ !$ || P === void 0 || $.getHTML() !== P && $.commands.setContent(P, { emitUpdate: !1 });
106
+ }, [$, P]), o(() => {
107
+ $ && $.setEditable(!y && !O);
101
108
  }, [
102
109
  $,
103
- x,
104
- A
110
+ y,
111
+ O
105
112
  ]);
106
- let fe = $?.storage.characterCount?.characters?.() ?? 0, pe = $?.storage.characterCount?.words?.() ?? 0, me = j || w !== void 0 && w > 0 || N || !!C;
107
- return /* @__PURE__ */ p(l, {
108
- sx: W ? {
113
+ let me = $?.storage.characterCount?.characters?.() ?? 0, he = $?.storage.characterCount?.words?.() ?? 0, ge = k || S !== void 0 && S > 0 || j || !!x;
114
+ return /* @__PURE__ */ f(l, {
115
+ sx: U ? {
109
116
  position: "fixed",
110
117
  top: 0,
111
118
  left: 0,
@@ -117,7 +124,7 @@ function x({ disabled: x = !1, error: S = !1, height: se, helperText: C, maxChar
117
124
  bgcolor: "background.default",
118
125
  p: 1
119
126
  } : {
120
- width: le ?? "100%",
127
+ width: de ?? "100%",
121
128
  ...X ? {
122
129
  display: "flex",
123
130
  flexDirection: "column",
@@ -125,52 +132,52 @@ function x({ disabled: x = !1, error: S = !1, height: se, helperText: C, maxChar
125
132
  } : {}
126
133
  },
127
134
  children: [
128
- /* @__PURE__ */ p(ne, {
135
+ /* @__PURE__ */ f(re, {
129
136
  variant: "outlined",
130
137
  sx: {
131
138
  display: "flex",
132
139
  flexDirection: "column",
133
140
  overflow: "hidden",
134
- ...W || X ? { flex: 1 } : { height: ue },
135
- borderColor: S ? "error.main" : void 0,
141
+ ...U || X ? { flex: 1 } : { height: fe },
142
+ borderColor: b ? "error.main" : void 0,
136
143
  "&:focus-within": {
137
- borderColor: S ? "error.main" : "primary.main",
144
+ borderColor: b ? "error.main" : "primary.main",
138
145
  borderWidth: 2
139
146
  }
140
147
  },
141
- children: [M && !A && /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(a, {
148
+ children: [A && !O && /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(ee, {
142
149
  editor: $,
143
- toolbarConfig: U,
144
- translation: H,
145
- disabled: x,
146
- isFullscreen: W,
147
- onToggleFullscreen: () => G((e) => !e),
148
- pasteAsPlainText: K,
149
- onTogglePasteAsPlainText: () => ce((e) => !e)
150
- }), /* @__PURE__ */ f(u, {})] }), /* @__PURE__ */ f(i, {
150
+ toolbarConfig: H,
151
+ translation: V,
152
+ disabled: y,
153
+ isFullscreen: U,
154
+ onToggleFullscreen: () => W((e) => !e),
155
+ pasteAsPlainText: G,
156
+ onTogglePasteAsPlainText: () => ue((e) => !e)
157
+ }), /* @__PURE__ */ d(ne, {})] }), /* @__PURE__ */ d(i, {
151
158
  editor: $,
152
- error: S,
153
- disabled: x,
154
- readonly: A
159
+ error: b,
160
+ disabled: y,
161
+ readonly: O
155
162
  })]
156
163
  }),
157
- me && /* @__PURE__ */ f(ee, {
158
- helperText: C,
159
- error: S,
160
- showCharacterCount: j || w !== void 0 && w > 0,
161
- charCount: fe,
162
- maxCharacters: w && w > 0 ? w : void 0,
163
- showWordCount: N,
164
- wordCount: pe,
165
- translation: H
164
+ ge && /* @__PURE__ */ d(a, {
165
+ helperText: x,
166
+ error: b,
167
+ showCharacterCount: k || S !== void 0 && S > 0,
168
+ charCount: me,
169
+ maxCharacters: S && S > 0 ? S : void 0,
170
+ showWordCount: j,
171
+ wordCount: he,
172
+ translation: V
166
173
  }),
167
- D && /* @__PURE__ */ f("input", {
174
+ T && /* @__PURE__ */ d("input", {
168
175
  type: "hidden",
169
- name: D,
176
+ name: T,
170
177
  value: $ ? $.getHTML() : ""
171
178
  })
172
179
  ]
173
180
  });
174
181
  }
175
182
  //#endregion
176
- export { x as RichTextEditor };
183
+ export { y as RichTextEditor };
@@ -150,4 +150,10 @@ export type RichTextEditorProps = {
150
150
  onFocus?: () => void;
151
151
  /** Wird bei jeder Änderung zusätzlich zu onChange mit dem Inhalt als Markdown aufgerufen */
152
152
  onMarkdownChange?: (markdown: string) => void;
153
+ /**
154
+ * Fires when the user presses Ctrl+S (Windows/Linux) or Cmd+S (macOS) inside the editor.
155
+ * The browser's native "Save Page" dialog is always suppressed within the editor.
156
+ * When provided, implement your own save logic (API call, local storage, etc.).
157
+ */
158
+ onSave?: () => void;
153
159
  };