customise-text-editor 1.0.2 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kanhaiya-dct
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,108 +1,164 @@
1
- # Customise Text Editor 🚀
2
-
3
- A highly customizable, production-ready Rich Text Editor for React, powered by Tiptap and Lucide Icons. Designed for developers who need full control over their editor's look, feel, and features without the bloat.
4
-
5
- ## 💎 Features
6
-
7
- - **Rich Text Formatting**:
8
- - `Bold`, `Italic`, `Underline`, `Strikethrough` - Essential text styling.
9
- - `Code`, `Code Block` - Inline and multi-line code support with syntax highlighting.
10
- - `Headings` (H1, H2, H3) - Structured hierarchy for your content.
11
- - **Advanced Tools**:
12
- - `Links` - Easy URL management and persistent hyperlinks.
13
- - `Text Color` & `Highlight` - Full spectrum color pickers for text and backgrounds.
14
- - `Blockquote` - Stylish quote blocks.
15
- - `Horizontal Rule` - Clean content separators.
16
- - **Layout & UX**:
17
- - **Toolbar Placement**: Choose between `top-bar` or `bottom-bar` layout.
18
- - **Floating Menu**: Optional bubble menu for quick styling on selection.
19
- - **Responsive Design**: Mobile-friendly toolbar with smooth horizontal scrolling.
20
- - **Customization**:
21
- - **Dynamic Theming**: Control primary colors, border styles, and border radius via a simple `theme` object.
22
- - **Feature Toggling**: Choose exactly which tools to show using the `features` array.
23
- - **Placeholders**: Custom placeholder text for empty states.
24
- - **Pro Features**:
25
- - **Character Count**: Built-in tracking with optional character limits.
26
- - **Undo/Redo**: Full history management.
27
- - **Type Safety**: Built with TypeScript for 100% type coverage.
28
-
29
- ## 🚀 Installation
1
+ <div align="center">
2
+
3
+ # 💎 Customise-Text-Editor
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/customise-text-editor?style=for-the-badge&logo=npm&color=2e2e2e&logoColor=CB3837)](https://www.npmjs.com/package/customise-text-editor)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-2e2e2e?style=for-the-badge&logo=github&color=2e2e2e)](https://opensource.org/licenses/MIT)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/min/customise-text-editor?style=for-the-badge&color=2e2e2e)](https://bundlephobia.com/package/customise-text-editor)
8
+
9
+ **A highly customizable, production-ready Rich Text Editor for React, built on the solid foundation of Tiptap.**
10
+
11
+ ---
12
+ </div>
13
+
14
+ ## Features at a Glance
15
+
16
+ | Feature | Description |
17
+ | :--- | :--- |
18
+ | 🎨 **Premium UI** | Modern, sleek design with smooth transitions and glassmorphism support. |
19
+ | 🔢 **Real-time Stats** | Automatic footer with character counting when `maxLength` is enabled. |
20
+ | 📝 **Dual Output** | Seamlessly switch between `HTML` and clean `Plain Text` exports. |
21
+ | 🔗 **Hooks-First** | First-class support for `react-hook-form` via standard `Controller` APIs. |
22
+ | 💅 **Total Control** | Custom width, alignment, margins, and branding-specific themes. |
23
+ | **Ultra-Light** | Meticulously optimized bundle (only **~11KB**) with zero bloated dependencies. |
24
+
25
+ ---
26
+
27
+ ## 📦 Installation
28
+
29
+ Get started in seconds:
30
30
 
31
31
  ```bash
32
32
  npm install customise-text-editor
33
- # or
34
- yarn add customise-text-editor
35
33
  ```
36
34
 
37
- ## 💻 Usage
35
+ > [!IMPORTANT]
36
+ > Since this is a library, ensure you import the CSS in your main entry file (e.g., `App.tsx` or `main.tsx`) to enable the premium aesthetics.
37
+
38
+ ```tsx
39
+ import 'customise-text-editor/style.css';
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 🚀 Quick Start
45
+
46
+ Creating a powerful editor has never been easier:
47
+
48
+ ```tsx
49
+ import { CustomEditor } from 'customise-text-editor';
50
+ import 'customise-text-editor/style.css';
51
+
52
+ function App() {
53
+ const handleSave = (content) => {
54
+ console.log('Editor Content:', content);
55
+ };
56
+
57
+ return (
58
+ <div className="container">
59
+ <CustomEditor
60
+ config={{
61
+ width: '800px',
62
+ align: 'center',
63
+ placeholder: 'Start writing your story...',
64
+ maxLength: 1000,
65
+ outputFormat: 'html',
66
+ onChange: handleSave,
67
+ theme: {
68
+ primaryColor: '#6366f1',
69
+ borderRadius: '12px'
70
+ }
71
+ }}
72
+ />
73
+ </div>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 🏗️ Advanced Integration: React Hook Form
81
+
82
+ We built this editor with form libraries in mind. It integrates perfectly with `Controller` and validation rules.
38
83
 
39
84
  ```tsx
85
+ import { useForm, Controller } from 'react-hook-form';
40
86
  import { CustomEditor } from 'customise-text-editor';
41
- import 'customise-text-editor/style.css'; // Don't forget to import styles!
42
87
 
43
- function MyEditor() {
88
+ const BlogEditor = () => {
89
+ const { control, handleSubmit, formState: { errors } } = useForm({
90
+ defaultValues: { postContent: '' }
91
+ });
92
+
93
+ const onSubmit = (data) => console.log('Payload:', data);
94
+
44
95
  return (
45
- <CustomEditor
46
- config={{
47
- theme: {
48
- primaryColor: '#3b82f6',
49
- backgroundColor: '#ffffff',
50
- textColor: '#1e293b',
51
- borderRadius: '12px'
52
- },
53
- layout: 'top-bar',
54
- placeholder: 'Start typing...',
55
- features: ['bold', 'italic', 'underline', 'link', 'highlight', 'undo', 'redo'],
56
- onChange: (content) => console.log(content)
57
- }}
58
- />
96
+ <form onSubmit={handleSubmit(onSubmit)}>
97
+ <Controller
98
+ name="postContent"
99
+ control={control}
100
+ rules={{
101
+ required: 'Please write something before publishing',
102
+ minLength: { value: 50, message: 'Your post is too short (min 50 chars)' }
103
+ }}
104
+ render={({ field }) => (
105
+ <CustomEditor
106
+ config={{
107
+ initialContent: field.value,
108
+ onChange: (val) => field.onChange(val),
109
+ maxLength: 5000,
110
+ placeholder: 'Write your masterpiece...'
111
+ }}
112
+ />
113
+ )}
114
+ />
115
+ {errors.postContent && <p className="error">{errors.postContent.message}</p>}
116
+ <button type="submit">Publish Masterpiece</button>
117
+ </form>
59
118
  );
119
+ };
120
+ ```
121
+
122
+ ---
123
+
124
+ ## 📑 Detailed API Configuration
125
+
126
+ ### `EditorConfig` Props
127
+
128
+ | Prop | Type | Default | Description |
129
+ | :--- | :--- | :--- | :--- |
130
+ | `width` | `string \| number` | `'100%'` | Width of the editor (e.g., `'800px'`, `'100%'`). |
131
+ | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Alignment of the editor container. |
132
+ | `outputFormat` | `'html' \| 'text'` | `'html'` | Content format sent to the `onChange` callback. |
133
+ | `maxLength` | `number` | - | Character limit. Enables the live-counter footer auto-magically. |
134
+ | `placeholder` | `string` | `'Write here...'` | Placeholder text when the editor is empty. |
135
+ | `theme` | `ThemeConfig` | - | Custom theme object for colors and styling (see below). |
136
+ | `features` | `string[]` | All | Array of enabled toolbar features (bold, italic, etc.). |
137
+
138
+ ### `ThemeConfig` Options
139
+
140
+ ```typescript
141
+ {
142
+ primaryColor: string; // Accent color for icons & progress
143
+ borderRadius: string; // Control the "roundness" (e.g., '16px')
144
+ backgroundColor: string; // Background of the editor area
145
+ textColor: string; // Color of the content text
60
146
  }
61
147
  ```
62
148
 
63
- ## 🛠️ Configuration (Props)
64
-
65
- The `CustomEditor` component accepts a `config` object with the following properties:
66
-
67
- ### Main Config
68
- | Property | Type | Description |
69
- | :--- | :--- | :--- |
70
- | `layout` | `'top-bar' \| 'bottom-bar'` | Position of the toolbar. |
71
- | `features` | `string[]` | Array of tools to display (e.g., `['bold', 'italic', 'link']`). |
72
- | `theme` | `ThemeConfig` | Custom theme object (see section below). |
73
- | `title` | `string` | Optional editor title. |
74
- | `showTitle` | `boolean` | Toggle title visibility. |
75
- | `placeholder` | `string` | Custom placeholder text. |
76
- | `width` | `string \| number` | Number or string (e.g., `800` or `'100%'`). |
77
- | `maxLength` | `number` | Maximum character limit. |
78
- | `initialContent` | `string` | Initial HTML content for the editor. |
79
- | `onChange` | `(content: string) => void` | Callback when content changes (returns HTML). |
80
- | `onCharacterCountChange` | `(count: number) => void` | Callback when character count changes. |
81
-
82
- ### 🎨 Theme Configuration
83
- | Property | Description | Default |
84
- | :--- | :--- | :--- |
85
- | `primaryColor` | Brand color for active states & borders | `#3b82f6` |
86
- | `secondaryColor` | Secondary brand color | - |
87
- | `backgroundColor` | Editor and toolbar background | `#ffffff` |
88
- | `textColor` | Main text color | `#0f172a` |
89
- | `accentColor` | Background for hovered buttons | `#f8fafc` |
90
- | `borderColor` | Border color for the container | `#e2e8f0` |
91
- | `borderRadius` | Main corner radius | `0.75rem` |
92
- | `fontFamily` | Custom font family | `'Inter', sans-serif` |
93
-
94
- ## 🛠️ Troubleshooting
95
-
96
- ### "Invalid hook call" or "Double React"
97
- If you encounter an "Invalid hook call" error while using `npm link`, it's likely because React is being loaded twice (once from your project and once from the library's `node_modules`).
98
-
99
- **Fix:** Update your project's `vite.config.ts` to alias React to your local version:
149
+ ---
150
+
151
+ ## 🛠️ Troubleshooting: Vite Configuration
152
+
153
+ If you encounter issues like **"Outdated Optimize Dep"** or React version mismatches while using this package, you must add the following aliases to your **consumer project's** `vite.config.ts`. This ensures that both your project and the editor use the same instance of React.
100
154
 
101
155
  ```typescript
102
- // vite.config.ts
156
+ import { defineConfig } from 'vite';
157
+ import react from '@vitejs/plugin-react';
103
158
  import path from 'path';
104
159
 
105
160
  export default defineConfig({
161
+ plugins: [react()],
106
162
  resolve: {
107
163
  alias: {
108
164
  'react': path.resolve(__dirname, 'node_modules/react'),
@@ -112,3 +168,14 @@ export default defineConfig({
112
168
  });
113
169
  ```
114
170
 
171
+ ---
172
+
173
+ ## 📄 License
174
+ Proudly open-source under the **MIT License**. Created by [kanhaiya-dct](https://github.com/kanhaiya-dct).
175
+
176
+ ---
177
+
178
+ <div align="center">
179
+ <h3>Show your support! ⭐</h3>
180
+ <p>If you find this editor useful, please consider giving it a star on GitHub!</p>
181
+ </div>
@@ -8,91 +8,92 @@ import { TextStyle as s } from "@tiptap/extension-text-style";
8
8
  import c from "@tiptap/extension-color";
9
9
  import l from "@tiptap/extension-placeholder";
10
10
  import u from "@tiptap/extension-font-family";
11
- import { Bold as d, Code as f, Heading1 as p, Heading2 as m, Heading3 as h, Highlighter as g, Italic as _, Link as v, List as y, ListOrdered as b, Minus as x, Palette as S, Quote as C, Redo as w, Strikethrough as T, Terminal as E, Underline as D, Undo as O } from "lucide-react";
12
- import { jsx as k, jsxs as A } from "react/jsx-runtime";
11
+ import d from "@tiptap/extension-character-count";
12
+ import { Bold as f, Code as p, Heading1 as m, Heading2 as h, Heading3 as g, Highlighter as _, Italic as v, Link as y, List as b, ListOrdered as x, Minus as S, Palette as C, Quote as w, Redo as T, Strikethrough as E, Terminal as D, Underline as O, Undo as k } from "lucide-react";
13
+ import { jsx as A, jsxs as j } from "react/jsx-runtime";
13
14
  //#region src/lib/constants/toolbarConfig.tsx
14
- var j = (e) => [
15
+ var M = (e) => [
15
16
  {
16
17
  name: "bold",
17
- icon: /* @__PURE__ */ k(d, { size: 18 }),
18
+ icon: /* @__PURE__ */ A(f, { size: 18 }),
18
19
  action: (e) => e.chain().focus().toggleBold().run(),
19
20
  isActive: (e) => e.isActive("bold")
20
21
  },
21
22
  {
22
23
  name: "italic",
23
- icon: /* @__PURE__ */ k(_, { size: 18 }),
24
+ icon: /* @__PURE__ */ A(v, { size: 18 }),
24
25
  action: (e) => e.chain().focus().toggleItalic().run(),
25
26
  isActive: (e) => e.isActive("italic")
26
27
  },
27
28
  {
28
29
  name: "underline",
29
- icon: /* @__PURE__ */ k(D, { size: 18 }),
30
+ icon: /* @__PURE__ */ A(O, { size: 18 }),
30
31
  action: (e) => e.chain().focus().toggleUnderline().run(),
31
32
  isActive: (e) => e.isActive("underline")
32
33
  },
33
34
  {
34
35
  name: "strike",
35
- icon: /* @__PURE__ */ k(T, { size: 18 }),
36
+ icon: /* @__PURE__ */ A(E, { size: 18 }),
36
37
  action: (e) => e.chain().focus().toggleStrike().run(),
37
38
  isActive: (e) => e.isActive("strike")
38
39
  },
39
40
  {
40
41
  name: "heading1",
41
- icon: /* @__PURE__ */ k(p, { size: 18 }),
42
+ icon: /* @__PURE__ */ A(m, { size: 18 }),
42
43
  action: (e) => e.chain().focus().toggleHeading({ level: 1 }).run(),
43
44
  isActive: (e) => e.isActive("heading", { level: 1 })
44
45
  },
45
46
  {
46
47
  name: "heading2",
47
- icon: /* @__PURE__ */ k(m, { size: 18 }),
48
+ icon: /* @__PURE__ */ A(h, { size: 18 }),
48
49
  action: (e) => e.chain().focus().toggleHeading({ level: 2 }).run(),
49
50
  isActive: (e) => e.isActive("heading", { level: 2 })
50
51
  },
51
52
  {
52
53
  name: "heading3",
53
- icon: /* @__PURE__ */ k(h, { size: 18 }),
54
+ icon: /* @__PURE__ */ A(g, { size: 18 }),
54
55
  action: (e) => e.chain().focus().toggleHeading({ level: 3 }).run(),
55
56
  isActive: (e) => e.isActive("heading", { level: 3 })
56
57
  },
57
58
  {
58
59
  name: "bulletList",
59
- icon: /* @__PURE__ */ k(y, { size: 18 }),
60
+ icon: /* @__PURE__ */ A(b, { size: 18 }),
60
61
  action: (e) => e.chain().focus().toggleBulletList().run(),
61
62
  isActive: (e) => e.isActive("bulletList")
62
63
  },
63
64
  {
64
65
  name: "orderedList",
65
- icon: /* @__PURE__ */ k(b, { size: 18 }),
66
+ icon: /* @__PURE__ */ A(x, { size: 18 }),
66
67
  action: (e) => e.chain().focus().toggleOrderedList().run(),
67
68
  isActive: (e) => e.isActive("orderedList")
68
69
  },
69
70
  {
70
71
  name: "blockquote",
71
- icon: /* @__PURE__ */ k(C, { size: 18 }),
72
+ icon: /* @__PURE__ */ A(w, { size: 18 }),
72
73
  action: (e) => e.chain().focus().toggleBlockquote().run(),
73
74
  isActive: (e) => e.isActive("blockquote")
74
75
  },
75
76
  {
76
77
  name: "code",
77
- icon: /* @__PURE__ */ k(f, { size: 18 }),
78
+ icon: /* @__PURE__ */ A(p, { size: 18 }),
78
79
  action: (e) => e.chain().focus().toggleCode().run(),
79
80
  isActive: (e) => e.isActive("code")
80
81
  },
81
82
  {
82
83
  name: "codeBlock",
83
- icon: /* @__PURE__ */ k(E, { size: 18 }),
84
+ icon: /* @__PURE__ */ A(D, { size: 18 }),
84
85
  action: (e) => e.chain().focus().toggleCodeBlock().run(),
85
86
  isActive: (e) => e.isActive("codeBlock")
86
87
  },
87
88
  {
88
89
  name: "color",
89
- icon: /* @__PURE__ */ A("div", {
90
+ icon: /* @__PURE__ */ j("div", {
90
91
  style: {
91
92
  position: "relative",
92
93
  display: "flex",
93
94
  alignItems: "center"
94
95
  },
95
- children: [/* @__PURE__ */ k(S, { size: 18 }), /* @__PURE__ */ k("input", {
96
+ children: [/* @__PURE__ */ A(C, { size: 18 }), /* @__PURE__ */ A("input", {
96
97
  type: "color",
97
98
  onInput: (t) => {
98
99
  let n = t.target.value;
@@ -113,13 +114,13 @@ var j = (e) => [
113
114
  },
114
115
  {
115
116
  name: "highlight",
116
- icon: /* @__PURE__ */ A("div", {
117
+ icon: /* @__PURE__ */ j("div", {
117
118
  style: {
118
119
  position: "relative",
119
120
  display: "flex",
120
121
  alignItems: "center"
121
122
  },
122
- children: [/* @__PURE__ */ k(g, { size: 18 }), /* @__PURE__ */ k("input", {
123
+ children: [/* @__PURE__ */ A(_, { size: 18 }), /* @__PURE__ */ A("input", {
123
124
  type: "color",
124
125
  onInput: (t) => {
125
126
  let n = t.target.value;
@@ -140,7 +141,7 @@ var j = (e) => [
140
141
  },
141
142
  {
142
143
  name: "link",
143
- icon: /* @__PURE__ */ k(v, { size: 18 }),
144
+ icon: /* @__PURE__ */ A(y, { size: 18 }),
144
145
  action: (e) => {
145
146
  if (e.isActive("link")) {
146
147
  e.chain().focus().unsetLink().run();
@@ -153,27 +154,27 @@ var j = (e) => [
153
154
  },
154
155
  {
155
156
  name: "horizontalRule",
156
- icon: /* @__PURE__ */ k(x, { size: 18 }),
157
+ icon: /* @__PURE__ */ A(S, { size: 18 }),
157
158
  action: (e) => e.chain().focus().setHorizontalRule().run(),
158
159
  isActive: () => !1
159
160
  },
160
161
  {
161
162
  name: "undo",
162
- icon: /* @__PURE__ */ k(O, { size: 18 }),
163
+ icon: /* @__PURE__ */ A(k, { size: 18 }),
163
164
  action: (e) => e.chain().focus().undo().run(),
164
165
  isActive: () => !1,
165
166
  disabled: (e) => !e.can().undo()
166
167
  },
167
168
  {
168
169
  name: "redo",
169
- icon: /* @__PURE__ */ k(w, { size: 18 }),
170
+ icon: /* @__PURE__ */ A(T, { size: 18 }),
170
171
  action: (e) => e.chain().focus().redo().run(),
171
172
  isActive: () => !1,
172
173
  disabled: (e) => !e.can().redo()
173
174
  }
174
- ], M = ({ editor: e, config: t }) => {
175
+ ], N = ({ editor: e, config: t }) => {
175
176
  let n = t.isActive(e);
176
- return /* @__PURE__ */ k("button", {
177
+ return /* @__PURE__ */ A("button", {
177
178
  type: "button",
178
179
  onMouseDown: (e) => e.preventDefault(),
179
180
  onClick: () => t.action(e),
@@ -182,7 +183,7 @@ var j = (e) => [
182
183
  title: t.name.charAt(0).toUpperCase() + t.name.slice(1),
183
184
  children: t.icon
184
185
  });
185
- }, N = ({ editor: t, features: n = [
186
+ }, P = ({ editor: t, features: n = [
186
187
  "bold",
187
188
  "italic",
188
189
  "underline",
@@ -197,8 +198,8 @@ var j = (e) => [
197
198
  t.off("transaction", e);
198
199
  };
199
200
  }, [t]), !t) return null;
200
- let a = j(t);
201
- return /* @__PURE__ */ k("div", {
201
+ let a = M(t);
202
+ return /* @__PURE__ */ A("div", {
202
203
  className: `cte-toolbar ${r}`,
203
204
  children: [
204
205
  {
@@ -235,16 +236,16 @@ var j = (e) => [
235
236
  }
236
237
  ].map((e) => {
237
238
  let r = a.filter((t) => e.features.includes(t.name) && n.includes(t.name));
238
- return r.length === 0 ? null : /* @__PURE__ */ k("div", {
239
+ return r.length === 0 ? null : /* @__PURE__ */ A("div", {
239
240
  className: "cte-toolbar-group",
240
- children: r.map((e) => /* @__PURE__ */ k(M, {
241
+ children: r.map((e) => /* @__PURE__ */ A(N, {
241
242
  editor: t,
242
243
  config: e
243
244
  }, e.name))
244
245
  }, e.name);
245
246
  })
246
247
  });
247
- }, P = (t, n) => {
248
+ }, F = (t, n) => {
248
249
  e.useEffect(() => {
249
250
  if (!t.current || !n?.theme) return;
250
251
  let { theme: e } = n, r = t.current, i = {
@@ -255,14 +256,13 @@ var j = (e) => [
255
256
  "--cte-accent": e.accentColor,
256
257
  "--cte-toolbar-btn-bg": e.toolbarButtonBackgroundColor,
257
258
  "--cte-border": e.borderColor,
258
- "--cte-radius": e.borderRadius,
259
- "--cte-width": typeof n.width == "number" ? `${n.width}px` : n.width || "100%"
259
+ "--cte-radius": e.borderRadius
260
260
  };
261
261
  Object.entries(i).forEach(([e, t]) => {
262
262
  t && r.style.setProperty(e, t);
263
263
  });
264
264
  }, [n, t]);
265
- }, F = [
265
+ }, I = [
266
266
  "bold",
267
267
  "italic",
268
268
  "underline",
@@ -281,15 +281,15 @@ var j = (e) => [
281
281
  "horizontalRule",
282
282
  "undo",
283
283
  "redo"
284
- ], I = ({ config: d }) => {
285
- let f = e.useRef(null);
286
- e.useLayoutEffect(() => {
284
+ ], L = e.forwardRef(({ config: f }, p) => {
285
+ let m = e.useRef(null), [h, g] = e.useState(f?.initialContent?.length || 0);
286
+ e.useImperativeHandle(p, () => m.current), e.useLayoutEffect(() => {
287
287
  if (typeof document < "u" && !document.getElementById("cte-styles")) {
288
288
  let e = document.createElement("style");
289
- e.id = "cte-styles", e.textContent = "\n:root {\n --cte-primary: #3b82f6;\n --cte-primary-foreground: #ffffff;\n --cte-background: #ffffff;\n --cte-foreground: #0f172a;\n --cte-muted: #f1f5f9;\n --cte-muted-foreground: #64748b;\n --cte-border: #e2e8f0;\n --cte-accent: #f8fafc;\n --cte-accent-foreground: #0f172a;\n --cte-radius: 0.75rem;\n --cte-font-family: 'Inter', system-ui, sans-serif;\n}\n.cte-editor-container {\n display: flex;\n flex-direction: column;\n background-color: var(--cte-background);\n color: var(--cte-foreground);\n border: 1px solid var(--cte-border);\n border-radius: var(--cte-radius);\n font-family: var(--cte-font-family);\n width: var(--cte-width, 100%);\n max-width: 100%;\n margin: 0 auto;\n overflow: hidden;\n transition: all 0.3s ease;\n box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);\n}\n.cte-toolbar {\n display: flex;\n align-items: center;\n padding: 0.5rem;\n border-bottom: 1px solid var(--cte-border);\n background-color: var(--cte-background);\n gap: 0.25rem;\n overflow-x: auto;\n}\n.cte-toolbar-group {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding-right: 0.5rem;\n border-right: 1px solid var(--cte-border);\n}\n.cte-toolbar-group:last-child { border-right: none; }\n.cte-toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n border-radius: 0.375rem;\n border: none;\n background: transparent;\n color: var(--cte-foreground);\n cursor: pointer;\n transition: all 0.2s;\n}\n.cte-toolbar-btn:hover { background-color: var(--cte-accent); }\n.cte-toolbar-btn.is-active {\n background-color: var(--cte-primary);\n color: var(--cte-primary-foreground);\n}\n.cte-content-area {\n padding: 1rem;\n min-height: 150px;\n outline: none;\n}\n.ProseMirror { outline: none; }\n ", document.head.appendChild(e);
289
+ e.id = "cte-styles", e.textContent = "\n:root {\n --cte-primary: #3b82f6;\n --cte-primary-foreground: #ffffff;\n --cte-background: #ffffff;\n --cte-foreground: #0f172a;\n --cte-muted: #f1f5f9;\n --cte-muted-foreground: #64748b;\n --cte-border: #e2e8f0;\n --cte-accent: #f8fafc;\n --cte-accent-foreground: #0f172a;\n --cte-radius: 0.75rem;\n --cte-font-family: 'Inter', system-ui, sans-serif;\n}\n.cte-editor-container {\n display: flex;\n flex-direction: column;\n background-color: var(--cte-background);\n color: var(--cte-foreground);\n border: 1px solid var(--cte-border);\n border-radius: var(--cte-radius);\n font-family: var(--cte-font-family);\n overflow: hidden;\n transition: all 0.3s ease;\n box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);\n}\n.cte-toolbar {\n display: flex;\n align-items: center;\n padding: 0.5rem;\n border-bottom: 1px solid var(--cte-border);\n background-color: var(--cte-background);\n gap: 0.25rem;\n overflow-x: auto;\n}\n.cte-toolbar-group {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding-right: 0.5rem;\n border-right: 1px solid var(--cte-border);\n}\n.cte-toolbar-group:last-child { border-right: none; }\n.cte-toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n border-radius: 0.375rem;\n border: none;\n background: transparent;\n color: var(--cte-foreground);\n cursor: pointer;\n transition: all 0.2s;\n}\n.cte-toolbar-btn:hover { background-color: var(--cte-accent); }\n.cte-toolbar-btn.is-active {\n background-color: var(--cte-primary);\n color: var(--cte-primary-foreground);\n}\n.cte-content-area {\n padding: 1rem;\n min-height: 150px;\n outline: none;\n}\n.ProseMirror { outline: none; }\n.cte-footer {\n display: flex;\n justify-content: flex-end;\n padding: 0.5rem 1rem;\n border-top: 1px solid var(--cte-border);\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--cte-muted-foreground);\n background-color: #f8fafc;\n border-bottom-left-radius: var(--cte-radius);\n border-bottom-right-radius: var(--cte-radius);\n z-index: 10;\n}\n ", document.head.appendChild(e);
290
290
  }
291
- }, []), P(f, d);
292
- let p = n({
291
+ }, []), F(m, f);
292
+ let _ = n({
293
293
  extensions: [
294
294
  r,
295
295
  i,
@@ -298,33 +298,75 @@ var j = (e) => [
298
298
  s,
299
299
  c,
300
300
  u,
301
- l.configure({ placeholder: d?.placeholder || "Write here..." })
301
+ l.configure({ placeholder: f?.placeholder || "Write here..." }),
302
+ d.configure({ limit: f?.maxLength })
302
303
  ],
303
- content: d?.initialContent || ""
304
- }), m = d?.layout === "bottom-bar", h = d?.features || F;
305
- return /* @__PURE__ */ A("div", {
306
- className: "cte-editor-container",
307
- ref: f,
304
+ content: f?.initialContent || "",
305
+ onUpdate: ({ editor: e }) => {
306
+ let t = f?.outputFormat == "text" ? e.getText() : e.getHTML();
307
+ f?.onChange?.(t);
308
+ let n = t.length;
309
+ g(n), f?.onCharacterCountChange?.(n);
310
+ }
311
+ }), v = f?.layout === "bottom-bar", y = f?.features || I, b = {
312
+ width: typeof f?.width == "number" ? `${f.width}px` : f?.width || "100%",
313
+ margin: f?.margin || "0 auto",
314
+ ...(() => {
315
+ if (!f?.align) return {};
316
+ switch (f.align) {
317
+ case "left": return {
318
+ marginLeft: "0",
319
+ marginRight: "auto"
320
+ };
321
+ case "center": return {
322
+ marginLeft: "auto",
323
+ marginRight: "auto"
324
+ };
325
+ case "right": return {
326
+ marginLeft: "auto",
327
+ marginRight: "0"
328
+ };
329
+ default: return {};
330
+ }
331
+ })()
332
+ };
333
+ return /* @__PURE__ */ j("div", {
334
+ ...f?.containerProps,
335
+ className: `cte-editor-container ${f?.containerProps?.className || ""}`,
336
+ ref: m,
337
+ style: {
338
+ ...b,
339
+ ...f?.containerProps?.style
340
+ },
308
341
  children: [
309
- d?.showTitle && d.title && /* @__PURE__ */ k("div", {
342
+ f?.showTitle && f.title && /* @__PURE__ */ A("div", {
310
343
  className: "cte-title-bar",
311
- children: d.title
344
+ children: f.title
312
345
  }),
313
- !m && /* @__PURE__ */ k(N, {
314
- editor: p,
315
- features: h
346
+ !v && /* @__PURE__ */ A(P, {
347
+ editor: _,
348
+ features: y
316
349
  }),
317
- /* @__PURE__ */ k(t, {
318
- editor: p,
350
+ /* @__PURE__ */ A(t, {
351
+ editor: _,
319
352
  className: "cte-content-area"
320
353
  }),
321
- m && /* @__PURE__ */ k(N, {
322
- editor: p,
323
- features: h,
354
+ v && /* @__PURE__ */ A(P, {
355
+ editor: _,
356
+ features: y,
324
357
  className: "cte-bottom"
358
+ }),
359
+ f?.maxLength && /* @__PURE__ */ j("div", {
360
+ className: "cte-footer",
361
+ children: [
362
+ h,
363
+ " / ",
364
+ f.maxLength,
365
+ " characters"
366
+ ]
325
367
  })
326
368
  ]
327
369
  });
328
- };
370
+ });
329
371
  //#endregion
330
- export { I as CustomEditor };
372
+ export { L as CustomEditor };
@@ -1,4 +1,4 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`@tiptap/react`),require(`@tiptap/starter-kit`),require(`@tiptap/extension-underline`),require(`@tiptap/extension-link`),require(`@tiptap/extension-highlight`),require(`@tiptap/extension-text-style`),require(`@tiptap/extension-color`),require(`@tiptap/extension-placeholder`),require(`@tiptap/extension-font-family`),require(`lucide-react`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`@tiptap/react`,`@tiptap/starter-kit`,`@tiptap/extension-underline`,`@tiptap/extension-link`,`@tiptap/extension-highlight`,`@tiptap/extension-text-style`,`@tiptap/extension-color`,`@tiptap/extension-placeholder`,`@tiptap/extension-font-family`,`lucide-react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.CustomiseTextEditor={},e.React,e.TiptapReact,e.TiptapStarterKit,e._tiptap_extension_underline,e._tiptap_extension_link,e._tiptap_extension_highlight,e._tiptap_extension_text_style,e._tiptap_extension_color,e._tiptap_extension_placeholder,e._tiptap_extension_font_family,e.LucideReact,e.react_jsx_runtime))})(this,function(e,t,n,r,i,a,o,s,c,l,u,d,f){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var p=Object.create,m=Object.defineProperty,h=Object.getOwnPropertyDescriptor,g=Object.getOwnPropertyNames,_=Object.getPrototypeOf,v=Object.prototype.hasOwnProperty,y=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=g(t),a=0,o=i.length,s;a<o;a++)s=i[a],!v.call(e,s)&&s!==n&&m(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=h(t,s))||r.enumerable});return e},b=(e,t,n)=>(n=e==null?{}:p(_(e)),y(t||!e||!e.__esModule?m(n,`default`,{value:e,enumerable:!0}):n,e));t=b(t),r=b(r),i=b(i),a=b(a),o=b(o),c=b(c),l=b(l),u=b(u);var x=e=>[{name:`bold`,icon:(0,f.jsx)(d.Bold,{size:18}),action:e=>e.chain().focus().toggleBold().run(),isActive:e=>e.isActive(`bold`)},{name:`italic`,icon:(0,f.jsx)(d.Italic,{size:18}),action:e=>e.chain().focus().toggleItalic().run(),isActive:e=>e.isActive(`italic`)},{name:`underline`,icon:(0,f.jsx)(d.Underline,{size:18}),action:e=>e.chain().focus().toggleUnderline().run(),isActive:e=>e.isActive(`underline`)},{name:`strike`,icon:(0,f.jsx)(d.Strikethrough,{size:18}),action:e=>e.chain().focus().toggleStrike().run(),isActive:e=>e.isActive(`strike`)},{name:`heading1`,icon:(0,f.jsx)(d.Heading1,{size:18}),action:e=>e.chain().focus().toggleHeading({level:1}).run(),isActive:e=>e.isActive(`heading`,{level:1})},{name:`heading2`,icon:(0,f.jsx)(d.Heading2,{size:18}),action:e=>e.chain().focus().toggleHeading({level:2}).run(),isActive:e=>e.isActive(`heading`,{level:2})},{name:`heading3`,icon:(0,f.jsx)(d.Heading3,{size:18}),action:e=>e.chain().focus().toggleHeading({level:3}).run(),isActive:e=>e.isActive(`heading`,{level:3})},{name:`bulletList`,icon:(0,f.jsx)(d.List,{size:18}),action:e=>e.chain().focus().toggleBulletList().run(),isActive:e=>e.isActive(`bulletList`)},{name:`orderedList`,icon:(0,f.jsx)(d.ListOrdered,{size:18}),action:e=>e.chain().focus().toggleOrderedList().run(),isActive:e=>e.isActive(`orderedList`)},{name:`blockquote`,icon:(0,f.jsx)(d.Quote,{size:18}),action:e=>e.chain().focus().toggleBlockquote().run(),isActive:e=>e.isActive(`blockquote`)},{name:`code`,icon:(0,f.jsx)(d.Code,{size:18}),action:e=>e.chain().focus().toggleCode().run(),isActive:e=>e.isActive(`code`)},{name:`codeBlock`,icon:(0,f.jsx)(d.Terminal,{size:18}),action:e=>e.chain().focus().toggleCodeBlock().run(),isActive:e=>e.isActive(`codeBlock`)},{name:`color`,icon:(0,f.jsxs)(`div`,{style:{position:`relative`,display:`flex`,alignItems:`center`},children:[(0,f.jsx)(d.Palette,{size:18}),(0,f.jsx)(`input`,{type:`color`,onInput:t=>{let n=t.target.value;e.chain().focus().setColor(n).run()},style:{position:`absolute`,inset:0,opacity:0,cursor:`pointer`,width:`100%`,height:`100%`}})]}),action:()=>{},isActive:e=>e.isActive(`textStyle`)},{name:`highlight`,icon:(0,f.jsxs)(`div`,{style:{position:`relative`,display:`flex`,alignItems:`center`},children:[(0,f.jsx)(d.Highlighter,{size:18}),(0,f.jsx)(`input`,{type:`color`,onInput:t=>{let n=t.target.value;e.chain().focus().toggleHighlight({color:n}).run()},style:{position:`absolute`,inset:0,opacity:0,cursor:`pointer`,width:`100%`,height:`100%`}})]}),action:()=>{},isActive:e=>e.isActive(`highlight`)},{name:`link`,icon:(0,f.jsx)(d.Link,{size:18}),action:e=>{if(e.isActive(`link`)){e.chain().focus().unsetLink().run();return}let t=window.prompt(`Enter URL`);t&&(!t.startsWith(`http://`)&&!t.startsWith(`https://`)&&!t.startsWith(`mailto:`)&&(t=`https://`+t),e.chain().focus().setLink({href:t}).run())},isActive:e=>e.isActive(`link`)},{name:`horizontalRule`,icon:(0,f.jsx)(d.Minus,{size:18}),action:e=>e.chain().focus().setHorizontalRule().run(),isActive:()=>!1},{name:`undo`,icon:(0,f.jsx)(d.Undo,{size:18}),action:e=>e.chain().focus().undo().run(),isActive:()=>!1,disabled:e=>!e.can().undo()},{name:`redo`,icon:(0,f.jsx)(d.Redo,{size:18}),action:e=>e.chain().focus().redo().run(),isActive:()=>!1,disabled:e=>!e.can().redo()}],S=({editor:e,config:t})=>{let n=t.isActive(e);return(0,f.jsx)(`button`,{type:`button`,onMouseDown:e=>e.preventDefault(),onClick:()=>t.action(e),disabled:t.disabled?t.disabled(e):!1,className:`cte-toolbar-btn ${n?`is-active`:``}`,title:t.name.charAt(0).toUpperCase()+t.name.slice(1),children:t.icon})},C=({editor:e,features:n=[`bold`,`italic`,`underline`,`undo`,`redo`],className:r=``})=>{let[,i]=t.default.useState(0);if(t.default.useEffect(()=>{if(!e)return;let t=()=>i(e=>e+1);return e.on(`transaction`,t),()=>{e.off(`transaction`,t)}},[e]),!e)return null;let a=x(e);return(0,f.jsx)(`div`,{className:`cte-toolbar ${r}`,children:[{name:`formatting`,features:[`bold`,`italic`,`underline`,`strike`,`color`,`highlight`]},{name:`blocks`,features:[`heading1`,`heading2`,`heading3`,`bulletList`,`orderedList`,`blockquote`,`code`,`codeBlock`]},{name:`actions`,features:[`link`,`horizontalRule`]},{name:`history`,features:[`undo`,`redo`]}].map(t=>{let r=a.filter(e=>t.features.includes(e.name)&&n.includes(e.name));return r.length===0?null:(0,f.jsx)(`div`,{className:`cte-toolbar-group`,children:r.map(t=>(0,f.jsx)(S,{editor:e,config:t},t.name))},t.name)})})},w=(e,n)=>{t.default.useEffect(()=>{if(!e.current||!n?.theme)return;let{theme:t}=n,r=e.current,i={"--cte-primary":t.primaryColor,"--cte-primary-foreground":t.primaryForegroundColor,"--cte-background":t.backgroundColor,"--cte-text":t.textColor,"--cte-accent":t.accentColor,"--cte-toolbar-btn-bg":t.toolbarButtonBackgroundColor,"--cte-border":t.borderColor,"--cte-radius":t.borderRadius,"--cte-width":typeof n.width==`number`?`${n.width}px`:n.width||`100%`};Object.entries(i).forEach(([e,t])=>{t&&r.style.setProperty(e,t)})},[n,e])},T=[`bold`,`italic`,`underline`,`strike`,`heading1`,`heading2`,`heading3`,`bulletList`,`orderedList`,`blockquote`,`code`,`codeBlock`,`link`,`color`,`highlight`,`horizontalRule`,`undo`,`redo`];e.CustomEditor=({config:e})=>{let d=t.default.useRef(null);t.default.useLayoutEffect(()=>{if(typeof document<`u`&&!document.getElementById(`cte-styles`)){let e=document.createElement(`style`);e.id=`cte-styles`,e.textContent=`
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`@tiptap/react`),require(`@tiptap/starter-kit`),require(`@tiptap/extension-underline`),require(`@tiptap/extension-link`),require(`@tiptap/extension-highlight`),require(`@tiptap/extension-text-style`),require(`@tiptap/extension-color`),require(`@tiptap/extension-placeholder`),require(`@tiptap/extension-font-family`),require(`@tiptap/extension-character-count`),require(`lucide-react`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`@tiptap/react`,`@tiptap/starter-kit`,`@tiptap/extension-underline`,`@tiptap/extension-link`,`@tiptap/extension-highlight`,`@tiptap/extension-text-style`,`@tiptap/extension-color`,`@tiptap/extension-placeholder`,`@tiptap/extension-font-family`,`@tiptap/extension-character-count`,`lucide-react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.CustomiseTextEditor={},e.React,e.TiptapReact,e.TiptapStarterKit,e._tiptap_extension_underline,e._tiptap_extension_link,e._tiptap_extension_highlight,e._tiptap_extension_text_style,e._tiptap_extension_color,e._tiptap_extension_placeholder,e._tiptap_extension_font_family,e._tiptap_extension_character_count,e.LucideReact,e.react_jsx_runtime))})(this,function(e,t,n,r,i,a,o,s,c,l,u,d,f,p){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var m=Object.create,h=Object.defineProperty,g=Object.getOwnPropertyDescriptor,_=Object.getOwnPropertyNames,v=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty,b=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=_(t),a=0,o=i.length,s;a<o;a++)s=i[a],!y.call(e,s)&&s!==n&&h(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=g(t,s))||r.enumerable});return e},x=(e,t,n)=>(n=e==null?{}:m(v(e)),b(t||!e||!e.__esModule?h(n,`default`,{value:e,enumerable:!0}):n,e));t=x(t),r=x(r),i=x(i),a=x(a),o=x(o),c=x(c),l=x(l),u=x(u),d=x(d);var S=e=>[{name:`bold`,icon:(0,p.jsx)(f.Bold,{size:18}),action:e=>e.chain().focus().toggleBold().run(),isActive:e=>e.isActive(`bold`)},{name:`italic`,icon:(0,p.jsx)(f.Italic,{size:18}),action:e=>e.chain().focus().toggleItalic().run(),isActive:e=>e.isActive(`italic`)},{name:`underline`,icon:(0,p.jsx)(f.Underline,{size:18}),action:e=>e.chain().focus().toggleUnderline().run(),isActive:e=>e.isActive(`underline`)},{name:`strike`,icon:(0,p.jsx)(f.Strikethrough,{size:18}),action:e=>e.chain().focus().toggleStrike().run(),isActive:e=>e.isActive(`strike`)},{name:`heading1`,icon:(0,p.jsx)(f.Heading1,{size:18}),action:e=>e.chain().focus().toggleHeading({level:1}).run(),isActive:e=>e.isActive(`heading`,{level:1})},{name:`heading2`,icon:(0,p.jsx)(f.Heading2,{size:18}),action:e=>e.chain().focus().toggleHeading({level:2}).run(),isActive:e=>e.isActive(`heading`,{level:2})},{name:`heading3`,icon:(0,p.jsx)(f.Heading3,{size:18}),action:e=>e.chain().focus().toggleHeading({level:3}).run(),isActive:e=>e.isActive(`heading`,{level:3})},{name:`bulletList`,icon:(0,p.jsx)(f.List,{size:18}),action:e=>e.chain().focus().toggleBulletList().run(),isActive:e=>e.isActive(`bulletList`)},{name:`orderedList`,icon:(0,p.jsx)(f.ListOrdered,{size:18}),action:e=>e.chain().focus().toggleOrderedList().run(),isActive:e=>e.isActive(`orderedList`)},{name:`blockquote`,icon:(0,p.jsx)(f.Quote,{size:18}),action:e=>e.chain().focus().toggleBlockquote().run(),isActive:e=>e.isActive(`blockquote`)},{name:`code`,icon:(0,p.jsx)(f.Code,{size:18}),action:e=>e.chain().focus().toggleCode().run(),isActive:e=>e.isActive(`code`)},{name:`codeBlock`,icon:(0,p.jsx)(f.Terminal,{size:18}),action:e=>e.chain().focus().toggleCodeBlock().run(),isActive:e=>e.isActive(`codeBlock`)},{name:`color`,icon:(0,p.jsxs)(`div`,{style:{position:`relative`,display:`flex`,alignItems:`center`},children:[(0,p.jsx)(f.Palette,{size:18}),(0,p.jsx)(`input`,{type:`color`,onInput:t=>{let n=t.target.value;e.chain().focus().setColor(n).run()},style:{position:`absolute`,inset:0,opacity:0,cursor:`pointer`,width:`100%`,height:`100%`}})]}),action:()=>{},isActive:e=>e.isActive(`textStyle`)},{name:`highlight`,icon:(0,p.jsxs)(`div`,{style:{position:`relative`,display:`flex`,alignItems:`center`},children:[(0,p.jsx)(f.Highlighter,{size:18}),(0,p.jsx)(`input`,{type:`color`,onInput:t=>{let n=t.target.value;e.chain().focus().toggleHighlight({color:n}).run()},style:{position:`absolute`,inset:0,opacity:0,cursor:`pointer`,width:`100%`,height:`100%`}})]}),action:()=>{},isActive:e=>e.isActive(`highlight`)},{name:`link`,icon:(0,p.jsx)(f.Link,{size:18}),action:e=>{if(e.isActive(`link`)){e.chain().focus().unsetLink().run();return}let t=window.prompt(`Enter URL`);t&&(!t.startsWith(`http://`)&&!t.startsWith(`https://`)&&!t.startsWith(`mailto:`)&&(t=`https://`+t),e.chain().focus().setLink({href:t}).run())},isActive:e=>e.isActive(`link`)},{name:`horizontalRule`,icon:(0,p.jsx)(f.Minus,{size:18}),action:e=>e.chain().focus().setHorizontalRule().run(),isActive:()=>!1},{name:`undo`,icon:(0,p.jsx)(f.Undo,{size:18}),action:e=>e.chain().focus().undo().run(),isActive:()=>!1,disabled:e=>!e.can().undo()},{name:`redo`,icon:(0,p.jsx)(f.Redo,{size:18}),action:e=>e.chain().focus().redo().run(),isActive:()=>!1,disabled:e=>!e.can().redo()}],C=({editor:e,config:t})=>{let n=t.isActive(e);return(0,p.jsx)(`button`,{type:`button`,onMouseDown:e=>e.preventDefault(),onClick:()=>t.action(e),disabled:t.disabled?t.disabled(e):!1,className:`cte-toolbar-btn ${n?`is-active`:``}`,title:t.name.charAt(0).toUpperCase()+t.name.slice(1),children:t.icon})},w=({editor:e,features:n=[`bold`,`italic`,`underline`,`undo`,`redo`],className:r=``})=>{let[,i]=t.default.useState(0);if(t.default.useEffect(()=>{if(!e)return;let t=()=>i(e=>e+1);return e.on(`transaction`,t),()=>{e.off(`transaction`,t)}},[e]),!e)return null;let a=S(e);return(0,p.jsx)(`div`,{className:`cte-toolbar ${r}`,children:[{name:`formatting`,features:[`bold`,`italic`,`underline`,`strike`,`color`,`highlight`]},{name:`blocks`,features:[`heading1`,`heading2`,`heading3`,`bulletList`,`orderedList`,`blockquote`,`code`,`codeBlock`]},{name:`actions`,features:[`link`,`horizontalRule`]},{name:`history`,features:[`undo`,`redo`]}].map(t=>{let r=a.filter(e=>t.features.includes(e.name)&&n.includes(e.name));return r.length===0?null:(0,p.jsx)(`div`,{className:`cte-toolbar-group`,children:r.map(t=>(0,p.jsx)(C,{editor:e,config:t},t.name))},t.name)})})},T=(e,n)=>{t.default.useEffect(()=>{if(!e.current||!n?.theme)return;let{theme:t}=n,r=e.current,i={"--cte-primary":t.primaryColor,"--cte-primary-foreground":t.primaryForegroundColor,"--cte-background":t.backgroundColor,"--cte-text":t.textColor,"--cte-accent":t.accentColor,"--cte-toolbar-btn-bg":t.toolbarButtonBackgroundColor,"--cte-border":t.borderColor,"--cte-radius":t.borderRadius};Object.entries(i).forEach(([e,t])=>{t&&r.style.setProperty(e,t)})},[n,e])},E=[`bold`,`italic`,`underline`,`strike`,`heading1`,`heading2`,`heading3`,`bulletList`,`orderedList`,`blockquote`,`code`,`codeBlock`,`link`,`color`,`highlight`,`horizontalRule`,`undo`,`redo`];e.CustomEditor=t.default.forwardRef(({config:e},f)=>{let m=t.default.useRef(null),[h,g]=t.default.useState(e?.initialContent?.length||0);t.default.useImperativeHandle(f,()=>m.current),t.default.useLayoutEffect(()=>{if(typeof document<`u`&&!document.getElementById(`cte-styles`)){let e=document.createElement(`style`);e.id=`cte-styles`,e.textContent=`
2
2
  :root {
3
3
  --cte-primary: #3b82f6;
4
4
  --cte-primary-foreground: #ffffff;
@@ -20,9 +20,6 @@
20
20
  border: 1px solid var(--cte-border);
21
21
  border-radius: var(--cte-radius);
22
22
  font-family: var(--cte-font-family);
23
- width: var(--cte-width, 100%);
24
- max-width: 100%;
25
- margin: 0 auto;
26
23
  overflow: hidden;
27
24
  transition: all 0.3s ease;
28
25
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
@@ -68,4 +65,17 @@
68
65
  outline: none;
69
66
  }
70
67
  .ProseMirror { outline: none; }
71
- `,document.head.appendChild(e)}},[]),w(d,e);let p=(0,n.useEditor)({extensions:[r.default,i.default,a.default.configure({openOnClick:!1}),o.default.configure({multicolor:!0}),s.TextStyle,c.default,u.default,l.default.configure({placeholder:e?.placeholder||`Write here...`})],content:e?.initialContent||``}),m=e?.layout===`bottom-bar`,h=e?.features||T;return(0,f.jsxs)(`div`,{className:`cte-editor-container`,ref:d,children:[e?.showTitle&&e.title&&(0,f.jsx)(`div`,{className:`cte-title-bar`,children:e.title}),!m&&(0,f.jsx)(C,{editor:p,features:h}),(0,f.jsx)(n.EditorContent,{editor:p,className:`cte-content-area`}),m&&(0,f.jsx)(C,{editor:p,features:h,className:`cte-bottom`})]})}});
68
+ .cte-footer {
69
+ display: flex;
70
+ justify-content: flex-end;
71
+ padding: 0.5rem 1rem;
72
+ border-top: 1px solid var(--cte-border);
73
+ font-size: 0.75rem;
74
+ font-weight: 500;
75
+ color: var(--cte-muted-foreground);
76
+ background-color: #f8fafc;
77
+ border-bottom-left-radius: var(--cte-radius);
78
+ border-bottom-right-radius: var(--cte-radius);
79
+ z-index: 10;
80
+ }
81
+ `,document.head.appendChild(e)}},[]),T(m,e);let _=(0,n.useEditor)({extensions:[r.default,i.default,a.default.configure({openOnClick:!1}),o.default.configure({multicolor:!0}),s.TextStyle,c.default,u.default,l.default.configure({placeholder:e?.placeholder||`Write here...`}),d.default.configure({limit:e?.maxLength})],content:e?.initialContent||``,onUpdate:({editor:t})=>{let n=e?.outputFormat==`text`?t.getText():t.getHTML();e?.onChange?.(n);let r=n.length;g(r),e?.onCharacterCountChange?.(r)}}),v=e?.layout===`bottom-bar`,y=e?.features||E,b={width:typeof e?.width==`number`?`${e.width}px`:e?.width||`100%`,margin:e?.margin||`0 auto`,...(()=>{if(!e?.align)return{};switch(e.align){case`left`:return{marginLeft:`0`,marginRight:`auto`};case`center`:return{marginLeft:`auto`,marginRight:`auto`};case`right`:return{marginLeft:`auto`,marginRight:`0`};default:return{}}})()};return(0,p.jsxs)(`div`,{...e?.containerProps,className:`cte-editor-container ${e?.containerProps?.className||``}`,ref:m,style:{...b,...e?.containerProps?.style},children:[e?.showTitle&&e.title&&(0,p.jsx)(`div`,{className:`cte-title-bar`,children:e.title}),!v&&(0,p.jsx)(w,{editor:_,features:y}),(0,p.jsx)(n.EditorContent,{editor:_,className:`cte-content-area`}),v&&(0,p.jsx)(w,{editor:_,features:y,className:`cte-bottom`}),e?.maxLength&&(0,p.jsxs)(`div`,{className:`cte-footer`,children:[h,` / `,e.maxLength,` characters`]})]})})});
package/dist/index.d.ts CHANGED
@@ -1,21 +1,25 @@
1
1
  import { default as default_2 } from 'react';
2
2
 
3
- export declare const CustomEditor: default_2.FC<{
3
+ export declare const CustomEditor: default_2.ForwardRefExoticComponent<{
4
4
  config?: EditorConfig;
5
- }>;
5
+ } & default_2.RefAttributes<HTMLDivElement>>;
6
6
 
7
7
  export declare interface EditorConfig {
8
8
  theme?: ThemeConfig;
9
9
  layout?: 'top-bar' | 'bottom-bar';
10
10
  width?: string | number;
11
+ align?: 'left' | 'center' | 'right';
12
+ margin?: string;
11
13
  placeholder?: string;
12
14
  title?: string;
13
15
  showTitle?: boolean;
14
16
  maxLength?: number;
15
17
  initialContent?: string;
18
+ outputFormat?: 'html' | 'text';
16
19
  onChange?: (content: string) => void;
17
20
  onCharacterCountChange?: (count: number) => void;
18
21
  features?: string[];
22
+ containerProps?: default_2.HTMLAttributes<HTMLDivElement>;
19
23
  }
20
24
 
21
25
  export declare interface ThemeConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "customise-text-editor",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A highly customizable, production-ready Rich Text Editor for React, powered by Tiptap.",
5
5
  "type": "module",
6
6
  "main": "./dist/customise-text-editor.umd.js",
@@ -44,6 +44,7 @@
44
44
  "@tiptap/starter-kit": "^3.22.0",
45
45
  "clsx": "^2.1.1",
46
46
  "lucide-react": "^1.7.0",
47
+ "react-hook-form": "^7.72.0",
47
48
  "tailwind-merge": "^3.5.0"
48
49
  },
49
50
  "devDependencies": {
@@ -56,6 +57,8 @@
56
57
  "eslint-plugin-react-hooks": "^7.0.1",
57
58
  "eslint-plugin-react-refresh": "^0.5.2",
58
59
  "globals": "^17.4.0",
60
+ "react": "^19.0.0",
61
+ "react-dom": "^19.0.0",
59
62
  "typescript": "~5.9.3",
60
63
  "typescript-eslint": "^8.57.0",
61
64
  "vite": "^8.0.1",