@melv1c/rich-text-editor 0.0.0 → 1.0.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/index.d.mts +253 -1
- package/dist/index.mjs +388 -1
- package/package.json +10 -5
package/dist/index.d.mts
CHANGED
|
@@ -1 +1,253 @@
|
|
|
1
|
-
|
|
1
|
+
import { Editor, EditorContent, useEditor } from "@tiptap/react";
|
|
2
|
+
import { Button, ButtonGroup, Select, SelectContent, SelectTrigger, ToggleGroup, ToggleGroupItem } from "@melv1c/ui-core";
|
|
3
|
+
import * as react from "react";
|
|
4
|
+
import { ComponentProps, HTMLAttributes } from "react";
|
|
5
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
6
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
7
|
+
|
|
8
|
+
//#region src/content.d.ts
|
|
9
|
+
type RichTextEditorContentProps = Omit<ComponentProps<typeof EditorContent>, 'editor'> & {
|
|
10
|
+
/**
|
|
11
|
+
* Minimum height of the editor content area.
|
|
12
|
+
* Accepts a number (treated as pixels) or any valid CSS size string.
|
|
13
|
+
* @default 180
|
|
14
|
+
*/
|
|
15
|
+
minHeight?: number | string;
|
|
16
|
+
/**
|
|
17
|
+
* Maximum height of the editor content area. When set, the content area becomes
|
|
18
|
+
* scrollable once the content exceeds this height.
|
|
19
|
+
* Accepts a number (treated as pixels) or any valid CSS size string.
|
|
20
|
+
*/
|
|
21
|
+
maxHeight?: number | string;
|
|
22
|
+
};
|
|
23
|
+
declare function RichTextEditorContent({
|
|
24
|
+
className,
|
|
25
|
+
minHeight,
|
|
26
|
+
maxHeight,
|
|
27
|
+
style,
|
|
28
|
+
...props
|
|
29
|
+
}: RichTextEditorContentProps): react_jsx_runtime0.JSX.Element;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/context.d.ts
|
|
32
|
+
type RichTextEditorContextValue = {
|
|
33
|
+
editor: Editor | null;
|
|
34
|
+
};
|
|
35
|
+
declare const RichTextEditorContext: react.Context<RichTextEditorContextValue | null>;
|
|
36
|
+
declare function useRichTextEditor(): RichTextEditorContextValue;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/editor.d.ts
|
|
39
|
+
type RichTextEditorProps = HTMLAttributes<HTMLDivElement> & {
|
|
40
|
+
value?: string;
|
|
41
|
+
defaultValue?: string;
|
|
42
|
+
onValueChange?: (value: string, editor: Editor) => void;
|
|
43
|
+
editable?: boolean;
|
|
44
|
+
extensions?: Parameters<typeof useEditor>[0]['extensions'];
|
|
45
|
+
starterKit?: Parameters<typeof StarterKit.configure>[0];
|
|
46
|
+
};
|
|
47
|
+
declare function RichTextEditor({
|
|
48
|
+
children,
|
|
49
|
+
className,
|
|
50
|
+
value,
|
|
51
|
+
defaultValue,
|
|
52
|
+
onValueChange,
|
|
53
|
+
editable,
|
|
54
|
+
extensions,
|
|
55
|
+
starterKit,
|
|
56
|
+
...props
|
|
57
|
+
}: RichTextEditorProps): react_jsx_runtime0.JSX.Element;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/groups/formatting-group.d.ts
|
|
60
|
+
type RichTextEditorToggleButtonProps$2 = Omit<ComponentProps<typeof ToggleGroupItem>, 'value'>;
|
|
61
|
+
type RichTextEditorFormattingItem = 'bold' | 'italic' | 'strike' | 'code';
|
|
62
|
+
type RichTextEditorFormattingValue = RichTextEditorFormattingItem[];
|
|
63
|
+
declare function BoldButton({
|
|
64
|
+
className,
|
|
65
|
+
...props
|
|
66
|
+
}: RichTextEditorToggleButtonProps$2): react_jsx_runtime0.JSX.Element;
|
|
67
|
+
declare function ItalicButton({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: RichTextEditorToggleButtonProps$2): react_jsx_runtime0.JSX.Element;
|
|
71
|
+
declare function StrikethroughButton({
|
|
72
|
+
className,
|
|
73
|
+
...props
|
|
74
|
+
}: RichTextEditorToggleButtonProps$2): react_jsx_runtime0.JSX.Element;
|
|
75
|
+
declare function CodeButton({
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: RichTextEditorToggleButtonProps$2): react_jsx_runtime0.JSX.Element;
|
|
79
|
+
type RichTextEditorToggleGroupProps$2 = Omit<ComponentProps<typeof ToggleGroup>, 'type' | 'value' | 'onValueChange'>;
|
|
80
|
+
type RichTextEditorFormattingGroupProps = RichTextEditorToggleGroupProps$2 & {
|
|
81
|
+
boldProps?: ComponentProps<typeof BoldButton>;
|
|
82
|
+
italicProps?: ComponentProps<typeof ItalicButton>;
|
|
83
|
+
strikethroughProps?: ComponentProps<typeof StrikethroughButton>;
|
|
84
|
+
codeProps?: ComponentProps<typeof CodeButton>;
|
|
85
|
+
items?: RichTextEditorFormattingItem[];
|
|
86
|
+
defaultValue?: RichTextEditorFormattingValue;
|
|
87
|
+
};
|
|
88
|
+
declare function RichTextEditorFormattingGroup({
|
|
89
|
+
boldProps,
|
|
90
|
+
italicProps,
|
|
91
|
+
strikethroughProps,
|
|
92
|
+
codeProps,
|
|
93
|
+
items,
|
|
94
|
+
variant,
|
|
95
|
+
size,
|
|
96
|
+
...props
|
|
97
|
+
}: RichTextEditorFormattingGroupProps): react_jsx_runtime0.JSX.Element;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/groups/heading-group.d.ts
|
|
100
|
+
type RichTextEditorToggleButtonProps$1 = Omit<ComponentProps<typeof ToggleGroupItem>, 'value'>;
|
|
101
|
+
type RichTextEditorHeadingLevel = 1 | 2 | 3;
|
|
102
|
+
type RichTextEditorHeadingMode = 'toggle' | 'select';
|
|
103
|
+
type RichTextEditorHeadingValue = `heading-${RichTextEditorHeadingLevel}` | '';
|
|
104
|
+
declare function Heading1Button({
|
|
105
|
+
className,
|
|
106
|
+
...props
|
|
107
|
+
}: RichTextEditorToggleButtonProps$1): react_jsx_runtime0.JSX.Element;
|
|
108
|
+
declare function Heading2Button({
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: RichTextEditorToggleButtonProps$1): react_jsx_runtime0.JSX.Element;
|
|
112
|
+
declare function Heading3Button({
|
|
113
|
+
className,
|
|
114
|
+
...props
|
|
115
|
+
}: RichTextEditorToggleButtonProps$1): react_jsx_runtime0.JSX.Element;
|
|
116
|
+
type RichTextEditorToggleGroupProps$1 = Omit<ComponentProps<typeof ToggleGroup>, 'type' | 'value' | 'onValueChange'>;
|
|
117
|
+
type RichTextEditorHeadingSelectProps = Omit<ComponentProps<typeof Select>, 'value' | 'onValueChange'>;
|
|
118
|
+
type RichTextEditorHeadingGroupProps = RichTextEditorToggleGroupProps$1 & {
|
|
119
|
+
mode?: RichTextEditorHeadingMode;
|
|
120
|
+
levels?: RichTextEditorHeadingLevel[];
|
|
121
|
+
heading1Props?: ComponentProps<typeof Heading1Button>;
|
|
122
|
+
heading2Props?: ComponentProps<typeof Heading2Button>;
|
|
123
|
+
heading3Props?: ComponentProps<typeof Heading3Button>;
|
|
124
|
+
includeParagraphOption?: boolean;
|
|
125
|
+
paragraphLabel?: string;
|
|
126
|
+
selectPlaceholder?: string;
|
|
127
|
+
selectProps?: RichTextEditorHeadingSelectProps;
|
|
128
|
+
selectTriggerProps?: Omit<ComponentProps<typeof SelectTrigger>, 'children'>;
|
|
129
|
+
selectContentProps?: ComponentProps<typeof SelectContent>;
|
|
130
|
+
defaultValue?: RichTextEditorHeadingValue;
|
|
131
|
+
};
|
|
132
|
+
declare function RichTextEditorHeadingGroup({
|
|
133
|
+
mode,
|
|
134
|
+
levels,
|
|
135
|
+
heading1Props,
|
|
136
|
+
heading2Props,
|
|
137
|
+
heading3Props,
|
|
138
|
+
includeParagraphOption,
|
|
139
|
+
paragraphLabel,
|
|
140
|
+
selectPlaceholder,
|
|
141
|
+
selectProps,
|
|
142
|
+
selectTriggerProps,
|
|
143
|
+
selectContentProps,
|
|
144
|
+
variant,
|
|
145
|
+
size,
|
|
146
|
+
...props
|
|
147
|
+
}: RichTextEditorHeadingGroupProps): react_jsx_runtime0.JSX.Element;
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/groups/history-group.d.ts
|
|
150
|
+
type RichTextEditorActionButtonProps = ComponentProps<typeof Button>;
|
|
151
|
+
type RichTextEditorHistoryItem = 'undo' | 'redo';
|
|
152
|
+
declare function UndoButton({
|
|
153
|
+
className,
|
|
154
|
+
...props
|
|
155
|
+
}: RichTextEditorActionButtonProps): react_jsx_runtime0.JSX.Element;
|
|
156
|
+
declare function RedoButton({
|
|
157
|
+
className,
|
|
158
|
+
...props
|
|
159
|
+
}: RichTextEditorActionButtonProps): react_jsx_runtime0.JSX.Element;
|
|
160
|
+
type RichTextEditorHistoryGroupProps = ComponentProps<typeof ButtonGroup> & {
|
|
161
|
+
undoProps?: ComponentProps<typeof UndoButton>;
|
|
162
|
+
redoProps?: ComponentProps<typeof RedoButton>;
|
|
163
|
+
items?: RichTextEditorHistoryItem[];
|
|
164
|
+
};
|
|
165
|
+
declare function RichTextEditorHistoryGroup({
|
|
166
|
+
undoProps,
|
|
167
|
+
redoProps,
|
|
168
|
+
items,
|
|
169
|
+
...props
|
|
170
|
+
}: RichTextEditorHistoryGroupProps): react_jsx_runtime0.JSX.Element;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/groups/list-group.d.ts
|
|
173
|
+
type RichTextEditorToggleButtonProps = Omit<ComponentProps<typeof ToggleGroupItem>, 'value'>;
|
|
174
|
+
type RichTextEditorListValue = 'bullet-list' | 'ordered-list' | '';
|
|
175
|
+
type RichTextEditorListItem = Exclude<RichTextEditorListValue, ''>;
|
|
176
|
+
declare function BulletListButton({
|
|
177
|
+
className,
|
|
178
|
+
...props
|
|
179
|
+
}: RichTextEditorToggleButtonProps): react_jsx_runtime0.JSX.Element;
|
|
180
|
+
declare function OrderedListButton({
|
|
181
|
+
className,
|
|
182
|
+
...props
|
|
183
|
+
}: RichTextEditorToggleButtonProps): react_jsx_runtime0.JSX.Element;
|
|
184
|
+
type RichTextEditorToggleGroupProps = Omit<ComponentProps<typeof ToggleGroup>, 'type' | 'value' | 'onValueChange'>;
|
|
185
|
+
type RichTextEditorListGroupProps = RichTextEditorToggleGroupProps & {
|
|
186
|
+
bulletListProps?: ComponentProps<typeof BulletListButton>;
|
|
187
|
+
orderedListProps?: ComponentProps<typeof OrderedListButton>;
|
|
188
|
+
items?: RichTextEditorListItem[];
|
|
189
|
+
defaultValue?: RichTextEditorListValue;
|
|
190
|
+
};
|
|
191
|
+
declare function RichTextEditorListGroup({
|
|
192
|
+
bulletListProps,
|
|
193
|
+
orderedListProps,
|
|
194
|
+
items,
|
|
195
|
+
variant,
|
|
196
|
+
size,
|
|
197
|
+
...props
|
|
198
|
+
}: RichTextEditorListGroupProps): react_jsx_runtime0.JSX.Element;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/built-in.d.ts
|
|
201
|
+
/**
|
|
202
|
+
* Built-in editor presets:
|
|
203
|
+
* - `"minimal"`: Formatting controls (bold, italic, strike, code) + undo/redo
|
|
204
|
+
* - `"simple"` (default): Heading select + formatting + undo/redo
|
|
205
|
+
* - `"complete"`: Heading toggles + formatting + lists + undo/redo
|
|
206
|
+
* - `"oneline"`: No toolbar, single-line (Enter is blocked)
|
|
207
|
+
*/
|
|
208
|
+
type RichTextEditorPreset = 'minimal' | 'simple' | 'complete' | 'oneline';
|
|
209
|
+
type RichTextEditorBuiltInProps = Omit<RichTextEditorProps, 'children'> & {
|
|
210
|
+
contentClassName?: string;
|
|
211
|
+
toolbarClassName?: string;
|
|
212
|
+
/**
|
|
213
|
+
* Selects a built-in toolbar / behaviour configuration.
|
|
214
|
+
* @default "complete"
|
|
215
|
+
*/
|
|
216
|
+
preset?: RichTextEditorPreset;
|
|
217
|
+
/**
|
|
218
|
+
* Minimum height of the editor content area.
|
|
219
|
+
* Accepts a number (px) or any valid CSS size string.
|
|
220
|
+
* Forwarded to `RichTextEditorContent`.
|
|
221
|
+
*/
|
|
222
|
+
minHeight?: RichTextEditorContentProps['minHeight'];
|
|
223
|
+
/**
|
|
224
|
+
* Maximum height of the editor content area. When set the content area becomes
|
|
225
|
+
* scrollable once the content exceeds this height.
|
|
226
|
+
* Accepts a number (px) or any valid CSS size string.
|
|
227
|
+
* Forwarded to `RichTextEditorContent`.
|
|
228
|
+
*/
|
|
229
|
+
maxHeight?: RichTextEditorContentProps['maxHeight'];
|
|
230
|
+
};
|
|
231
|
+
declare function RichTextEditorBuiltIn({
|
|
232
|
+
className,
|
|
233
|
+
contentClassName,
|
|
234
|
+
toolbarClassName,
|
|
235
|
+
preset,
|
|
236
|
+
minHeight,
|
|
237
|
+
maxHeight,
|
|
238
|
+
extensions,
|
|
239
|
+
...props
|
|
240
|
+
}: RichTextEditorBuiltInProps): react_jsx_runtime0.JSX.Element;
|
|
241
|
+
/** @deprecated Use `RichTextEditorBuiltIn` instead. */
|
|
242
|
+
declare const RichTextEditorSimple: typeof RichTextEditorBuiltIn;
|
|
243
|
+
/** @deprecated Use `RichTextEditorBuiltInProps` instead. */
|
|
244
|
+
type RichTextEditorSimpleProps = RichTextEditorBuiltInProps;
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/toolbar.d.ts
|
|
247
|
+
type RichTextEditorToolbarProps = HTMLAttributes<HTMLDivElement>;
|
|
248
|
+
declare function RichTextEditorToolbar({
|
|
249
|
+
className,
|
|
250
|
+
...props
|
|
251
|
+
}: RichTextEditorToolbarProps): react_jsx_runtime0.JSX.Element;
|
|
252
|
+
//#endregion
|
|
253
|
+
export { RichTextEditor, RichTextEditorBuiltIn, RichTextEditorBuiltInProps, RichTextEditorContent, RichTextEditorContentProps, RichTextEditorContext, RichTextEditorContextValue, RichTextEditorFormattingGroup, RichTextEditorFormattingGroupProps, RichTextEditorHeadingGroup, RichTextEditorHeadingGroupProps, RichTextEditorHistoryGroup, RichTextEditorHistoryGroupProps, RichTextEditorListGroup, RichTextEditorListGroupProps, RichTextEditorPreset, RichTextEditorProps, RichTextEditorSimple, RichTextEditorSimpleProps, RichTextEditorToolbar, RichTextEditorToolbarProps, useRichTextEditor };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1,388 @@
|
|
|
1
|
-
|
|
1
|
+
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
|
|
2
|
+
import { Button, ButtonGroup, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, ToggleGroup, ToggleGroupItem, cn } from "@melv1c/ui-core";
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
6
|
+
import { Bold, Code, Heading1, Heading2, Heading3, Italic, List, ListOrdered, Redo2, Strikethrough, Undo2 } from "lucide-react";
|
|
7
|
+
import { Extension } from "@tiptap/core";
|
|
8
|
+
|
|
9
|
+
//#region src/context.tsx
|
|
10
|
+
const RichTextEditorContext = createContext(null);
|
|
11
|
+
function useRichTextEditor() {
|
|
12
|
+
const context = useContext(RichTextEditorContext);
|
|
13
|
+
if (!context) throw new Error("useRichTextEditor must be used within RichTextEditor.");
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/content.tsx
|
|
19
|
+
function toSize(value) {
|
|
20
|
+
if (value === void 0) return void 0;
|
|
21
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
22
|
+
}
|
|
23
|
+
function RichTextEditorContent({ className, minHeight = 180, maxHeight, style, ...props }) {
|
|
24
|
+
const { editor } = useRichTextEditor();
|
|
25
|
+
const heightStyles = {
|
|
26
|
+
minHeight: toSize(minHeight),
|
|
27
|
+
maxHeight: toSize(maxHeight),
|
|
28
|
+
overflowY: maxHeight !== void 0 ? "auto" : void 0
|
|
29
|
+
};
|
|
30
|
+
return /* @__PURE__ */ jsx(EditorContent, {
|
|
31
|
+
"data-slot": "rich-text-editor-content",
|
|
32
|
+
editor,
|
|
33
|
+
className: cn("text-foreground [&_.ProseMirror]:px-2 [&_.ProseMirror]:py-1 [&_.ProseMirror]:outline-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground [&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none [&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left [&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0 [&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]", className),
|
|
34
|
+
style: {
|
|
35
|
+
...heightStyles,
|
|
36
|
+
...style
|
|
37
|
+
},
|
|
38
|
+
...props
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/editor.tsx
|
|
44
|
+
function RichTextEditor({ children, className, value, defaultValue, onValueChange, editable = true, extensions, starterKit, ...props }) {
|
|
45
|
+
const mergedExtensions = useMemo(() => [StarterKit.configure(starterKit ?? {}), ...extensions ?? []], [extensions, starterKit]);
|
|
46
|
+
const editor = useEditor({
|
|
47
|
+
extensions: mergedExtensions,
|
|
48
|
+
editable,
|
|
49
|
+
content: value ?? defaultValue ?? "",
|
|
50
|
+
onUpdate({ editor: currentEditor }) {
|
|
51
|
+
onValueChange?.(currentEditor.getHTML(), currentEditor);
|
|
52
|
+
}
|
|
53
|
+
}, [mergedExtensions, editable]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!editor || value === void 0) return;
|
|
56
|
+
if (editor.getHTML() === value) return;
|
|
57
|
+
editor.commands.setContent(value, { emitUpdate: false });
|
|
58
|
+
}, [editor, value]);
|
|
59
|
+
const contextValue = useMemo(() => ({ editor }), [editor]);
|
|
60
|
+
return /* @__PURE__ */ jsx(RichTextEditorContext.Provider, {
|
|
61
|
+
value: contextValue,
|
|
62
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
63
|
+
"data-slot": "rich-text-editor",
|
|
64
|
+
className: cn("bg-background border-border rounded-lg border", className),
|
|
65
|
+
...props,
|
|
66
|
+
children
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/groups/formatting-group.tsx
|
|
73
|
+
const ALL_FORMATTING_ITEMS = [
|
|
74
|
+
"bold",
|
|
75
|
+
"italic",
|
|
76
|
+
"strike",
|
|
77
|
+
"code"
|
|
78
|
+
];
|
|
79
|
+
function BoldButton({ className, ...props }) {
|
|
80
|
+
const { editor } = useRichTextEditor();
|
|
81
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
82
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
83
|
+
value: "bold",
|
|
84
|
+
"aria-label": "Bold",
|
|
85
|
+
disabled: editor ? !editor.can().chain().focus().toggleBold().run() : true,
|
|
86
|
+
onClick: () => editor?.chain().focus().toggleBold().run(),
|
|
87
|
+
className: cn("shadow-none", className),
|
|
88
|
+
...props,
|
|
89
|
+
children: props.children ?? /* @__PURE__ */ jsx(Bold, {})
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function ItalicButton({ className, ...props }) {
|
|
93
|
+
const { editor } = useRichTextEditor();
|
|
94
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
95
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
96
|
+
value: "italic",
|
|
97
|
+
"aria-label": "Italic",
|
|
98
|
+
disabled: editor ? !editor.can().chain().focus().toggleItalic().run() : true,
|
|
99
|
+
onClick: () => editor?.chain().focus().toggleItalic().run(),
|
|
100
|
+
className: cn("shadow-none", className),
|
|
101
|
+
...props,
|
|
102
|
+
children: props.children ?? /* @__PURE__ */ jsx(Italic, {})
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function StrikethroughButton({ className, ...props }) {
|
|
106
|
+
const { editor } = useRichTextEditor();
|
|
107
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
108
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
109
|
+
value: "strike",
|
|
110
|
+
"aria-label": "Strikethrough",
|
|
111
|
+
disabled: editor ? !editor.can().chain().focus().toggleStrike().run() : true,
|
|
112
|
+
onClick: () => editor?.chain().focus().toggleStrike().run(),
|
|
113
|
+
className: cn("shadow-none", className),
|
|
114
|
+
...props,
|
|
115
|
+
children: props.children ?? /* @__PURE__ */ jsx(Strikethrough, {})
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function CodeButton({ className, ...props }) {
|
|
119
|
+
const { editor } = useRichTextEditor();
|
|
120
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
121
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
122
|
+
value: "code",
|
|
123
|
+
"aria-label": "Inline code",
|
|
124
|
+
disabled: editor ? !editor.can().chain().focus().toggleCode().run() : true,
|
|
125
|
+
onClick: () => editor?.chain().focus().toggleCode().run(),
|
|
126
|
+
className: cn("shadow-none", className),
|
|
127
|
+
...props,
|
|
128
|
+
children: props.children ?? /* @__PURE__ */ jsx(Code, {})
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function RichTextEditorFormattingGroup({ boldProps, italicProps, strikethroughProps, codeProps, items = ALL_FORMATTING_ITEMS, variant = "outline", size = "sm", ...props }) {
|
|
132
|
+
const { editor } = useRichTextEditor();
|
|
133
|
+
const visibleItems = new Set(items);
|
|
134
|
+
const activeValue = useEditorState({
|
|
135
|
+
editor,
|
|
136
|
+
selector: ({ editor: currentEditor }) => {
|
|
137
|
+
if (!currentEditor) return [];
|
|
138
|
+
return ALL_FORMATTING_ITEMS.filter((format) => visibleItems.has(format) && currentEditor.isActive(format));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return /* @__PURE__ */ jsxs(ToggleGroup, {
|
|
142
|
+
type: "multiple",
|
|
143
|
+
variant,
|
|
144
|
+
size,
|
|
145
|
+
"aria-label": "Inline formatting",
|
|
146
|
+
value: activeValue ?? [],
|
|
147
|
+
...props,
|
|
148
|
+
children: [
|
|
149
|
+
visibleItems.has("bold") && /* @__PURE__ */ jsx(BoldButton, { ...boldProps }),
|
|
150
|
+
visibleItems.has("italic") && /* @__PURE__ */ jsx(ItalicButton, { ...italicProps }),
|
|
151
|
+
visibleItems.has("strike") && /* @__PURE__ */ jsx(StrikethroughButton, { ...strikethroughProps }),
|
|
152
|
+
visibleItems.has("code") && /* @__PURE__ */ jsx(CodeButton, { ...codeProps })
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/groups/heading-group.tsx
|
|
159
|
+
const ALL_HEADING_LEVELS = [
|
|
160
|
+
1,
|
|
161
|
+
2,
|
|
162
|
+
3
|
|
163
|
+
];
|
|
164
|
+
const HEADING_ICONS = {
|
|
165
|
+
1: Heading1,
|
|
166
|
+
2: Heading2,
|
|
167
|
+
3: Heading3
|
|
168
|
+
};
|
|
169
|
+
function HeadingButton({ className, level, ...props }) {
|
|
170
|
+
const { editor } = useRichTextEditor();
|
|
171
|
+
const Icon = HEADING_ICONS[level];
|
|
172
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
173
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
174
|
+
value: `heading-${level}`,
|
|
175
|
+
"aria-label": `Heading ${level}`,
|
|
176
|
+
disabled: editor ? !editor.can().chain().focus().toggleHeading({ level }).run() : true,
|
|
177
|
+
onClick: () => editor?.chain().focus().toggleHeading({ level }).run(),
|
|
178
|
+
className: cn("shadow-none", className),
|
|
179
|
+
...props,
|
|
180
|
+
children: props.children ?? /* @__PURE__ */ jsx(Icon, {})
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const headingItemLabelMap = {
|
|
184
|
+
1: "Heading 1",
|
|
185
|
+
2: "Heading 2",
|
|
186
|
+
3: "Heading 3"
|
|
187
|
+
};
|
|
188
|
+
function RichTextEditorHeadingGroup({ mode = "select", levels, heading1Props, heading2Props, heading3Props, includeParagraphOption = true, paragraphLabel = "Paragraph", selectPlaceholder = "Select heading", selectProps, selectTriggerProps, selectContentProps, variant = "outline", size = "sm", ...props }) {
|
|
189
|
+
const { editor } = useRichTextEditor();
|
|
190
|
+
const resolvedLevels = levels ?? ALL_HEADING_LEVELS;
|
|
191
|
+
const visibleLevels = new Set(resolvedLevels);
|
|
192
|
+
const headingPropsByLevel = {
|
|
193
|
+
1: heading1Props,
|
|
194
|
+
2: heading2Props,
|
|
195
|
+
3: heading3Props
|
|
196
|
+
};
|
|
197
|
+
const activeValue = useEditorState({
|
|
198
|
+
editor,
|
|
199
|
+
selector: ({ editor: currentEditor }) => {
|
|
200
|
+
if (!currentEditor) return "";
|
|
201
|
+
for (const level of ALL_HEADING_LEVELS) if (currentEditor.isActive("heading", { level })) return `heading-${level}`;
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (mode === "select") return /* @__PURE__ */ jsxs(Select, {
|
|
206
|
+
value: activeValue || (includeParagraphOption ? "paragraph" : void 0),
|
|
207
|
+
onValueChange: (value) => {
|
|
208
|
+
if (!editor) return;
|
|
209
|
+
if (value === "paragraph") {
|
|
210
|
+
editor.chain().focus().setParagraph().run();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const level = Number(value.replace("heading-", ""));
|
|
214
|
+
editor.chain().focus().setHeading({ level }).run();
|
|
215
|
+
},
|
|
216
|
+
...selectProps,
|
|
217
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
218
|
+
"aria-label": "Heading levels",
|
|
219
|
+
size: "sm",
|
|
220
|
+
disabled: !editor,
|
|
221
|
+
className: "shadow-none",
|
|
222
|
+
...selectTriggerProps,
|
|
223
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: selectPlaceholder })
|
|
224
|
+
}), /* @__PURE__ */ jsxs(SelectContent, {
|
|
225
|
+
...selectContentProps,
|
|
226
|
+
children: [includeParagraphOption && /* @__PURE__ */ jsx(SelectItem, {
|
|
227
|
+
value: "paragraph",
|
|
228
|
+
disabled: editor ? !editor.can().chain().focus().setParagraph().run() : true,
|
|
229
|
+
children: paragraphLabel
|
|
230
|
+
}), ALL_HEADING_LEVELS.filter((level) => visibleLevels.has(level)).map((level) => /* @__PURE__ */ jsx(SelectItem, {
|
|
231
|
+
value: `heading-${level}`,
|
|
232
|
+
disabled: editor ? !editor.can().chain().focus().setHeading({ level }).run() : true,
|
|
233
|
+
children: headingItemLabelMap[level]
|
|
234
|
+
}, level))]
|
|
235
|
+
})]
|
|
236
|
+
});
|
|
237
|
+
return /* @__PURE__ */ jsx(ToggleGroup, {
|
|
238
|
+
type: "single",
|
|
239
|
+
variant,
|
|
240
|
+
size,
|
|
241
|
+
"aria-label": "Heading levels",
|
|
242
|
+
value: activeValue ?? "",
|
|
243
|
+
...props,
|
|
244
|
+
children: ALL_HEADING_LEVELS.filter((level) => visibleLevels.has(level)).map((level) => /* @__PURE__ */ jsx(HeadingButton, {
|
|
245
|
+
level,
|
|
246
|
+
...headingPropsByLevel[level]
|
|
247
|
+
}, level))
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/groups/history-group.tsx
|
|
253
|
+
function UndoButton({ className, ...props }) {
|
|
254
|
+
const { editor } = useRichTextEditor();
|
|
255
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
256
|
+
"data-slot": "rich-text-editor-action-button",
|
|
257
|
+
"aria-label": "Undo",
|
|
258
|
+
variant: "outline",
|
|
259
|
+
size: "icon-sm",
|
|
260
|
+
disabled: editor ? !editor.can().chain().focus().undo().run() : true,
|
|
261
|
+
onClick: () => editor?.chain().focus().undo().run(),
|
|
262
|
+
className: cn("shadow-none", className),
|
|
263
|
+
...props,
|
|
264
|
+
children: props.children ?? /* @__PURE__ */ jsx(Undo2, {})
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function RedoButton({ className, ...props }) {
|
|
268
|
+
const { editor } = useRichTextEditor();
|
|
269
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
270
|
+
"data-slot": "rich-text-editor-action-button",
|
|
271
|
+
"aria-label": "Redo",
|
|
272
|
+
variant: "outline",
|
|
273
|
+
size: "icon-sm",
|
|
274
|
+
disabled: editor ? !editor.can().chain().focus().redo().run() : true,
|
|
275
|
+
onClick: () => editor?.chain().focus().redo().run(),
|
|
276
|
+
className: cn("shadow-none", className),
|
|
277
|
+
...props,
|
|
278
|
+
children: props.children ?? /* @__PURE__ */ jsx(Redo2, {})
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function RichTextEditorHistoryGroup({ undoProps, redoProps, items = ["undo", "redo"], ...props }) {
|
|
282
|
+
const visibleItems = new Set(items);
|
|
283
|
+
return /* @__PURE__ */ jsxs(ButtonGroup, {
|
|
284
|
+
...props,
|
|
285
|
+
children: [visibleItems.has("undo") && /* @__PURE__ */ jsx(UndoButton, { ...undoProps }), visibleItems.has("redo") && /* @__PURE__ */ jsx(RedoButton, { ...redoProps })]
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/groups/list-group.tsx
|
|
291
|
+
function BulletListButton({ className, ...props }) {
|
|
292
|
+
const { editor } = useRichTextEditor();
|
|
293
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
294
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
295
|
+
value: "bullet-list",
|
|
296
|
+
"aria-label": "Bullet list",
|
|
297
|
+
disabled: editor ? !editor.can().chain().focus().toggleBulletList().run() : true,
|
|
298
|
+
onClick: () => editor?.chain().focus().toggleBulletList().run(),
|
|
299
|
+
className: cn("shadow-none", className),
|
|
300
|
+
...props,
|
|
301
|
+
children: props.children ?? /* @__PURE__ */ jsx(List, {})
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function OrderedListButton({ className, ...props }) {
|
|
305
|
+
const { editor } = useRichTextEditor();
|
|
306
|
+
return /* @__PURE__ */ jsx(ToggleGroupItem, {
|
|
307
|
+
"data-slot": "rich-text-editor-toggle-button",
|
|
308
|
+
value: "ordered-list",
|
|
309
|
+
"aria-label": "Ordered list",
|
|
310
|
+
disabled: editor ? !editor.can().chain().focus().toggleOrderedList().run() : true,
|
|
311
|
+
onClick: () => editor?.chain().focus().toggleOrderedList().run(),
|
|
312
|
+
className: cn("shadow-none", className),
|
|
313
|
+
...props,
|
|
314
|
+
children: props.children ?? /* @__PURE__ */ jsx(ListOrdered, {})
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function RichTextEditorListGroup({ bulletListProps, orderedListProps, items = ["bullet-list", "ordered-list"], variant = "outline", size = "sm", ...props }) {
|
|
318
|
+
const { editor } = useRichTextEditor();
|
|
319
|
+
const visibleItems = new Set(items);
|
|
320
|
+
const activeValue = useEditorState({
|
|
321
|
+
editor,
|
|
322
|
+
selector: ({ editor: currentEditor }) => {
|
|
323
|
+
if (!currentEditor) return "";
|
|
324
|
+
if (currentEditor.isActive("bulletList")) return "bullet-list";
|
|
325
|
+
if (currentEditor.isActive("orderedList")) return "ordered-list";
|
|
326
|
+
return "";
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
return /* @__PURE__ */ jsxs(ToggleGroup, {
|
|
330
|
+
type: "single",
|
|
331
|
+
variant,
|
|
332
|
+
size,
|
|
333
|
+
"aria-label": "List type",
|
|
334
|
+
value: activeValue ?? "",
|
|
335
|
+
...props,
|
|
336
|
+
children: [visibleItems.has("bullet-list") && /* @__PURE__ */ jsx(BulletListButton, { ...bulletListProps }), visibleItems.has("ordered-list") && /* @__PURE__ */ jsx(OrderedListButton, { ...orderedListProps })]
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/toolbar.tsx
|
|
342
|
+
function RichTextEditorToolbar({ className, ...props }) {
|
|
343
|
+
return /* @__PURE__ */ jsx("div", {
|
|
344
|
+
"data-slot": "rich-text-editor-toolbar",
|
|
345
|
+
className: cn("border-border flex flex-wrap items-center gap-2 border-b p-2", className),
|
|
346
|
+
...props
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/built-in.tsx
|
|
352
|
+
/** Blocks Enter / Shift-Enter so the editor stays on a single line. */
|
|
353
|
+
const OneLinerExtension = Extension.create({
|
|
354
|
+
name: "oneLiner",
|
|
355
|
+
addKeyboardShortcuts() {
|
|
356
|
+
return {
|
|
357
|
+
Enter: () => true,
|
|
358
|
+
"Shift-Enter": () => true
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
function RichTextEditorBuiltIn({ className, contentClassName, toolbarClassName, preset = "complete", minHeight, maxHeight, extensions, ...props }) {
|
|
363
|
+
const isOneline = preset === "oneline";
|
|
364
|
+
const resolvedMinHeight = minHeight ?? (isOneline ? 0 : 180);
|
|
365
|
+
return /* @__PURE__ */ jsxs(RichTextEditor, {
|
|
366
|
+
className,
|
|
367
|
+
extensions: useMemo(() => isOneline ? [OneLinerExtension, ...extensions ?? []] : extensions, [isOneline, extensions]),
|
|
368
|
+
...props,
|
|
369
|
+
children: [/* @__PURE__ */ jsxs(RichTextEditorToolbar, {
|
|
370
|
+
className: toolbarClassName,
|
|
371
|
+
children: [
|
|
372
|
+
(preset === "complete" || preset === "simple") && /* @__PURE__ */ jsx(RichTextEditorHistoryGroup, {}),
|
|
373
|
+
preset !== "oneline" && /* @__PURE__ */ jsx(RichTextEditorHeadingGroup, { mode: "select" }),
|
|
374
|
+
/* @__PURE__ */ jsx(RichTextEditorFormattingGroup, {}),
|
|
375
|
+
preset === "complete" && /* @__PURE__ */ jsx(RichTextEditorListGroup, {})
|
|
376
|
+
]
|
|
377
|
+
}), /* @__PURE__ */ jsx(RichTextEditorContent, {
|
|
378
|
+
className: contentClassName,
|
|
379
|
+
minHeight: resolvedMinHeight,
|
|
380
|
+
maxHeight
|
|
381
|
+
})]
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/** @deprecated Use `RichTextEditorBuiltIn` instead. */
|
|
385
|
+
const RichTextEditorSimple = RichTextEditorBuiltIn;
|
|
386
|
+
|
|
387
|
+
//#endregion
|
|
388
|
+
export { RichTextEditor, RichTextEditorBuiltIn, RichTextEditorContent, RichTextEditorContext, RichTextEditorFormattingGroup, RichTextEditorHeadingGroup, RichTextEditorHistoryGroup, RichTextEditorListGroup, RichTextEditorSimple, RichTextEditorToolbar, useRichTextEditor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melv1c/rich-text-editor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A rich text editor component for React, built with TipTap and styled with Tailwind CSS.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
],
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "./dist/index.mjs",
|
|
14
|
-
"types": "./
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"types": "./
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
18
18
|
"import": "./dist/index.mjs",
|
|
19
19
|
"default": "./dist/index.mjs"
|
|
20
20
|
}
|
|
@@ -26,7 +26,12 @@
|
|
|
26
26
|
"build": "tsdown",
|
|
27
27
|
"dev": "tsdown --watch --no-clean"
|
|
28
28
|
},
|
|
29
|
-
"dependencies": {
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@tiptap/core": "^3.20.0",
|
|
31
|
+
"@tiptap/react": "^3.7.2",
|
|
32
|
+
"@tiptap/starter-kit": "^3.7.2",
|
|
33
|
+
"lucide-react": "^0.513.0"
|
|
34
|
+
},
|
|
30
35
|
"devDependencies": {
|
|
31
36
|
"@types/node": "^25.2.3",
|
|
32
37
|
"@types/react": "^19.2.14",
|
|
@@ -37,7 +42,7 @@
|
|
|
37
42
|
"typescript": "~5.9.3"
|
|
38
43
|
},
|
|
39
44
|
"peerDependencies": {
|
|
40
|
-
"@melv1c/ui-core": ">=1.1.
|
|
45
|
+
"@melv1c/ui-core": ">=1.1.2",
|
|
41
46
|
"react": ">=19",
|
|
42
47
|
"react-dom": ">=19",
|
|
43
48
|
"tailwindcss": "^4.1.17",
|