open-mcp-app-ui 0.0.2 → 0.0.4
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/dist/editor/index.d.ts +73 -14
- package/dist/editor/index.js +424 -5
- package/dist/editor/index.js.map +1 -1
- package/dist/index.d.ts +501 -19
- package/dist/index.js +1717 -153
- package/dist/index.js.map +1 -1
- package/dist/styles/fallbacks.css +2 -2
- package/dist/styles.css +2 -0
- package/dist/table/index.d.ts +59 -15
- package/dist/table/index.js +267 -5
- package/dist/table/index.js.map +1 -1
- package/package.json +17 -4
package/dist/editor/index.d.ts
CHANGED
|
@@ -1,22 +1,81 @@
|
|
|
1
|
+
import react__default from 'react';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Editor — Markdown + rich text editor.
|
|
5
|
+
*
|
|
6
|
+
* Built on Milkdown (ProseMirror + Remark) for markdown-first editing
|
|
7
|
+
* with perfect round-trip fidelity. Supports WYSIWYG, raw markdown,
|
|
8
|
+
* and split (side-by-side) editing modes.
|
|
3
9
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
10
|
+
* Styled exclusively with MCP Apps spec CSS variables. All typography,
|
|
11
|
+
* colors, borders, and spacing use the host's theme tokens.
|
|
6
12
|
*
|
|
7
|
-
*
|
|
13
|
+
* Imported separately from the core library to keep apps that don't
|
|
14
|
+
* need it from paying any bundle cost:
|
|
8
15
|
*
|
|
9
|
-
* Planned usage:
|
|
10
16
|
* import { Editor } from "open-mcp-app-ui/editor";
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Available toolbar action items. */
|
|
20
|
+
type ToolbarItem = "bold" | "italic" | "strikethrough" | "heading" | "bulletList" | "orderedList" | "taskList" | "code" | "codeBlock" | "blockquote" | "link" | "divider" | "undo" | "redo";
|
|
21
|
+
/** Editing mode. */
|
|
22
|
+
type EditorMode = "wysiwyg" | "markdown" | "split";
|
|
23
|
+
interface EditorProps {
|
|
24
|
+
/** Markdown string (source of truth). */
|
|
25
|
+
value?: string;
|
|
26
|
+
/** Called when content changes. Receives the updated markdown string. */
|
|
27
|
+
onChange?: (markdown: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Editing mode:
|
|
30
|
+
* - "wysiwyg" — Rich text rendering with formatting (default)
|
|
31
|
+
* - "markdown" — Raw markdown text editing
|
|
32
|
+
* - "split" — Side-by-side WYSIWYG and markdown
|
|
33
|
+
*/
|
|
34
|
+
mode?: EditorMode;
|
|
35
|
+
/** Placeholder text when the editor is empty. */
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Toolbar buttons to show, or `false` to hide the toolbar entirely.
|
|
39
|
+
* Defaults to a sensible set of common formatting actions.
|
|
40
|
+
*/
|
|
41
|
+
toolbar?: ToolbarItem[] | false;
|
|
42
|
+
/** View-only mode. Renders markdown as styled content without editing. */
|
|
43
|
+
readOnly?: boolean;
|
|
44
|
+
/** Minimum editor height in pixels. */
|
|
45
|
+
minHeight?: number;
|
|
46
|
+
/** Maximum editor height in pixels before scrolling. */
|
|
47
|
+
maxHeight?: number;
|
|
48
|
+
/** Focus the editor on mount. */
|
|
49
|
+
autoFocus?: boolean;
|
|
50
|
+
/** Additional CSS classes on the outermost wrapper. */
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
interface EditorRef {
|
|
54
|
+
/** Get the current markdown content. */
|
|
55
|
+
getMarkdown: () => string;
|
|
56
|
+
/** Imperatively set editor content without triggering onChange. */
|
|
57
|
+
setMarkdown: (markdown: string) => void;
|
|
58
|
+
/** Focus the editor. */
|
|
59
|
+
focus: () => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Markdown + rich text editor with toolbar and mode switching.
|
|
63
|
+
*
|
|
64
|
+
* Built on Milkdown (ProseMirror + Remark) for markdown-first editing.
|
|
65
|
+
* Supports WYSIWYG, raw markdown, and split editing modes. Styled with
|
|
66
|
+
* MCP Apps spec CSS variables for automatic host theming.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* import { Editor } from "open-mcp-app-ui/editor";
|
|
11
71
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
72
|
+
* <Editor
|
|
73
|
+
* value={markdown}
|
|
74
|
+
* onChange={setMarkdown}
|
|
75
|
+
* placeholder="Start writing..."
|
|
76
|
+
* />
|
|
77
|
+
* ```
|
|
18
78
|
*/
|
|
19
|
-
declare const
|
|
20
|
-
declare const EDITOR_STATUS: "planned";
|
|
79
|
+
declare const Editor: react__default.ForwardRefExoticComponent<EditorProps & react__default.RefAttributes<EditorRef>>;
|
|
21
80
|
|
|
22
|
-
export {
|
|
81
|
+
export { Editor, type EditorMode, type EditorProps, type EditorRef, type ToolbarItem };
|
package/dist/editor/index.js
CHANGED
|
@@ -1,8 +1,427 @@
|
|
|
1
|
-
// src/editor/
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/editor/Editor.tsx
|
|
2
|
+
import {
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useImperativeHandle
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
Editor as MilkdownEditor,
|
|
12
|
+
rootCtx,
|
|
13
|
+
defaultValueCtx
|
|
14
|
+
} from "@milkdown/kit/core";
|
|
15
|
+
import { commonmark } from "@milkdown/kit/preset/commonmark";
|
|
16
|
+
import { gfm } from "@milkdown/kit/preset/gfm";
|
|
17
|
+
import { history } from "@milkdown/kit/plugin/history";
|
|
18
|
+
import { clipboard } from "@milkdown/kit/plugin/clipboard";
|
|
19
|
+
import { listener, listenerCtx } from "@milkdown/plugin-listener";
|
|
20
|
+
import { replaceAll } from "@milkdown/utils";
|
|
21
|
+
import { Milkdown, MilkdownProvider, useEditor } from "@milkdown/react";
|
|
22
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
+
var DEFAULT_TOOLBAR = [
|
|
24
|
+
"bold",
|
|
25
|
+
"italic",
|
|
26
|
+
"strikethrough",
|
|
27
|
+
"divider",
|
|
28
|
+
"heading",
|
|
29
|
+
"bulletList",
|
|
30
|
+
"orderedList",
|
|
31
|
+
"taskList",
|
|
32
|
+
"divider",
|
|
33
|
+
"code",
|
|
34
|
+
"codeBlock",
|
|
35
|
+
"blockquote",
|
|
36
|
+
"link",
|
|
37
|
+
"divider",
|
|
38
|
+
"undo",
|
|
39
|
+
"redo"
|
|
40
|
+
];
|
|
41
|
+
var ToolbarIcons = {
|
|
42
|
+
bold: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("path", { d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" }),
|
|
44
|
+
/* @__PURE__ */ jsx("path", { d: "M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" })
|
|
45
|
+
] }),
|
|
46
|
+
italic: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
47
|
+
/* @__PURE__ */ jsx("line", { x1: "19", y1: "4", x2: "10", y2: "4" }),
|
|
48
|
+
/* @__PURE__ */ jsx("line", { x1: "14", y1: "20", x2: "5", y2: "20" }),
|
|
49
|
+
/* @__PURE__ */ jsx("line", { x1: "15", y1: "4", x2: "9", y2: "20" })
|
|
50
|
+
] }),
|
|
51
|
+
strikethrough: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
52
|
+
/* @__PURE__ */ jsx("path", { d: "M16 4H9a3 3 0 0 0-3 3c0 2 1.5 3 3 3h6" }),
|
|
53
|
+
/* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
|
|
54
|
+
/* @__PURE__ */ jsx("path", { d: "M15 12c1.5 0 3 1 3 3a3 3 0 0 1-3 3H8" })
|
|
55
|
+
] }),
|
|
56
|
+
heading: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
57
|
+
/* @__PURE__ */ jsx("path", { d: "M6 4v16" }),
|
|
58
|
+
/* @__PURE__ */ jsx("path", { d: "M18 4v16" }),
|
|
59
|
+
/* @__PURE__ */ jsx("path", { d: "M6 12h12" })
|
|
60
|
+
] }),
|
|
61
|
+
bulletList: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "6", x2: "20", y2: "6" }),
|
|
63
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "12", x2: "20", y2: "12" }),
|
|
64
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "18", x2: "20", y2: "18" }),
|
|
65
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "6", r: "1", fill: "currentColor" }),
|
|
66
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "12", r: "1", fill: "currentColor" }),
|
|
67
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "18", r: "1", fill: "currentColor" })
|
|
68
|
+
] }),
|
|
69
|
+
orderedList: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("line", { x1: "10", y1: "6", x2: "21", y2: "6" }),
|
|
71
|
+
/* @__PURE__ */ jsx("line", { x1: "10", y1: "12", x2: "21", y2: "12" }),
|
|
72
|
+
/* @__PURE__ */ jsx("line", { x1: "10", y1: "18", x2: "21", y2: "18" }),
|
|
73
|
+
/* @__PURE__ */ jsx("text", { x: "3", y: "7", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif", children: "1" }),
|
|
74
|
+
/* @__PURE__ */ jsx("text", { x: "3", y: "13", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif", children: "2" }),
|
|
75
|
+
/* @__PURE__ */ jsx("text", { x: "3", y: "19", fontSize: "7", fill: "currentColor", stroke: "none", fontFamily: "sans-serif", children: "3" })
|
|
76
|
+
] }),
|
|
77
|
+
taskList: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
78
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "6", height: "6", rx: "1" }),
|
|
79
|
+
/* @__PURE__ */ jsx("path", { d: "M5 8l1.5 1.5L9 7" }),
|
|
80
|
+
/* @__PURE__ */ jsx("line", { x1: "13", y1: "8", x2: "21", y2: "8" }),
|
|
81
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "14", width: "6", height: "6", rx: "1" }),
|
|
82
|
+
/* @__PURE__ */ jsx("line", { x1: "13", y1: "17", x2: "21", y2: "17" })
|
|
83
|
+
] }),
|
|
84
|
+
code: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
85
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 18 22 12 16 6" }),
|
|
86
|
+
/* @__PURE__ */ jsx("polyline", { points: "8 6 2 12 8 18" })
|
|
87
|
+
] }),
|
|
88
|
+
codeBlock: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
89
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
90
|
+
/* @__PURE__ */ jsx("polyline", { points: "9 8 5 12 9 16" }),
|
|
91
|
+
/* @__PURE__ */ jsx("polyline", { points: "15 8 19 12 15 16" })
|
|
92
|
+
] }),
|
|
93
|
+
blockquote: () => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M10 8V6a6 6 0 0 0-6 6v4h4v-4H6a4 4 0 0 1 4-4zm10 0V6a6 6 0 0 0-6 6v4h4v-4h-2a4 4 0 0 1 4-4z" }) }),
|
|
94
|
+
link: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
95
|
+
/* @__PURE__ */ jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
|
|
96
|
+
/* @__PURE__ */ jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
97
|
+
] }),
|
|
98
|
+
undo: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
99
|
+
/* @__PURE__ */ jsx("polyline", { points: "1 4 1 10 7 10" }),
|
|
100
|
+
/* @__PURE__ */ jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
|
|
101
|
+
] }),
|
|
102
|
+
redo: () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
103
|
+
/* @__PURE__ */ jsx("polyline", { points: "23 4 23 10 17 10" }),
|
|
104
|
+
/* @__PURE__ */ jsx("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
|
|
105
|
+
] })
|
|
106
|
+
};
|
|
107
|
+
var TOOLBAR_LABELS = {
|
|
108
|
+
bold: "Bold",
|
|
109
|
+
italic: "Italic",
|
|
110
|
+
strikethrough: "Strikethrough",
|
|
111
|
+
heading: "Heading",
|
|
112
|
+
bulletList: "Bullet List",
|
|
113
|
+
orderedList: "Ordered List",
|
|
114
|
+
taskList: "Task List",
|
|
115
|
+
code: "Inline Code",
|
|
116
|
+
codeBlock: "Code Block",
|
|
117
|
+
blockquote: "Blockquote",
|
|
118
|
+
link: "Link",
|
|
119
|
+
undo: "Undo",
|
|
120
|
+
redo: "Redo"
|
|
121
|
+
};
|
|
122
|
+
var TOOLBAR_MARKDOWN = {
|
|
123
|
+
bold: "**bold**",
|
|
124
|
+
italic: "*italic*",
|
|
125
|
+
strikethrough: "~~strikethrough~~",
|
|
126
|
+
heading: "## ",
|
|
127
|
+
bulletList: "- ",
|
|
128
|
+
orderedList: "1. ",
|
|
129
|
+
taskList: "- [ ] ",
|
|
130
|
+
code: "`code`",
|
|
131
|
+
codeBlock: "```\n\n```",
|
|
132
|
+
blockquote: "> ",
|
|
133
|
+
link: "[text](url)",
|
|
134
|
+
undo: "",
|
|
135
|
+
redo: ""
|
|
136
|
+
};
|
|
137
|
+
var EditorToolbar = ({
|
|
138
|
+
items,
|
|
139
|
+
onAction
|
|
140
|
+
}) => /* @__PURE__ */ jsx("div", { className: "omu-editor-toolbar flex items-center gap-0.5 px-2 py-1.5 bg-bg-secondary border-b border-bdr-secondary rounded-t-md overflow-x-auto", children: items.map((item, i) => {
|
|
141
|
+
if (item === "divider") {
|
|
142
|
+
return /* @__PURE__ */ jsx(
|
|
143
|
+
"div",
|
|
144
|
+
{
|
|
145
|
+
className: "w-px h-4 bg-bdr-secondary mx-1 shrink-0"
|
|
146
|
+
},
|
|
147
|
+
`divider-${i}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const Icon = ToolbarIcons[item];
|
|
151
|
+
const label = TOOLBAR_LABELS[item];
|
|
152
|
+
return /* @__PURE__ */ jsx(
|
|
153
|
+
"button",
|
|
154
|
+
{
|
|
155
|
+
type: "button",
|
|
156
|
+
className: "inline-flex items-center justify-center w-7 h-7 rounded-sm text-txt-secondary hover:text-txt-primary hover:bg-bg-tertiary transition-colors shrink-0 cursor-pointer",
|
|
157
|
+
onClick: () => onAction(item),
|
|
158
|
+
title: label,
|
|
159
|
+
"aria-label": label,
|
|
160
|
+
children: /* @__PURE__ */ jsx(Icon, {})
|
|
161
|
+
},
|
|
162
|
+
item
|
|
163
|
+
);
|
|
164
|
+
}) });
|
|
165
|
+
var ModeToggle = ({
|
|
166
|
+
mode,
|
|
167
|
+
onModeChange
|
|
168
|
+
}) => {
|
|
169
|
+
const modes = [
|
|
170
|
+
{ value: "wysiwyg", label: "Rich" },
|
|
171
|
+
{ value: "markdown", label: "MD" },
|
|
172
|
+
{ value: "split", label: "Split" }
|
|
173
|
+
];
|
|
174
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5 ml-auto pl-2", children: modes.map((m) => /* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
className: [
|
|
179
|
+
"px-2 py-0.5 rounded-sm text-xs font-medium transition-colors cursor-pointer",
|
|
180
|
+
mode === m.value ? "bg-bg-tertiary text-txt-primary" : "text-txt-tertiary hover:text-txt-secondary hover:bg-bg-tertiary"
|
|
181
|
+
].join(" "),
|
|
182
|
+
onClick: () => onModeChange(m.value),
|
|
183
|
+
children: m.label
|
|
184
|
+
},
|
|
185
|
+
m.value
|
|
186
|
+
)) });
|
|
187
|
+
};
|
|
188
|
+
var MilkdownInner = ({
|
|
189
|
+
defaultValue,
|
|
190
|
+
onChange,
|
|
191
|
+
readOnly,
|
|
192
|
+
placeholder,
|
|
193
|
+
autoFocus,
|
|
194
|
+
skipRef,
|
|
195
|
+
actionsRef
|
|
196
|
+
}) => {
|
|
197
|
+
const contentRef = useRef(defaultValue);
|
|
198
|
+
const wrapperRef = useRef(null);
|
|
199
|
+
const { get } = useEditor(
|
|
200
|
+
(root) => MilkdownEditor.make().config((ctx) => {
|
|
201
|
+
ctx.set(rootCtx, root);
|
|
202
|
+
ctx.set(defaultValueCtx, defaultValue);
|
|
203
|
+
ctx.get(listenerCtx).markdownUpdated((_, markdown) => {
|
|
204
|
+
contentRef.current = markdown;
|
|
205
|
+
if (skipRef.current) {
|
|
206
|
+
skipRef.current = false;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
onChange(markdown);
|
|
210
|
+
});
|
|
211
|
+
}).use(commonmark).use(gfm).use(history).use(clipboard).use(listener)
|
|
212
|
+
);
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
actionsRef.current = {
|
|
215
|
+
getMarkdown: () => contentRef.current,
|
|
216
|
+
setMarkdown: (md) => {
|
|
217
|
+
const editor = get();
|
|
218
|
+
if (!editor) return;
|
|
219
|
+
skipRef.current = true;
|
|
220
|
+
editor.action(replaceAll(md));
|
|
221
|
+
contentRef.current = md;
|
|
222
|
+
},
|
|
223
|
+
focus: () => {
|
|
224
|
+
const el = wrapperRef.current?.querySelector(".ProseMirror");
|
|
225
|
+
if (el instanceof HTMLElement) el.focus();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}, [get, skipRef, actionsRef]);
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (autoFocus) {
|
|
231
|
+
requestAnimationFrame(() => {
|
|
232
|
+
const el = wrapperRef.current?.querySelector(".ProseMirror");
|
|
233
|
+
if (el instanceof HTMLElement) el.focus();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}, [autoFocus]);
|
|
237
|
+
const handleWrapperClick = useCallback(() => {
|
|
238
|
+
const el = wrapperRef.current?.querySelector(".ProseMirror");
|
|
239
|
+
if (el instanceof HTMLElement) el.focus();
|
|
240
|
+
}, []);
|
|
241
|
+
return /* @__PURE__ */ jsx(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
ref: wrapperRef,
|
|
245
|
+
className: "omu-editor-content flex-1 overflow-y-auto px-4 py-3 flex flex-col min-h-0 cursor-text",
|
|
246
|
+
"data-placeholder": placeholder,
|
|
247
|
+
"data-readonly": readOnly || void 0,
|
|
248
|
+
onClick: readOnly ? void 0 : handleWrapperClick,
|
|
249
|
+
children: /* @__PURE__ */ jsx(Milkdown, {})
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
var MarkdownTextarea = ({
|
|
254
|
+
value,
|
|
255
|
+
onChange,
|
|
256
|
+
readOnly,
|
|
257
|
+
placeholder,
|
|
258
|
+
autoFocus,
|
|
259
|
+
minHeight,
|
|
260
|
+
maxHeight
|
|
261
|
+
}) => /* @__PURE__ */ jsx(
|
|
262
|
+
"textarea",
|
|
263
|
+
{
|
|
264
|
+
className: [
|
|
265
|
+
"omu-editor-textarea flex-1 w-full px-4 py-3 resize-none",
|
|
266
|
+
"bg-bg-primary text-txt-primary font-mono text-sm",
|
|
267
|
+
"outline-none border-none",
|
|
268
|
+
"placeholder:text-txt-tertiary"
|
|
269
|
+
].join(" "),
|
|
270
|
+
value,
|
|
271
|
+
onChange: (e) => onChange(e.target.value),
|
|
272
|
+
readOnly,
|
|
273
|
+
placeholder,
|
|
274
|
+
autoFocus,
|
|
275
|
+
style: {
|
|
276
|
+
minHeight: minHeight ? `${minHeight}px` : void 0,
|
|
277
|
+
maxHeight: maxHeight ? `${maxHeight}px` : void 0
|
|
278
|
+
},
|
|
279
|
+
spellCheck: false
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
var Editor = forwardRef(
|
|
283
|
+
({
|
|
284
|
+
value = "",
|
|
285
|
+
onChange,
|
|
286
|
+
mode: modeProp,
|
|
287
|
+
placeholder,
|
|
288
|
+
toolbar: toolbarProp,
|
|
289
|
+
readOnly = false,
|
|
290
|
+
minHeight = 120,
|
|
291
|
+
maxHeight,
|
|
292
|
+
autoFocus = false,
|
|
293
|
+
className = ""
|
|
294
|
+
}, ref) => {
|
|
295
|
+
const [internalMode, setInternalMode] = useState(modeProp ?? "wysiwyg");
|
|
296
|
+
const mode = modeProp ?? internalMode;
|
|
297
|
+
const showModeToggle = modeProp == null;
|
|
298
|
+
const [rawValue, setRawValue] = useState(value);
|
|
299
|
+
const skipRef = useRef(false);
|
|
300
|
+
const actionsRef = useRef(null);
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
setRawValue(value);
|
|
303
|
+
}, [value]);
|
|
304
|
+
useImperativeHandle(ref, () => ({
|
|
305
|
+
getMarkdown: () => {
|
|
306
|
+
if (mode === "markdown") return rawValue;
|
|
307
|
+
return actionsRef.current?.getMarkdown() ?? rawValue;
|
|
308
|
+
},
|
|
309
|
+
setMarkdown: (md) => {
|
|
310
|
+
setRawValue(md);
|
|
311
|
+
actionsRef.current?.setMarkdown(md);
|
|
312
|
+
},
|
|
313
|
+
focus: () => {
|
|
314
|
+
actionsRef.current?.focus();
|
|
315
|
+
}
|
|
316
|
+
}), [mode, rawValue]);
|
|
317
|
+
const handleWysiwygChange = useCallback(
|
|
318
|
+
(markdown) => {
|
|
319
|
+
setRawValue(markdown);
|
|
320
|
+
onChange?.(markdown);
|
|
321
|
+
},
|
|
322
|
+
[onChange]
|
|
323
|
+
);
|
|
324
|
+
const handleRawChange = useCallback(
|
|
325
|
+
(markdown) => {
|
|
326
|
+
setRawValue(markdown);
|
|
327
|
+
onChange?.(markdown);
|
|
328
|
+
if (mode === "split") {
|
|
329
|
+
actionsRef.current?.setMarkdown(markdown);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
[onChange, mode]
|
|
333
|
+
);
|
|
334
|
+
const handleToolbarAction = useCallback(
|
|
335
|
+
(item) => {
|
|
336
|
+
if (item === "divider") return;
|
|
337
|
+
if (mode === "markdown") {
|
|
338
|
+
const syntax2 = TOOLBAR_MARKDOWN[item];
|
|
339
|
+
if (syntax2) {
|
|
340
|
+
const newValue = rawValue + syntax2;
|
|
341
|
+
setRawValue(newValue);
|
|
342
|
+
onChange?.(newValue);
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const syntax = TOOLBAR_MARKDOWN[item];
|
|
347
|
+
if (syntax && actionsRef.current) {
|
|
348
|
+
const current = actionsRef.current.getMarkdown();
|
|
349
|
+
const newValue = current + syntax;
|
|
350
|
+
actionsRef.current.setMarkdown(newValue);
|
|
351
|
+
setRawValue(newValue);
|
|
352
|
+
onChange?.(newValue);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
[mode, rawValue, onChange]
|
|
356
|
+
);
|
|
357
|
+
const handleModeChange = useCallback(
|
|
358
|
+
(newMode) => {
|
|
359
|
+
if (mode === "wysiwyg" || mode === "split") {
|
|
360
|
+
const current = actionsRef.current?.getMarkdown() ?? rawValue;
|
|
361
|
+
setRawValue(current);
|
|
362
|
+
}
|
|
363
|
+
if ((newMode === "wysiwyg" || newMode === "split") && mode === "markdown") {
|
|
364
|
+
actionsRef.current?.setMarkdown(rawValue);
|
|
365
|
+
}
|
|
366
|
+
setInternalMode(newMode);
|
|
367
|
+
},
|
|
368
|
+
[mode, rawValue]
|
|
369
|
+
);
|
|
370
|
+
const toolbarItems = toolbarProp === false ? null : toolbarProp ?? DEFAULT_TOOLBAR;
|
|
371
|
+
const showToolbar = toolbarItems != null && !readOnly;
|
|
372
|
+
const wrapperStyle = {
|
|
373
|
+
minHeight: `${minHeight}px`,
|
|
374
|
+
...maxHeight ? { maxHeight: `${maxHeight}px` } : {}
|
|
375
|
+
};
|
|
376
|
+
return /* @__PURE__ */ jsxs(
|
|
377
|
+
"div",
|
|
378
|
+
{
|
|
379
|
+
className: [
|
|
380
|
+
"omu-editor flex flex-col rounded-md overflow-hidden",
|
|
381
|
+
"border border-bdr-primary",
|
|
382
|
+
"bg-bg-primary text-txt-primary",
|
|
383
|
+
"focus-within:outline focus-within:outline-2 focus-within:outline-offset-0 focus-within:outline-ring-primary",
|
|
384
|
+
className
|
|
385
|
+
].join(" "),
|
|
386
|
+
style: wrapperStyle,
|
|
387
|
+
children: [
|
|
388
|
+
(showToolbar || showModeToggle) && /* @__PURE__ */ jsxs("div", { className: "flex items-center bg-bg-secondary border-b border-bdr-secondary rounded-t-md", children: [
|
|
389
|
+
showToolbar && /* @__PURE__ */ jsx(EditorToolbar, { items: toolbarItems, onAction: handleToolbarAction }),
|
|
390
|
+
showModeToggle && !readOnly && /* @__PURE__ */ jsx(ModeToggle, { mode, onModeChange: handleModeChange })
|
|
391
|
+
] }),
|
|
392
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0 overflow-hidden", children: [
|
|
393
|
+
(mode === "wysiwyg" || mode === "split") && /* @__PURE__ */ jsx("div", { className: `flex flex-col flex-1 min-h-0 ${mode === "split" ? "border-r border-bdr-secondary" : ""}`, children: /* @__PURE__ */ jsx(MilkdownProvider, { children: /* @__PURE__ */ jsx(
|
|
394
|
+
MilkdownInner,
|
|
395
|
+
{
|
|
396
|
+
defaultValue: value,
|
|
397
|
+
onChange: handleWysiwygChange,
|
|
398
|
+
readOnly,
|
|
399
|
+
placeholder,
|
|
400
|
+
autoFocus: autoFocus && mode === "wysiwyg",
|
|
401
|
+
skipRef,
|
|
402
|
+
actionsRef
|
|
403
|
+
}
|
|
404
|
+
) }) }),
|
|
405
|
+
(mode === "markdown" || mode === "split") && /* @__PURE__ */ jsx("div", { className: "flex flex-col flex-1 min-h-0", children: /* @__PURE__ */ jsx(
|
|
406
|
+
MarkdownTextarea,
|
|
407
|
+
{
|
|
408
|
+
value: rawValue,
|
|
409
|
+
onChange: handleRawChange,
|
|
410
|
+
readOnly,
|
|
411
|
+
placeholder: mode === "markdown" ? placeholder : "Raw markdown...",
|
|
412
|
+
autoFocus: autoFocus && mode === "markdown",
|
|
413
|
+
minHeight: minHeight - 40,
|
|
414
|
+
maxHeight: maxHeight ? maxHeight - 40 : void 0
|
|
415
|
+
}
|
|
416
|
+
) })
|
|
417
|
+
] })
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
Editor.displayName = "Editor";
|
|
4
424
|
export {
|
|
5
|
-
|
|
6
|
-
EDITOR_VERSION
|
|
425
|
+
Editor
|
|
7
426
|
};
|
|
8
427
|
//# sourceMappingURL=index.js.map
|
package/dist/editor/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/index.ts"],"sourcesContent":["/**\n * open-mcp-app-ui/editor\n *\n * Markdown + rich text editor built on Milkdown (ProseMirror + Remark).\n * This is an optional import — apps that don't use it pay zero bundle cost.\n *\n * Phase 2: Placeholder export. Implementation coming soon.\n *\n * Planned usage:\n * import { Editor } from \"open-mcp-app-ui/editor\";\n *\n * <Editor\n * value={markdownString}\n * onChange={setMarkdownString}\n * mode=\"wysiwyg\"\n * toolbar={[\"bold\", \"italic\", \"heading\", \"list\", \"code\", \"link\"]}\n * />\n */\n\nexport const EDITOR_VERSION = \"0.0.1\";\nexport const EDITOR_STATUS = \"planned\" as const;\n"],"mappings":";AAmBO,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/editor/Editor.tsx"],"sourcesContent":["/**\n * Editor — Markdown + rich text editor.\n *\n * Built on Milkdown (ProseMirror + Remark) for markdown-first editing\n * with perfect round-trip fidelity. Supports WYSIWYG, raw markdown,\n * and split (side-by-side) editing modes.\n *\n * Styled exclusively with MCP Apps spec CSS variables. All typography,\n * colors, borders, and spacing use the host's theme tokens.\n *\n * Imported separately from the core library to keep apps that don't\n * need it from paying any bundle cost:\n *\n * import { Editor } from \"open-mcp-app-ui/editor\";\n */\n\nimport React, {\n useState,\n useRef,\n useCallback,\n useEffect,\n forwardRef,\n useImperativeHandle,\n type CSSProperties,\n} from \"react\";\nimport {\n Editor as MilkdownEditor,\n rootCtx,\n defaultValueCtx,\n} from \"@milkdown/kit/core\";\nimport { commonmark } from \"@milkdown/kit/preset/commonmark\";\nimport { gfm } from \"@milkdown/kit/preset/gfm\";\nimport { history } from \"@milkdown/kit/plugin/history\";\nimport { clipboard } from \"@milkdown/kit/plugin/clipboard\";\nimport { listener, listenerCtx } from \"@milkdown/plugin-listener\";\nimport { replaceAll, getMarkdown } from \"@milkdown/utils\";\nimport { Milkdown, MilkdownProvider, useEditor } from \"@milkdown/react\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Available toolbar action items. */\nexport type ToolbarItem =\n | \"bold\"\n | \"italic\"\n | \"strikethrough\"\n | \"heading\"\n | \"bulletList\"\n | \"orderedList\"\n | \"taskList\"\n | \"code\"\n | \"codeBlock\"\n | \"blockquote\"\n | \"link\"\n | \"divider\"\n | \"undo\"\n | \"redo\";\n\n/** Editing mode. */\nexport type EditorMode = \"wysiwyg\" | \"markdown\" | \"split\";\n\nexport interface EditorProps {\n /** Markdown string (source of truth). */\n value?: string;\n /** Called when content changes. Receives the updated markdown string. */\n onChange?: (markdown: string) => void;\n /**\n * Editing mode:\n * - \"wysiwyg\" — Rich text rendering with formatting (default)\n * - \"markdown\" — Raw markdown text editing\n * - \"split\" — Side-by-side WYSIWYG and markdown\n */\n mode?: EditorMode;\n /** Placeholder text when the editor is empty. */\n placeholder?: string;\n /**\n * Toolbar buttons to show, or `false` to hide the toolbar entirely.\n * Defaults to a sensible set of common formatting actions.\n */\n toolbar?: ToolbarItem[] | false;\n /** View-only mode. Renders markdown as styled content without editing. */\n readOnly?: boolean;\n /** Minimum editor height in pixels. */\n minHeight?: number;\n /** Maximum editor height in pixels before scrolling. */\n maxHeight?: number;\n /** Focus the editor on mount. */\n autoFocus?: boolean;\n /** Additional CSS classes on the outermost wrapper. */\n className?: string;\n}\n\nexport interface EditorRef {\n /** Get the current markdown content. */\n getMarkdown: () => string;\n /** Imperatively set editor content without triggering onChange. */\n setMarkdown: (markdown: string) => void;\n /** Focus the editor. */\n focus: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_TOOLBAR: ToolbarItem[] = [\n \"bold\",\n \"italic\",\n \"strikethrough\",\n \"divider\",\n \"heading\",\n \"bulletList\",\n \"orderedList\",\n \"taskList\",\n \"divider\",\n \"code\",\n \"codeBlock\",\n \"blockquote\",\n \"link\",\n \"divider\",\n \"undo\",\n \"redo\",\n];\n\n// ---------------------------------------------------------------------------\n// Toolbar\n// ---------------------------------------------------------------------------\n\n/**\n * Icon components for toolbar buttons.\n * Minimal SVG icons sized at 16x16, using currentColor for theme awareness.\n */\nconst ToolbarIcons: Record<Exclude<ToolbarItem, \"divider\">, () => React.ReactElement> = {\n bold: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" /><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" />\n </svg>\n ),\n italic: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\" /><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\" /><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\" />\n </svg>\n ),\n strikethrough: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M16 4H9a3 3 0 0 0-3 3c0 2 1.5 3 3 3h6\" /><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\" /><path d=\"M15 12c1.5 0 3 1 3 3a3 3 0 0 1-3 3H8\" />\n </svg>\n ),\n heading: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M6 4v16\" /><path d=\"M18 4v16\" /><path d=\"M6 12h12\" />\n </svg>\n ),\n bulletList: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"9\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"9\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"9\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"4\" cy=\"6\" r=\"1\" fill=\"currentColor\" /><circle cx=\"4\" cy=\"12\" r=\"1\" fill=\"currentColor\" /><circle cx=\"4\" cy=\"18\" r=\"1\" fill=\"currentColor\" />\n </svg>\n ),\n orderedList: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\" /><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\" /><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\" />\n <text x=\"3\" y=\"7\" fontSize=\"7\" fill=\"currentColor\" stroke=\"none\" fontFamily=\"sans-serif\">1</text>\n <text x=\"3\" y=\"13\" fontSize=\"7\" fill=\"currentColor\" stroke=\"none\" fontFamily=\"sans-serif\">2</text>\n <text x=\"3\" y=\"19\" fontSize=\"7\" fill=\"currentColor\" stroke=\"none\" fontFamily=\"sans-serif\">3</text>\n </svg>\n ),\n taskList: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"6\" height=\"6\" rx=\"1\" /><path d=\"M5 8l1.5 1.5L9 7\" /><line x1=\"13\" y1=\"8\" x2=\"21\" y2=\"8\" />\n <rect x=\"3\" y=\"14\" width=\"6\" height=\"6\" rx=\"1\" /><line x1=\"13\" y1=\"17\" x2=\"21\" y2=\"17\" />\n </svg>\n ),\n code: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"16 18 22 12 16 6\" /><polyline points=\"8 6 2 12 8 18\" />\n </svg>\n ),\n codeBlock: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" /><polyline points=\"9 8 5 12 9 16\" /><polyline points=\"15 8 19 12 15 16\" />\n </svg>\n ),\n blockquote: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M10 8V6a6 6 0 0 0-6 6v4h4v-4H6a4 4 0 0 1 4-4zm10 0V6a6 6 0 0 0-6 6v4h4v-4h-2a4 4 0 0 1 4-4z\" />\n </svg>\n ),\n link: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\n </svg>\n ),\n undo: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"1 4 1 10 7 10\" /><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\" />\n </svg>\n ),\n redo: () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"23 4 23 10 17 10\" /><path d=\"M20.49 15a9 9 0 1 1-2.12-9.36L23 10\" />\n </svg>\n ),\n};\n\n/**\n * Toolbar button labels for accessibility and tooltips.\n */\nconst TOOLBAR_LABELS: Record<Exclude<ToolbarItem, \"divider\">, string> = {\n bold: \"Bold\",\n italic: \"Italic\",\n strikethrough: \"Strikethrough\",\n heading: \"Heading\",\n bulletList: \"Bullet List\",\n orderedList: \"Ordered List\",\n taskList: \"Task List\",\n code: \"Inline Code\",\n codeBlock: \"Code Block\",\n blockquote: \"Blockquote\",\n link: \"Link\",\n undo: \"Undo\",\n redo: \"Redo\",\n};\n\n/**\n * Markdown syntax inserted when a toolbar button is clicked.\n * For WYSIWYG mode, commands would go through ProseMirror, but for\n * markdown mode or as a fallback, we insert raw syntax at the cursor.\n */\nconst TOOLBAR_MARKDOWN: Record<Exclude<ToolbarItem, \"divider\">, string> = {\n bold: \"**bold**\",\n italic: \"*italic*\",\n strikethrough: \"~~strikethrough~~\",\n heading: \"## \",\n bulletList: \"- \",\n orderedList: \"1. \",\n taskList: \"- [ ] \",\n code: \"`code`\",\n codeBlock: \"```\\n\\n```\",\n blockquote: \"> \",\n link: \"[text](url)\",\n undo: \"\",\n redo: \"\",\n};\n\n/**\n * Toolbar component rendered above the editor.\n * Provides formatting action buttons styled with spec CSS variables.\n */\nconst EditorToolbar = ({\n items,\n onAction,\n}: {\n items: ToolbarItem[];\n onAction: (item: ToolbarItem) => void;\n}) => (\n <div className=\"omu-editor-toolbar flex items-center gap-0.5 px-2 py-1.5 bg-bg-secondary border-b border-bdr-secondary rounded-t-md overflow-x-auto\">\n {items.map((item, i) => {\n if (item === \"divider\") {\n return (\n <div\n key={`divider-${i}`}\n className=\"w-px h-4 bg-bdr-secondary mx-1 shrink-0\"\n />\n );\n }\n\n const Icon = ToolbarIcons[item];\n const label = TOOLBAR_LABELS[item];\n\n return (\n <button\n key={item}\n type=\"button\"\n className=\"inline-flex items-center justify-center w-7 h-7 rounded-sm text-txt-secondary hover:text-txt-primary hover:bg-bg-tertiary transition-colors shrink-0 cursor-pointer\"\n onClick={() => onAction(item)}\n title={label}\n aria-label={label}\n >\n <Icon />\n </button>\n );\n })}\n </div>\n);\n\n// ---------------------------------------------------------------------------\n// Mode Toggle\n// ---------------------------------------------------------------------------\n\n/**\n * Mode toggle buttons shown at the right of the toolbar.\n * Allows switching between wysiwyg, markdown, and split modes.\n */\nconst ModeToggle = ({\n mode,\n onModeChange,\n}: {\n mode: EditorMode;\n onModeChange: (mode: EditorMode) => void;\n}) => {\n const modes: { value: EditorMode; label: string }[] = [\n { value: \"wysiwyg\", label: \"Rich\" },\n { value: \"markdown\", label: \"MD\" },\n { value: \"split\", label: \"Split\" },\n ];\n\n return (\n <div className=\"flex items-center gap-0.5 ml-auto pl-2\">\n {modes.map((m) => (\n <button\n key={m.value}\n type=\"button\"\n className={[\n \"px-2 py-0.5 rounded-sm text-xs font-medium transition-colors cursor-pointer\",\n mode === m.value\n ? \"bg-bg-tertiary text-txt-primary\"\n : \"text-txt-tertiary hover:text-txt-secondary hover:bg-bg-tertiary\",\n ].join(\" \")}\n onClick={() => onModeChange(m.value)}\n >\n {m.label}\n </button>\n ))}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Milkdown WYSIWYG Editor (inner component)\n// ---------------------------------------------------------------------------\n\ninterface MilkdownInnerProps {\n defaultValue: string;\n onChange: (markdown: string) => void;\n readOnly: boolean;\n placeholder?: string;\n autoFocus?: boolean;\n /** Ref for skipping onChange on imperative updates */\n skipRef: React.MutableRefObject<boolean>;\n /** Ref to expose editor actions to parent */\n actionsRef: React.MutableRefObject<{\n getMarkdown: () => string;\n setMarkdown: (md: string) => void;\n focus: () => void;\n } | null>;\n}\n\n/**\n * Inner Milkdown component that must render inside MilkdownProvider.\n * Handles the actual editor setup with plugins and listeners.\n */\nconst MilkdownInner = ({\n defaultValue,\n onChange,\n readOnly,\n placeholder,\n autoFocus,\n skipRef,\n actionsRef,\n}: MilkdownInnerProps) => {\n const contentRef = useRef(defaultValue);\n const wrapperRef = useRef<HTMLDivElement>(null);\n\n const { get } = useEditor((root) =>\n MilkdownEditor.make()\n .config((ctx) => {\n ctx.set(rootCtx, root);\n ctx.set(defaultValueCtx, defaultValue);\n ctx.get(listenerCtx).markdownUpdated((_, markdown) => {\n contentRef.current = markdown;\n if (skipRef.current) {\n skipRef.current = false;\n return;\n }\n onChange(markdown);\n });\n })\n .use(commonmark)\n .use(gfm)\n .use(history)\n .use(clipboard)\n .use(listener)\n );\n\n // Expose actions to parent\n useEffect(() => {\n actionsRef.current = {\n getMarkdown: () => contentRef.current,\n setMarkdown: (md: string) => {\n const editor = get();\n if (!editor) return;\n skipRef.current = true;\n editor.action(replaceAll(md));\n contentRef.current = md;\n },\n focus: () => {\n const el = wrapperRef.current?.querySelector(\".ProseMirror\");\n if (el instanceof HTMLElement) el.focus();\n },\n };\n }, [get, skipRef, actionsRef]);\n\n // Auto-focus on mount\n useEffect(() => {\n if (autoFocus) {\n requestAnimationFrame(() => {\n const el = wrapperRef.current?.querySelector(\".ProseMirror\");\n if (el instanceof HTMLElement) el.focus();\n });\n }\n }, [autoFocus]);\n\n /**\n * Handle clicks on the wrapper to focus the editor.\n * Allows clicking empty space below content to start editing.\n */\n const handleWrapperClick = useCallback(() => {\n const el = wrapperRef.current?.querySelector(\".ProseMirror\");\n if (el instanceof HTMLElement) el.focus();\n }, []);\n\n return (\n <div\n ref={wrapperRef}\n className=\"omu-editor-content flex-1 overflow-y-auto px-4 py-3 flex flex-col min-h-0 cursor-text\"\n data-placeholder={placeholder}\n data-readonly={readOnly || undefined}\n onClick={readOnly ? undefined : handleWrapperClick}\n >\n <Milkdown />\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Raw Markdown Editor\n// ---------------------------------------------------------------------------\n\n/**\n * Simple textarea for raw markdown editing.\n * Provides a monospace code-editing experience.\n */\nconst MarkdownTextarea = ({\n value,\n onChange,\n readOnly,\n placeholder,\n autoFocus,\n minHeight,\n maxHeight,\n}: {\n value: string;\n onChange: (markdown: string) => void;\n readOnly: boolean;\n placeholder?: string;\n autoFocus?: boolean;\n minHeight?: number;\n maxHeight?: number;\n}) => (\n <textarea\n className={[\n \"omu-editor-textarea flex-1 w-full px-4 py-3 resize-none\",\n \"bg-bg-primary text-txt-primary font-mono text-sm\",\n \"outline-none border-none\",\n \"placeholder:text-txt-tertiary\",\n ].join(\" \")}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n readOnly={readOnly}\n placeholder={placeholder}\n autoFocus={autoFocus}\n style={{\n minHeight: minHeight ? `${minHeight}px` : undefined,\n maxHeight: maxHeight ? `${maxHeight}px` : undefined,\n }}\n spellCheck={false}\n />\n);\n\n// ---------------------------------------------------------------------------\n// Editor (Main Export)\n// ---------------------------------------------------------------------------\n\n/**\n * Markdown + rich text editor with toolbar and mode switching.\n *\n * Built on Milkdown (ProseMirror + Remark) for markdown-first editing.\n * Supports WYSIWYG, raw markdown, and split editing modes. Styled with\n * MCP Apps spec CSS variables for automatic host theming.\n *\n * @example\n * ```tsx\n * import { Editor } from \"open-mcp-app-ui/editor\";\n *\n * <Editor\n * value={markdown}\n * onChange={setMarkdown}\n * placeholder=\"Start writing...\"\n * />\n * ```\n */\nexport const Editor = forwardRef<EditorRef, EditorProps>(\n (\n {\n value = \"\",\n onChange,\n mode: modeProp,\n placeholder,\n toolbar: toolbarProp,\n readOnly = false,\n minHeight = 120,\n maxHeight,\n autoFocus = false,\n className = \"\",\n },\n ref\n ) => {\n // -----------------------------------------------------------------------\n // State\n // -----------------------------------------------------------------------\n\n const [internalMode, setInternalMode] = useState<EditorMode>(modeProp ?? \"wysiwyg\");\n const mode = modeProp ?? internalMode;\n const showModeToggle = modeProp == null;\n\n /**\n * Internal markdown state used for the raw markdown textarea.\n * The WYSIWYG editor uses Milkdown's internal state; this mirrors\n * it for the markdown pane and is synced on mode switches.\n */\n const [rawValue, setRawValue] = useState(value);\n\n const skipRef = useRef(false);\n const actionsRef = useRef<{\n getMarkdown: () => string;\n setMarkdown: (md: string) => void;\n focus: () => void;\n } | null>(null);\n\n // Sync raw value when external value changes\n useEffect(() => {\n setRawValue(value);\n }, [value]);\n\n // -----------------------------------------------------------------------\n // Imperative handle\n // -----------------------------------------------------------------------\n\n useImperativeHandle(ref, () => ({\n getMarkdown: () => {\n if (mode === \"markdown\") return rawValue;\n return actionsRef.current?.getMarkdown() ?? rawValue;\n },\n setMarkdown: (md: string) => {\n setRawValue(md);\n actionsRef.current?.setMarkdown(md);\n },\n focus: () => {\n actionsRef.current?.focus();\n },\n }), [mode, rawValue]);\n\n // -----------------------------------------------------------------------\n // Handlers\n // -----------------------------------------------------------------------\n\n /** Handle changes from the WYSIWYG editor. */\n const handleWysiwygChange = useCallback(\n (markdown: string) => {\n setRawValue(markdown);\n onChange?.(markdown);\n },\n [onChange]\n );\n\n /** Handle changes from the raw markdown textarea. */\n const handleRawChange = useCallback(\n (markdown: string) => {\n setRawValue(markdown);\n onChange?.(markdown);\n // Sync to WYSIWYG if in split mode\n if (mode === \"split\") {\n actionsRef.current?.setMarkdown(markdown);\n }\n },\n [onChange, mode]\n );\n\n /** Handle toolbar button clicks. */\n const handleToolbarAction = useCallback(\n (item: ToolbarItem) => {\n if (item === \"divider\") return;\n\n if (mode === \"markdown\") {\n // In markdown mode, insert raw syntax\n const syntax = TOOLBAR_MARKDOWN[item];\n if (syntax) {\n const newValue = rawValue + syntax;\n setRawValue(newValue);\n onChange?.(newValue);\n }\n return;\n }\n\n // In WYSIWYG/split mode, insert via Milkdown\n const syntax = TOOLBAR_MARKDOWN[item];\n if (syntax && actionsRef.current) {\n const current = actionsRef.current.getMarkdown();\n const newValue = current + syntax;\n actionsRef.current.setMarkdown(newValue);\n setRawValue(newValue);\n onChange?.(newValue);\n }\n },\n [mode, rawValue, onChange]\n );\n\n /** Handle mode switching. */\n const handleModeChange = useCallback(\n (newMode: EditorMode) => {\n // Sync content between modes before switching\n if (mode === \"wysiwyg\" || mode === \"split\") {\n const current = actionsRef.current?.getMarkdown() ?? rawValue;\n setRawValue(current);\n }\n if ((newMode === \"wysiwyg\" || newMode === \"split\") && mode === \"markdown\") {\n actionsRef.current?.setMarkdown(rawValue);\n }\n setInternalMode(newMode);\n },\n [mode, rawValue]\n );\n\n // -----------------------------------------------------------------------\n // Toolbar config\n // -----------------------------------------------------------------------\n\n const toolbarItems = toolbarProp === false ? null : (toolbarProp ?? DEFAULT_TOOLBAR);\n const showToolbar = toolbarItems != null && !readOnly;\n\n // -----------------------------------------------------------------------\n // Render\n // -----------------------------------------------------------------------\n\n const wrapperStyle: CSSProperties = {\n minHeight: `${minHeight}px`,\n ...(maxHeight ? { maxHeight: `${maxHeight}px` } : {}),\n };\n\n return (\n <div\n className={[\n \"omu-editor flex flex-col rounded-md overflow-hidden\",\n \"border border-bdr-primary\",\n \"bg-bg-primary text-txt-primary\",\n \"focus-within:outline focus-within:outline-2 focus-within:outline-offset-0 focus-within:outline-ring-primary\",\n className,\n ].join(\" \")}\n style={wrapperStyle}\n >\n {/* Toolbar */}\n {(showToolbar || showModeToggle) && (\n <div className=\"flex items-center bg-bg-secondary border-b border-bdr-secondary rounded-t-md\">\n {showToolbar && (\n <EditorToolbar items={toolbarItems} onAction={handleToolbarAction} />\n )}\n {showModeToggle && !readOnly && (\n <ModeToggle mode={mode} onModeChange={handleModeChange} />\n )}\n </div>\n )}\n\n {/* Editor content */}\n <div className=\"flex flex-1 min-h-0 overflow-hidden\">\n {/* WYSIWYG pane */}\n {(mode === \"wysiwyg\" || mode === \"split\") && (\n <div className={`flex flex-col flex-1 min-h-0 ${mode === \"split\" ? \"border-r border-bdr-secondary\" : \"\"}`}>\n <MilkdownProvider>\n <MilkdownInner\n defaultValue={value}\n onChange={handleWysiwygChange}\n readOnly={readOnly}\n placeholder={placeholder}\n autoFocus={autoFocus && mode === \"wysiwyg\"}\n skipRef={skipRef}\n actionsRef={actionsRef}\n />\n </MilkdownProvider>\n </div>\n )}\n\n {/* Markdown pane */}\n {(mode === \"markdown\" || mode === \"split\") && (\n <div className=\"flex flex-col flex-1 min-h-0\">\n <MarkdownTextarea\n value={rawValue}\n onChange={handleRawChange}\n readOnly={readOnly}\n placeholder={mode === \"markdown\" ? placeholder : \"Raw markdown...\"}\n autoFocus={autoFocus && mode === \"markdown\"}\n minHeight={minHeight - 40}\n maxHeight={maxHeight ? maxHeight - 40 : undefined}\n />\n </div>\n )}\n </div>\n </div>\n );\n }\n);\n\nEditor.displayName = \"Editor\";\n"],"mappings":"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","names":["syntax"]}
|