@ai-matrx/rich-editor 0.4.1 → 0.4.3
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.3
|
|
4
|
+
### `chrome="format"`: the toolbar for hosts with no row of their own (2026-10-08)
|
|
5
|
+
|
|
6
|
+
- `RichEditor` `chrome="format"` renders ONE slim row carrying only the formatting toolbar (no view switch, tools or Save) in every editable view — for hosts like a record's body that had `chrome="bare"` and so no formatting buttons. Editable text always has the toolbar (Arman, 2026-10-08).
|
|
7
|
+
|
|
8
|
+
Consumer action: a `chrome="bare"` host with no toolbar row of its own switches to `chrome="format"`.
|
|
9
|
+
|
|
10
|
+
## 0.4.2
|
|
11
|
+
### A short toolbar keeps what people reach for (2026-10-08)
|
|
12
|
+
|
|
13
|
+
- `FormatButtons` folds by priority, not position: Bold, Italic, Text style, Bulleted list, Checklist and Link stay in view longest; history, inserts and the rest fold into More first. Display order never changes; dividers sit only between visible groups. The text-style menu is narrower (88px). Live: notes beside the chat panel showed only Undo, Redo, Text style and More.
|
|
14
|
+
|
|
15
|
+
Consumer action: none.
|
|
16
|
+
|
|
3
17
|
## 0.4.1
|
|
4
18
|
### Edit in place: Write, Split, Source, Preview (2026-10-08)
|
|
5
19
|
|
|
@@ -86,9 +86,11 @@ export interface RichEditorProps {
|
|
|
86
86
|
* "full" (default): the editor's toolbar row — views, tools, dictation, Save.
|
|
87
87
|
* "bare": no row of its own; the host's chrome carries the view switch, find,
|
|
88
88
|
* outline and dictation (a host that saves by itself — autosave — has no Save).
|
|
89
|
+
* "format": a host with no row of its own (a record's body): ONE slim row
|
|
90
|
+
* carrying only the formatting toolbar (2026-10-08: editable text always has it).
|
|
89
91
|
* Shortcuts (⌘F, ⌘⌥H, ⌘⇧F, ⌘/) keep working either way.
|
|
90
92
|
*/
|
|
91
|
-
chrome?: "full" | "bare";
|
|
93
|
+
chrome?: "full" | "bare" | "format";
|
|
92
94
|
/**
|
|
93
95
|
* The host wraps the editor in its OWN right-click menu (its surface, scope and
|
|
94
96
|
* record rows) — the editor then mounts none, so one menu answers. Drive text
|
|
@@ -123,7 +123,7 @@ function RichEditorImpl({
|
|
|
123
123
|
const [mountKey, setMountKey] = useState(0);
|
|
124
124
|
const [findMode, setFindMode] = useState(null);
|
|
125
125
|
const [outlineChoice, setOutlineChoice] = useState(
|
|
126
|
-
defaultOutlineOpen ?? (chrome
|
|
126
|
+
defaultOutlineOpen ?? (chrome !== "full" ? false : null)
|
|
127
127
|
);
|
|
128
128
|
const outlineOpen = outlineChoice ?? !isMobile;
|
|
129
129
|
const setOutlineOpen = (next) => setOutlineChoice(typeof next === "function" ? next(outlineOpen) : next);
|
|
@@ -538,6 +538,7 @@ function RichEditorImpl({
|
|
|
538
538
|
saveButton
|
|
539
539
|
] }),
|
|
540
540
|
/* @__PURE__ */ jsxs("div", { className: "relative z-20 shrink-0", children: [
|
|
541
|
+
!focusMode && chrome === "format" && editable && /* @__PURE__ */ jsx("div", { className: "matrx-touch-targets flex min-h-9 items-center border-b border-border bg-card/80 px-1.5 py-0.5 backdrop-blur", "data-rich-editor-format-row": "", children: /* @__PURE__ */ jsx(FormatButtons, { className: "flex-1", resolve: () => handle.current?.formatTarget?.() ?? null }) }),
|
|
541
542
|
!focusMode && chrome === "full" && /* @__PURE__ */ jsxs("div", { ref: toolbarRowRef, className: "matrx-touch-targets flex min-h-10 items-center gap-1 border-b border-border bg-card/80 px-2 py-1 backdrop-blur", children: [
|
|
542
543
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto", children: [
|
|
543
544
|
isMobile ? (
|
|
@@ -54,15 +54,41 @@ const FORMAT_GROUPS = [
|
|
|
54
54
|
];
|
|
55
55
|
const FORMAT_BUTTONS = FORMAT_GROUPS.flat();
|
|
56
56
|
const ESSENTIAL_COMMANDS = ["bold", "italic", "bulletList", "taskList", "link"];
|
|
57
|
+
const KEEP = {
|
|
58
|
+
bold: 20,
|
|
59
|
+
italic: 19,
|
|
60
|
+
style: 18,
|
|
61
|
+
bulletList: 17,
|
|
62
|
+
taskList: 16,
|
|
63
|
+
link: 15,
|
|
64
|
+
orderedList: 14,
|
|
65
|
+
undo: 13,
|
|
66
|
+
redo: 12,
|
|
67
|
+
strike: 11,
|
|
68
|
+
code: 10,
|
|
69
|
+
quote: 9,
|
|
70
|
+
codeBlock: 8,
|
|
71
|
+
table: 7,
|
|
72
|
+
horizontalRule: 6
|
|
73
|
+
};
|
|
74
|
+
const keepOf = (slot) => slot.kind === "button" ? KEEP[slot.spec.command] ?? 0 : slot.kind === "style" ? KEEP.style : 0;
|
|
57
75
|
function slotsOf(variant) {
|
|
58
76
|
if (variant === "essential") {
|
|
59
77
|
return [{ kind: "style" }, ...ESSENTIAL_COMMANDS.map((command) => ({ kind: "button", spec: FORMAT_BUTTONS.find((b) => b.command === command) }))];
|
|
60
78
|
}
|
|
61
79
|
const out = [];
|
|
62
80
|
FORMAT_GROUPS.forEach((group, index) => {
|
|
63
|
-
|
|
64
|
-
out.push(...group.map((b) => ({ kind: "button", spec: b })));
|
|
65
|
-
if (index === 0) out.push({ kind: "
|
|
81
|
+
const g = index === 0 ? 0 : index + 1;
|
|
82
|
+
out.push(...group.map((b) => ({ kind: "button", spec: b, group: g })));
|
|
83
|
+
if (index === 0) out.push({ kind: "style", group: 1 });
|
|
84
|
+
});
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function withDividers(slots) {
|
|
88
|
+
const out = [];
|
|
89
|
+
slots.forEach((slot, i) => {
|
|
90
|
+
if (i > 0 && slot.group !== void 0 && slot.group !== slots[i - 1].group) out.push({ kind: "divider" });
|
|
91
|
+
out.push(slot);
|
|
66
92
|
});
|
|
67
93
|
return out;
|
|
68
94
|
}
|
|
@@ -123,27 +149,23 @@ function FormatButtons({ resolve, disabled = false, className, size = "sm", vari
|
|
|
123
149
|
resolve()?.run(command);
|
|
124
150
|
};
|
|
125
151
|
const button = size === "xs" ? 26 : 30;
|
|
126
|
-
const styleWidth = variant === "essential" ? size === "xs" ? 40 : 44 :
|
|
152
|
+
const styleWidth = variant === "essential" ? size === "xs" ? 40 : 44 : 90;
|
|
127
153
|
const widthOf = (slot) => slot.kind === "button" ? button : slot.kind === "style" ? styleWidth : 11;
|
|
128
154
|
const all = slotsOf(variant);
|
|
129
|
-
let
|
|
155
|
+
let kept = all;
|
|
130
156
|
let folded = variant === "essential" ? slotsOf("full").filter((s) => s.kind === "button" && !ESSENTIAL_COMMANDS.includes(s.spec.command)) : [];
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (used + widthOf(slot) > budget) break;
|
|
139
|
-
used += widthOf(slot);
|
|
140
|
-
count += 1;
|
|
141
|
-
}
|
|
142
|
-
shown = all.slice(0, count);
|
|
143
|
-
folded = [...all.slice(count), ...folded];
|
|
157
|
+
const rowWidth = (slots, more) => withDividers(slots).reduce((sum, s) => sum + widthOf(s), 0) + (more ? button : 0);
|
|
158
|
+
if (width > 0 && rowWidth(kept, folded.length > 0) > width + 4) {
|
|
159
|
+
const byKeep = [...all].sort((x, y) => keepOf(x) - keepOf(y));
|
|
160
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const slot of byKeep) {
|
|
162
|
+
if (rowWidth(all.filter((s) => !dropped.has(s)), true) <= width + 4) break;
|
|
163
|
+
dropped.add(slot);
|
|
144
164
|
}
|
|
165
|
+
kept = all.filter((s) => !dropped.has(s));
|
|
166
|
+
folded = [...all.filter((s) => dropped.has(s)), ...folded];
|
|
145
167
|
}
|
|
146
|
-
|
|
168
|
+
const shown = withDividers(kept);
|
|
147
169
|
const foldedButtons = folded.filter((s) => s.kind === "button");
|
|
148
170
|
const styleFolded = folded.some((s) => s.kind === "style");
|
|
149
171
|
const iconClass = size === "xs" ? "h-3.5 w-3.5" : "h-4 w-4";
|
|
@@ -178,7 +200,7 @@ function FormatButtons({ resolve, disabled = false, className, size = "sm", vari
|
|
|
178
200
|
className: cn(
|
|
179
201
|
"inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
180
202
|
size === "xs" ? "h-6" : "h-7",
|
|
181
|
-
variant === "full" && "w-[
|
|
203
|
+
variant === "full" && "w-[88px] justify-between"
|
|
182
204
|
),
|
|
183
205
|
children: [
|
|
184
206
|
variant === "essential" ? /* @__PURE__ */ jsx(StyleIcon, { className: iconClass }) : /* @__PURE__ */ jsx("span", { className: "truncate", children: BLOCK_SHORT[currentStyle.command] }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/rich-editor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "The AI Matrx editor: Visual (Tiptap), Source (CodeMirror), Preview, the markdown formatting command layer, and edit-in-place — over @ai-matrx/rich-content. Consumers that only render never install it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -93,9 +93,9 @@
|
|
|
93
93
|
"@ai-matrx/agents": "latest",
|
|
94
94
|
"@ai-matrx/alchemy": "latest",
|
|
95
95
|
"@ai-matrx/content-ir": "latest",
|
|
96
|
+
"@ai-matrx/design-system": "latest",
|
|
96
97
|
"@ai-matrx/kit": "latest",
|
|
97
98
|
"@ai-matrx/print": "latest",
|
|
98
|
-
"@ai-matrx/design-system": "latest",
|
|
99
99
|
"@ai-matrx/rich-content": "latest"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|