smartrte-react 0.1.9 β†’ 0.1.13

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 ADDED
@@ -0,0 +1,687 @@
1
+ # Smart RTE (Rich Text Editor) - React
2
+
3
+ [![npm version](https://img.shields.io/npm/v/smartrte-react.svg)](https://www.npmjs.com/package/smartrte-react)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A powerful, feature-rich Rich Text Editor built for React applications with support for tables, formulas (LaTeX/KaTeX), media management, and advanced text formatting.
7
+
8
+ ## 🌟 Features
9
+
10
+ - **πŸ“ Rich Text Editing**: Full-featured WYSIWYG editor with all standard formatting options
11
+ - **πŸ“Š Advanced Table Support**: Create, edit, merge, split cells, and customize tables
12
+ - **πŸ”’ Mathematical Formulas**: LaTeX/KaTeX integration for rendering mathematical expressions
13
+ - **πŸ–ΌοΈ Media Management**: Image upload, resize, drag-and-drop, and custom media manager integration
14
+ - **🎨 Styling Options**: Font sizes (8-96pt), text colors, background colors, and more
15
+ - **πŸ”— Link Management**: Easy insertion and editing of hyperlinks
16
+ - **πŸ“± Responsive**: Works seamlessly across different screen sizes
17
+ - **⚑ Lightweight**: Minimal dependencies, optimized for performance
18
+ - **🎯 TypeScript Support**: Fully typed for better developer experience
19
+ - **πŸ”§ Customizable**: Toggle features on/off, custom media managers, and more
20
+
21
+ ## πŸ“¦ Installation
22
+
23
+ ### Using npm
24
+
25
+ ```bash
26
+ npm install smartrte-react
27
+ ```
28
+
29
+ ### Using yarn
30
+
31
+ ```bash
32
+ yarn add smartrte-react
33
+ ```
34
+
35
+ ### Using pnpm
36
+
37
+ ```bash
38
+ pnpm add smartrte-react
39
+ ```
40
+
41
+ ## πŸš€ Quick Start
42
+
43
+ ### Basic Usage
44
+
45
+ ```tsx
46
+ import React, { useState } from 'react';
47
+ import { ClassicEditor } from 'smartrte-react';
48
+
49
+ function App() {
50
+ const [content, setContent] = useState('<p>Start typing...</p>');
51
+
52
+ return (
53
+ <div>
54
+ <ClassicEditor
55
+ value={content}
56
+ onChange={(html) => setContent(html)}
57
+ placeholder="Type here…"
58
+ />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ export default App;
64
+ ```
65
+
66
+ ## πŸ“š Documentation
67
+
68
+ ### Component API
69
+
70
+ #### ClassicEditor Props
71
+
72
+ | Prop | Type | Default | Description |
73
+ |------|------|---------|-------------|
74
+ | `value` | `string` | `undefined` | HTML content of the editor |
75
+ | `onChange` | `(html: string) => void` | `undefined` | Callback fired when content changes |
76
+ | `placeholder` | `string` | `"Type here…"` | Placeholder text when editor is empty |
77
+ | `minHeight` | `number \| string` | `200` | Minimum height of the editor (in pixels) |
78
+ | `maxHeight` | `number \| string` | `500` | Maximum height of the editor (in pixels) |
79
+ | `readOnly` | `boolean` | `false` | Make the editor read-only |
80
+ | `table` | `boolean` | `true` | Enable/disable table functionality |
81
+ | `media` | `boolean` | `true` | Enable/disable media/image functionality |
82
+ | `formula` | `boolean` | `true` | Enable/disable formula/LaTeX functionality |
83
+ | `mediaManager` | `MediaManagerAdapter` | `undefined` | Custom media manager for handling images |
84
+
85
+ ### Advanced Examples
86
+
87
+ #### Complete Example with All Features
88
+
89
+ ```tsx
90
+ import React, { useState } from 'react';
91
+ import { ClassicEditor, MediaManagerAdapter } from 'smartrte-react';
92
+
93
+ // Custom media manager implementation
94
+ const customMediaManager: MediaManagerAdapter = {
95
+ async search(query) {
96
+ // Implement your media search logic
97
+ const response = await fetch(`/api/media/search?q=${query.text}`);
98
+ const data = await response.json();
99
+ return {
100
+ items: data.items.map(item => ({
101
+ id: item.id,
102
+ url: item.url,
103
+ thumbnailUrl: item.thumbnailUrl,
104
+ title: item.title,
105
+ })),
106
+ };
107
+ },
108
+ async upload(file) {
109
+ // Implement your file upload logic
110
+ const formData = new FormData();
111
+ formData.append('file', file);
112
+ const response = await fetch('/api/media/upload', {
113
+ method: 'POST',
114
+ body: formData,
115
+ });
116
+ const data = await response.json();
117
+ return {
118
+ id: data.id,
119
+ url: data.url,
120
+ thumbnailUrl: data.thumbnailUrl,
121
+ title: data.title,
122
+ };
123
+ },
124
+ };
125
+
126
+ function AdvancedEditor() {
127
+ const [content, setContent] = useState('');
128
+
129
+ return (
130
+ <ClassicEditor
131
+ value={content}
132
+ onChange={(html) => {
133
+ console.log('Content changed:', html);
134
+ setContent(html);
135
+ }}
136
+ placeholder="Start editing..."
137
+ minHeight={300}
138
+ maxHeight={800}
139
+ table={true}
140
+ media={true}
141
+ formula={true}
142
+ mediaManager={customMediaManager}
143
+ />
144
+ );
145
+ }
146
+
147
+ export default AdvancedEditor;
148
+ ```
149
+
150
+ #### Read-Only Mode
151
+
152
+ ```tsx
153
+ import { ClassicEditor } from 'smartrte-react';
154
+
155
+ function ReadOnlyEditor({ content }) {
156
+ return (
157
+ <ClassicEditor
158
+ value={content}
159
+ readOnly={true}
160
+ minHeight={200}
161
+ />
162
+ );
163
+ }
164
+ ```
165
+
166
+ #### Minimal Editor (No Tables, Media, or Formulas)
167
+
168
+ ```tsx
169
+ import { ClassicEditor } from 'smartrte-react';
170
+
171
+ function MinimalEditor() {
172
+ const [content, setContent] = useState('');
173
+
174
+ return (
175
+ <ClassicEditor
176
+ value={content}
177
+ onChange={setContent}
178
+ table={false}
179
+ media={false}
180
+ formula={false}
181
+ placeholder="Simple text editor"
182
+ />
183
+ );
184
+ }
185
+ ```
186
+
187
+ #### Next.js Integration
188
+
189
+ For Next.js applications, you may need to use dynamic imports to avoid SSR issues:
190
+
191
+ ```tsx
192
+ import dynamic from 'next/dynamic';
193
+ import { useState } from 'react';
194
+
195
+ const ClassicEditor = dynamic(
196
+ () => import('smartrte-react').then(mod => mod.ClassicEditor),
197
+ { ssr: false }
198
+ );
199
+
200
+ export default function Page() {
201
+ const [content, setContent] = useState('');
202
+
203
+ return (
204
+ <div>
205
+ <ClassicEditor
206
+ value={content}
207
+ onChange={setContent}
208
+ placeholder="Start typing..."
209
+ />
210
+ </div>
211
+ );
212
+ }
213
+ ```
214
+
215
+ ## πŸ”§ Features Deep Dive
216
+
217
+ ### Text Formatting
218
+
219
+ The editor supports all standard text formatting options:
220
+
221
+ - **Bold**, *Italic*, <u>Underline</u>, ~~Strikethrough~~
222
+ - Font sizes from 8pt to 96pt
223
+ - Text color and background color
224
+ - Headings (H1-H6)
225
+ - Paragraph, blockquote, code block
226
+ - Ordered and unordered lists
227
+ - Text alignment (left, center, right, justify)
228
+ - Superscript and subscript
229
+
230
+ ### Tables
231
+
232
+ Full-featured table support includes:
233
+
234
+ - Create tables with custom rows and columns
235
+ - Add/delete rows and columns
236
+ - Merge and split cells
237
+ - Toggle header rows/cells
238
+ - Cell background colors
239
+ - Cell borders toggle
240
+ - Right-click context menu for table operations
241
+ - Keyboard navigation (Tab, Shift+Tab, Arrow keys)
242
+
243
+ **Keyboard Shortcuts:**
244
+ - `Tab` - Move to next cell
245
+ - `Shift+Tab` - Move to previous cell
246
+ - `Arrow keys` - Navigate between cells
247
+ - Right-click on cell - Open context menu
248
+
249
+ ### Mathematical Formulas
250
+
251
+ LaTeX/KaTeX support for mathematical expressions:
252
+
253
+ ```tsx
254
+ // The editor automatically loads KaTeX
255
+ // Users can insert formulas using the formula button
256
+ // Examples of supported LaTeX:
257
+ // - E=mc^2
258
+ // - \frac{a}{b}
259
+ // - \sqrt{x}
260
+ // - \sum_{i=1}^{n} x_i
261
+ ```
262
+
263
+ **Required External Dependency:**
264
+
265
+ To use formulas, include KaTeX in your HTML:
266
+
267
+ ```html
268
+ <!-- In your public/index.html or _app.tsx -->
269
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
270
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
271
+ ```
272
+
273
+ ### Media Management
274
+
275
+ Built-in image support with optional custom media manager:
276
+
277
+ **Default behavior:**
278
+ - Local file upload
279
+ - Drag and drop images
280
+ - Image resize handles
281
+ - Right-click context menu for image operations
282
+
283
+ **Custom Media Manager Implementation:**
284
+
285
+ ```typescript
286
+ import { MediaManagerAdapter, MediaItem, MediaSearchQuery } from 'smartrte-react';
287
+
288
+ const myMediaManager: MediaManagerAdapter = {
289
+ async search(query: MediaSearchQuery) {
290
+ // Search your media library
291
+ return {
292
+ items: [/* array of MediaItem */],
293
+ hasMore: false,
294
+ nextPage: undefined,
295
+ };
296
+ },
297
+
298
+ async upload(file: File) {
299
+ // Upload file to your server
300
+ return {
301
+ id: 'unique-id',
302
+ url: 'https://example.com/image.jpg',
303
+ thumbnailUrl: 'https://example.com/thumb.jpg',
304
+ title: 'Image title',
305
+ };
306
+ },
307
+ };
308
+ ```
309
+
310
+ ## 🎨 Styling
311
+
312
+ The editor comes with built-in styles. You can customize the appearance by wrapping it in a container:
313
+
314
+ ```tsx
315
+ <div style={{
316
+ border: '1px solid #ddd',
317
+ borderRadius: '8px',
318
+ overflow: 'hidden',
319
+ }}>
320
+ <ClassicEditor
321
+ value={content}
322
+ onChange={setContent}
323
+ />
324
+ </div>
325
+ ```
326
+
327
+ ## πŸ› οΈ Development
328
+
329
+ ### Prerequisites
330
+
331
+ - Node.js 18+
332
+ - pnpm 9.10.0+
333
+
334
+ ### Setting Up Development Environment
335
+
336
+ 1. **Clone the repository**
337
+
338
+ ```bash
339
+ git clone https://github.com/ayush1852017/smart-rte.git
340
+ cd smart-rte
341
+ ```
342
+
343
+ 2. **Install dependencies**
344
+
345
+ ```bash
346
+ pnpm install
347
+ ```
348
+
349
+ 3. **Build the project**
350
+
351
+ ```bash
352
+ # Build TypeScript packages
353
+ pnpm build
354
+ ```
355
+
356
+ 4. **Run the development playground**
357
+
358
+ ```bash
359
+ cd packages/react/playground
360
+ pnpm install
361
+ pnpm dev
362
+ ```
363
+
364
+ The playground will be available at `http://localhost:5173`
365
+
366
+ ### Project Structure
367
+
368
+ ```
369
+ smart-rte/
370
+ β”œβ”€β”€ packages/
371
+ β”‚ └── react/ # Main React package (smartrte-react)
372
+ β”‚ β”œβ”€β”€ src/
373
+ β”‚ β”‚ β”œβ”€β”€ components/
374
+ β”‚ β”‚ β”‚ β”œβ”€β”€ ClassicEditor.tsx # Main editor component
375
+ β”‚ β”‚ β”‚ └── MediaManager.tsx # Media management component
376
+ β”‚ β”‚ └── index.ts
377
+ β”‚ β”œβ”€β”€ playground/ # Development playground
378
+ β”‚ └── package.json
379
+ β”œβ”€β”€ dart/ # Flutter/Dart packages
380
+ β”‚ β”œβ”€β”€ smartrte_flutter/ # Flutter WebView integration
381
+ β”‚ └── example_app/ # Flutter example
382
+ └── package.json
383
+ ```
384
+
385
+ ### Building for Production
386
+
387
+ ```bash
388
+ # Build the React package
389
+ cd packages/react
390
+ pnpm build
391
+
392
+ # This creates:
393
+ # - dist/index.js - ES module
394
+ # - dist/index.d.ts - TypeScript definitions
395
+ # - dist/embed.js - Standalone embed bundle
396
+ ```
397
+
398
+ ### Running Tests
399
+
400
+ ```bash
401
+ # Run vitest
402
+ pnpm test
403
+
404
+ # Run E2E tests with Playwright
405
+ pnpm e2e
406
+ ```
407
+
408
+ ### Running Storybook
409
+
410
+ ```bash
411
+ cd packages/react
412
+ pnpm storybook
413
+ ```
414
+
415
+ Storybook will be available at `http://localhost:6006`
416
+
417
+ ## πŸ“ Publishing
418
+
419
+ ### For Package Maintainers
420
+
421
+ The package is published to npm as `smartrte-react`.
422
+
423
+ ```bash
424
+ # Make sure you're in packages/react
425
+ cd packages/react
426
+
427
+ # Update version in package.json
428
+ # Then publish
429
+ pnpm publish
430
+ ```
431
+
432
+ The `prepublishOnly` script automatically runs `build:all` before publishing.
433
+
434
+ ### Version Management
435
+
436
+ We follow [Semantic Versioning](https://semver.org/):
437
+
438
+ - **MAJOR** version for incompatible API changes
439
+ - **MINOR** version for backwards-compatible functionality
440
+ - **PATCH** version for backwards-compatible bug fixes
441
+
442
+ ## 🀝 Contributing
443
+
444
+ We welcome contributions! Here's how you can help:
445
+
446
+ ### Reporting Bugs
447
+
448
+ 1. Check if the bug has already been reported in [Issues](https://github.com/ayush1852017/smart-rte/issues)
449
+ 2. If not, create a new issue with:
450
+ - Clear title and description
451
+ - Steps to reproduce
452
+ - Expected vs actual behavior
453
+ - Screenshots if applicable
454
+ - Your environment (browser, OS, React version)
455
+
456
+ ### Suggesting Features
457
+
458
+ 1. Check [existing feature requests](https://github.com/ayush1852017/smart-rte/issues?q=is%3Aissue+label%3Aenhancement)
459
+ 2. Create a new issue with:
460
+ - Clear description of the feature
461
+ - Use cases
462
+ - Proposed API (if applicable)
463
+
464
+ ### Pull Requests
465
+
466
+ 1. **Fork** the repository
467
+ 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
468
+ 3. **Make** your changes
469
+ 4. **Test** your changes thoroughly
470
+ 5. **Commit** with clear messages (`git commit -m 'Add amazing feature'`)
471
+ 6. **Push** to your fork (`git push origin feature/amazing-feature`)
472
+ 7. **Open** a Pull Request
473
+
474
+ #### PR Guidelines
475
+
476
+ - Follow the existing code style
477
+ - Add tests for new features
478
+ - Update documentation
479
+ - Keep PRs focused on a single feature/fix
480
+ - Write clear commit messages
481
+
482
+ ### Development Workflow
483
+
484
+ ```bash
485
+ # 1. Create a feature branch
486
+ git checkout -b feature/my-feature
487
+
488
+ # 2. Make changes and test
489
+ pnpm dev # Run playground
490
+ pnpm test # Run tests
491
+
492
+ # 3. Build to ensure no errors
493
+ pnpm build
494
+
495
+ # 4. Commit and push
496
+ git add .
497
+ git commit -m "feat: add my feature"
498
+ git push origin feature/my-feature
499
+
500
+ # 5. Create PR on GitHub
501
+ ```
502
+
503
+ ## πŸ› Troubleshooting
504
+
505
+ ### Common Issues
506
+
507
+ #### Issue: Editor not showing/rendering
508
+
509
+ **Solution:** Make sure React and React-DOM are installed as peer dependencies:
510
+
511
+ ```bash
512
+ npm install react@18 react-dom@18
513
+ ```
514
+
515
+ #### Issue: Formula rendering not working
516
+
517
+ **Solution:** Ensure KaTeX is loaded in your HTML:
518
+
519
+ ```html
520
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
521
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
522
+ ```
523
+
524
+ #### Issue: TypeScript errors
525
+
526
+ **Solution:** Make sure you have the latest type definitions:
527
+
528
+ ```bash
529
+ npm install --save-dev @types/react@18 @types/react-dom@18
530
+ ```
531
+
532
+ #### Issue: Build errors in Next.js
533
+
534
+ **Solution:** Use dynamic imports to disable SSR:
535
+
536
+ ```tsx
537
+ const ClassicEditor = dynamic(
538
+ () => import('smartrte-react').then(mod => mod.ClassicEditor),
539
+ { ssr: false }
540
+ );
541
+ ```
542
+
543
+ #### Issue: Images not uploading
544
+
545
+ **Solution:** Check that the `media` prop is set to `true` and implement a custom `mediaManager` if you need server-side uploads.
546
+
547
+ ## πŸ” Security
548
+
549
+ ### Reporting Security Issues
550
+
551
+ If you discover a security vulnerability, please email [security@yourdomain.com] instead of using the issue tracker.
552
+
553
+ ### Content Sanitization
554
+
555
+ **⚠️ Important:** The editor outputs raw HTML. Always sanitize user-generated content before displaying it to prevent XSS attacks.
556
+
557
+ Recommended libraries:
558
+ - [DOMPurify](https://github.com/cure53/DOMPurify)
559
+ - [sanitize-html](https://github.com/apostrophecms/sanitize-html)
560
+
561
+ Example:
562
+
563
+ ```tsx
564
+ import DOMPurify from 'dompurify';
565
+
566
+ function DisplayContent({ html }) {
567
+ const sanitized = DOMPurify.sanitize(html);
568
+ return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
569
+ }
570
+ ```
571
+
572
+ ## πŸ“„ License
573
+
574
+ This project is licensed under the MIT License - see the [LICENSE](../../dart/smartrte_flutter/LICENSE) file for details.
575
+
576
+ ```
577
+ MIT License
578
+
579
+ Copyright (c) 2025 Smart RTE Contributors
580
+
581
+ Permission is hereby granted, free of charge, to any person obtaining a copy
582
+ of this software and associated documentation files (the "Software"), to deal
583
+ in the Software without restriction, including without limitation the rights
584
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
585
+ copies of the Software, and to permit persons to whom the Software is
586
+ furnished to do so, subject to the following conditions:
587
+
588
+ The above copyright notice and this permission notice shall be included in all
589
+ copies or substantial portions of the Software.
590
+
591
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
592
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
593
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
594
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
595
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
596
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
597
+ SOFTWARE.
598
+ ```
599
+
600
+ ## πŸ‘₯ Authors & Contributors
601
+
602
+ - **Smart RTE Team** - Initial work and maintenance
603
+
604
+ See the list of [contributors](https://github.com/ayush1852017/smart-rte/contributors) who participated in this project.
605
+
606
+ ## πŸ™ Acknowledgments
607
+
608
+ - [KaTeX](https://katex.org/) - For mathematical formula rendering
609
+ - [React](https://reactjs.org/) - The UI library
610
+ - [Vite](https://vitejs.dev/) - Build tool
611
+ - All our amazing [contributors](https://github.com/ayush1852017/smart-rte/contributors)
612
+
613
+ ## πŸ“ž Support
614
+
615
+ - **Documentation:** You're reading it! πŸ“–
616
+ - **Issues:** [GitHub Issues](https://github.com/ayush1852017/smart-rte/issues)
617
+ - **Discussions:** [GitHub Discussions](https://github.com/ayush1852017/smart-rte/discussions)
618
+ - **Twitter:** [@smartrte](https://twitter.com/smartrte) (if applicable)
619
+
620
+ ## πŸ—ΊοΈ Roadmap
621
+
622
+ ### Current Version (0.1.x)
623
+
624
+ - βœ… Rich text editing
625
+ - βœ… Table support
626
+ - βœ… Formula support (LaTeX/KaTeX)
627
+ - βœ… Media management
628
+ - βœ… TypeScript support
629
+
630
+ ### Upcoming Features
631
+
632
+ - πŸ”„ Collaborative editing
633
+ - πŸ”„ Undo/Redo improvements
634
+ - πŸ”„ Code syntax highlighting
635
+ - πŸ”„ Markdown import/export
636
+ - πŸ”„ Custom toolbar configuration
637
+ - πŸ”„ Mobile optimization
638
+ - πŸ”„ Accessibility improvements (ARIA labels, keyboard shortcuts)
639
+
640
+ ## πŸ“Š Browser Support
641
+
642
+ | Browser | Version |
643
+ |---------|---------|
644
+ | Chrome | Last 2 versions |
645
+ | Firefox | Last 2 versions |
646
+ | Safari | Last 2 versions |
647
+ | Edge | Last 2 versions |
648
+
649
+ ## πŸ”— Related Packages
650
+
651
+ - **smartrte-flutter** - Flutter/Dart WebView implementation
652
+
653
+ ## πŸ’‘ Tips & Best Practices
654
+
655
+ 1. **Performance**: For large documents, consider implementing lazy loading or pagination
656
+ 2. **State Management**: Use React state or a state management library (Redux, Zustand) for complex applications
657
+ 3. **Validation**: Always validate and sanitize HTML content before storing or displaying
658
+ 4. **Accessibility**: Test with screen readers and keyboard navigation
659
+ 5. **Mobile**: Test on mobile devices as touch interactions may differ
660
+ 6. **Auto-save**: Implement auto-save functionality to prevent data loss
661
+
662
+ ## πŸŽ“ Learning Resources
663
+
664
+ ### For Entry-Level Developers
665
+
666
+ 1. **Getting Started with React**: [React Official Tutorial](https://react.dev/learn)
667
+ 2. **Understanding Rich Text Editors**: [MDN ContentEditable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable)
668
+ 3. **TypeScript Basics**: [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
669
+
670
+ ### For Mid-Level Developers
671
+
672
+ 1. **Advanced React Patterns**: Hooks, Context, Performance Optimization
673
+ 2. **Component Design**: Building reusable, maintainable components
674
+ 3. **State Management**: When and how to use external state management
675
+
676
+ ### For Senior Developers
677
+
678
+ 1. **Architecture**: Designing scalable editor implementations
679
+ 2. **Performance**: Optimization techniques for large documents
680
+ 3. **Extensibility**: Building plugin systems and custom extensions
681
+ 4. **Cross-platform**: Adapting the editor for different frameworks
682
+
683
+ ---
684
+
685
+ **Happy Editing! πŸŽ‰**
686
+
687
+ If you find this package useful, please consider giving it a ⭐ on [GitHub](https://github.com/ayush1852017/smart-rte)!
@@ -11,6 +11,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
11
11
  const [imageOverlay, setImageOverlay] = useState(null);
12
12
  const resizingRef = useRef(null);
13
13
  const draggedImageRef = useRef(null);
14
+ const tableResizeRef = useRef(null);
14
15
  const [showTableDialog, setShowTableDialog] = useState(false);
15
16
  const [tableRows, setTableRows] = useState(3);
16
17
  const [tableCols, setTableCols] = useState(3);
@@ -194,6 +195,124 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
194
195
  window.removeEventListener("resize", onResize);
195
196
  };
196
197
  }, []);
198
+ // Table resize event listeners
199
+ useEffect(() => {
200
+ const el = editableRef.current;
201
+ if (!el)
202
+ return;
203
+ addTableResizeHandles();
204
+ const onMouseDown = (e) => {
205
+ if (!table)
206
+ return;
207
+ const target = e.target;
208
+ if (target.tagName === 'TD' || target.tagName === 'TH') {
209
+ const rect = target.getBoundingClientRect();
210
+ const rightEdge = rect.right;
211
+ const clickX = e.clientX;
212
+ if (Math.abs(clickX - rightEdge) < 5) {
213
+ e.preventDefault();
214
+ const tableElem = target.closest('table');
215
+ const colIndex = parseInt(target.getAttribute('data-col-index') || '0', 10);
216
+ if (tableElem) {
217
+ startColumnResize(tableElem, colIndex, e.clientX);
218
+ }
219
+ return;
220
+ }
221
+ const bottomEdge = rect.bottom;
222
+ const clickY = e.clientY;
223
+ if (Math.abs(clickY - bottomEdge) < 5) {
224
+ e.preventDefault();
225
+ const tableElem = target.closest('table');
226
+ const row = target.closest('tr');
227
+ if (tableElem && row) {
228
+ const rowIndex = parseInt(row.getAttribute('data-row-index') || '0', 10);
229
+ startRowResize(tableElem, rowIndex, e.clientY);
230
+ }
231
+ return;
232
+ }
233
+ }
234
+ };
235
+ const onMouseMove = (e) => {
236
+ if (tableResizeRef.current) {
237
+ handleTableResizeMove(e);
238
+ return;
239
+ }
240
+ if (!table)
241
+ return;
242
+ const target = e.target;
243
+ if (target.tagName === 'TD' || target.tagName === 'TH') {
244
+ const rect = target.getBoundingClientRect();
245
+ const clickX = e.clientX;
246
+ const clickY = e.clientY;
247
+ if (Math.abs(clickX - rect.right) < 5) {
248
+ el.style.cursor = 'col-resize';
249
+ return;
250
+ }
251
+ if (Math.abs(clickY - rect.bottom) < 5) {
252
+ el.style.cursor = 'row-resize';
253
+ return;
254
+ }
255
+ if (el.style.cursor === 'col-resize' || el.style.cursor === 'row-resize') {
256
+ el.style.cursor = '';
257
+ }
258
+ }
259
+ };
260
+ const onMouseUp = () => {
261
+ handleTableResizeEnd();
262
+ };
263
+ const onTouchStart = (e) => {
264
+ if (!table)
265
+ return;
266
+ const target = e.target;
267
+ if (target.tagName === 'TD' || target.tagName === 'TH') {
268
+ const rect = target.getBoundingClientRect();
269
+ const touch = e.touches[0];
270
+ const clickX = touch.clientX;
271
+ const clickY = touch.clientY;
272
+ if (Math.abs(clickX - rect.right) < 15) {
273
+ e.preventDefault();
274
+ const tableElem = target.closest('table');
275
+ const colIndex = parseInt(target.getAttribute('data-col-index') || '0', 10);
276
+ if (tableElem) {
277
+ startColumnResize(tableElem, colIndex, clickX);
278
+ }
279
+ return;
280
+ }
281
+ if (Math.abs(clickY - rect.bottom) < 15) {
282
+ e.preventDefault();
283
+ const tableElem = target.closest('table');
284
+ const row = target.closest('tr');
285
+ if (tableElem && row) {
286
+ const rowIndex = parseInt(row.getAttribute('data-row-index') || '0', 10);
287
+ startRowResize(tableElem, rowIndex, clickY);
288
+ }
289
+ return;
290
+ }
291
+ }
292
+ };
293
+ const onTouchMove = (e) => {
294
+ if (tableResizeRef.current) {
295
+ handleTableResizeMove(e);
296
+ }
297
+ };
298
+ const onTouchEnd = () => {
299
+ handleTableResizeEnd();
300
+ };
301
+ el.addEventListener('mousedown', onMouseDown);
302
+ window.addEventListener('mousemove', onMouseMove);
303
+ window.addEventListener('mouseup', onMouseUp);
304
+ el.addEventListener('touchstart', onTouchStart, { passive: false });
305
+ window.addEventListener('touchmove', onTouchMove, { passive: false });
306
+ window.addEventListener('touchend', onTouchEnd);
307
+ return () => {
308
+ el.removeEventListener('mousedown', onMouseDown);
309
+ window.removeEventListener('mousemove', onMouseMove);
310
+ window.removeEventListener('mouseup', onMouseUp);
311
+ el.removeEventListener('touchstart', onTouchStart);
312
+ window.removeEventListener('touchmove', onTouchMove);
313
+ window.removeEventListener('touchend', onTouchEnd);
314
+ };
315
+ }, [table]);
197
316
  const insertImageAtSelection = (src) => {
198
317
  try {
199
318
  const host = editableRef.current;
@@ -400,6 +519,20 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
400
519
  if (!node || !range)
401
520
  return;
402
521
  range.insertNode(node);
522
+ // Add resize handles to the new table
523
+ if (node instanceof HTMLTableElement) {
524
+ const tbody = node.querySelector('tbody');
525
+ if (tbody) {
526
+ const rows = Array.from(tbody.querySelectorAll('tr'));
527
+ rows.forEach((row, index) => {
528
+ row.setAttribute('data-row-index', String(index));
529
+ const cells = cellsOfRow(row);
530
+ cells.forEach((cell, cellIndex) => {
531
+ cell.setAttribute('data-col-index', String(cellIndex));
532
+ });
533
+ });
534
+ }
535
+ }
403
536
  // Move caret into first cell
404
537
  const firstCell = node.querySelector("td,th");
405
538
  if (firstCell)
@@ -741,6 +874,123 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
741
874
  applyToggle(fallbackCell);
742
875
  }
743
876
  };
877
+ // Table column and row resizing functions
878
+ const getColumnCells = (table, colIndex) => {
879
+ const tbody = table.querySelector('tbody');
880
+ if (!tbody)
881
+ return [];
882
+ const cells = [];
883
+ const rows = Array.from(tbody.querySelectorAll('tr'));
884
+ rows.forEach(row => {
885
+ const rowCells = cellsOfRow(row);
886
+ if (rowCells[colIndex]) {
887
+ cells.push(rowCells[colIndex]);
888
+ }
889
+ });
890
+ return cells;
891
+ };
892
+ const startColumnResize = (table, colIndex, clientX) => {
893
+ const cells = getColumnCells(table, colIndex);
894
+ if (cells.length === 0)
895
+ return;
896
+ const firstCell = cells[0];
897
+ const currentWidth = firstCell.offsetWidth;
898
+ tableResizeRef.current = {
899
+ type: 'column',
900
+ table,
901
+ index: colIndex,
902
+ startPos: clientX,
903
+ startSize: currentWidth,
904
+ cells,
905
+ };
906
+ document.body.style.cursor = 'col-resize';
907
+ document.body.style.userSelect = 'none';
908
+ };
909
+ const startRowResize = (table, rowIndex, clientY) => {
910
+ const tbody = table.querySelector('tbody');
911
+ if (!tbody)
912
+ return;
913
+ const rows = Array.from(tbody.querySelectorAll('tr'));
914
+ const row = rows[rowIndex];
915
+ if (!row)
916
+ return;
917
+ const cells = cellsOfRow(row);
918
+ const currentHeight = row.offsetHeight;
919
+ tableResizeRef.current = {
920
+ type: 'row',
921
+ table,
922
+ index: rowIndex,
923
+ startPos: clientY,
924
+ startSize: currentHeight,
925
+ cells,
926
+ };
927
+ document.body.style.cursor = 'row-resize';
928
+ document.body.style.userSelect = 'none';
929
+ };
930
+ const handleTableResizeMove = (e) => {
931
+ const resize = tableResizeRef.current;
932
+ if (!resize)
933
+ return;
934
+ e.preventDefault();
935
+ const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
936
+ const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
937
+ if (resize.type === 'column') {
938
+ const delta = clientX - resize.startPos;
939
+ const newWidth = Math.max(60, resize.startSize + delta);
940
+ resize.cells.forEach(cell => {
941
+ cell.style.width = `${newWidth}px`;
942
+ cell.style.minWidth = `${newWidth}px`;
943
+ cell.style.maxWidth = `${newWidth}px`;
944
+ });
945
+ }
946
+ else if (resize.type === 'row') {
947
+ const delta = clientY - resize.startPos;
948
+ const newHeight = Math.max(30, resize.startSize + delta);
949
+ const tbody = resize.table.querySelector('tbody');
950
+ if (tbody) {
951
+ const rows = Array.from(tbody.querySelectorAll('tr'));
952
+ const row = rows[resize.index];
953
+ if (row) {
954
+ row.style.height = `${newHeight}px`;
955
+ resize.cells.forEach(cell => {
956
+ cell.style.height = `${newHeight}px`;
957
+ });
958
+ }
959
+ }
960
+ }
961
+ };
962
+ const handleTableResizeEnd = () => {
963
+ if (tableResizeRef.current) {
964
+ tableResizeRef.current = null;
965
+ document.body.style.cursor = '';
966
+ document.body.style.userSelect = '';
967
+ handleInput();
968
+ }
969
+ };
970
+ const addTableResizeHandles = () => {
971
+ if (!table)
972
+ return;
973
+ const el = editableRef.current;
974
+ if (!el)
975
+ return;
976
+ const tables = el.querySelectorAll('table');
977
+ tables.forEach(tableElem => {
978
+ const tbody = tableElem.querySelector('tbody');
979
+ if (!tbody)
980
+ return;
981
+ const firstRow = tbody.querySelector('tr');
982
+ if (firstRow) {
983
+ const cells = cellsOfRow(firstRow);
984
+ cells.forEach((cell, index) => {
985
+ cell.setAttribute('data-col-index', String(index));
986
+ });
987
+ }
988
+ const rows = Array.from(tbody.querySelectorAll('tr'));
989
+ rows.forEach((row, index) => {
990
+ row.setAttribute('data-row-index', String(index));
991
+ });
992
+ });
993
+ };
744
994
  return (_jsxs("div", { style: { border: "1px solid #ddd", borderRadius: 6 }, children: [_jsxs("div", { style: {
745
995
  display: "flex",
746
996
  flexWrap: "wrap",
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "smartrte-react",
3
- "version": "0.1.9",
3
+ "version": "0.1.13",
4
+ "description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
4
5
  "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "module": "dist/index.js",
@@ -9,11 +10,37 @@
9
10
  "dist",
10
11
  "README.md"
11
12
  ],
13
+ "keywords": [
14
+ "react",
15
+ "editor",
16
+ "rich-text-editor",
17
+ "wysiwyg",
18
+ "rte",
19
+ "text-editor",
20
+ "contenteditable",
21
+ "table-editor",
22
+ "latex",
23
+ "katex",
24
+ "formula-editor",
25
+ "media-manager",
26
+ "image-editor",
27
+ "react-component",
28
+ "typescript"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/ayush1852017/smart-rte.git",
33
+ "directory": "packages/react"
34
+ },
35
+ "homepage": "https://github.com/ayush1852017/smart-rte#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/ayush1852017/smart-rte/issues"
38
+ },
39
+ "author": "Smart RTE Contributors",
40
+ "license": "MIT",
12
41
  "scripts": {
13
42
  "build": "tsc -p tsconfig.json",
14
- "build:embed": "vite build",
15
- "build:all": "pnpm run build && pnpm run build:embed",
16
- "prepublishOnly": "pnpm run build:all",
43
+ "prepublishOnly": "pnpm run build",
17
44
  "dev": "pnpm build",
18
45
  "lint": "eslint . || true",
19
46
  "test": "vitest run || true",