@abduljebar/text-editor 2.4.1 → 2.5.2

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/README.md CHANGED
@@ -1,565 +1,565 @@
1
- # @abduljebar/text-editor
2
-
3
- A modern, feature-rich React text editor component with beautiful styling and extensive customization options. Perfect for blogs, content management systems, and any application requiring rich text editing capabilities.
4
-
5
- ## ✨ Features
6
-
7
- ### 🎨 Rich Text Editing
8
- - **Text Formatting**: Bold, italic, underline, strikethrough
9
- - **Headings**: H1, H2 with automatic styling
10
- - **Lists**: Bulleted and numbered lists
11
- - **Alignment**: Left, center, right alignment
12
- - **Block Elements**: Quotes and code blocks
13
- - **Undo/Redo**: Full history support
14
- - **Additional Formatting**: Superscript, subscript, indentation
15
-
16
- ### 📁 File Management
17
- - **Image Upload**: Drag & drop, paste, or file picker
18
- - **Image Validation**: File type and size validation
19
- - **Pending Images**: Track and upload pending images
20
- - **HTML Export**: Generate complete HTML documents with styling
21
- - **Auto-save**: Debounced content changes with configurable delay
22
-
23
- ### 🎯 Smart UX
24
- - **Contextual Toolbar**: Appears only when focused and editable
25
- - **Real-time Stats**: Word and character count in status bar
26
- - **Smart Format Detection**: Visual indicators for active text formats
27
- - **Keyboard Shortcuts**: Ctrl+S (save), Ctrl+E (export)
28
- - **Auto-focus**: Automatic focus on load
29
- - **Placeholder Support**: Visual placeholder when empty
30
-
31
- ### 🔧 Flexible Configuration
32
- - **Read-only Mode**: Display content without editing capabilities
33
- - **Customizable Height**: Flexible sizing options
34
- - **Title Support**: Optional document title with real-time updates
35
- - **Action Buttons**: Built-in save and export functionality
36
- - **Event Handling**: Comprehensive callback system
37
- - **Debounce Control**: Configurable change debouncing
38
-
39
- ### 🚀 Advanced Features
40
- - **React Hook**: `useTextEditor` for custom implementations
41
- - **Ref API**: Exposed methods for programmatic control
42
- - **Selection Management**: Proper cursor and selection handling
43
- - **Paste/Upload Handling**: Smart image and content paste handling
44
- - **TypeScript**: Fully typed for better development experience
45
- - **Validation**: Built-in content validation
46
-
47
- ## 📦 Installation
48
-
49
- ```bash
50
- npm install @abduljebar/text-editor
51
- # or
52
- yarn add @abduljebar/text-editor
53
- # or
54
- pnpm add @abduljebar/text-editor
55
- ```
56
-
57
- ## 🚀 Quick Start
58
-
59
- ```tsx
60
- import '@abduljebar/text-editor/dist/index.css';
61
- import { TextEditor } from "@abduljebar/text-editor";
62
-
63
- function App() {
64
- return (
65
- <TextEditor
66
- height="500px"
67
- onChange={(content, html, title) => {
68
- console.log("Content:", content);
69
- console.log("HTML:", html);
70
- console.log("Title:", title);
71
- }}
72
- initialContent="<h1>Welcome to Your Document</h1><p>Start editing here...</p>"
73
- />
74
- );
75
- }
76
- ```
77
-
78
- ## 📖 Basic Usage
79
-
80
- ### Simple Implementation
81
-
82
- ```tsx
83
- import '@abduljebar/text-editor/dist/index.css';
84
- import { TextEditor } from "@abduljebar/text-editor";
85
-
86
- function MyEditor() {
87
- const handleSave = (content: string, html: string) => {
88
- // Save to your backend or state management
89
- console.log("Saving:", { content, html });
90
- };
91
-
92
- const handleExport = (html: string) => {
93
- // Export as HTML file
94
- const blob = new Blob([html], { type: 'text/html' });
95
- const url = URL.createObjectURL(blob);
96
- const a = document.createElement('a');
97
- a.href = url;
98
- a.download = 'document.html';
99
- a.click();
100
- };
101
-
102
- return (
103
- <TextEditor
104
- showButtons={true}
105
- showSaveTitle={true}
106
- showStatusBar={true}
107
- onSave={handleSave}
108
- onExport={handleExport}
109
- onChange={(content, html, title) => {
110
- // Real-time updates
111
- console.log("Changes:", { content, html, title });
112
- }}
113
- />
114
- );
115
- }
116
- ```
117
-
118
- ### Read-only Mode
119
-
120
- ```tsx
121
- import '@abduljebar/text-editor/dist/index.css';
122
- import { TextEditor } from "@abduljebar/text-editor";
123
-
124
- function ReadOnlyView() {
125
- return (
126
- <TextEditor
127
- readOnly={true}
128
- initialContent="<h1>Published Article</h1><p>This content cannot be edited.</p>"
129
- showStatusBar={true}
130
- />
131
- );
132
- }
133
- ```
134
-
135
- ### With Image Upload
136
-
137
- ```tsx
138
- import '@abduljebar/text-editor/dist/index.css';
139
- import { TextEditor } from "@abduljebar/text-editor";
140
-
141
- function EditorWithImages() {
142
- const handleImageUpload = async (file: File) => {
143
- // Upload to your backend
144
- const formData = new FormData();
145
- formData.append('image', file);
146
-
147
- const response = await fetch('/api/upload', {
148
- method: 'POST',
149
- body: formData,
150
- });
151
-
152
- const data = await response.json();
153
- return data.url; // Return the uploaded image URL
154
- };
155
-
156
- return (
157
- <TextEditor
158
- showButtons={true}
159
- onImageUpload={handleImageUpload}
160
- allowedImageTypes={['image/jpeg', 'image/png', 'image/webp']}
161
- maxImageSize={10 * 1024 * 1024} // 10MB
162
- />
163
- );
164
- }
165
- ```
166
-
167
- ## ⚙️ API Reference
168
-
169
- ### TextEditor Props
170
-
171
- | Prop | Type | Default | Description |
172
- |------|------|---------|-------------|
173
- | `initialContent` | `string` | `""` | Initial HTML content for the editor |
174
- | `onChange` | `(content: string, html: string, title?: string) => void` | `undefined` | Callback when content changes |
175
- | `onSave` | `(content: string, html: string) => void` | `undefined` | Callback when save is triggered |
176
- | `onExport` | `(html: string) => void` | `undefined` | Callback when export is triggered |
177
- | `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
178
- | `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
179
- | `readOnly` | `boolean` | `false` | Disable editing when true |
180
- | `showButtons` | `boolean` | `false` | Show save/export buttons |
181
- | `showSaveTitle` | `boolean` | `false` | Show document title input |
182
- | `showStatusBar` | `boolean` | `false` | Show word/character count |
183
- | `height` | `string` | `"500px"` | Editor height (any CSS value) |
184
- | `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image MIME types |
185
- | `maxImageSize` | `number` | `5242880` (5MB) | Maximum image size in bytes |
186
- | `debounceDelay` | `number` | `300` | Debounce delay for onChange in ms |
187
- | `className` | `string` | `""` | Additional CSS class name |
188
- | `placeholder` | `string` | `"Start typing here..."` | Placeholder text when empty |
189
- | `autoFocus` | `boolean` | `false` | Auto-focus editor on load |
190
- | `onInit` | `(editor: HTMLDivElement) => void` | `undefined` | Callback after editor initialization |
191
-
192
- ### TextEditor Ref API
193
-
194
- The component exposes a ref with the following methods:
195
-
196
- ```typescript
197
- interface TextEditorRef {
198
- getContent: () => string;
199
- getHTML: () => string;
200
- getTitle: () => string;
201
- clear: () => void;
202
- focus: () => void;
203
- insertText: (text: string) => void;
204
- insertHTML: (html: string) => void;
205
- executeCommand: (command: string, value?: string) => void;
206
- }
207
- ```
208
-
209
- Example usage:
210
- ```tsx
211
- import { useRef } from 'react';
212
- import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
213
-
214
- function EditorWithRef() {
215
- const editorRef = useRef<TextEditorRef>(null);
216
-
217
- const handleGetContent = () => {
218
- if (editorRef.current) {
219
- const content = editorRef.current.getContent();
220
- const html = editorRef.current.getHTML();
221
- console.log({ content, html });
222
- }
223
- };
224
-
225
- return (
226
- <>
227
- <TextEditor ref={editorRef} />
228
- <button onClick={handleGetContent}>Get Content</button>
229
- </>
230
- );
231
- }
232
- ```
233
-
234
- ### useTextEditor Hook
235
-
236
- For advanced usage and custom implementations:
237
-
238
- ```tsx
239
- import '@abduljebar/text-editor/dist/index.css';
240
- import { useTextEditor } from "@abduljebar/text-editor";
241
-
242
- function CustomEditor() {
243
- const {
244
- editorState,
245
- editorRef,
246
- updateContent,
247
- updateTitle,
248
- executeCommand,
249
- getValidationResult,
250
- exportToHTML,
251
- clearEditor,
252
- handlePaste,
253
- handleDrop,
254
- insertImage,
255
- uploadPendingImages,
256
- } = useTextEditor({
257
- initialContent: "Initial content",
258
- onImageUpload: async (file) => {
259
- // Custom upload logic
260
- return "https://example.com/image.jpg";
261
- },
262
- allowedImageTypes: ["image/jpeg", "image/png"],
263
- maxImageSize: 10 * 1024 * 1024,
264
- });
265
-
266
- return (
267
- <div>
268
- <div
269
- ref={editorRef}
270
- contentEditable
271
- onInput={(e) => updateContent(e.currentTarget.innerHTML)}
272
- className="border p-4 min-h-[200px]"
273
- onPaste={handlePaste}
274
- onDrop={handleDrop}
275
- />
276
- <button onClick={() => executeCommand('bold')}>
277
- Bold
278
- </button>
279
- <button onClick={() => insertImage(someFile)}>
280
- Insert Image
281
- </button>
282
- </div>
283
- );
284
- }
285
- ```
286
-
287
- #### Hook Parameters
288
-
289
- | Parameter | Type | Default | Description |
290
- |-----------|------|---------|-------------|
291
- | `initialContent` | `string` | `""` | Initial HTML content |
292
- | `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
293
- | `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
294
- | `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image types |
295
- | `maxImageSize` | `number` | `5242880` | Max image size in bytes |
296
-
297
- #### Hook Return Values
298
-
299
- | Property | Type | Description |
300
- |----------|------|-------------|
301
- | `editorState` | `object` | Current editor state with content, title, counts, pending images |
302
- | `editorRef` | `RefObject<HTMLDivElement>` | Reference to the editable element |
303
- | `updateContent` | `(content: string) => void` | Update editor content |
304
- | `updateTitle` | `(title: string) => void` | Update document title |
305
- | `executeCommand` | `(command: string, value?: string) => void` | Execute formatting commands |
306
- | `getValidationResult` | `() => ValidationResult` | Validate and get editor data |
307
- | `exportToHTML` | `(options?) => string` | Generate HTML export |
308
- | `clearEditor` | `() => void` | Clear all content |
309
- | `handlePaste` | `(e: React.ClipboardEvent) => void` | Handle paste events |
310
- | `handleDrop` | `(e: React.DragEvent) => void` | Handle drop events |
311
- | `insertImage` | `(file: File, atCursor?: boolean) => Promise<void>` | Insert image into editor |
312
- | `uploadPendingImages` | `() => Promise<void>` | Upload all pending images |
313
-
314
- ## 🎨 Styling & Customization
315
-
316
- ### Default Styling
317
-
318
- The editor comes with beautiful default styling:
319
-
320
- - **Headings**: Proper hierarchy with appropriate sizing
321
- - **Paragraphs**: Optimal line height and margins
322
- - **Lists**: Clean indentation and spacing
323
- - **Code Blocks**: Proper monospace fonts
324
- - **Quotes**: Elegant bordered design
325
- - **Links**: Proper styling with hover effects
326
- - **Images**: Responsive with rounded corners
327
-
328
- ### Custom Styling
329
-
330
- You can override the default styles by targeting the editor's CSS classes or using the `className` prop:
331
-
332
- ```tsx
333
- import '@abduljebar/text-editor/dist/index.css';
334
- import { TextEditor } from "@abduljebar/text-editor";
335
-
336
- <TextEditor
337
- className="my-custom-editor"
338
- // ... other props
339
- />
340
- ```
341
-
342
- ```css
343
- .my-custom-editor {
344
- border: 2px solid #4f46e5;
345
- border-radius: 12px;
346
- box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
347
- }
348
-
349
- .my-custom-editor h1 {
350
- color: #4f46e5;
351
- border-bottom: 2px solid #e0e7ff;
352
- }
353
-
354
- .my-custom-editor .toolbar {
355
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
356
- }
357
- ```
358
-
359
- ## 🔧 Advanced Examples
360
-
361
- ### Integration with Form Libraries
362
-
363
- ```tsx
364
- import '@abduljebar/text-editor/dist/index.css';
365
- import { useForm } from 'react-hook-form';
366
- import { TextEditor } from "@abduljebar/text-editor";
367
-
368
- function ArticleForm() {
369
- const { register, handleSubmit, setValue, watch } = useForm();
370
-
371
- const handleEditorChange = (content: string, html: string, title?: string) => {
372
- setValue('content', html);
373
- setValue('title', title);
374
- };
375
-
376
- return (
377
- <form onSubmit={handleSubmit(data => console.log(data))}>
378
- <TextEditor
379
- showSaveTitle={true}
380
- showButtons={true}
381
- onChange={handleEditorChange}
382
- onSave={(content, html) => {
383
- // Handle form submission
384
- handleSubmit(data => console.log(data))();
385
- }}
386
- />
387
- </form>
388
- );
389
- }
390
- ```
391
-
392
- ### Custom Toolbar Implementation
393
-
394
- ```tsx
395
- import '@abduljebar/text-editor/dist/index.css';
396
- import { useTextEditor } from "@abduljebar/text-editor";
397
-
398
- function CustomToolbarEditor() {
399
- const { executeCommand, editorRef, insertImage } = useTextEditor({
400
- initialContent: "Start typing...",
401
- });
402
-
403
- const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
404
- const file = e.target.files?.[0];
405
- if (file) {
406
- insertImage(file);
407
- }
408
- };
409
-
410
- return (
411
- <div className="editor-container">
412
- <div className="custom-toolbar">
413
- <button onClick={() => executeCommand('bold')}>Bold</button>
414
- <button onClick={() => executeCommand('italic')}>Italic</button>
415
- <button onClick={() => executeCommand('formatBlock', 'h1')}>H1</button>
416
- <button onClick={() => executeCommand('formatBlock', 'h2')}>H2</button>
417
- <input type="file" accept="image/*" onChange={handleFileSelect} />
418
- </div>
419
- <div
420
- ref={editorRef}
421
- contentEditable
422
- className="editor-content"
423
- />
424
- </div>
425
- );
426
- }
427
- ```
428
-
429
- ### Programmatic Control
430
-
431
- ```tsx
432
- import { useRef, useEffect } from 'react';
433
- import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
434
-
435
- function ProgrammaticEditor() {
436
- const editorRef = useRef<TextEditorRef>(null);
437
-
438
- useEffect(() => {
439
- // Example: Auto-insert content after 2 seconds
440
- const timer = setTimeout(() => {
441
- if (editorRef.current) {
442
- editorRef.current.insertText("Hello, world!");
443
- editorRef.current.focus();
444
- }
445
- }, 2000);
446
-
447
- return () => clearTimeout(timer);
448
- }, []);
449
-
450
- const handleCommand = (command: string) => {
451
- if (editorRef.current) {
452
- editorRef.current.executeCommand(command);
453
- }
454
- };
455
-
456
- return (
457
- <div>
458
- <TextEditor ref={editorRef} />
459
- <div className="mt-4">
460
- <button onClick={() => handleCommand('bold')}>Make Selection Bold</button>
461
- <button onClick={() => editorRef.current?.clear()}>Clear Editor</button>
462
- </div>
463
- </div>
464
- );
465
- }
466
- ```
467
-
468
- ## 📋 Browser Support
469
-
470
- - Chrome 60+
471
- - Firefox 55+
472
- - Safari 12+
473
- - Edge 79+
474
- - Opera 47+
475
-
476
- ## 🔒 Accessibility
477
-
478
- - Keyboard navigation support
479
- - ARIA labels for toolbar buttons
480
- - Focus management
481
- - Screen reader compatible
482
- - Proper semantic HTML structure
483
-
484
- ## 🐛 Troubleshooting
485
-
486
- ### Common Issues
487
-
488
- 1. **Toolbar buttons not working**: Ensure the editor is focused and content is selected
489
- 2. **Images not uploading**: Check CORS settings and upload endpoint configuration
490
- 3. **Styles not appearing**: Import the CSS file: `import '@abduljebar/text-editor/dist/index.css';`
491
- 4. **Content not saving**: Check `onSave` callback and validation messages
492
- 5. **Formatting lost on paste**: Use the built-in `handlePaste` function
493
-
494
- ### Performance Tips
495
-
496
- - Use appropriate `debounceDelay` for `onChange` to prevent excessive updates
497
- - Implement proper image compression before upload
498
- - Consider using `React.memo` if embedding in frequently re-rendering components
499
- - Use the ref API for programmatic control instead of frequent state updates
500
-
501
- ## 📄 Supported Commands
502
-
503
- The editor supports standard `document.execCommand` APIs:
504
-
505
- - **Formatting**: `bold`, `italic`, `underline`, `strikeThrough`
506
- - **Headings**: `formatBlock` (with `h1`, `h2`, `h3`, `p` values)
507
- - **Lists**: `insertUnorderedList`, `insertOrderedList`
508
- - **Alignment**: `justifyLeft`, `justifyCenter`, `justifyRight`
509
- - **Indentation**: `indent`, `outdent`
510
- - **Links**: `createLink` (with URL value)
511
- - **History**: `undo`, `redo`
512
- - **Special**: `superscript`, `subscript`, `formatBlock` (with `blockquote`, `pre` values)
513
-
514
- ## 🤝 Contributing
515
-
516
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
517
-
518
- 1. Fork the repository
519
- 2. Create a feature branch
520
- 3. Make your changes
521
- 4. Add tests if applicable
522
- 5. Submit a pull request
523
-
524
- ## 📄 License
525
-
526
- MIT License - see the [LICENSE](LICENSE) file for details.
527
-
528
- ## 🆘 Support
529
-
530
- If you encounter any issues or have questions:
531
-
532
- 1. Check the documentation above
533
- 2. Search existing GitHub issues
534
- 3. Create a new issue with:
535
- - A clear description of the problem
536
- - Steps to reproduce
537
- - Expected vs actual behavior
538
- - Code examples if applicable
539
-
540
- ## 🚀 Changelog
541
-
542
- ### v1.1.0
543
- - Added image upload support with drag & drop
544
- - Improved toolbar with better selection tracking
545
- - Added ref API for programmatic control
546
- - Enhanced keyboard shortcuts
547
- - Better validation and error handling
548
- - Added pending images tracking
549
-
550
- ### v1.0.0
551
- - Initial release with core editing features
552
- - Basic text formatting and styling
553
- - HTML export functionality
554
- - React hook for advanced usage
555
-
556
- ---
557
-
558
- Built with ❤️ by [AbdulJebar Sani](https://github.com/abduljebar49)
559
-
560
- ## 🔗 Links
561
-
562
- - [GitHub Repository](https://github.com/abduljebar/text-editor)
563
- - [npm Package](https://www.npmjs.com/package/@abduljebar/text-editor)
564
- - [Issue Tracker](https://github.com/abduljebar/text-editor/issues)
1
+ # @abduljebar/text-editor
2
+
3
+ A modern, feature-rich React text editor component with beautiful styling and extensive customization options. Perfect for blogs, content management systems, and any application requiring rich text editing capabilities.
4
+
5
+ ## ✨ Features
6
+
7
+ ### 🎨 Rich Text Editing
8
+ - **Text Formatting**: Bold, italic, underline, strikethrough
9
+ - **Headings**: H1, H2 with automatic styling
10
+ - **Lists**: Bulleted and numbered lists
11
+ - **Alignment**: Left, center, right alignment
12
+ - **Block Elements**: Quotes and code blocks
13
+ - **Undo/Redo**: Full history support
14
+ - **Additional Formatting**: Superscript, subscript, indentation
15
+
16
+ ### 📁 File Management
17
+ - **Image Upload**: Drag & drop, paste, or file picker
18
+ - **Image Validation**: File type and size validation
19
+ - **Pending Images**: Track and upload pending images
20
+ - **HTML Export**: Generate complete HTML documents with styling
21
+ - **Auto-save**: Debounced content changes with configurable delay
22
+
23
+ ### 🎯 Smart UX
24
+ - **Contextual Toolbar**: Appears only when focused and editable
25
+ - **Real-time Stats**: Word and character count in status bar
26
+ - **Smart Format Detection**: Visual indicators for active text formats
27
+ - **Keyboard Shortcuts**: Ctrl+S (save), Ctrl+E (export)
28
+ - **Auto-focus**: Automatic focus on load
29
+ - **Placeholder Support**: Visual placeholder when empty
30
+
31
+ ### 🔧 Flexible Configuration
32
+ - **Read-only Mode**: Display content without editing capabilities
33
+ - **Customizable Height**: Flexible sizing options
34
+ - **Title Support**: Optional document title with real-time updates
35
+ - **Action Buttons**: Built-in save and export functionality
36
+ - **Event Handling**: Comprehensive callback system
37
+ - **Debounce Control**: Configurable change debouncing
38
+
39
+ ### 🚀 Advanced Features
40
+ - **React Hook**: `useTextEditor` for custom implementations
41
+ - **Ref API**: Exposed methods for programmatic control
42
+ - **Selection Management**: Proper cursor and selection handling
43
+ - **Paste/Upload Handling**: Smart image and content paste handling
44
+ - **TypeScript**: Fully typed for better development experience
45
+ - **Validation**: Built-in content validation
46
+
47
+ ## 📦 Installation
48
+
49
+ ```bash
50
+ npm install @abduljebar/text-editor
51
+ # or
52
+ yarn add @abduljebar/text-editor
53
+ # or
54
+ pnpm add @abduljebar/text-editor
55
+ ```
56
+
57
+ ## 🚀 Quick Start
58
+
59
+ ```tsx
60
+ import '@abduljebar/text-editor/dist/index.css';
61
+ import { TextEditor } from "@abduljebar/text-editor";
62
+
63
+ function App() {
64
+ return (
65
+ <TextEditor
66
+ height="500px"
67
+ onChange={(content, html, title) => {
68
+ console.log("Content:", content);
69
+ console.log("HTML:", html);
70
+ console.log("Title:", title);
71
+ }}
72
+ initialContent="<h1>Welcome to Your Document</h1><p>Start editing here...</p>"
73
+ />
74
+ );
75
+ }
76
+ ```
77
+
78
+ ## 📖 Basic Usage
79
+
80
+ ### Simple Implementation
81
+
82
+ ```tsx
83
+ import '@abduljebar/text-editor/dist/index.css';
84
+ import { TextEditor } from "@abduljebar/text-editor";
85
+
86
+ function MyEditor() {
87
+ const handleSave = (content: string, html: string) => {
88
+ // Save to your backend or state management
89
+ console.log("Saving:", { content, html });
90
+ };
91
+
92
+ const handleExport = (html: string) => {
93
+ // Export as HTML file
94
+ const blob = new Blob([html], { type: 'text/html' });
95
+ const url = URL.createObjectURL(blob);
96
+ const a = document.createElement('a');
97
+ a.href = url;
98
+ a.download = 'document.html';
99
+ a.click();
100
+ };
101
+
102
+ return (
103
+ <TextEditor
104
+ showButtons={true}
105
+ showSaveTitle={true}
106
+ showStatusBar={true}
107
+ onSave={handleSave}
108
+ onExport={handleExport}
109
+ onChange={(content, html, title) => {
110
+ // Real-time updates
111
+ console.log("Changes:", { content, html, title });
112
+ }}
113
+ />
114
+ );
115
+ }
116
+ ```
117
+
118
+ ### Read-only Mode
119
+
120
+ ```tsx
121
+ import '@abduljebar/text-editor/dist/index.css';
122
+ import { TextEditor } from "@abduljebar/text-editor";
123
+
124
+ function ReadOnlyView() {
125
+ return (
126
+ <TextEditor
127
+ readOnly={true}
128
+ initialContent="<h1>Published Article</h1><p>This content cannot be edited.</p>"
129
+ showStatusBar={true}
130
+ />
131
+ );
132
+ }
133
+ ```
134
+
135
+ ### With Image Upload
136
+
137
+ ```tsx
138
+ import '@abduljebar/text-editor/dist/index.css';
139
+ import { TextEditor } from "@abduljebar/text-editor";
140
+
141
+ function EditorWithImages() {
142
+ const handleImageUpload = async (file: File) => {
143
+ // Upload to your backend
144
+ const formData = new FormData();
145
+ formData.append('image', file);
146
+
147
+ const response = await fetch('/api/upload', {
148
+ method: 'POST',
149
+ body: formData,
150
+ });
151
+
152
+ const data = await response.json();
153
+ return data.url; // Return the uploaded image URL
154
+ };
155
+
156
+ return (
157
+ <TextEditor
158
+ showButtons={true}
159
+ onImageUpload={handleImageUpload}
160
+ allowedImageTypes={['image/jpeg', 'image/png', 'image/webp']}
161
+ maxImageSize={10 * 1024 * 1024} // 10MB
162
+ />
163
+ );
164
+ }
165
+ ```
166
+
167
+ ## ⚙️ API Reference
168
+
169
+ ### TextEditor Props
170
+
171
+ | Prop | Type | Default | Description |
172
+ |------|------|---------|-------------|
173
+ | `initialContent` | `string` | `""` | Initial HTML content for the editor |
174
+ | `onChange` | `(content: string, html: string, title?: string) => void` | `undefined` | Callback when content changes |
175
+ | `onSave` | `(content: string, html: string) => void` | `undefined` | Callback when save is triggered |
176
+ | `onExport` | `(html: string) => void` | `undefined` | Callback when export is triggered |
177
+ | `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
178
+ | `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
179
+ | `readOnly` | `boolean` | `false` | Disable editing when true |
180
+ | `showButtons` | `boolean` | `false` | Show save/export buttons |
181
+ | `showSaveTitle` | `boolean` | `false` | Show document title input |
182
+ | `showStatusBar` | `boolean` | `false` | Show word/character count |
183
+ | `height` | `string` | `"500px"` | Editor height (any CSS value) |
184
+ | `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image MIME types |
185
+ | `maxImageSize` | `number` | `5242880` (5MB) | Maximum image size in bytes |
186
+ | `debounceDelay` | `number` | `300` | Debounce delay for onChange in ms |
187
+ | `className` | `string` | `""` | Additional CSS class name |
188
+ | `placeholder` | `string` | `"Start typing here..."` | Placeholder text when empty |
189
+ | `autoFocus` | `boolean` | `false` | Auto-focus editor on load |
190
+ | `onInit` | `(editor: HTMLDivElement) => void` | `undefined` | Callback after editor initialization |
191
+
192
+ ### TextEditor Ref API
193
+
194
+ The component exposes a ref with the following methods:
195
+
196
+ ```typescript
197
+ interface TextEditorRef {
198
+ getContent: () => string;
199
+ getHTML: () => string;
200
+ getTitle: () => string;
201
+ clear: () => void;
202
+ focus: () => void;
203
+ insertText: (text: string) => void;
204
+ insertHTML: (html: string) => void;
205
+ executeCommand: (command: string, value?: string) => void;
206
+ }
207
+ ```
208
+
209
+ Example usage:
210
+ ```tsx
211
+ import { useRef } from 'react';
212
+ import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
213
+
214
+ function EditorWithRef() {
215
+ const editorRef = useRef<TextEditorRef>(null);
216
+
217
+ const handleGetContent = () => {
218
+ if (editorRef.current) {
219
+ const content = editorRef.current.getContent();
220
+ const html = editorRef.current.getHTML();
221
+ console.log({ content, html });
222
+ }
223
+ };
224
+
225
+ return (
226
+ <>
227
+ <TextEditor ref={editorRef} />
228
+ <button onClick={handleGetContent}>Get Content</button>
229
+ </>
230
+ );
231
+ }
232
+ ```
233
+
234
+ ### useTextEditor Hook
235
+
236
+ For advanced usage and custom implementations:
237
+
238
+ ```tsx
239
+ import '@abduljebar/text-editor/dist/index.css';
240
+ import { useTextEditor } from "@abduljebar/text-editor";
241
+
242
+ function CustomEditor() {
243
+ const {
244
+ editorState,
245
+ editorRef,
246
+ updateContent,
247
+ updateTitle,
248
+ executeCommand,
249
+ getValidationResult,
250
+ exportToHTML,
251
+ clearEditor,
252
+ handlePaste,
253
+ handleDrop,
254
+ insertImage,
255
+ uploadPendingImages,
256
+ } = useTextEditor({
257
+ initialContent: "Initial content",
258
+ onImageUpload: async (file) => {
259
+ // Custom upload logic
260
+ return "https://example.com/image.jpg";
261
+ },
262
+ allowedImageTypes: ["image/jpeg", "image/png"],
263
+ maxImageSize: 10 * 1024 * 1024,
264
+ });
265
+
266
+ return (
267
+ <div>
268
+ <div
269
+ ref={editorRef}
270
+ contentEditable
271
+ onInput={(e) => updateContent(e.currentTarget.innerHTML)}
272
+ className="border p-4 min-h-[200px]"
273
+ onPaste={handlePaste}
274
+ onDrop={handleDrop}
275
+ />
276
+ <button onClick={() => executeCommand('bold')}>
277
+ Bold
278
+ </button>
279
+ <button onClick={() => insertImage(someFile)}>
280
+ Insert Image
281
+ </button>
282
+ </div>
283
+ );
284
+ }
285
+ ```
286
+
287
+ #### Hook Parameters
288
+
289
+ | Parameter | Type | Default | Description |
290
+ |-----------|------|---------|-------------|
291
+ | `initialContent` | `string` | `""` | Initial HTML content |
292
+ | `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
293
+ | `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
294
+ | `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image types |
295
+ | `maxImageSize` | `number` | `5242880` | Max image size in bytes |
296
+
297
+ #### Hook Return Values
298
+
299
+ | Property | Type | Description |
300
+ |----------|------|-------------|
301
+ | `editorState` | `object` | Current editor state with content, title, counts, pending images |
302
+ | `editorRef` | `RefObject<HTMLDivElement>` | Reference to the editable element |
303
+ | `updateContent` | `(content: string) => void` | Update editor content |
304
+ | `updateTitle` | `(title: string) => void` | Update document title |
305
+ | `executeCommand` | `(command: string, value?: string) => void` | Execute formatting commands |
306
+ | `getValidationResult` | `() => ValidationResult` | Validate and get editor data |
307
+ | `exportToHTML` | `(options?) => string` | Generate HTML export |
308
+ | `clearEditor` | `() => void` | Clear all content |
309
+ | `handlePaste` | `(e: React.ClipboardEvent) => void` | Handle paste events |
310
+ | `handleDrop` | `(e: React.DragEvent) => void` | Handle drop events |
311
+ | `insertImage` | `(file: File, atCursor?: boolean) => Promise<void>` | Insert image into editor |
312
+ | `uploadPendingImages` | `() => Promise<void>` | Upload all pending images |
313
+
314
+ ## 🎨 Styling & Customization
315
+
316
+ ### Default Styling
317
+
318
+ The editor comes with beautiful default styling:
319
+
320
+ - **Headings**: Proper hierarchy with appropriate sizing
321
+ - **Paragraphs**: Optimal line height and margins
322
+ - **Lists**: Clean indentation and spacing
323
+ - **Code Blocks**: Proper monospace fonts
324
+ - **Quotes**: Elegant bordered design
325
+ - **Links**: Proper styling with hover effects
326
+ - **Images**: Responsive with rounded corners
327
+
328
+ ### Custom Styling
329
+
330
+ You can override the default styles by targeting the editor's CSS classes or using the `className` prop:
331
+
332
+ ```tsx
333
+ import '@abduljebar/text-editor/dist/index.css';
334
+ import { TextEditor } from "@abduljebar/text-editor";
335
+
336
+ <TextEditor
337
+ className="my-custom-editor"
338
+ // ... other props
339
+ />
340
+ ```
341
+
342
+ ```css
343
+ .my-custom-editor {
344
+ border: 2px solid #4f46e5;
345
+ border-radius: 12px;
346
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
347
+ }
348
+
349
+ .my-custom-editor h1 {
350
+ color: #4f46e5;
351
+ border-bottom: 2px solid #e0e7ff;
352
+ }
353
+
354
+ .my-custom-editor .toolbar {
355
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
356
+ }
357
+ ```
358
+
359
+ ## 🔧 Advanced Examples
360
+
361
+ ### Integration with Form Libraries
362
+
363
+ ```tsx
364
+ import '@abduljebar/text-editor/dist/index.css';
365
+ import { useForm } from 'react-hook-form';
366
+ import { TextEditor } from "@abduljebar/text-editor";
367
+
368
+ function ArticleForm() {
369
+ const { register, handleSubmit, setValue, watch } = useForm();
370
+
371
+ const handleEditorChange = (content: string, html: string, title?: string) => {
372
+ setValue('content', html);
373
+ setValue('title', title);
374
+ };
375
+
376
+ return (
377
+ <form onSubmit={handleSubmit(data => console.log(data))}>
378
+ <TextEditor
379
+ showSaveTitle={true}
380
+ showButtons={true}
381
+ onChange={handleEditorChange}
382
+ onSave={(content, html) => {
383
+ // Handle form submission
384
+ handleSubmit(data => console.log(data))();
385
+ }}
386
+ />
387
+ </form>
388
+ );
389
+ }
390
+ ```
391
+
392
+ ### Custom Toolbar Implementation
393
+
394
+ ```tsx
395
+ import '@abduljebar/text-editor/dist/index.css';
396
+ import { useTextEditor } from "@abduljebar/text-editor";
397
+
398
+ function CustomToolbarEditor() {
399
+ const { executeCommand, editorRef, insertImage } = useTextEditor({
400
+ initialContent: "Start typing...",
401
+ });
402
+
403
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
404
+ const file = e.target.files?.[0];
405
+ if (file) {
406
+ insertImage(file);
407
+ }
408
+ };
409
+
410
+ return (
411
+ <div className="editor-container">
412
+ <div className="custom-toolbar">
413
+ <button onClick={() => executeCommand('bold')}>Bold</button>
414
+ <button onClick={() => executeCommand('italic')}>Italic</button>
415
+ <button onClick={() => executeCommand('formatBlock', 'h1')}>H1</button>
416
+ <button onClick={() => executeCommand('formatBlock', 'h2')}>H2</button>
417
+ <input type="file" accept="image/*" onChange={handleFileSelect} />
418
+ </div>
419
+ <div
420
+ ref={editorRef}
421
+ contentEditable
422
+ className="editor-content"
423
+ />
424
+ </div>
425
+ );
426
+ }
427
+ ```
428
+
429
+ ### Programmatic Control
430
+
431
+ ```tsx
432
+ import { useRef, useEffect } from 'react';
433
+ import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
434
+
435
+ function ProgrammaticEditor() {
436
+ const editorRef = useRef<TextEditorRef>(null);
437
+
438
+ useEffect(() => {
439
+ // Example: Auto-insert content after 2 seconds
440
+ const timer = setTimeout(() => {
441
+ if (editorRef.current) {
442
+ editorRef.current.insertText("Hello, world!");
443
+ editorRef.current.focus();
444
+ }
445
+ }, 2000);
446
+
447
+ return () => clearTimeout(timer);
448
+ }, []);
449
+
450
+ const handleCommand = (command: string) => {
451
+ if (editorRef.current) {
452
+ editorRef.current.executeCommand(command);
453
+ }
454
+ };
455
+
456
+ return (
457
+ <div>
458
+ <TextEditor ref={editorRef} />
459
+ <div className="mt-4">
460
+ <button onClick={() => handleCommand('bold')}>Make Selection Bold</button>
461
+ <button onClick={() => editorRef.current?.clear()}>Clear Editor</button>
462
+ </div>
463
+ </div>
464
+ );
465
+ }
466
+ ```
467
+
468
+ ## 📋 Browser Support
469
+
470
+ - Chrome 60+
471
+ - Firefox 55+
472
+ - Safari 12+
473
+ - Edge 79+
474
+ - Opera 47+
475
+
476
+ ## 🔒 Accessibility
477
+
478
+ - Keyboard navigation support
479
+ - ARIA labels for toolbar buttons
480
+ - Focus management
481
+ - Screen reader compatible
482
+ - Proper semantic HTML structure
483
+
484
+ ## 🐛 Troubleshooting
485
+
486
+ ### Common Issues
487
+
488
+ 1. **Toolbar buttons not working**: Ensure the editor is focused and content is selected
489
+ 2. **Images not uploading**: Check CORS settings and upload endpoint configuration
490
+ 3. **Styles not appearing**: Import the CSS file: `import '@abduljebar/text-editor/dist/index.css';`
491
+ 4. **Content not saving**: Check `onSave` callback and validation messages
492
+ 5. **Formatting lost on paste**: Use the built-in `handlePaste` function
493
+
494
+ ### Performance Tips
495
+
496
+ - Use appropriate `debounceDelay` for `onChange` to prevent excessive updates
497
+ - Implement proper image compression before upload
498
+ - Consider using `React.memo` if embedding in frequently re-rendering components
499
+ - Use the ref API for programmatic control instead of frequent state updates
500
+
501
+ ## 📄 Supported Commands
502
+
503
+ The editor supports standard `document.execCommand` APIs:
504
+
505
+ - **Formatting**: `bold`, `italic`, `underline`, `strikeThrough`
506
+ - **Headings**: `formatBlock` (with `h1`, `h2`, `h3`, `p` values)
507
+ - **Lists**: `insertUnorderedList`, `insertOrderedList`
508
+ - **Alignment**: `justifyLeft`, `justifyCenter`, `justifyRight`
509
+ - **Indentation**: `indent`, `outdent`
510
+ - **Links**: `createLink` (with URL value)
511
+ - **History**: `undo`, `redo`
512
+ - **Special**: `superscript`, `subscript`, `formatBlock` (with `blockquote`, `pre` values)
513
+
514
+ ## 🤝 Contributing
515
+
516
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
517
+
518
+ 1. Fork the repository
519
+ 2. Create a feature branch
520
+ 3. Make your changes
521
+ 4. Add tests if applicable
522
+ 5. Submit a pull request
523
+
524
+ ## 📄 License
525
+
526
+ MIT License - see the [LICENSE](LICENSE) file for details.
527
+
528
+ ## 🆘 Support
529
+
530
+ If you encounter any issues or have questions:
531
+
532
+ 1. Check the documentation above
533
+ 2. Search existing GitHub issues
534
+ 3. Create a new issue with:
535
+ - A clear description of the problem
536
+ - Steps to reproduce
537
+ - Expected vs actual behavior
538
+ - Code examples if applicable
539
+
540
+ ## 🚀 Changelog
541
+
542
+ ### v1.1.0
543
+ - Added image upload support with drag & drop
544
+ - Improved toolbar with better selection tracking
545
+ - Added ref API for programmatic control
546
+ - Enhanced keyboard shortcuts
547
+ - Better validation and error handling
548
+ - Added pending images tracking
549
+
550
+ ### v1.0.0
551
+ - Initial release with core editing features
552
+ - Basic text formatting and styling
553
+ - HTML export functionality
554
+ - React hook for advanced usage
555
+
556
+ ---
557
+
558
+ Built with ❤️ by [AbdulJebar Sani](https://github.com/abduljebar49)
559
+
560
+ ## 🔗 Links
561
+
562
+ - [GitHub Repository](https://github.com/abduljebar/text-editor)
563
+ - [npm Package](https://www.npmjs.com/package/@abduljebar/text-editor)
564
+ - [Issue Tracker](https://github.com/abduljebar/text-editor/issues)
565
565
  - [Documentation](https://github.com/abduljebar/text-editor#readme)