react-lite-rich-text-editor 1.1.2 → 1.1.3

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,18 +1,35 @@
1
- # React Lite Rich Text
1
+ # React Lite Rich Text Editor
2
2
 
3
- A premium, lightweight, and highly customizable rich text editor for React.
3
+ A **premium, zero-dependency**, and industry-standard rich text editor for React.
4
4
 
5
- ![Rich Text Editor Preview](assets/preview.png)
5
+ <div align="center">
6
+ <a href="https://elangodev.com/npm">
7
+ <img src="https://img.shields.io/badge/TRY%20IT-LIVE%20DEMO-blueviolet?style=for-the-badge&logo=rocket" alt="Try it out" />
8
+ </a>
9
+ <a href="https://www.npmjs.com/package/react-lite-rich-text-editor">
10
+ <img src="https://img.shields.io/npm/v/react-lite-rich-text-editor?style=for-the-badge" alt="NPM Version" />
11
+ </a>
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## Try it Out
17
+
18
+ - **Live Demo**: [Industrial Rich Text Editor Demo](https://elangodev.com/npm)
19
+ - **Local Preview**: Clone this repo and run `npm install && npm run dev` inside the `example/` folder.
20
+
21
+ ---
6
22
 
7
23
  ## Features
8
24
 
9
- - ✨ **Premium UI**: Modern, glassmorphism-inspired design with smooth transitions.
10
- - 📝 **Rich Formatting**: Bold, italic, underline, font sizes, colors, and line heights.
11
- - 🔗 **Smart Links**: Automatic protocol handling (prepends `https://`) and new window navigation.
12
- - 🖼️ **Image Support**: Easy image uploads with delete functionality.
13
- - 🎨 **Vanilla CSS**: Premium, dependency-free styling with a modern aesthetic.
14
- - **Lightweight**: Zero-dependency core (except for React and Lucide-style icons).
15
- - 🔍 **HTML Preview**: Real-time access to the underlying HTML content.
25
+ - ✨ **Zero-Dependency Core**: Built entirely with native Browser APIs for maximum performance and stability.
26
+ - 📊 **Advanced Tables**: Insert tables, add/delete rows/columns, and merge cells with intuitive toolbar controls.
27
+ - 🎥 **Universal Video Embedding**: Seamlessly embed videos from **YouTube, Vimeo, DailyMotion**, and more.
28
+ - 📏 **Image Resizing**: Interactive 4-handle resizing system for uploaded images.
29
+ - 🧮 **Content Metrics**: Stealthy, professional footer showing real-time **Word and Character counts**.
30
+ - 📝 **Rich Formatting**: Bold, italic, underline, font sizes, colors, alignment, and lists.
31
+ - 🔗 **Smart Links**: Automatic protocol handling and new window navigation.
32
+ - 🎨 **Premium UI**: Modern, glassmorphism-inspired design with a polished Look & Feel.
16
33
 
17
34
  ## Installation
18
35
 
package/dist/index.cjs.js CHANGED
@@ -526,11 +526,11 @@ var FaTable = function FaTable(_ref14) {
526
526
  }
527
527
  });
528
528
  };
529
- var FaYoutube = function FaYoutube(_ref15) {
530
- var className = _ref15.className,
531
- size = _ref15.size,
532
- color = _ref15.color,
533
- style = _ref15.style;
529
+ var FaObjectGroup = function FaObjectGroup(_ref16) {
530
+ var className = _ref16.className,
531
+ size = _ref16.size,
532
+ color = _ref16.color,
533
+ style = _ref16.style;
534
534
  return /*#__PURE__*/React.createElement("span", {
535
535
  className: className,
536
536
  style: _objectSpread2({
@@ -541,15 +541,15 @@ var FaYoutube = function FaYoutube(_ref15) {
541
541
  color: color || 'inherit'
542
542
  }, style),
543
543
  dangerouslySetInnerHTML: {
544
- __html: "<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 576 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.781 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z\"></path></svg>"
544
+ __html: "<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 512 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M480 320h-48v48c0 17.7-14.3 32-32 32h-48v32c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V160c0-26.5 21.5-48 48-48h32v-48c0-17.7 14.3-32 32-32h48V0h32c26.5 0 48 21.5 48 48v48h48c17.7 0 32 14.3 32 32v48h32c26.5 0 48 21.5 48 48v128c0 26.5-21.5 48-48 48z\"></path></svg>"
545
545
  }
546
546
  });
547
547
  };
548
- var FaObjectGroup = function FaObjectGroup(_ref16) {
549
- var className = _ref16.className,
550
- size = _ref16.size,
551
- color = _ref16.color,
552
- style = _ref16.style;
548
+ var FaTrash = function FaTrash(_ref17) {
549
+ var className = _ref17.className,
550
+ size = _ref17.size,
551
+ color = _ref17.color,
552
+ style = _ref17.style;
553
553
  return /*#__PURE__*/React.createElement("span", {
554
554
  className: className,
555
555
  style: _objectSpread2({
@@ -560,15 +560,15 @@ var FaObjectGroup = function FaObjectGroup(_ref16) {
560
560
  color: color || 'inherit'
561
561
  }, style),
562
562
  dangerouslySetInnerHTML: {
563
- __html: "<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 512 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M480 320h-48v48c0 17.7-14.3 32-32 32h-48v32c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V160c0-26.5 21.5-48 48-48h32v-48c0-17.7 14.3-32 32-32h48V0h32c26.5 0 48 21.5 48 48v48h48c17.7 0 32 14.3 32 32v48h32c26.5 0 48 21.5 48 48v128c0 26.5-21.5 48-48 48z\"></path></svg>"
563
+ __html: "<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 448 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z\"></path></svg>"
564
564
  }
565
565
  });
566
566
  };
567
- var FaTrash = function FaTrash(_ref17) {
568
- var className = _ref17.className,
569
- size = _ref17.size,
570
- color = _ref17.color,
571
- style = _ref17.style;
567
+ var FaVideo = function FaVideo(_ref18) {
568
+ var className = _ref18.className,
569
+ size = _ref18.size,
570
+ color = _ref18.color,
571
+ style = _ref18.style;
572
572
  return /*#__PURE__*/React.createElement("span", {
573
573
  className: className,
574
574
  style: _objectSpread2({
@@ -579,7 +579,7 @@ var FaTrash = function FaTrash(_ref17) {
579
579
  color: color || 'inherit'
580
580
  }, style),
581
581
  dangerouslySetInnerHTML: {
582
- __html: "<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 448 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z\"></path></svg>"
582
+ __html: '<svg stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 576 512\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM559.1 99.8c10.4 5.6 16.9 16.3 16.9 28.2V384c0 11.9-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1V174.9l14.2-9.5 96-64c9.9-6.6 22.6-7.1 32.9-1.6z\"></path></svg>'
583
583
  }
584
584
  });
585
585
  };
@@ -769,12 +769,12 @@ function RichTextEditor(_ref) {
769
769
  setIsUploading = _useState30[1];
770
770
  var _useState31 = React.useState(false),
771
771
  _useState32 = _slicedToArray(_useState31, 2),
772
- youtubeModalOpen = _useState32[0],
773
- setYoutubeModalOpen = _useState32[1];
772
+ videoModalOpen = _useState32[0],
773
+ setVideoModalOpen = _useState32[1];
774
774
  var _useState33 = React.useState(""),
775
775
  _useState34 = _slicedToArray(_useState33, 2),
776
- youtubeUrl = _useState34[0],
777
- setYoutubeUrl = _useState34[1];
776
+ videoUrl = _useState34[0],
777
+ setVideoUrl = _useState34[1];
778
778
  var _useState35 = React.useState(false),
779
779
  _useState36 = _slicedToArray(_useState35, 2),
780
780
  tableModalOpen = _useState36[0],
@@ -815,6 +815,13 @@ function RichTextEditor(_ref) {
815
815
  setSelectedImageUrl("");
816
816
  setZoomLevel(1);
817
817
  };
818
+ var saveSelection = function saveSelection() {
819
+ if (typeof window === "undefined") return;
820
+ var sel = window.getSelection();
821
+ if (sel && sel.rangeCount > 0) {
822
+ selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
823
+ }
824
+ };
818
825
  var handleZoomIn = function handleZoomIn() {
819
826
  setZoomLevel(function (prevZoom) {
820
827
  return prevZoom + 0.1;
@@ -1232,13 +1239,35 @@ function RichTextEditor(_ref) {
1232
1239
  setTableModalOpen(false);
1233
1240
  triggerChange && triggerChange();
1234
1241
  };
1235
- var insertYoutube = function insertYoutube() {
1236
- var url = youtubeUrl.trim();
1237
- // More robust regex for various YouTube formats
1238
- var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|watch\?vi=|\&vi=)([^#\&\?]*).*/;
1239
- var match = url.match(regExp);
1240
- var videoId = match && match[2].length === 11 ? match[2] : null;
1241
- if (videoId) {
1242
+ var parseVideoUrl = function parseVideoUrl(url) {
1243
+ url = url.trim();
1244
+ if (!url) return null;
1245
+
1246
+ // YouTube
1247
+ var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|watch\?vi=|\&vi=)([^#\&\?]*).*/;
1248
+ var ytMatch = url.match(ytRegExp);
1249
+ if (ytMatch && ytMatch[2].length === 11) {
1250
+ return "https://www.youtube.com/embed/".concat(ytMatch[2]);
1251
+ }
1252
+
1253
+ // Vimeo
1254
+ var vimeoRegExp = /vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)/;
1255
+ var vimeoMatch = url.match(vimeoRegExp);
1256
+ if (vimeoMatch && vimeoMatch[3]) {
1257
+ return "https://player.vimeo.com/video/".concat(vimeoMatch[3]);
1258
+ }
1259
+
1260
+ // DailyMotion
1261
+ var dmRegExp = /dailymotion\.com\/video\/([a-zA-Z0-9]+)/;
1262
+ var dmMatch = url.match(dmRegExp);
1263
+ if (dmMatch && dmMatch[1]) {
1264
+ return "https://www.dailymotion.com/embed/video/".concat(dmMatch[1]);
1265
+ }
1266
+ return null;
1267
+ };
1268
+ var insertVideo = function insertVideo() {
1269
+ var embedUrl = parseVideoUrl(videoUrl);
1270
+ if (embedUrl) {
1242
1271
  if (editorRef.current) {
1243
1272
  editorRef.current.focus();
1244
1273
  }
@@ -1247,24 +1276,23 @@ function RichTextEditor(_ref) {
1247
1276
  sel.removeAllRanges();
1248
1277
  sel.addRange(selectionRangeRef.current);
1249
1278
  }
1250
- var embedHtml = "<div class=\"video-container\">\n <iframe \n src=\"https://www.youtube.com/embed/".concat(videoId, "\" \n frameborder=\"0\" \n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" \n allowfullscreen\n ></iframe>\n </div><p>&nbsp;</p>");
1279
+ var embedHtml = "<div class=\"video-container\">\n <iframe \n src=\"".concat(embedUrl, "\" \n frameborder=\"0\" \n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" \n allowfullscreen\n ></iframe>\n </div><p>&nbsp;</p>");
1251
1280
  try {
1252
1281
  document.execCommand("insertHTML", false, embedHtml);
1253
1282
  } catch (err) {
1254
- console.error("Failed to insert YouTube HTML:", err);
1255
- // Fallback: append to the end of the editor
1283
+ console.error("Failed to insert Video HTML:", err);
1256
1284
  if (editorRef.current) {
1257
1285
  var div = document.createElement('div');
1258
1286
  div.innerHTML = embedHtml;
1259
1287
  editorRef.current.appendChild(div);
1260
1288
  }
1261
1289
  }
1290
+ setVideoModalOpen(false);
1291
+ setVideoUrl("");
1292
+ triggerChange && triggerChange();
1262
1293
  } else {
1263
- console.warn("Invalid YouTube URL or Video ID not found");
1294
+ console.warn("Invalid Video URL or Platform not supported");
1264
1295
  }
1265
- setYoutubeModalOpen(false);
1266
- setYoutubeUrl("");
1267
- triggerChange && triggerChange();
1268
1296
  };
1269
1297
  var processExistingImages = function processExistingImages(container) {
1270
1298
  if (!container) return;
@@ -2337,17 +2365,14 @@ function RichTextEditor(_ref) {
2337
2365
  size: 14
2338
2366
  })), /*#__PURE__*/React.createElement("button", {
2339
2367
  type: "button",
2340
- title: "Embed YouTube Video",
2341
- className: "rte-toolbar-button",
2368
+ title: "Embed Video (YouTube, Vimeo, etc.)",
2369
+ className: "rte-toolbar-button ".concat(videoModalOpen ? 'active' : ''),
2342
2370
  onMouseDown: function onMouseDown(e) {
2343
2371
  e.preventDefault();
2344
- var sel = window.getSelection();
2345
- if (sel && sel.rangeCount > 0) {
2346
- selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
2347
- }
2348
- setYoutubeModalOpen(true);
2372
+ saveSelection();
2373
+ setVideoModalOpen(true);
2349
2374
  }
2350
- }, /*#__PURE__*/React.createElement(FaYoutube, {
2375
+ }, /*#__PURE__*/React.createElement(FaVideo, {
2351
2376
  size: 14
2352
2377
  })), function () {
2353
2378
  if (typeof window === "undefined") return null;
@@ -2395,14 +2420,7 @@ function RichTextEditor(_ref) {
2395
2420
  e.preventDefault();
2396
2421
  tableAction('addColAfter');
2397
2422
  }
2398
- }, "+C\u2192"), /*#__PURE__*/React.createElement("div", {
2399
- style: {
2400
- width: '1px',
2401
- height: '20px',
2402
- backgroundColor: '#f3f4f6',
2403
- margin: '0 4px'
2404
- }
2405
- }), /*#__PURE__*/React.createElement("button", {
2423
+ }, "+C\u2192"), /*#__PURE__*/React.createElement("button", {
2406
2424
  type: "button",
2407
2425
  title: "Merge Cells (Right)",
2408
2426
  className: "rte-toolbar-button",
@@ -2412,17 +2430,22 @@ function RichTextEditor(_ref) {
2412
2430
  }
2413
2431
  }, /*#__PURE__*/React.createElement(FaObjectGroup, {
2414
2432
  size: 14
2415
- })), /*#__PURE__*/React.createElement("button", {
2433
+ })), /*#__PURE__*/React.createElement("div", {
2434
+ style: {
2435
+ display: 'flex',
2436
+ gap: '10px'
2437
+ }
2438
+ }, /*#__PURE__*/React.createElement("button", {
2416
2439
  type: "button",
2417
2440
  title: "Delete Row",
2418
- className: "rte-toolbar-button rte-toolbar-button-danger ",
2441
+ className: "rte-toolbar-button rte-toolbar-button-danger",
2419
2442
  onMouseDown: function onMouseDown(e) {
2420
2443
  e.preventDefault();
2421
2444
  tableAction('deleteRow');
2422
2445
  }
2423
2446
  }, /*#__PURE__*/React.createElement(FaTrash, {
2424
2447
  size: 12
2425
- }), " ", /*#__PURE__*/React.createElement("span", {
2448
+ }), /*#__PURE__*/React.createElement("span", {
2426
2449
  style: {
2427
2450
  fontSize: '10px'
2428
2451
  }
@@ -2436,10 +2459,9 @@ function RichTextEditor(_ref) {
2436
2459
  }
2437
2460
  }, /*#__PURE__*/React.createElement(FaTrash, {
2438
2461
  size: 12
2439
- }), " ", /*#__PURE__*/React.createElement("span", {
2462
+ }), /*#__PURE__*/React.createElement("span", {
2440
2463
  style: {
2441
- fontSize: '10px',
2442
- marginRight: "5px"
2464
+ fontSize: '10px'
2443
2465
  }
2444
2466
  }, "Col")), /*#__PURE__*/React.createElement("button", {
2445
2467
  type: "button",
@@ -2450,12 +2472,12 @@ function RichTextEditor(_ref) {
2450
2472
  tableAction('deleteTable');
2451
2473
  }
2452
2474
  }, /*#__PURE__*/React.createElement(FaTrash, {
2453
- size: 14
2454
- }), " ", /*#__PURE__*/React.createElement("span", {
2475
+ size: 12
2476
+ }), /*#__PURE__*/React.createElement("span", {
2455
2477
  style: {
2456
- fontWeight: '600'
2478
+ fontSize: '10px'
2457
2479
  }
2458
- }, "Table")));
2480
+ }, "Table"))));
2459
2481
  }
2460
2482
  return null;
2461
2483
  }()), /*#__PURE__*/React.createElement("div", {
@@ -2603,10 +2625,10 @@ function RichTextEditor(_ref) {
2603
2625
  type: "button",
2604
2626
  className: "rte-button rte-button-primary",
2605
2627
  onClick: insertTable
2606
- }, "Insert")))), youtubeModalOpen && /*#__PURE__*/React.createElement("div", {
2628
+ }, "Insert")))), videoModalOpen && /*#__PURE__*/React.createElement("div", {
2607
2629
  className: "rte-modal-overlay",
2608
2630
  onClick: function onClick() {
2609
- return setYoutubeModalOpen(false);
2631
+ return setVideoModalOpen(false);
2610
2632
  }
2611
2633
  }, /*#__PURE__*/React.createElement("div", {
2612
2634
  className: "rte-modal",
@@ -2617,22 +2639,26 @@ function RichTextEditor(_ref) {
2617
2639
  className: "rte-modal-header"
2618
2640
  }, /*#__PURE__*/React.createElement("h3", {
2619
2641
  className: "rte-modal-title"
2620
- }, "Embed YouTube Video")), /*#__PURE__*/React.createElement("div", {
2642
+ }, "Embed Video")), /*#__PURE__*/React.createElement("div", {
2621
2643
  className: "rte-form-group"
2622
2644
  }, /*#__PURE__*/React.createElement("label", {
2623
2645
  className: "rte-label"
2624
- }, "Paste YouTube Video URL"), /*#__PURE__*/React.createElement("input", {
2646
+ }, "Paste Video URL (YouTube, Vimeo...) ", /*#__PURE__*/React.createElement("span", {
2647
+ style: {
2648
+ color: '#ef4444'
2649
+ }
2650
+ }, "*")), /*#__PURE__*/React.createElement("input", {
2625
2651
  type: "text",
2626
2652
  className: "rte-input",
2627
- value: youtubeUrl,
2653
+ value: videoUrl,
2628
2654
  onChange: function onChange(e) {
2629
- return setYoutubeUrl(e.target.value);
2655
+ return setVideoUrl(e.target.value);
2630
2656
  },
2631
- placeholder: "https://www.youtube.com/watch?v=...",
2657
+ placeholder: "Paste URL here...",
2632
2658
  autoFocus: true,
2633
2659
  onKeyDown: function onKeyDown(e) {
2634
- if (e.key === 'Enter') insertYoutube();
2635
- if (e.key === 'Escape') setYoutubeModalOpen(false);
2660
+ if (e.key === 'Enter' && videoUrl.trim()) insertVideo();
2661
+ if (e.key === 'Escape') setVideoModalOpen(false);
2636
2662
  }
2637
2663
  })), /*#__PURE__*/React.createElement("div", {
2638
2664
  className: "rte-modal-actions"
@@ -2640,12 +2666,17 @@ function RichTextEditor(_ref) {
2640
2666
  type: "button",
2641
2667
  className: "rte-button rte-button-secondary",
2642
2668
  onClick: function onClick() {
2643
- return setYoutubeModalOpen(false);
2669
+ return setVideoModalOpen(false);
2644
2670
  }
2645
2671
  }, "Cancel"), /*#__PURE__*/React.createElement("button", {
2646
2672
  type: "button",
2647
2673
  className: "rte-button rte-button-primary",
2648
- onClick: insertYoutube
2674
+ onClick: function onClick() {
2675
+ if (videoUrl.trim()) {
2676
+ insertVideo();
2677
+ }
2678
+ },
2679
+ disabled: !videoUrl.trim()
2649
2680
  }, "Embed Video")))), imageModalOpen && /*#__PURE__*/React.createElement("div", {
2650
2681
  className: "rte-modal-overlay",
2651
2682
  onClick: closeImageModal