@melv1c/rich-text-editor 0.0.0 → 1.0.1
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 +196 -1
- package/dist/index.mjs +374 -1
- package/package.json +10 -5
package/dist/index.d.mts
CHANGED
|
@@ -1 +1,196 @@
|
|
|
1
|
-
|
|
1
|
+
import * as react from "react";
|
|
2
|
+
import { ComponentProps, HTMLAttributes } from "react";
|
|
3
|
+
import { Button, Toggle } from "@melv1c/ui-core";
|
|
4
|
+
import { Editor, EditorContent, useEditor } from "@tiptap/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/editor.d.ts
|
|
32
|
+
type RichTextEditorProps = HTMLAttributes<HTMLDivElement> & {
|
|
33
|
+
value?: string;
|
|
34
|
+
defaultValue?: string;
|
|
35
|
+
onValueChange?: (value: string, editor: Editor) => void;
|
|
36
|
+
editable?: boolean;
|
|
37
|
+
extensions?: Parameters<typeof useEditor>[0]['extensions'];
|
|
38
|
+
starterKit?: Parameters<typeof StarterKit.configure>[0];
|
|
39
|
+
};
|
|
40
|
+
declare function RichTextEditor({
|
|
41
|
+
children,
|
|
42
|
+
className,
|
|
43
|
+
value,
|
|
44
|
+
defaultValue,
|
|
45
|
+
onValueChange,
|
|
46
|
+
editable,
|
|
47
|
+
extensions,
|
|
48
|
+
starterKit,
|
|
49
|
+
...props
|
|
50
|
+
}: RichTextEditorProps): react_jsx_runtime0.JSX.Element;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/built-in.d.ts
|
|
53
|
+
/**
|
|
54
|
+
* Built-in editor presets:
|
|
55
|
+
* - `"minimal"`: Formatting controls (bold, italic, strike, code) + undo/redo
|
|
56
|
+
* - `"simple"` (default): Heading select + formatting + undo/redo
|
|
57
|
+
* - `"complete"`: Heading toggles + formatting + lists + undo/redo
|
|
58
|
+
* - `"oneline"`: No toolbar, single-line (Enter is blocked)
|
|
59
|
+
*/
|
|
60
|
+
type RichTextEditorPreset = 'minimal' | 'simple' | 'complete' | 'oneline';
|
|
61
|
+
type RichTextEditorBuiltInProps = Omit<RichTextEditorProps, 'children'> & {
|
|
62
|
+
contentClassName?: string;
|
|
63
|
+
toolbarClassName?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Selects a built-in toolbar / behaviour configuration.
|
|
66
|
+
* @default "complete"
|
|
67
|
+
*/
|
|
68
|
+
preset?: RichTextEditorPreset;
|
|
69
|
+
/**
|
|
70
|
+
* Minimum height of the editor content area.
|
|
71
|
+
* Accepts a number (px) or any valid CSS size string.
|
|
72
|
+
* Forwarded to `RichTextEditorContent`.
|
|
73
|
+
*/
|
|
74
|
+
minHeight?: RichTextEditorContentProps['minHeight'];
|
|
75
|
+
/**
|
|
76
|
+
* Maximum height of the editor content area. When set the content area becomes
|
|
77
|
+
* scrollable once the content exceeds this height.
|
|
78
|
+
* Accepts a number (px) or any valid CSS size string.
|
|
79
|
+
* Forwarded to `RichTextEditorContent`.
|
|
80
|
+
*/
|
|
81
|
+
maxHeight?: RichTextEditorContentProps['maxHeight'];
|
|
82
|
+
};
|
|
83
|
+
declare function RichTextEditorBuiltIn({
|
|
84
|
+
className,
|
|
85
|
+
contentClassName,
|
|
86
|
+
toolbarClassName,
|
|
87
|
+
preset,
|
|
88
|
+
minHeight,
|
|
89
|
+
maxHeight,
|
|
90
|
+
extensions,
|
|
91
|
+
...props
|
|
92
|
+
}: RichTextEditorBuiltInProps): react_jsx_runtime0.JSX.Element;
|
|
93
|
+
/** @deprecated Use `RichTextEditorBuiltIn` instead. */
|
|
94
|
+
declare const RichTextEditorSimple: typeof RichTextEditorBuiltIn;
|
|
95
|
+
/** @deprecated Use `RichTextEditorBuiltInProps` instead. */
|
|
96
|
+
type RichTextEditorSimpleProps = RichTextEditorBuiltInProps;
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/context.d.ts
|
|
99
|
+
type RichTextEditorContextValue = {
|
|
100
|
+
editor: Editor | null;
|
|
101
|
+
};
|
|
102
|
+
declare const RichTextEditorContext: react.Context<RichTextEditorContextValue | null>;
|
|
103
|
+
declare function useRichTextEditor(): RichTextEditorContextValue;
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/toolbar/formatting-buttons.d.ts
|
|
106
|
+
type FormattingButtonProps = Omit<ComponentProps<typeof Toggle>, 'pressed' | 'defaultPressed' | 'onPressedChange'>;
|
|
107
|
+
declare function BoldButton({
|
|
108
|
+
children,
|
|
109
|
+
className,
|
|
110
|
+
variant,
|
|
111
|
+
size,
|
|
112
|
+
...props
|
|
113
|
+
}: FormattingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
114
|
+
declare function ItalicButton({
|
|
115
|
+
children,
|
|
116
|
+
className,
|
|
117
|
+
variant,
|
|
118
|
+
size,
|
|
119
|
+
...props
|
|
120
|
+
}: FormattingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
121
|
+
declare function StrikethroughButton({
|
|
122
|
+
children,
|
|
123
|
+
className,
|
|
124
|
+
variant,
|
|
125
|
+
size,
|
|
126
|
+
...props
|
|
127
|
+
}: FormattingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
128
|
+
declare function CodeButton({
|
|
129
|
+
children,
|
|
130
|
+
className,
|
|
131
|
+
variant,
|
|
132
|
+
size,
|
|
133
|
+
...props
|
|
134
|
+
}: FormattingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/toolbar/heading-buttons.d.ts
|
|
137
|
+
type HeadingButtonProps = Omit<ComponentProps<typeof Toggle>, 'pressed' | 'defaultPressed' | 'onPressedChange'>;
|
|
138
|
+
type RichTextEditorHeadingLevel = 1 | 2 | 3;
|
|
139
|
+
declare function Heading1Button(props: HeadingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
140
|
+
declare function Heading2Button(props: HeadingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
141
|
+
declare function Heading3Button(props: HeadingButtonProps): react_jsx_runtime0.JSX.Element;
|
|
142
|
+
type RichTextEditorHeadingSelectProps = {
|
|
143
|
+
levels?: RichTextEditorHeadingLevel[];
|
|
144
|
+
includeParagraphOption?: boolean;
|
|
145
|
+
paragraphLabel?: string;
|
|
146
|
+
selectPlaceholder?: string;
|
|
147
|
+
};
|
|
148
|
+
declare function RichTextEditorHeadingSelect({
|
|
149
|
+
levels,
|
|
150
|
+
includeParagraphOption,
|
|
151
|
+
paragraphLabel,
|
|
152
|
+
selectPlaceholder
|
|
153
|
+
}: RichTextEditorHeadingSelectProps): react_jsx_runtime0.JSX.Element;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/toolbar/history-buttons.d.ts
|
|
156
|
+
type HistoryButtonProps = ComponentProps<typeof Button>;
|
|
157
|
+
declare function UndoButton({
|
|
158
|
+
children,
|
|
159
|
+
className,
|
|
160
|
+
variant,
|
|
161
|
+
size,
|
|
162
|
+
...props
|
|
163
|
+
}: HistoryButtonProps): react_jsx_runtime0.JSX.Element;
|
|
164
|
+
declare function RedoButton({
|
|
165
|
+
children,
|
|
166
|
+
className,
|
|
167
|
+
variant,
|
|
168
|
+
size,
|
|
169
|
+
...props
|
|
170
|
+
}: HistoryButtonProps): react_jsx_runtime0.JSX.Element;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/toolbar/list-buttons.d.ts
|
|
173
|
+
type ListButtonProps = Omit<ComponentProps<typeof Toggle>, 'pressed' | 'defaultPressed' | 'onPressedChange'>;
|
|
174
|
+
declare function BulletListButton({
|
|
175
|
+
children,
|
|
176
|
+
className,
|
|
177
|
+
variant,
|
|
178
|
+
size,
|
|
179
|
+
...props
|
|
180
|
+
}: ListButtonProps): react_jsx_runtime0.JSX.Element;
|
|
181
|
+
declare function OrderedListButton({
|
|
182
|
+
children,
|
|
183
|
+
className,
|
|
184
|
+
variant,
|
|
185
|
+
size,
|
|
186
|
+
...props
|
|
187
|
+
}: ListButtonProps): react_jsx_runtime0.JSX.Element;
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/toolbar/toolbar.d.ts
|
|
190
|
+
type RichTextEditorToolbarProps = HTMLAttributes<HTMLDivElement>;
|
|
191
|
+
declare function RichTextEditorToolbar({
|
|
192
|
+
className,
|
|
193
|
+
...props
|
|
194
|
+
}: RichTextEditorToolbarProps): react_jsx_runtime0.JSX.Element;
|
|
195
|
+
//#endregion
|
|
196
|
+
export { BoldButton, BulletListButton, CodeButton, FormattingButtonProps, Heading1Button, Heading2Button, Heading3Button, HeadingButtonProps, HistoryButtonProps, ItalicButton, ListButtonProps, OrderedListButton, RedoButton, RichTextEditor, RichTextEditorBuiltIn, RichTextEditorBuiltInProps, RichTextEditorContent, RichTextEditorContentProps, RichTextEditorContext, RichTextEditorContextValue, RichTextEditorHeadingLevel, RichTextEditorHeadingSelect, RichTextEditorHeadingSelectProps, RichTextEditorPreset, RichTextEditorProps, RichTextEditorSimple, RichTextEditorSimpleProps, RichTextEditorToolbar, RichTextEditorToolbarProps, StrikethroughButton, UndoButton, useRichTextEditor };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1,374 @@
|
|
|
1
|
-
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { createContext, useContext, useEffect, useMemo } from "react";
|
|
3
|
+
import { Button, ButtonGroup, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Toggle, cn } from "@melv1c/ui-core";
|
|
4
|
+
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
7
|
+
import { Bold, Code, Heading1, Heading2, Heading3, Italic, List, ListOrdered, Redo2, Strikethrough, Undo2 } from "lucide-react";
|
|
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/toolbar/formatting-buttons.tsx
|
|
73
|
+
function BoldButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
74
|
+
const { editor } = useRichTextEditor();
|
|
75
|
+
const isActive = useEditorState({
|
|
76
|
+
editor,
|
|
77
|
+
selector: ({ editor: e }) => e?.isActive("bold") ?? false
|
|
78
|
+
});
|
|
79
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
80
|
+
"aria-label": "Bold",
|
|
81
|
+
pressed: !!isActive,
|
|
82
|
+
disabled: editor ? !editor.can().chain().focus().toggleBold().run() : true,
|
|
83
|
+
onPressedChange: () => editor?.chain().focus().toggleBold().run(),
|
|
84
|
+
variant,
|
|
85
|
+
size,
|
|
86
|
+
className: cn("shadow-none", className),
|
|
87
|
+
...props,
|
|
88
|
+
children: children ?? /* @__PURE__ */ jsx(Bold, {})
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function ItalicButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
92
|
+
const { editor } = useRichTextEditor();
|
|
93
|
+
const isActive = useEditorState({
|
|
94
|
+
editor,
|
|
95
|
+
selector: ({ editor: e }) => e?.isActive("italic") ?? false
|
|
96
|
+
});
|
|
97
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
98
|
+
"aria-label": "Italic",
|
|
99
|
+
pressed: !!isActive,
|
|
100
|
+
disabled: editor ? !editor.can().chain().focus().toggleItalic().run() : true,
|
|
101
|
+
onPressedChange: () => editor?.chain().focus().toggleItalic().run(),
|
|
102
|
+
variant,
|
|
103
|
+
size,
|
|
104
|
+
className: cn("shadow-none", className),
|
|
105
|
+
...props,
|
|
106
|
+
children: children ?? /* @__PURE__ */ jsx(Italic, {})
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function StrikethroughButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
110
|
+
const { editor } = useRichTextEditor();
|
|
111
|
+
const isActive = useEditorState({
|
|
112
|
+
editor,
|
|
113
|
+
selector: ({ editor: e }) => e?.isActive("strike") ?? false
|
|
114
|
+
});
|
|
115
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
116
|
+
"aria-label": "Strikethrough",
|
|
117
|
+
pressed: !!isActive,
|
|
118
|
+
disabled: editor ? !editor.can().chain().focus().toggleStrike().run() : true,
|
|
119
|
+
onPressedChange: () => editor?.chain().focus().toggleStrike().run(),
|
|
120
|
+
variant,
|
|
121
|
+
size,
|
|
122
|
+
className: cn("shadow-none", className),
|
|
123
|
+
...props,
|
|
124
|
+
children: children ?? /* @__PURE__ */ jsx(Strikethrough, {})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function CodeButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
128
|
+
const { editor } = useRichTextEditor();
|
|
129
|
+
const isActive = useEditorState({
|
|
130
|
+
editor,
|
|
131
|
+
selector: ({ editor: e }) => e?.isActive("code") ?? false
|
|
132
|
+
});
|
|
133
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
134
|
+
"aria-label": "Inline code",
|
|
135
|
+
pressed: !!isActive,
|
|
136
|
+
disabled: editor ? !editor.can().chain().focus().toggleCode().run() : true,
|
|
137
|
+
onPressedChange: () => editor?.chain().focus().toggleCode().run(),
|
|
138
|
+
variant,
|
|
139
|
+
size,
|
|
140
|
+
className: cn("shadow-none", className),
|
|
141
|
+
...props,
|
|
142
|
+
children: children ?? /* @__PURE__ */ jsx(Code, {})
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/toolbar/heading-buttons.tsx
|
|
148
|
+
const ALL_HEADING_LEVELS = [
|
|
149
|
+
1,
|
|
150
|
+
2,
|
|
151
|
+
3
|
|
152
|
+
];
|
|
153
|
+
const HEADING_ICONS = {
|
|
154
|
+
1: Heading1,
|
|
155
|
+
2: Heading2,
|
|
156
|
+
3: Heading3
|
|
157
|
+
};
|
|
158
|
+
const HEADING_LABELS = {
|
|
159
|
+
1: "Heading 1",
|
|
160
|
+
2: "Heading 2",
|
|
161
|
+
3: "Heading 3"
|
|
162
|
+
};
|
|
163
|
+
function HeadingButton({ level, children, className, variant = "outline", size = "sm", ...props }) {
|
|
164
|
+
const { editor } = useRichTextEditor();
|
|
165
|
+
const Icon = HEADING_ICONS[level];
|
|
166
|
+
const isActive = useEditorState({
|
|
167
|
+
editor,
|
|
168
|
+
selector: ({ editor: e }) => e?.isActive("heading", { level }) ?? false
|
|
169
|
+
});
|
|
170
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
171
|
+
"aria-label": `Heading ${level}`,
|
|
172
|
+
pressed: !!isActive,
|
|
173
|
+
disabled: editor ? !editor.can().chain().focus().toggleHeading({ level }).run() : true,
|
|
174
|
+
onPressedChange: () => editor?.chain().focus().toggleHeading({ level }).run(),
|
|
175
|
+
variant,
|
|
176
|
+
size,
|
|
177
|
+
className: cn("shadow-none", className),
|
|
178
|
+
...props,
|
|
179
|
+
children: children ?? /* @__PURE__ */ jsx(Icon, {})
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function Heading1Button(props) {
|
|
183
|
+
return /* @__PURE__ */ jsx(HeadingButton, {
|
|
184
|
+
level: 1,
|
|
185
|
+
...props
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function Heading2Button(props) {
|
|
189
|
+
return /* @__PURE__ */ jsx(HeadingButton, {
|
|
190
|
+
level: 2,
|
|
191
|
+
...props
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function Heading3Button(props) {
|
|
195
|
+
return /* @__PURE__ */ jsx(HeadingButton, {
|
|
196
|
+
level: 3,
|
|
197
|
+
...props
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function RichTextEditorHeadingSelect({ levels, includeParagraphOption = true, paragraphLabel = "Paragraph", selectPlaceholder = "Select heading" }) {
|
|
201
|
+
const { editor } = useRichTextEditor();
|
|
202
|
+
const resolvedLevels = levels ?? ALL_HEADING_LEVELS;
|
|
203
|
+
const visibleLevels = new Set(resolvedLevels);
|
|
204
|
+
const activeValue = useEditorState({
|
|
205
|
+
editor,
|
|
206
|
+
selector: ({ editor: e }) => {
|
|
207
|
+
if (!e) return "";
|
|
208
|
+
for (const level of ALL_HEADING_LEVELS) if (e.isActive("heading", { level })) return `heading-${level}`;
|
|
209
|
+
return "";
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
const paragraphItem = includeParagraphOption ? {
|
|
213
|
+
value: "paragraph",
|
|
214
|
+
label: paragraphLabel,
|
|
215
|
+
disabled: editor ? !editor.can().chain().focus().setParagraph().run() : true
|
|
216
|
+
} : null;
|
|
217
|
+
const headingItems = ALL_HEADING_LEVELS.filter((level) => visibleLevels.has(level)).map((level) => ({
|
|
218
|
+
value: `heading-${level}`,
|
|
219
|
+
label: HEADING_LABELS[level],
|
|
220
|
+
disabled: editor ? !editor.can().chain().focus().setHeading({ level }).run() : true
|
|
221
|
+
}));
|
|
222
|
+
const items = [...paragraphItem ? [paragraphItem] : [], ...headingItems];
|
|
223
|
+
return /* @__PURE__ */ jsxs(Select, {
|
|
224
|
+
value: activeValue || (includeParagraphOption ? "paragraph" : void 0),
|
|
225
|
+
onValueChange: (value) => {
|
|
226
|
+
if (!editor) return;
|
|
227
|
+
if (value === "paragraph") {
|
|
228
|
+
editor.chain().focus().setParagraph().run();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const level = Number(value.replace("heading-", ""));
|
|
232
|
+
editor.chain().focus().setHeading({ level }).run();
|
|
233
|
+
},
|
|
234
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
235
|
+
"aria-label": "Heading levels",
|
|
236
|
+
size: "sm",
|
|
237
|
+
disabled: !editor,
|
|
238
|
+
className: "shadow-none",
|
|
239
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: selectPlaceholder })
|
|
240
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: items.map((item) => /* @__PURE__ */ jsx(SelectItem, {
|
|
241
|
+
value: item.value,
|
|
242
|
+
disabled: item.disabled,
|
|
243
|
+
children: item.label
|
|
244
|
+
}, item.value)) })]
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/toolbar/history-buttons.tsx
|
|
250
|
+
function UndoButton({ children, className, variant = "outline", size = "icon-sm", ...props }) {
|
|
251
|
+
const { editor } = useRichTextEditor();
|
|
252
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
253
|
+
"aria-label": "Undo",
|
|
254
|
+
variant,
|
|
255
|
+
size,
|
|
256
|
+
disabled: editor ? !editor.can().chain().focus().undo().run() : true,
|
|
257
|
+
onClick: () => editor?.chain().focus().undo().run(),
|
|
258
|
+
className: cn("shadow-none", className),
|
|
259
|
+
...props,
|
|
260
|
+
children: children ?? /* @__PURE__ */ jsx(Undo2, {})
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function RedoButton({ children, className, variant = "outline", size = "icon-sm", ...props }) {
|
|
264
|
+
const { editor } = useRichTextEditor();
|
|
265
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
266
|
+
"aria-label": "Redo",
|
|
267
|
+
variant,
|
|
268
|
+
size,
|
|
269
|
+
disabled: editor ? !editor.can().chain().focus().redo().run() : true,
|
|
270
|
+
onClick: () => editor?.chain().focus().redo().run(),
|
|
271
|
+
className: cn("shadow-none", className),
|
|
272
|
+
...props,
|
|
273
|
+
children: children ?? /* @__PURE__ */ jsx(Redo2, {})
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/toolbar/list-buttons.tsx
|
|
279
|
+
function BulletListButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
280
|
+
const { editor } = useRichTextEditor();
|
|
281
|
+
const isActive = useEditorState({
|
|
282
|
+
editor,
|
|
283
|
+
selector: ({ editor: e }) => e?.isActive("bulletList") ?? false
|
|
284
|
+
});
|
|
285
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
286
|
+
"aria-label": "Bullet list",
|
|
287
|
+
pressed: !!isActive,
|
|
288
|
+
disabled: editor ? !editor.can().chain().focus().toggleBulletList().run() : true,
|
|
289
|
+
onPressedChange: () => editor?.chain().focus().toggleBulletList().run(),
|
|
290
|
+
variant,
|
|
291
|
+
size,
|
|
292
|
+
className: cn("shadow-none", className),
|
|
293
|
+
...props,
|
|
294
|
+
children: children ?? /* @__PURE__ */ jsx(List, {})
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function OrderedListButton({ children, className, variant = "outline", size = "sm", ...props }) {
|
|
298
|
+
const { editor } = useRichTextEditor();
|
|
299
|
+
const isActive = useEditorState({
|
|
300
|
+
editor,
|
|
301
|
+
selector: ({ editor: e }) => e?.isActive("orderedList") ?? false
|
|
302
|
+
});
|
|
303
|
+
return /* @__PURE__ */ jsx(Toggle, {
|
|
304
|
+
"aria-label": "Ordered list",
|
|
305
|
+
pressed: !!isActive,
|
|
306
|
+
disabled: editor ? !editor.can().chain().focus().toggleOrderedList().run() : true,
|
|
307
|
+
onPressedChange: () => editor?.chain().focus().toggleOrderedList().run(),
|
|
308
|
+
variant,
|
|
309
|
+
size,
|
|
310
|
+
className: cn("shadow-none", className),
|
|
311
|
+
...props,
|
|
312
|
+
children: children ?? /* @__PURE__ */ jsx(ListOrdered, {})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/toolbar/toolbar.tsx
|
|
318
|
+
function RichTextEditorToolbar({ className, ...props }) {
|
|
319
|
+
return /* @__PURE__ */ jsx("div", {
|
|
320
|
+
"data-slot": "rich-text-editor-toolbar",
|
|
321
|
+
className: cn("border-border flex flex-wrap items-center gap-2 border-b p-2", className),
|
|
322
|
+
...props
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/built-in.tsx
|
|
328
|
+
/** Blocks Enter / Shift-Enter so the editor stays on a single line. */
|
|
329
|
+
const OneLinerExtension = Extension.create({
|
|
330
|
+
name: "oneLiner",
|
|
331
|
+
addKeyboardShortcuts() {
|
|
332
|
+
return {
|
|
333
|
+
Enter: () => true,
|
|
334
|
+
"Shift-Enter": () => true
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
function RichTextEditorBuiltIn({ className, contentClassName, toolbarClassName, preset = "complete", minHeight, maxHeight, extensions, ...props }) {
|
|
339
|
+
const isOneline = preset === "oneline";
|
|
340
|
+
const resolvedMinHeight = minHeight ?? (isOneline ? 0 : 180);
|
|
341
|
+
return /* @__PURE__ */ jsxs(RichTextEditor, {
|
|
342
|
+
className,
|
|
343
|
+
extensions: useMemo(() => isOneline ? [OneLinerExtension, ...extensions ?? []] : extensions, [isOneline, extensions]),
|
|
344
|
+
...props,
|
|
345
|
+
children: [/* @__PURE__ */ jsxs(RichTextEditorToolbar, {
|
|
346
|
+
className: toolbarClassName,
|
|
347
|
+
children: [
|
|
348
|
+
(preset === "complete" || preset === "simple") && /* @__PURE__ */ jsxs(ButtonGroup, { children: [/* @__PURE__ */ jsx(UndoButton, {}), /* @__PURE__ */ jsx(RedoButton, {})] }),
|
|
349
|
+
preset === "simple" && /* @__PURE__ */ jsx(RichTextEditorHeadingSelect, {}),
|
|
350
|
+
preset === "complete" && /* @__PURE__ */ jsxs(ButtonGroup, { children: [
|
|
351
|
+
/* @__PURE__ */ jsx(Heading1Button, {}),
|
|
352
|
+
/* @__PURE__ */ jsx(Heading2Button, {}),
|
|
353
|
+
/* @__PURE__ */ jsx(Heading3Button, {})
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ jsxs(ButtonGroup, { children: [
|
|
356
|
+
/* @__PURE__ */ jsx(BoldButton, {}),
|
|
357
|
+
/* @__PURE__ */ jsx(ItalicButton, {}),
|
|
358
|
+
/* @__PURE__ */ jsx(StrikethroughButton, {}),
|
|
359
|
+
/* @__PURE__ */ jsx(CodeButton, {})
|
|
360
|
+
] }),
|
|
361
|
+
preset === "complete" && /* @__PURE__ */ jsxs(ButtonGroup, { children: [/* @__PURE__ */ jsx(BulletListButton, {}), /* @__PURE__ */ jsx(OrderedListButton, {})] })
|
|
362
|
+
]
|
|
363
|
+
}), /* @__PURE__ */ jsx(RichTextEditorContent, {
|
|
364
|
+
className: contentClassName,
|
|
365
|
+
minHeight: resolvedMinHeight,
|
|
366
|
+
maxHeight
|
|
367
|
+
})]
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/** @deprecated Use `RichTextEditorBuiltIn` instead. */
|
|
371
|
+
const RichTextEditorSimple = RichTextEditorBuiltIn;
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
export { BoldButton, BulletListButton, CodeButton, Heading1Button, Heading2Button, Heading3Button, ItalicButton, OrderedListButton, RedoButton, RichTextEditor, RichTextEditorBuiltIn, RichTextEditorContent, RichTextEditorContext, RichTextEditorHeadingSelect, RichTextEditorSimple, RichTextEditorToolbar, StrikethroughButton, UndoButton, useRichTextEditor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melv1c/rich-text-editor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|