react-lite-rich-text-editor 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -388,7 +388,7 @@ function styleInject(css, ref) {
388
388
  }
389
389
  }
390
390
 
391
- var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);height:0;margin:16px 0;overflow:hidden;padding-bottom:56.25%;position:relative}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;outline:none;padding:8px 12px;transition:all .15s ease;width:93%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{display:inline-block;line-height:0;margin:15px;max-width:100%;position:relative}.image-container.image-align-center{display:block;margin:24px auto;text-align:center}.image-container.image-align-left,.image-container.image-align-right{display:block}.image-container.image-align-left,.image-container.image-align-right{margin:15px 0}.image-container.image-align-left img{margin-left:0!important;margin-right:auto!important}.image-container.image-align-center img{margin-left:auto!important;margin-right:auto!important}.image-container.image-align-right img{margin-left:auto!important;margin-right:0!important}.image-container img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-container.image-small img{width:50%!important}.image-delete-button{align-items:center;background:#ef4444;border:3px solid #fff;border-radius:9999px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#fff;cursor:pointer;display:flex;font-size:18px;font-weight:700;height:26px;justify-content:center;line-height:1;padding:0;position:absolute;right:0;top:0;transform:translate(50%,-50%);transition:all .2s cubic-bezier(.4,0,.2,1);width:26px;z-index:50}.image-delete-button:hover{background:#b91c1c;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);transform:translate(50%,-50%) scale(1.1)}.rte-table-delete-btn,.rte-table-delete-hover{align-items:center;display:flex;justify-content:center}.rte-table-delete-btn{background:#fff;border:1px solid #ef4444;border-radius:6px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#ef4444;cursor:pointer;height:28px;padding:0;transition:all .2s ease;width:28px}.rte-table-delete-btn:hover{background:#ef4444;color:#fff;transform:scale(1.1)}.rte-table-delete-btn:active{transform:scale(.95)}.rte-image-toolbar{background:#fff!important;border:1px solid #e5e7eb!important;border-radius:8px!important;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;display:flex;gap:4px!important;padding:4px!important;pointer-events:auto!important}.rte-image-toolbar button{align-items:center;background:transparent;border:none;border-radius:4px;color:#4b5563;cursor:pointer;display:flex;font-size:11px;font-weight:600;height:28px;justify-content:center;min-width:32px;padding:0 4px;transition:all .15s ease}.rte-image-toolbar button:hover{background-color:#f3f4f6;color:#111827}.rte-image-toolbar button.danger{color:#ef4444!important}.rte-image-toolbar button.danger:hover{background-color:#fef2f2!important}.image-container:after{clear:both;content:\"\";display:table}.rte-footer{background-color:#fcfcfd;border-top:1px solid #f3f4f6;display:flex;justify-content:flex-end;padding:6px 16px;user-select:none}.rte-footer-content{align-items:center;color:#9ca3af;display:flex;font-size:11px;gap:10px;letter-spacing:.025em}.rte-footer-separator{color:#e5e7eb;font-size:14px;line-height:1}.rte-footer-item b{color:#6b7280;font-weight:600}";
391
+ var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;outline:none;padding:8px 12px;transition:all .15s ease;width:93%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{display:block;line-height:0;margin:16px 0;max-width:100%;width:fit-content}.image-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right{margin-left:auto;margin-right:0}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:fit-content}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-media-frame[style*=width] img{width:100%}.image-media-frame[data-explicit-height=true] img,.image-media-frame[style*=height] img{height:100%;object-fit:contain}.image-container.image-small .image-media-frame img{width:50%!important}.image-delete-button{align-items:center;background:#ef4444;border:3px solid #fff;border-radius:9999px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#fff;cursor:pointer;display:none;font-size:18px;font-weight:700;height:26px;justify-content:center;line-height:1;padding:0;pointer-events:none;position:absolute;right:0;top:0;transform:translate(50%,-50%);transition:all .2s cubic-bezier(.4,0,.2,1);width:26px;z-index:50}.rte-content.rte-is-editable.rte-is-focused .image-delete-button,.rte-content.rte-is-editable.rte-is-focused .video-delete-button{display:flex;pointer-events:auto}.image-delete-button:hover{background:#b91c1c;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);transform:translate(50%,-50%) scale(1.1)}.media-resize-handles{inset:0;pointer-events:none;position:absolute;z-index:55}.media-resize-handle{background:#dbeafe;border:2px solid #fff;border-radius:4px;box-shadow:0 1px 3px rgba(15,23,42,.12);pointer-events:auto;position:absolute;z-index:60}.media-resize-handle:hover{background:#bfdbfe}.media-resize-handle-left,.media-resize-handle-right{cursor:ew-resize;height:28px;top:50%;transform:translateY(-50%);width:10px}.media-resize-handle-right{right:-5px}.media-resize-handle-left{left:-5px}.media-resize-handle-bottom,.media-resize-handle-top{cursor:ns-resize;height:10px;left:50%;transform:translateX(-50%);width:28px}.media-resize-handle-top{top:-5px}.media-resize-handle-bottom{bottom:-5px}.video-container .media-resize-handle-right{right:4px}.video-container .media-resize-handle-left{left:4px}.video-container .media-resize-handle-top{top:4px}.video-container .media-resize-handle-bottom{bottom:4px}.video-container .video-delete-button{z-index:70}.rte-table-delete-btn,.rte-table-delete-hover{align-items:center;display:flex;justify-content:center}.rte-table-delete-btn{background:#fff;border:1px solid #ef4444;border-radius:6px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#ef4444;cursor:pointer;height:28px;padding:0;transition:all .2s ease;width:28px}.rte-table-delete-btn:hover{background:#ef4444;color:#fff;transform:scale(1.1)}.rte-table-delete-btn:active{transform:scale(.95)}.rte-image-toolbar{background:#fff!important;border:1px solid #e5e7eb!important;border-radius:8px!important;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;display:flex;gap:4px!important;padding:4px!important;pointer-events:auto!important}.rte-image-toolbar button{align-items:center;background:transparent;border:none;border-radius:4px;color:#4b5563;cursor:pointer;display:flex;font-size:11px;font-weight:600;height:28px;justify-content:center;min-width:32px;padding:0 4px;transition:all .15s ease}.rte-image-toolbar button:hover{background-color:#f3f4f6;color:#111827}.rte-image-toolbar button.danger{color:#ef4444!important}.rte-image-toolbar button.danger:hover{background-color:#fef2f2!important}.image-container:after{clear:both;content:\"\";display:table}.rte-footer{background-color:#fcfcfd;border-top:1px solid #f3f4f6;display:flex;justify-content:flex-end;padding:6px 16px;user-select:none}.rte-footer-content{align-items:center;color:#9ca3af;display:flex;font-size:11px;gap:10px;letter-spacing:.025em}.rte-footer-separator{color:#e5e7eb;font-size:14px;line-height:1}.rte-footer-item b{color:#6b7280;font-weight:600}";
392
392
  styleInject(css_248z);
393
393
 
394
394
  // Helper functions for HTML escaping
@@ -416,9 +416,6 @@ function RichTextEditor({
416
416
  maxHeight,
417
417
  onImageUpload
418
418
  }) {
419
- if (isLoading) {
420
- return /*#__PURE__*/React.createElement(Spinner, null);
421
- }
422
419
  const editorRef = useRef(null);
423
420
  const fileInputRef = useRef(null);
424
421
  const scrollTopRef = useRef(0);
@@ -428,6 +425,7 @@ function RichTextEditor({
428
425
  const [linkText, setLinkText] = useState("");
429
426
  const selectionRangeRef = useRef(null);
430
427
  const [editable, setEditable] = useState(initialEditable);
428
+ const [editorFocused, setEditorFocused] = useState(false);
431
429
  const lastSynchronizedHtmlRef = useRef("");
432
430
  useEffect(() => {
433
431
  setEditable(initialEditable);
@@ -533,40 +531,25 @@ function RichTextEditor({
533
531
  window.removeEventListener('wheel', handleWheel);
534
532
  };
535
533
  }, [imageModalOpen]);
536
- useEffect(() => {
537
- const handleClick = e => {
538
- // Trigger selection update for toolbar reactivity
539
- setSelectionVersion(v => v + 1);
540
- const deleteBtn = e.target.closest('button[title="Remove image"]');
541
- if (deleteBtn && editable) {
542
- e.preventDefault();
543
- e.stopPropagation();
544
- const wrapper = deleteBtn.closest('.image-container');
545
- if (wrapper && wrapper.parentNode) {
546
- wrapper.parentNode.removeChild(wrapper);
547
- triggerChange && triggerChange();
548
- }
549
- }
550
- };
551
- const editor = editorRef.current;
552
- if (editor) {
553
- editor.addEventListener('click', handleClick);
554
- return () => {
555
- editor.removeEventListener('click', handleClick);
556
- };
557
- }
558
- // Removed dependency on editable to minimize listener churn
559
- }, []);
560
534
  useEffect(() => {
561
535
  if (editorRef.current && value && value !== lastSynchronizedHtmlRef.current) {
562
- requestAnimationFrame(() => processExistingImages(editorRef.current));
536
+ requestAnimationFrame(() => processExistingMedia(editorRef.current));
563
537
  }
564
538
  }, [value]);
565
539
 
566
540
  // Runs whenever editable changes (toggles delete icon visibility)
567
541
  useEffect(() => {
568
- processExistingImages(editorRef.current);
542
+ if (!editable) {
543
+ setEditorFocused(false);
544
+ }
545
+ processExistingMedia(editorRef.current);
569
546
  }, [editable]);
547
+ useEffect(() => {
548
+ if (!editorRef.current) return;
549
+ editorRef.current.querySelectorAll(".image-container, .video-container").forEach(container => {
550
+ updateMediaControlVisibility(container);
551
+ });
552
+ }, [editorFocused, editable]);
570
553
  useEffect(() => {
571
554
  // Only update if value is different from our last known synced state
572
555
  if (value && value !== lastSynchronizedHtmlRef.current) {
@@ -590,6 +573,7 @@ function RichTextEditor({
590
573
  if (editorRef.current && editorRef.current.innerHTML !== newContent) {
591
574
  editorRef.current.innerHTML = newContent;
592
575
  }
576
+ requestAnimationFrame(() => processExistingMedia(editorRef.current));
593
577
  updateMetrics();
594
578
  }
595
579
  } catch (e) {
@@ -604,6 +588,17 @@ function RichTextEditor({
604
588
  }
605
589
  }
606
590
  }, [value, initialEditable, updateMetrics]);
591
+ const processExistingMedia = container => {
592
+ if (!container) return;
593
+ processExistingImages(container);
594
+ processExistingVideos(container);
595
+ };
596
+ const getCleanHtml = () => {
597
+ if (!editorRef.current) return "";
598
+ const clone = editorRef.current.cloneNode(true);
599
+ stripEditorChrome(clone);
600
+ return clone.innerHTML;
601
+ };
607
602
 
608
603
  // Trigger change manually
609
604
  const triggerChange = useCallback(() => {
@@ -647,14 +642,220 @@ function RichTextEditor({
647
642
  return hex.length === 1 ? "0" + hex : hex;
648
643
  }).join("");
649
644
  }
645
+ const getColorAtCursor = () => {
646
+ const sel = window.getSelection();
647
+ if (!sel || !sel.rangeCount || !editorRef.current) return null;
648
+ let node = sel.anchorNode;
649
+ if (node.nodeType === 3) node = node.parentNode;
650
+ while (node && node !== editorRef.current) {
651
+ if (node.nodeType === 1) {
652
+ if (node.style && node.style.color) {
653
+ return rgbToHex(node.style.color);
654
+ }
655
+ if (node.tagName === "FONT" && node.getAttribute("color")) {
656
+ return node.getAttribute("color");
657
+ }
658
+ }
659
+ node = node.parentNode;
660
+ }
661
+ const computedColor = window.getComputedStyle(sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode).color;
662
+ return rgbToHex(computedColor);
663
+ };
664
+ const stripEditorChrome = root => {
665
+ root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handles, .media-resize-handle").forEach(element => element.remove());
666
+ return root;
667
+ };
668
+ const getMediaSizeLimits = () => {
669
+ const maxWidth = editorRef.current ? editorRef.current.getBoundingClientRect().width - 24 : 800;
670
+ return {
671
+ minWidth: 120,
672
+ minHeight: 80,
673
+ maxWidth,
674
+ maxHeight: 720
675
+ };
676
+ };
677
+ const ensureImageMediaFrame = imageContainer => {
678
+ if (!imageContainer) return null;
679
+ let frame = imageContainer.querySelector(":scope > .image-media-frame");
680
+ if (frame) return frame;
681
+ frame = document.createElement("div");
682
+ frame.className = "image-media-frame";
683
+ ["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
684
+ if (imageContainer.style[prop]) {
685
+ frame.style[prop] = imageContainer.style[prop];
686
+ imageContainer.style[prop] = "";
687
+ }
688
+ });
689
+ if (imageContainer.dataset.explicitHeight) {
690
+ frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
691
+ delete imageContainer.dataset.explicitHeight;
692
+ }
693
+ const children = Array.from(imageContainer.children);
694
+ imageContainer.appendChild(frame);
695
+ children.forEach(child => frame.appendChild(child));
696
+ return frame;
697
+ };
698
+ const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
699
+ const applyImageMediaSize = (frame, width, height, edge) => {
700
+ const img = frame.querySelector("img");
701
+ const isVertical = edge === "top" || edge === "bottom";
702
+ frame.style.width = `${Math.round(width)}px`;
703
+ frame.style.maxWidth = "100%";
704
+ if (isVertical) {
705
+ frame.style.height = `${Math.round(height)}px`;
706
+ frame.dataset.explicitHeight = "true";
707
+ if (img) {
708
+ img.style.width = "100%";
709
+ img.style.height = "100%";
710
+ img.style.objectFit = "contain";
711
+ }
712
+ return;
713
+ }
714
+ if (!frame.dataset.explicitHeight) {
715
+ frame.style.height = "";
716
+ }
717
+ if (img) {
718
+ img.style.width = "100%";
719
+ if (frame.dataset.explicitHeight) {
720
+ img.style.height = "100%";
721
+ img.style.objectFit = "contain";
722
+ } else {
723
+ img.style.height = "auto";
724
+ img.style.objectFit = "";
725
+ }
726
+ }
727
+ };
728
+ const applyVideoMediaSize = (container, width, height) => {
729
+ container.style.paddingBottom = "0";
730
+ container.style.width = `${Math.round(width)}px`;
731
+ container.style.maxWidth = "100%";
732
+ container.style.height = `${Math.round(height)}px`;
733
+ };
734
+ const attachMediaResizeHandle = container => {
735
+ if (!container || container.querySelector(".media-resize-handles")) return;
736
+ const isVideo = container.classList.contains("video-container");
737
+ const resizeTarget = isVideo ? container : getImageMediaTarget(container);
738
+ if (!resizeTarget) return;
739
+ const handlesWrapper = document.createElement("div");
740
+ handlesWrapper.className = "media-resize-handles";
741
+ handlesWrapper.setAttribute("contenteditable", "false");
742
+ const edges = [{
743
+ edge: "left",
744
+ title: "Drag to resize width"
745
+ }, {
746
+ edge: "right",
747
+ title: "Drag to resize width"
748
+ }, {
749
+ edge: "top",
750
+ title: "Drag to resize height"
751
+ }, {
752
+ edge: "bottom",
753
+ title: "Drag to resize height"
754
+ }];
755
+ edges.forEach(({
756
+ edge,
757
+ title
758
+ }) => {
759
+ const handle = document.createElement("div");
760
+ handle.className = `media-resize-handle media-resize-handle-${edge}`;
761
+ handle.title = title;
762
+ handle.setAttribute("contenteditable", "false");
763
+ handle.dataset.edge = edge;
764
+ handle.addEventListener("mousedown", event => {
765
+ if (!editable) return;
766
+ event.preventDefault();
767
+ event.stopPropagation();
768
+ const limits = getMediaSizeLimits();
769
+ const rect = resizeTarget.getBoundingClientRect();
770
+ const startX = event.clientX;
771
+ const startY = event.clientY;
772
+ const startWidth = rect.width;
773
+ const startHeight = rect.height;
774
+ const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
775
+ const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
776
+ if (isVideo) {
777
+ resizeTarget.style.paddingBottom = "0";
778
+ }
779
+ const onMouseMove = moveEvent => {
780
+ const deltaX = moveEvent.clientX - startX;
781
+ const deltaY = moveEvent.clientY - startY;
782
+ let nextWidth = startWidth;
783
+ let nextHeight = startHeight;
784
+ if (edge === "right") {
785
+ nextWidth = startWidth + deltaX;
786
+ } else if (edge === "left") {
787
+ nextWidth = startWidth - deltaX;
788
+ } else if (edge === "bottom") {
789
+ nextHeight = startHeight + deltaY;
790
+ } else if (edge === "top") {
791
+ nextHeight = startHeight - deltaY;
792
+ }
793
+ nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
794
+ nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
795
+ if (edge === "left") {
796
+ resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
797
+ }
798
+ if (edge === "top") {
799
+ resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
800
+ }
801
+ if (isVideo) {
802
+ applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
803
+ } else {
804
+ applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
805
+ }
806
+ };
807
+ const onMouseUp = () => {
808
+ document.removeEventListener("mousemove", onMouseMove);
809
+ document.removeEventListener("mouseup", onMouseUp);
810
+ triggerChange();
811
+ };
812
+ document.addEventListener("mousemove", onMouseMove);
813
+ document.addEventListener("mouseup", onMouseUp);
814
+ });
815
+ handlesWrapper.appendChild(handle);
816
+ });
817
+ resizeTarget.appendChild(handlesWrapper);
818
+ };
819
+ const handleEditorFocus = () => {
820
+ setEditorFocused(true);
821
+ };
822
+ const handleEditorBlur = () => {
823
+ requestAnimationFrame(() => {
824
+ var _editorRef$current;
825
+ if (!((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(document.activeElement))) {
826
+ setEditorFocused(false);
827
+ }
828
+ });
829
+ };
830
+ const updateMediaControlVisibility = container => {
831
+ const handles = container.querySelector(".media-resize-handles");
832
+ if (handles instanceof HTMLElement) {
833
+ handles.style.display = editable ? "block" : "none";
834
+ handles.style.pointerEvents = editable ? "auto" : "none";
835
+ }
836
+ };
837
+ const createMediaDeleteButton = (title, className, onRemove) => {
838
+ const deleteBtn = document.createElement("button");
839
+ deleteBtn.type = "button";
840
+ deleteBtn.innerHTML = "×";
841
+ deleteBtn.className = className;
842
+ deleteBtn.title = title;
843
+ deleteBtn.setAttribute("contenteditable", "false");
844
+ deleteBtn.onclick = event => {
845
+ event.preventDefault();
846
+ event.stopPropagation();
847
+ onRemove();
848
+ };
849
+ return deleteBtn;
850
+ };
650
851
 
651
852
  // Listen for selection changes globally to update styles and list type in one pass
652
853
  useEffect(() => {
653
854
  const handleGlobalSelectionSync = () => {
654
- var _editorRef$current;
855
+ var _editorRef$current2;
655
856
  // Only sync if the editor has focus
656
857
  const sel = window.getSelection();
657
- if (!sel || !sel.rangeCount || !((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(sel.anchorNode))) {
858
+ if (!sel || !sel.rangeCount || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(sel.anchorNode))) {
658
859
  return;
659
860
  }
660
861
 
@@ -715,6 +916,7 @@ function RichTextEditor({
715
916
  focus();
716
917
  };
717
918
  const [fontColor, setFontColor] = useState("#000000");
919
+ const getActiveTextColor = () => getColorAtCursor() || fontColor;
718
920
  const handleColorChange = color => {
719
921
  setFontColor(color);
720
922
  exec("foreColor", color);
@@ -925,58 +1127,82 @@ function RichTextEditor({
925
1127
  setVideoModalOpen(false);
926
1128
  setVideoUrl("");
927
1129
  triggerChange && triggerChange();
1130
+ requestAnimationFrame(() => processExistingMedia(editorRef.current));
928
1131
  } else {
929
1132
  console.warn("Invalid Video URL or Platform not supported");
930
1133
  }
931
1134
  };
1135
+ const processExistingVideos = container => {
1136
+ if (!container) return;
1137
+ container.querySelectorAll(".video-container").forEach(videoContainer => {
1138
+ if (!videoContainer.querySelector(".video-delete-button")) {
1139
+ const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
1140
+ videoContainer.remove();
1141
+ triggerChange && triggerChange();
1142
+ });
1143
+ videoContainer.appendChild(deleteBtn);
1144
+ }
1145
+ attachMediaResizeHandle(videoContainer);
1146
+ updateMediaControlVisibility(videoContainer);
1147
+ });
1148
+ };
932
1149
  const processExistingImages = container => {
933
1150
  if (!container) return;
934
1151
  container.querySelectorAll("img").forEach(img => {
935
- // ONLY wrap if it's not already inside a wrapper
1152
+ var _img$closest;
1153
+ if (img.closest(".rte-modal")) return;
936
1154
  const existingWrapper = img.closest(".image-container");
937
1155
  if (existingWrapper) {
938
- // Just update existing wrapper state if needed
939
- existingWrapper.style.cursor = editable ? 'pointer' : 'default';
940
- const deleteBtn = existingWrapper.querySelector('.image-delete-button');
941
- if (deleteBtn) {
942
- deleteBtn.style.display = editable ? 'flex' : 'none';
1156
+ existingWrapper.style.cursor = editable ? "pointer" : "default";
1157
+ const frame = ensureImageMediaFrame(existingWrapper);
1158
+ if (frame && !frame.querySelector(".image-delete-button")) {
1159
+ frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
1160
+ existingWrapper.remove();
1161
+ triggerChange && triggerChange();
1162
+ }));
943
1163
  }
1164
+ attachMediaResizeHandle(existingWrapper);
1165
+ updateMediaControlVisibility(existingWrapper);
944
1166
  return;
945
1167
  }
946
1168
  const wrapper = document.createElement("div");
947
- const align = img.getAttribute('data-align') || 'center';
1169
+ const align = img.getAttribute("data-align") || ((_img$closest = img.closest("[data-align]")) === null || _img$closest === void 0 ? void 0 : _img$closest.getAttribute("data-align")) || "left";
948
1170
  wrapper.className = `image-container image-align-${align}`;
949
- wrapper.style.cursor = editable ? 'pointer' : 'default';
950
- img.className = "rte-image";
951
- img.style.cssText = ""; // Reset inline styles
952
- img.setAttribute('data-align', align);
953
- img.dataset.hasDeleteButton = "true";
954
-
955
- // Add click listener to open modal
956
- img.addEventListener("click", () => openImageModal(img.src));
957
- const deleteBtn = document.createElement("button");
958
- deleteBtn.innerHTML = "×";
959
- deleteBtn.className = "image-delete-button";
960
- deleteBtn.style.display = editable ? 'flex' : 'none';
961
- deleteBtn.style.pointerEvents = editable ? 'auto' : 'none';
962
- deleteBtn.title = "Remove image";
963
- deleteBtn.onclick = e => {
964
- e.preventDefault();
965
- e.stopPropagation();
966
- const wrapper = e.currentTarget.closest(".image-container");
967
- if (wrapper && wrapper.parentNode) {
968
- wrapper.parentNode.removeChild(wrapper);
969
- triggerChange && triggerChange();
970
- }
971
- };
1171
+ wrapper.style.cursor = editable ? "pointer" : "default";
1172
+ const frame = document.createElement("div");
1173
+ frame.className = "image-media-frame";
1174
+ if (img.getAttribute("width") && !frame.style.width) {
1175
+ frame.style.width = `${img.getAttribute("width")}px`;
1176
+ } else if (img.style.width && img.style.width.endsWith("px")) {
1177
+ frame.style.width = img.style.width;
1178
+ }
1179
+ img.classList.add("rte-image");
1180
+ img.setAttribute("data-align", align);
1181
+ if (frame.style.width) {
1182
+ img.style.width = "100%";
1183
+ img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
1184
+ } else {
1185
+ img.style.width = "";
1186
+ img.style.height = "auto";
1187
+ }
1188
+ img.addEventListener("click", event => {
1189
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1190
+ openImageModal(img.src);
1191
+ });
1192
+ const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1193
+ wrapper.remove();
1194
+ triggerChange && triggerChange();
1195
+ });
972
1196
  const {
973
1197
  parentNode,
974
1198
  nextSibling
975
1199
  } = img;
976
1200
  if (parentNode) {
977
1201
  parentNode.removeChild(img);
978
- wrapper.appendChild(img);
979
- wrapper.appendChild(deleteBtn);
1202
+ frame.appendChild(img);
1203
+ frame.appendChild(deleteBtn);
1204
+ wrapper.appendChild(frame);
1205
+ attachMediaResizeHandle(wrapper);
980
1206
  if (nextSibling) {
981
1207
  parentNode.insertBefore(wrapper, nextSibling);
982
1208
  } else {
@@ -985,11 +1211,6 @@ function RichTextEditor({
985
1211
  }
986
1212
  });
987
1213
  };
988
- useEffect(() => {
989
- if (editorRef.current && value) {
990
- requestAnimationFrame(() => processExistingImages(editorRef.current));
991
- }
992
- }, [value]);
993
1214
 
994
1215
  /*
995
1216
  Advanced Tip: Use the 'onImageUpload' prop to handle file uploads to a server
@@ -1000,24 +1221,23 @@ function RichTextEditor({
1000
1221
  if (editable) {
1001
1222
  // Create container for the image
1002
1223
  const container = document.createElement('div');
1003
- container.className = 'image-container';
1224
+ container.className = 'image-container image-align-left';
1225
+ const frame = document.createElement('div');
1226
+ frame.className = 'image-media-frame';
1004
1227
 
1005
1228
  // Create image element
1006
1229
  const img = document.createElement('img');
1007
1230
  img.src = dataUrl;
1008
1231
  img.alt = fileName || "image";
1009
1232
  img.addEventListener("click", () => openImageModal(dataUrl));
1010
-
1011
- // Add elements to container
1012
- container.appendChild(img);
1233
+ frame.appendChild(img);
1234
+ container.appendChild(frame);
1013
1235
 
1014
1236
  // Insert at cursor position
1015
1237
  insertNodeAtCursor(container);
1016
- triggerChange();
1017
-
1018
- // Immediately process newly inserted image so delete button appears
1019
1238
  requestAnimationFrame(() => {
1020
- processExistingImages(editorRef.current);
1239
+ processExistingMedia(editorRef.current);
1240
+ triggerChange();
1021
1241
  });
1022
1242
  }
1023
1243
  } catch (err) {
@@ -1146,10 +1366,6 @@ function RichTextEditor({
1146
1366
  }
1147
1367
  }
1148
1368
  };
1149
- const getCleanHtml = () => {
1150
- if (!editorRef.current) return "";
1151
- return editorRef.current.innerHTML;
1152
- };
1153
1369
 
1154
1370
  // Helper function to unescape HTML entities
1155
1371
  const unescapeHtml = html => {
@@ -1158,6 +1374,29 @@ function RichTextEditor({
1158
1374
  txt.innerHTML = html;
1159
1375
  return txt.value;
1160
1376
  };
1377
+ const isCursorAtEndOfListItem = (range, listItem) => {
1378
+ const suffixRange = document.createRange();
1379
+ suffixRange.setStart(range.startContainer, range.startOffset);
1380
+ suffixRange.setEnd(listItem, listItem.childNodes.length);
1381
+ return suffixRange.toString().replace(/\u200B/g, "").length === 0;
1382
+ };
1383
+ const prepareListItemForTyping = (listItem, selection) => {
1384
+ const activeColor = getActiveTextColor();
1385
+ const newRange = document.createRange();
1386
+ if (activeColor && activeColor.toLowerCase() !== "#000000") {
1387
+ const span = document.createElement("span");
1388
+ span.style.color = activeColor;
1389
+ span.appendChild(document.createTextNode("\u200B"));
1390
+ listItem.appendChild(span);
1391
+ newRange.setStart(span.firstChild, 1);
1392
+ } else {
1393
+ listItem.appendChild(document.createTextNode("\u200B"));
1394
+ newRange.setStart(listItem.firstChild, 1);
1395
+ }
1396
+ newRange.collapse(true);
1397
+ selection.removeAllRanges();
1398
+ selection.addRange(newRange);
1399
+ };
1161
1400
  const handleKeyDown = useCallback(e => {
1162
1401
  // Handle Enter key
1163
1402
  if (e.key === 'Enter') {
@@ -1172,15 +1411,14 @@ function RichTextEditor({
1172
1411
  const listItem = parent.closest('li');
1173
1412
  if (listItem) {
1174
1413
  const list = listItem.parentNode;
1175
- list.tagName === 'OL';
1176
1414
 
1177
1415
  // Create a new list item
1178
1416
  const newItem = document.createElement('li');
1179
1417
 
1180
1418
  // If we're at the end of a list item, add a new one
1181
- if (range.collapsed && range.endOffset === node.length) {
1419
+ if (range.collapsed && isCursorAtEndOfListItem(range, listItem)) {
1182
1420
  // If it's empty, create a regular paragraph instead
1183
- if (listItem.textContent.trim() === '') {
1421
+ if (listItem.textContent.replace(/\u200B/g, '').trim() === '') {
1184
1422
  document.execCommand('insertHTML', false, '<div><br></div>');
1185
1423
  // Move the cursor to the new line
1186
1424
  const newRange = document.createRange();
@@ -1189,6 +1427,7 @@ function RichTextEditor({
1189
1427
  newRange.collapse(true);
1190
1428
  selection.removeAllRanges();
1191
1429
  selection.addRange(newRange);
1430
+ triggerChange();
1192
1431
  return;
1193
1432
  }
1194
1433
 
@@ -1198,35 +1437,29 @@ function RichTextEditor({
1198
1437
  } else {
1199
1438
  list.appendChild(newItem);
1200
1439
  }
1201
-
1202
- // Move cursor to the new list item
1203
- const newRange = document.createRange();
1204
- newRange.setStart(newItem, 0);
1205
- newRange.collapse(true);
1206
- selection.removeAllRanges();
1207
- selection.addRange(newRange);
1440
+ prepareListItemForTyping(newItem, selection);
1208
1441
  } else {
1209
- // If we're in the middle of text, split the list item
1210
- const textBefore = node.textContent.substring(0, range.startOffset);
1211
- const textAfter = node.textContent.substring(range.startOffset);
1212
-
1213
- // Update current item
1214
- node.textContent = textBefore;
1215
-
1216
- // Insert new item after current one
1217
- newItem.textContent = textAfter;
1442
+ // If we're in the middle of text, split the list item while preserving formatting
1443
+ const afterRange = document.createRange();
1444
+ afterRange.setStart(range.startContainer, range.startOffset);
1445
+ afterRange.setEnd(listItem, listItem.childNodes.length);
1446
+ const movedFragment = afterRange.extractContents();
1447
+ newItem.appendChild(movedFragment);
1218
1448
  if (listItem.nextSibling) {
1219
1449
  list.insertBefore(newItem, listItem.nextSibling);
1220
1450
  } else {
1221
1451
  list.appendChild(newItem);
1222
1452
  }
1223
-
1224
- // Move cursor to the new list item
1225
- const newRange = document.createRange();
1226
- newRange.setStart(newItem.firstChild || newItem, 0);
1227
- newRange.collapse(true);
1228
- selection.removeAllRanges();
1229
- selection.addRange(newRange);
1453
+ if (!newItem.textContent.replace(/\u200B/g, '').trim()) {
1454
+ newItem.textContent = "";
1455
+ prepareListItemForTyping(newItem, selection);
1456
+ } else {
1457
+ const newRange = document.createRange();
1458
+ newRange.setStart(newItem, 0);
1459
+ newRange.collapse(true);
1460
+ selection.removeAllRanges();
1461
+ selection.addRange(newRange);
1462
+ }
1230
1463
  }
1231
1464
  } else {
1232
1465
  // Regular text, insert a new paragraph
@@ -1247,7 +1480,7 @@ function RichTextEditor({
1247
1480
  e.preventDefault();
1248
1481
  exec("underline");
1249
1482
  }
1250
- }, [exec, triggerChange]);
1483
+ }, [exec, triggerChange, fontColor]);
1251
1484
  const confirmLink = () => {
1252
1485
  // Add protocol if missing
1253
1486
  let url = linkUrl.trim();
@@ -1420,7 +1653,7 @@ function RichTextEditor({
1420
1653
  };
1421
1654
  const handleInput = useCallback(() => {
1422
1655
  if (editorRef.current) {
1423
- const next = editorRef.current.innerHTML;
1656
+ const next = getCleanHtml();
1424
1657
  setHtml(next);
1425
1658
  lastSynchronizedHtmlRef.current = next;
1426
1659
  onChange && onChange(next);
@@ -1457,6 +1690,18 @@ function RichTextEditor({
1457
1690
  }, [disabled]);
1458
1691
  const handleEditorClick = useCallback(e => {
1459
1692
  setSelectionVersion(v => v + 1);
1693
+ const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1694
+ if (deleteBtn && editable && editorFocused) {
1695
+ e.preventDefault();
1696
+ e.stopPropagation();
1697
+ const wrapper = deleteBtn.closest('.image-container, .video-container');
1698
+ if (wrapper) {
1699
+ wrapper.remove();
1700
+ triggerChange();
1701
+ }
1702
+ return;
1703
+ }
1704
+
1460
1705
  // Check if the click is on a link
1461
1706
  const clickedLink = e.target.closest('a');
1462
1707
  if (clickedLink) {
@@ -1489,7 +1734,7 @@ function RichTextEditor({
1489
1734
  }
1490
1735
  }, 0);
1491
1736
  }
1492
- }, [editable, disabled]);
1737
+ }, [editable, disabled, editorFocused, triggerChange]);
1493
1738
  const renderImageToolbar = () => {
1494
1739
  if (!selectedImage || !editorRef.current || !editable) return null;
1495
1740
  const editorRect = editorRef.current.getBoundingClientRect();
@@ -1559,6 +1804,9 @@ function RichTextEditor({
1559
1804
  title: "Remove Image"
1560
1805
  }, "\xD7"));
1561
1806
  };
1807
+ if (isLoading) {
1808
+ return /*#__PURE__*/React.createElement(Spinner, null);
1809
+ }
1562
1810
  return /*#__PURE__*/React.createElement("div", {
1563
1811
  className: "rte-main-wrapper",
1564
1812
  style: {
@@ -1984,12 +2232,14 @@ function RichTextEditor({
1984
2232
  onDragOver: e => e.preventDefault(),
1985
2233
  onKeyDown: handleKeyDown,
1986
2234
  onClick: handleEditorClick,
2235
+ onFocus: handleEditorFocus,
2236
+ onBlur: handleEditorBlur,
1987
2237
  style: {
1988
2238
  minHeight: minHeight || '150px',
1989
2239
  maxHeight: maxHeight || '500px',
1990
2240
  paddingLeft: paddingLeft || '12px'
1991
2241
  },
1992
- className: "rte-content"
2242
+ className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
1993
2243
  }), renderImageToolbar(), /*#__PURE__*/React.createElement("div", {
1994
2244
  className: "rte-footer"
1995
2245
  }, /*#__PURE__*/React.createElement("div", {