@fragments-sdk/ui 0.10.0 → 0.11.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/dist/assets/ui.css +304 -0
- package/dist/blocks/BlogEditor.block.d.ts +3 -0
- package/dist/blocks/BlogEditor.block.d.ts.map +1 -0
- package/dist/components/Editor/Editor.module.scss.cjs +57 -0
- package/dist/components/Editor/Editor.module.scss.cjs.map +1 -0
- package/dist/components/Editor/Editor.module.scss.js +57 -0
- package/dist/components/Editor/Editor.module.scss.js.map +1 -0
- package/dist/components/Editor/index.cjs +548 -0
- package/dist/components/Editor/index.cjs.map +1 -0
- package/dist/components/Editor/index.d.ts +107 -0
- package/dist/components/Editor/index.d.ts.map +1 -0
- package/dist/components/Editor/index.js +531 -0
- package/dist/components/Editor/index.js.map +1 -0
- package/dist/components/Sidebar/index.cjs +6 -11
- package/dist/components/Sidebar/index.cjs.map +1 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +6 -11
- package/dist/components/Sidebar/index.js.map +1 -1
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/keyboard-shortcuts.cjs +295 -0
- package/dist/utils/keyboard-shortcuts.cjs.map +1 -0
- package/dist/utils/keyboard-shortcuts.d.ts +293 -0
- package/dist/utils/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/utils/keyboard-shortcuts.js +295 -0
- package/dist/utils/keyboard-shortcuts.js.map +1 -0
- package/fragments.json +1 -1
- package/package.json +27 -2
- package/src/blocks/BlogEditor.block.ts +34 -0
- package/src/components/Editor/Editor.fragment.tsx +322 -0
- package/src/components/Editor/Editor.module.scss +333 -0
- package/src/components/Editor/Editor.test.tsx +174 -0
- package/src/components/Editor/index.tsx +815 -0
- package/src/components/Sidebar/index.tsx +7 -14
- package/src/index.ts +43 -0
- package/src/utils/keyboard-shortcuts.test.ts +357 -0
- package/src/utils/keyboard-shortcuts.ts +502 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type EditorFormat = 'bold' | 'italic' | 'strikethrough' | 'link' | 'code' | 'bulletList' | 'orderedList' | 'heading1' | 'heading2' | 'heading3' | 'blockquote' | 'undo' | 'redo';
|
|
3
|
+
export type EditorSaveStatus = 'idle' | 'saving' | 'saved' | 'error';
|
|
4
|
+
export type EditorMode = 'rich' | 'markdown';
|
|
5
|
+
export type EditorSize = 'sm' | 'md' | 'lg';
|
|
6
|
+
export interface EditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
/** Controlled value */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** Default value for uncontrolled usage */
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
/** Called when content changes */
|
|
13
|
+
onValueChange?: (value: string) => void;
|
|
14
|
+
/** Placeholder text */
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Disable the editor */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Read-only mode */
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
/** Which format buttons to show */
|
|
21
|
+
formats?: EditorFormat[];
|
|
22
|
+
/** Show default toolbar */
|
|
23
|
+
toolbar?: boolean;
|
|
24
|
+
/** Show default status bar */
|
|
25
|
+
statusBar?: boolean;
|
|
26
|
+
/** Auto-save callback */
|
|
27
|
+
onAutoSave?: (value: string) => void;
|
|
28
|
+
/** Auto-save interval in ms */
|
|
29
|
+
autoSaveInterval?: number;
|
|
30
|
+
/** Editor size preset */
|
|
31
|
+
size?: EditorSize;
|
|
32
|
+
/** Maximum character count (shows indicator in status bar) */
|
|
33
|
+
maxLength?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface EditorToolbarProps {
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface EditorToolbarGroupProps {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
'aria-label'?: string;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface EditorToolbarButtonProps {
|
|
45
|
+
/** Which format this button toggles */
|
|
46
|
+
format: EditorFormat;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface EditorSeparatorProps {
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface EditorStatusIndicatorProps {
|
|
53
|
+
/** Override the save status from context */
|
|
54
|
+
status?: EditorSaveStatus;
|
|
55
|
+
/** Custom labels per status */
|
|
56
|
+
labels?: Partial<Record<EditorSaveStatus, string>>;
|
|
57
|
+
className?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface EditorContentProps {
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface EditorStatusBarProps {
|
|
63
|
+
/** Show word count */
|
|
64
|
+
showWordCount?: boolean;
|
|
65
|
+
/** Show character count */
|
|
66
|
+
showCharCount?: boolean;
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
interface EditorContextValue {
|
|
70
|
+
value: string;
|
|
71
|
+
setValue: (v: string) => void;
|
|
72
|
+
placeholder: string;
|
|
73
|
+
disabled: boolean;
|
|
74
|
+
readOnly: boolean;
|
|
75
|
+
formats: EditorFormat[];
|
|
76
|
+
editor: any | null;
|
|
77
|
+
mode: EditorMode;
|
|
78
|
+
size: EditorSize;
|
|
79
|
+
maxLength?: number;
|
|
80
|
+
wordCount: number;
|
|
81
|
+
charCount: number;
|
|
82
|
+
toggleFormat: (f: EditorFormat) => void;
|
|
83
|
+
isFormatActive: (f: EditorFormat) => boolean;
|
|
84
|
+
saveStatus: EditorSaveStatus;
|
|
85
|
+
contentRef: React.RefObject<HTMLTextAreaElement | null>;
|
|
86
|
+
}
|
|
87
|
+
declare function useEditorContext(): EditorContextValue;
|
|
88
|
+
declare function EditorRoot({ children, value: controlledValue, defaultValue, onValueChange, placeholder, disabled, readOnly, formats, toolbar, statusBar, onAutoSave, autoSaveInterval, size, maxLength, className, ...htmlProps }: EditorProps): import("react/jsx-runtime").JSX.Element;
|
|
89
|
+
declare function EditorToolbar({ children, className }: EditorToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
90
|
+
declare function EditorToolbarGroup({ children, 'aria-label': ariaLabel, className }: EditorToolbarGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
declare function EditorToolbarButton({ format, className }: EditorToolbarButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
92
|
+
declare function EditorSeparator({ className }: EditorSeparatorProps): import("react/jsx-runtime").JSX.Element;
|
|
93
|
+
declare function EditorStatusIndicator({ status: statusOverride, labels, className }: EditorStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
94
|
+
declare function EditorContentArea({ className }: EditorContentProps): import("react/jsx-runtime").JSX.Element;
|
|
95
|
+
declare function EditorStatusBar({ showWordCount, showCharCount, className }: EditorStatusBarProps): import("react/jsx-runtime").JSX.Element;
|
|
96
|
+
export declare const Editor: typeof EditorRoot & {
|
|
97
|
+
Toolbar: typeof EditorToolbar;
|
|
98
|
+
ToolbarGroup: typeof EditorToolbarGroup;
|
|
99
|
+
ToolbarButton: typeof EditorToolbarButton;
|
|
100
|
+
Separator: typeof EditorSeparator;
|
|
101
|
+
StatusIndicator: typeof EditorStatusIndicator;
|
|
102
|
+
Content: typeof EditorContentArea;
|
|
103
|
+
StatusBar: typeof EditorStatusBar;
|
|
104
|
+
};
|
|
105
|
+
export { EditorRoot, EditorToolbar, EditorToolbarGroup, EditorToolbarButton, EditorSeparator, EditorStatusIndicator, EditorContentArea, EditorStatusBar, };
|
|
106
|
+
export { useEditorContext };
|
|
107
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Editor/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsD/B,MAAM,MAAM,YAAY,GACpB,MAAM,GAAG,QAAQ,GAAG,eAAe,GAAG,MAAM,GAAG,MAAM,GACrD,YAAY,GAAG,aAAa,GAC5B,UAAU,GAAG,UAAU,GAAG,UAAU,GACpC,YAAY,GACZ,MAAM,GAAG,MAAM,CAAC;AAEpB,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAErE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;IAC1G,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yBAAyB;IACzB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yBAAyB;IACzB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,uCAAuC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,sBAAsB;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2BAA2B;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4JD,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IAExB,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC;IAC7C,UAAU,EAAE,gBAAgB,CAAC;IAC7B,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;CACzD;AAID,iBAAS,gBAAgB,uBAMxB;AAsCD,iBAAS,UAAU,CAAC,EAClB,QAAQ,EACR,KAAK,EAAE,eAAe,EACtB,YAAiB,EACjB,aAAa,EACb,WAA+B,EAC/B,QAAgB,EAChB,QAAgB,EAChB,OAAyB,EACzB,OAAc,EACd,SAAgB,EAChB,UAAU,EACV,gBAAwB,EACxB,IAAW,EACX,SAAS,EACT,SAAS,EACT,GAAG,SAAS,EACb,EAAE,WAAW,2CAgQb;AAED,iBAAS,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAOjE;AAED,iBAAS,kBAAkB,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,uBAAuB,2CAOpG;AAED,iBAAS,mBAAmB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CAqC3E;AAED,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAG3D;AAED,iBAAS,qBAAqB,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,0BAA0B,kDAmBvG;AAED,iBAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA8B3D;AAED,iBAAS,eAAe,CAAC,EAAE,aAAoB,EAAE,aAAoB,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAqCvG;AAMD,eAAO,MAAM,MAAM;;;;;;;;CAQjB,CAAC;AAEH,OAAO,EACL,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,GAChB,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import styles from "./Editor.module.scss.js";
|
|
4
|
+
import { ArrowClockwise, ArrowCounterClockwise, Quotes, TextHThree, TextHTwo, TextHOne, ListNumbers, ListBullets, Code, LinkSimple, TextStrikethrough, TextItalic, TextB } from "@phosphor-icons/react";
|
|
5
|
+
import { KEYBOARD_SHORTCUTS } from "../../utils/keyboard-shortcuts.js";
|
|
6
|
+
let _useEditor = null;
|
|
7
|
+
let _EditorContent = null;
|
|
8
|
+
let _StarterKit = null;
|
|
9
|
+
let _LinkExtension = null;
|
|
10
|
+
let _tiptapLoaded = false;
|
|
11
|
+
let _tiptapFailed = false;
|
|
12
|
+
function loadTipTapDeps() {
|
|
13
|
+
if (_tiptapLoaded) return;
|
|
14
|
+
_tiptapLoaded = true;
|
|
15
|
+
try {
|
|
16
|
+
const tiptapReact = require("@tiptap/react");
|
|
17
|
+
const starterKit = require("@tiptap/starter-kit");
|
|
18
|
+
const linkExt = require("@tiptap/extension-link");
|
|
19
|
+
_useEditor = tiptapReact.useEditor;
|
|
20
|
+
_EditorContent = tiptapReact.EditorContent;
|
|
21
|
+
_StarterKit = starterKit.default ?? starterKit.StarterKit ?? starterKit;
|
|
22
|
+
_LinkExtension = linkExt.default ?? linkExt.Link ?? linkExt;
|
|
23
|
+
} catch {
|
|
24
|
+
_tiptapFailed = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const FORMAT_META = {
|
|
28
|
+
bold: { icon: TextB, label: "Bold", shortcut: KEYBOARD_SHORTCUTS.EDITOR_BOLD.label },
|
|
29
|
+
italic: { icon: TextItalic, label: "Italic", shortcut: KEYBOARD_SHORTCUTS.EDITOR_ITALIC.label },
|
|
30
|
+
strikethrough: { icon: TextStrikethrough, label: "Strikethrough", shortcut: KEYBOARD_SHORTCUTS.EDITOR_STRIKETHROUGH.label },
|
|
31
|
+
link: { icon: LinkSimple, label: "Link", shortcut: KEYBOARD_SHORTCUTS.EDITOR_LINK.label },
|
|
32
|
+
code: { icon: Code, label: "Code", shortcut: KEYBOARD_SHORTCUTS.EDITOR_CODE.label },
|
|
33
|
+
bulletList: { icon: ListBullets, label: "Bullet list", shortcut: KEYBOARD_SHORTCUTS.EDITOR_BULLET_LIST.label },
|
|
34
|
+
orderedList: { icon: ListNumbers, label: "Ordered list", shortcut: KEYBOARD_SHORTCUTS.EDITOR_ORDERED_LIST.label },
|
|
35
|
+
heading1: { icon: TextHOne, label: "Heading 1", shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING1.label },
|
|
36
|
+
heading2: { icon: TextHTwo, label: "Heading 2", shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING2.label },
|
|
37
|
+
heading3: { icon: TextHThree, label: "Heading 3", shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING3.label },
|
|
38
|
+
blockquote: { icon: Quotes, label: "Blockquote", shortcut: KEYBOARD_SHORTCUTS.EDITOR_BLOCKQUOTE.label },
|
|
39
|
+
undo: { icon: ArrowCounterClockwise, label: "Undo", shortcut: KEYBOARD_SHORTCUTS.EDITOR_UNDO.label },
|
|
40
|
+
redo: { icon: ArrowClockwise, label: "Redo", shortcut: KEYBOARD_SHORTCUTS.EDITOR_REDO.label }
|
|
41
|
+
};
|
|
42
|
+
const DEFAULT_FORMATS = ["bold", "italic", "strikethrough", "link", "code", "bulletList"];
|
|
43
|
+
const ACTION_FORMATS = /* @__PURE__ */ new Set(["undo", "redo"]);
|
|
44
|
+
const DEFAULT_STATUS_LABELS = {
|
|
45
|
+
idle: "",
|
|
46
|
+
saving: "SAVING...",
|
|
47
|
+
saved: "AUTO-SAVED",
|
|
48
|
+
error: "SAVE FAILED"
|
|
49
|
+
};
|
|
50
|
+
function getSelection(textarea) {
|
|
51
|
+
return {
|
|
52
|
+
start: textarea.selectionStart,
|
|
53
|
+
end: textarea.selectionEnd,
|
|
54
|
+
text: textarea.value.substring(textarea.selectionStart, textarea.selectionEnd)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function wrapSelection(textarea, prefix, suffix, setValue) {
|
|
58
|
+
const sel = getSelection(textarea);
|
|
59
|
+
const before = textarea.value.substring(0, sel.start);
|
|
60
|
+
const after = textarea.value.substring(sel.end);
|
|
61
|
+
const wrapped = `${prefix}${sel.text || "text"}${suffix}`;
|
|
62
|
+
const newValue = `${before}${wrapped}${after}`;
|
|
63
|
+
setValue(newValue);
|
|
64
|
+
requestAnimationFrame(() => {
|
|
65
|
+
textarea.focus();
|
|
66
|
+
const newStart = sel.start + prefix.length;
|
|
67
|
+
const newEnd = newStart + (sel.text || "text").length;
|
|
68
|
+
textarea.setSelectionRange(newStart, newEnd);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function applyMarkdownFormat(format, textarea, setValue) {
|
|
72
|
+
switch (format) {
|
|
73
|
+
case "bold":
|
|
74
|
+
wrapSelection(textarea, "**", "**", setValue);
|
|
75
|
+
break;
|
|
76
|
+
case "italic":
|
|
77
|
+
wrapSelection(textarea, "*", "*", setValue);
|
|
78
|
+
break;
|
|
79
|
+
case "strikethrough":
|
|
80
|
+
wrapSelection(textarea, "~~", "~~", setValue);
|
|
81
|
+
break;
|
|
82
|
+
case "code":
|
|
83
|
+
wrapSelection(textarea, "`", "`", setValue);
|
|
84
|
+
break;
|
|
85
|
+
case "link": {
|
|
86
|
+
const sel = getSelection(textarea);
|
|
87
|
+
const linkText = sel.text || "link text";
|
|
88
|
+
const before = textarea.value.substring(0, sel.start);
|
|
89
|
+
const after = textarea.value.substring(sel.end);
|
|
90
|
+
const newValue = `${before}[${linkText}](url)${after}`;
|
|
91
|
+
setValue(newValue);
|
|
92
|
+
requestAnimationFrame(() => {
|
|
93
|
+
textarea.focus();
|
|
94
|
+
const urlStart = sel.start + linkText.length + 3;
|
|
95
|
+
textarea.setSelectionRange(urlStart, urlStart + 3);
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "bulletList": {
|
|
100
|
+
const sel = getSelection(textarea);
|
|
101
|
+
const before = textarea.value.substring(0, sel.start);
|
|
102
|
+
const after = textarea.value.substring(sel.end);
|
|
103
|
+
const lines = (sel.text || "item").split("\n");
|
|
104
|
+
const bulleted = lines.map((l) => `- ${l}`).join("\n");
|
|
105
|
+
const newValue = `${before}${bulleted}${after}`;
|
|
106
|
+
setValue(newValue);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "orderedList": {
|
|
110
|
+
const sel = getSelection(textarea);
|
|
111
|
+
const before = textarea.value.substring(0, sel.start);
|
|
112
|
+
const after = textarea.value.substring(sel.end);
|
|
113
|
+
const lines = (sel.text || "item").split("\n");
|
|
114
|
+
const numbered = lines.map((l, i) => `${i + 1}. ${l}`).join("\n");
|
|
115
|
+
const newValue = `${before}${numbered}${after}`;
|
|
116
|
+
setValue(newValue);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "heading1":
|
|
120
|
+
wrapSelection(textarea, "# ", "", setValue);
|
|
121
|
+
break;
|
|
122
|
+
case "heading2":
|
|
123
|
+
wrapSelection(textarea, "## ", "", setValue);
|
|
124
|
+
break;
|
|
125
|
+
case "heading3":
|
|
126
|
+
wrapSelection(textarea, "### ", "", setValue);
|
|
127
|
+
break;
|
|
128
|
+
case "blockquote": {
|
|
129
|
+
const sel = getSelection(textarea);
|
|
130
|
+
const before = textarea.value.substring(0, sel.start);
|
|
131
|
+
const after = textarea.value.substring(sel.end);
|
|
132
|
+
const lines = (sel.text || "quote").split("\n");
|
|
133
|
+
const quoted = lines.map((l) => `> ${l}`).join("\n");
|
|
134
|
+
const newValue = `${before}${quoted}${after}`;
|
|
135
|
+
setValue(newValue);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const EditorContext = React.createContext(null);
|
|
141
|
+
function useEditorContext() {
|
|
142
|
+
const context = React.useContext(EditorContext);
|
|
143
|
+
if (!context) {
|
|
144
|
+
throw new Error("Editor compound components must be used within an Editor");
|
|
145
|
+
}
|
|
146
|
+
return context;
|
|
147
|
+
}
|
|
148
|
+
function useControllableState(controlledValue, defaultValue, onChange) {
|
|
149
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
150
|
+
const isControlled = controlledValue !== void 0;
|
|
151
|
+
const value = isControlled ? controlledValue : uncontrolledValue;
|
|
152
|
+
const setValue = React.useCallback(
|
|
153
|
+
(newValue) => {
|
|
154
|
+
if (!isControlled) {
|
|
155
|
+
setUncontrolledValue(newValue);
|
|
156
|
+
}
|
|
157
|
+
onChange == null ? void 0 : onChange(newValue);
|
|
158
|
+
},
|
|
159
|
+
[isControlled, onChange]
|
|
160
|
+
);
|
|
161
|
+
return [value, setValue];
|
|
162
|
+
}
|
|
163
|
+
function countWords(text) {
|
|
164
|
+
const trimmed = text.trim();
|
|
165
|
+
if (!trimmed) return 0;
|
|
166
|
+
return trimmed.split(/\s+/).length;
|
|
167
|
+
}
|
|
168
|
+
function EditorRoot({
|
|
169
|
+
children,
|
|
170
|
+
value: controlledValue,
|
|
171
|
+
defaultValue = "",
|
|
172
|
+
onValueChange,
|
|
173
|
+
placeholder = "Start typing...",
|
|
174
|
+
disabled = false,
|
|
175
|
+
readOnly = false,
|
|
176
|
+
formats = DEFAULT_FORMATS,
|
|
177
|
+
toolbar = true,
|
|
178
|
+
statusBar = true,
|
|
179
|
+
onAutoSave,
|
|
180
|
+
autoSaveInterval = 3e4,
|
|
181
|
+
size = "md",
|
|
182
|
+
maxLength,
|
|
183
|
+
className,
|
|
184
|
+
...htmlProps
|
|
185
|
+
}) {
|
|
186
|
+
const contentRef = React.useRef(null);
|
|
187
|
+
const [value, setValue] = useControllableState(
|
|
188
|
+
controlledValue,
|
|
189
|
+
defaultValue,
|
|
190
|
+
onValueChange
|
|
191
|
+
);
|
|
192
|
+
const [saveStatus, setSaveStatus] = React.useState("idle");
|
|
193
|
+
loadTipTapDeps();
|
|
194
|
+
const hasTipTap = !_tiptapFailed && _useEditor && _EditorContent && _StarterKit;
|
|
195
|
+
const mode = hasTipTap ? "rich" : "markdown";
|
|
196
|
+
const tiptapEditor = hasTipTap ? (
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
_useEditor({
|
|
199
|
+
extensions: [
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
201
|
+
_StarterKit.configure({
|
|
202
|
+
heading: { levels: [1, 2, 3] },
|
|
203
|
+
blockquote: {},
|
|
204
|
+
codeBlock: false,
|
|
205
|
+
horizontalRule: false,
|
|
206
|
+
hardBreak: false
|
|
207
|
+
}),
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
_LinkExtension.configure({
|
|
210
|
+
openOnClick: false,
|
|
211
|
+
HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }
|
|
212
|
+
})
|
|
213
|
+
],
|
|
214
|
+
editorProps: {
|
|
215
|
+
attributes: {
|
|
216
|
+
role: "textbox",
|
|
217
|
+
"aria-label": placeholder,
|
|
218
|
+
"aria-multiline": "true"
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
content: defaultValue || controlledValue || "",
|
|
222
|
+
editable: !disabled && !readOnly,
|
|
223
|
+
onUpdate: ({ editor: e }) => {
|
|
224
|
+
const html = e.getHTML();
|
|
225
|
+
setValue(html);
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
) : null;
|
|
229
|
+
React.useEffect(() => {
|
|
230
|
+
if (tiptapEditor && controlledValue !== void 0) {
|
|
231
|
+
const currentContent = tiptapEditor.getHTML();
|
|
232
|
+
if (currentContent !== controlledValue) {
|
|
233
|
+
tiptapEditor.commands.setContent(controlledValue, false);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}, [controlledValue, tiptapEditor]);
|
|
237
|
+
React.useEffect(() => {
|
|
238
|
+
if (tiptapEditor) {
|
|
239
|
+
tiptapEditor.setEditable(!disabled && !readOnly);
|
|
240
|
+
}
|
|
241
|
+
}, [tiptapEditor, disabled, readOnly]);
|
|
242
|
+
React.useEffect(() => {
|
|
243
|
+
if (!onAutoSave || !value) return;
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
setSaveStatus("saving");
|
|
246
|
+
try {
|
|
247
|
+
onAutoSave(value);
|
|
248
|
+
setSaveStatus("saved");
|
|
249
|
+
} catch {
|
|
250
|
+
setSaveStatus("error");
|
|
251
|
+
}
|
|
252
|
+
}, autoSaveInterval);
|
|
253
|
+
return () => clearTimeout(timer);
|
|
254
|
+
}, [value, onAutoSave, autoSaveInterval]);
|
|
255
|
+
const toggleFormat = React.useCallback(
|
|
256
|
+
(format) => {
|
|
257
|
+
if (disabled || readOnly) return;
|
|
258
|
+
if (tiptapEditor) {
|
|
259
|
+
switch (format) {
|
|
260
|
+
case "bold":
|
|
261
|
+
tiptapEditor.chain().focus().toggleBold().run();
|
|
262
|
+
break;
|
|
263
|
+
case "italic":
|
|
264
|
+
tiptapEditor.chain().focus().toggleItalic().run();
|
|
265
|
+
break;
|
|
266
|
+
case "strikethrough":
|
|
267
|
+
tiptapEditor.chain().focus().toggleStrike().run();
|
|
268
|
+
break;
|
|
269
|
+
case "code":
|
|
270
|
+
tiptapEditor.chain().focus().toggleCode().run();
|
|
271
|
+
break;
|
|
272
|
+
case "bulletList":
|
|
273
|
+
tiptapEditor.chain().focus().toggleBulletList().run();
|
|
274
|
+
break;
|
|
275
|
+
case "orderedList":
|
|
276
|
+
tiptapEditor.chain().focus().toggleOrderedList().run();
|
|
277
|
+
break;
|
|
278
|
+
case "heading1":
|
|
279
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 1 }).run();
|
|
280
|
+
break;
|
|
281
|
+
case "heading2":
|
|
282
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 2 }).run();
|
|
283
|
+
break;
|
|
284
|
+
case "heading3":
|
|
285
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 3 }).run();
|
|
286
|
+
break;
|
|
287
|
+
case "blockquote":
|
|
288
|
+
tiptapEditor.chain().focus().toggleBlockquote().run();
|
|
289
|
+
break;
|
|
290
|
+
case "undo":
|
|
291
|
+
tiptapEditor.chain().focus().undo().run();
|
|
292
|
+
break;
|
|
293
|
+
case "redo":
|
|
294
|
+
tiptapEditor.chain().focus().redo().run();
|
|
295
|
+
break;
|
|
296
|
+
case "link": {
|
|
297
|
+
const previousUrl = tiptapEditor.getAttributes("link").href;
|
|
298
|
+
if (previousUrl) {
|
|
299
|
+
tiptapEditor.chain().focus().unsetLink().run();
|
|
300
|
+
} else {
|
|
301
|
+
const url = window.prompt("Enter URL");
|
|
302
|
+
if (url) {
|
|
303
|
+
tiptapEditor.chain().focus().setLink({ href: url }).run();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} else if (contentRef.current) {
|
|
310
|
+
applyMarkdownFormat(format, contentRef.current, setValue);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
[disabled, readOnly, tiptapEditor, setValue]
|
|
314
|
+
);
|
|
315
|
+
const isFormatActive = React.useCallback(
|
|
316
|
+
(format) => {
|
|
317
|
+
if (!tiptapEditor) return false;
|
|
318
|
+
switch (format) {
|
|
319
|
+
case "bold":
|
|
320
|
+
return tiptapEditor.isActive("bold");
|
|
321
|
+
case "italic":
|
|
322
|
+
return tiptapEditor.isActive("italic");
|
|
323
|
+
case "strikethrough":
|
|
324
|
+
return tiptapEditor.isActive("strike");
|
|
325
|
+
case "code":
|
|
326
|
+
return tiptapEditor.isActive("code");
|
|
327
|
+
case "bulletList":
|
|
328
|
+
return tiptapEditor.isActive("bulletList");
|
|
329
|
+
case "orderedList":
|
|
330
|
+
return tiptapEditor.isActive("orderedList");
|
|
331
|
+
case "heading1":
|
|
332
|
+
return tiptapEditor.isActive("heading", { level: 1 });
|
|
333
|
+
case "heading2":
|
|
334
|
+
return tiptapEditor.isActive("heading", { level: 2 });
|
|
335
|
+
case "heading3":
|
|
336
|
+
return tiptapEditor.isActive("heading", { level: 3 });
|
|
337
|
+
case "blockquote":
|
|
338
|
+
return tiptapEditor.isActive("blockquote");
|
|
339
|
+
case "link":
|
|
340
|
+
return tiptapEditor.isActive("link");
|
|
341
|
+
case "undo":
|
|
342
|
+
case "redo":
|
|
343
|
+
return false;
|
|
344
|
+
// Actions don't have active state
|
|
345
|
+
default:
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
[tiptapEditor]
|
|
350
|
+
);
|
|
351
|
+
const wordCount = React.useMemo(() => {
|
|
352
|
+
var _a;
|
|
353
|
+
if (tiptapEditor) {
|
|
354
|
+
const text = ((_a = tiptapEditor.getText) == null ? void 0 : _a.call(tiptapEditor)) ?? "";
|
|
355
|
+
return countWords(text);
|
|
356
|
+
}
|
|
357
|
+
return countWords(value);
|
|
358
|
+
}, [value, tiptapEditor]);
|
|
359
|
+
const charCount = React.useMemo(() => {
|
|
360
|
+
var _a;
|
|
361
|
+
if (tiptapEditor) {
|
|
362
|
+
return (((_a = tiptapEditor.getText) == null ? void 0 : _a.call(tiptapEditor)) ?? "").length;
|
|
363
|
+
}
|
|
364
|
+
return value.length;
|
|
365
|
+
}, [value, tiptapEditor]);
|
|
366
|
+
const contextValue = {
|
|
367
|
+
value,
|
|
368
|
+
setValue,
|
|
369
|
+
placeholder,
|
|
370
|
+
disabled,
|
|
371
|
+
readOnly,
|
|
372
|
+
formats,
|
|
373
|
+
editor: tiptapEditor,
|
|
374
|
+
mode,
|
|
375
|
+
size,
|
|
376
|
+
maxLength,
|
|
377
|
+
wordCount,
|
|
378
|
+
charCount,
|
|
379
|
+
toggleFormat,
|
|
380
|
+
isFormatActive,
|
|
381
|
+
saveStatus,
|
|
382
|
+
contentRef
|
|
383
|
+
};
|
|
384
|
+
const classes = [
|
|
385
|
+
styles.editor,
|
|
386
|
+
disabled && styles.disabled,
|
|
387
|
+
readOnly && styles.readOnly,
|
|
388
|
+
className
|
|
389
|
+
].filter(Boolean).join(" ");
|
|
390
|
+
const hasCustomChildren = children !== void 0;
|
|
391
|
+
return /* @__PURE__ */ jsx(EditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
...htmlProps,
|
|
395
|
+
className: classes,
|
|
396
|
+
"data-disabled": disabled || void 0,
|
|
397
|
+
"data-readonly": readOnly || void 0,
|
|
398
|
+
"data-size": size,
|
|
399
|
+
children: hasCustomChildren ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
400
|
+
toolbar && /* @__PURE__ */ jsx(EditorToolbar, { children: /* @__PURE__ */ jsx(EditorToolbarGroup, { "aria-label": "Text formatting", children: formats.map((f) => /* @__PURE__ */ jsx(EditorToolbarButton, { format: f }, f)) }) }),
|
|
401
|
+
/* @__PURE__ */ jsx(EditorContentArea, {}),
|
|
402
|
+
statusBar && /* @__PURE__ */ jsx(EditorStatusBar, { showWordCount: true, showCharCount: true })
|
|
403
|
+
] })
|
|
404
|
+
}
|
|
405
|
+
) });
|
|
406
|
+
}
|
|
407
|
+
function EditorToolbar({ children, className }) {
|
|
408
|
+
const classes = [styles.toolbar, className].filter(Boolean).join(" ");
|
|
409
|
+
return /* @__PURE__ */ jsx("div", { className: classes, role: "toolbar", "aria-label": "Editor formatting", children });
|
|
410
|
+
}
|
|
411
|
+
function EditorToolbarGroup({ children, "aria-label": ariaLabel, className }) {
|
|
412
|
+
const classes = [styles.toolbarGroup, className].filter(Boolean).join(" ");
|
|
413
|
+
return /* @__PURE__ */ jsx("div", { className: classes, role: "group", "aria-label": ariaLabel, children });
|
|
414
|
+
}
|
|
415
|
+
function EditorToolbarButton({ format, className }) {
|
|
416
|
+
const { toggleFormat, isFormatActive, disabled, readOnly, editor, mode } = useEditorContext();
|
|
417
|
+
const meta = FORMAT_META[format];
|
|
418
|
+
const isAction = ACTION_FORMATS.has(format);
|
|
419
|
+
const active = isAction ? false : isFormatActive(format);
|
|
420
|
+
const IconComponent = meta.icon;
|
|
421
|
+
let isDisabled = disabled || readOnly;
|
|
422
|
+
if (isAction && !isDisabled) {
|
|
423
|
+
if (mode === "markdown") {
|
|
424
|
+
isDisabled = true;
|
|
425
|
+
} else if (editor) {
|
|
426
|
+
isDisabled = format === "undo" ? !editor.can().undo() : !editor.can().redo();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const classes = [
|
|
430
|
+
styles.toolbarButton,
|
|
431
|
+
active && styles.toolbarButtonActive,
|
|
432
|
+
className
|
|
433
|
+
].filter(Boolean).join(" ");
|
|
434
|
+
return /* @__PURE__ */ jsx(
|
|
435
|
+
"button",
|
|
436
|
+
{
|
|
437
|
+
type: "button",
|
|
438
|
+
className: classes,
|
|
439
|
+
onClick: () => toggleFormat(format),
|
|
440
|
+
disabled: isDisabled,
|
|
441
|
+
"aria-label": meta.label,
|
|
442
|
+
title: `${meta.label} (${meta.shortcut})`,
|
|
443
|
+
...isAction ? {} : { "aria-pressed": active },
|
|
444
|
+
children: /* @__PURE__ */ jsx(IconComponent, { size: 16, weight: active ? "bold" : "regular" })
|
|
445
|
+
}
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
function EditorSeparator({ className }) {
|
|
449
|
+
const classes = [styles.separator, className].filter(Boolean).join(" ");
|
|
450
|
+
return /* @__PURE__ */ jsx("div", { className: classes, role: "separator", "aria-orientation": "vertical" });
|
|
451
|
+
}
|
|
452
|
+
function EditorStatusIndicator({ status: statusOverride, labels, className }) {
|
|
453
|
+
const { saveStatus } = useEditorContext();
|
|
454
|
+
const status = statusOverride ?? saveStatus;
|
|
455
|
+
const mergedLabels = { ...DEFAULT_STATUS_LABELS, ...labels };
|
|
456
|
+
const label = mergedLabels[status];
|
|
457
|
+
if (!label) return null;
|
|
458
|
+
const classes = [
|
|
459
|
+
styles.statusIndicator,
|
|
460
|
+
status === "error" && styles.statusError,
|
|
461
|
+
className
|
|
462
|
+
].filter(Boolean).join(" ");
|
|
463
|
+
return /* @__PURE__ */ jsx("span", { className: classes, "aria-live": "polite", role: "status", children: label });
|
|
464
|
+
}
|
|
465
|
+
function EditorContentArea({ className }) {
|
|
466
|
+
const { value, setValue, placeholder, disabled, readOnly, editor, mode, contentRef } = useEditorContext();
|
|
467
|
+
if (mode === "rich" && editor && _EditorContent) {
|
|
468
|
+
const TipTapContent = _EditorContent;
|
|
469
|
+
const classes2 = [styles.content, styles.contentRich, className].filter(Boolean).join(" ");
|
|
470
|
+
return /* @__PURE__ */ jsx("div", { className: classes2, "data-placeholder": placeholder, children: /* @__PURE__ */ jsx(TipTapContent, { editor }) });
|
|
471
|
+
}
|
|
472
|
+
const classes = [styles.content, className].filter(Boolean).join(" ");
|
|
473
|
+
return /* @__PURE__ */ jsx("div", { className: classes, children: /* @__PURE__ */ jsx(
|
|
474
|
+
"textarea",
|
|
475
|
+
{
|
|
476
|
+
ref: contentRef,
|
|
477
|
+
className: styles.contentTextarea,
|
|
478
|
+
value,
|
|
479
|
+
onChange: (e) => setValue(e.target.value),
|
|
480
|
+
placeholder,
|
|
481
|
+
disabled,
|
|
482
|
+
readOnly,
|
|
483
|
+
"aria-label": placeholder
|
|
484
|
+
}
|
|
485
|
+
) });
|
|
486
|
+
}
|
|
487
|
+
function EditorStatusBar({ showWordCount = true, showCharCount = true, className }) {
|
|
488
|
+
const { wordCount, charCount, maxLength } = useEditorContext();
|
|
489
|
+
const classes = [styles.statusBar, className].filter(Boolean).join(" ");
|
|
490
|
+
const isOverLimit = maxLength !== void 0 && charCount > maxLength;
|
|
491
|
+
const isNearLimit = maxLength !== void 0 && !isOverLimit && charCount >= maxLength * 0.9;
|
|
492
|
+
const charLimitClasses = [
|
|
493
|
+
styles.statusBarItem,
|
|
494
|
+
isNearLimit && styles.statusBarItemWarning,
|
|
495
|
+
isOverLimit && styles.statusBarItemError
|
|
496
|
+
].filter(Boolean).join(" ");
|
|
497
|
+
return /* @__PURE__ */ jsxs("div", { className: classes, "aria-label": "Editor statistics", children: [
|
|
498
|
+
/* @__PURE__ */ jsx("div", { className: styles.statusBarLeft }),
|
|
499
|
+
/* @__PURE__ */ jsxs("div", { className: styles.statusBarRight, children: [
|
|
500
|
+
showWordCount && /* @__PURE__ */ jsxs("span", { className: styles.statusBarItem, children: [
|
|
501
|
+
wordCount,
|
|
502
|
+
" ",
|
|
503
|
+
wordCount === 1 ? "Word" : "Words"
|
|
504
|
+
] }),
|
|
505
|
+
showWordCount && showCharCount && /* @__PURE__ */ jsx(EditorSeparator, {}),
|
|
506
|
+
showCharCount && /* @__PURE__ */ jsx("span", { className: charLimitClasses, children: maxLength !== void 0 ? `${charCount} / ${maxLength}` : `${charCount} ${charCount === 1 ? "Character" : "Characters"}` })
|
|
507
|
+
] })
|
|
508
|
+
] });
|
|
509
|
+
}
|
|
510
|
+
const Editor = Object.assign(EditorRoot, {
|
|
511
|
+
Toolbar: EditorToolbar,
|
|
512
|
+
ToolbarGroup: EditorToolbarGroup,
|
|
513
|
+
ToolbarButton: EditorToolbarButton,
|
|
514
|
+
Separator: EditorSeparator,
|
|
515
|
+
StatusIndicator: EditorStatusIndicator,
|
|
516
|
+
Content: EditorContentArea,
|
|
517
|
+
StatusBar: EditorStatusBar
|
|
518
|
+
});
|
|
519
|
+
export {
|
|
520
|
+
Editor,
|
|
521
|
+
EditorContentArea,
|
|
522
|
+
EditorRoot,
|
|
523
|
+
EditorSeparator,
|
|
524
|
+
EditorStatusBar,
|
|
525
|
+
EditorStatusIndicator,
|
|
526
|
+
EditorToolbar,
|
|
527
|
+
EditorToolbarButton,
|
|
528
|
+
EditorToolbarGroup,
|
|
529
|
+
useEditorContext
|
|
530
|
+
};
|
|
531
|
+
//# sourceMappingURL=index.js.map
|