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

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
@@ -427,6 +427,7 @@ function RichTextEditor({
427
427
  const [editable, setEditable] = useState(initialEditable);
428
428
  const [editorFocused, setEditorFocused] = useState(false);
429
429
  const lastSynchronizedHtmlRef = useRef("");
430
+ const syncProcessedMediaRef = useRef(() => {});
430
431
  useEffect(() => {
431
432
  setEditable(initialEditable);
432
433
  }, [initialEditable]);
@@ -533,7 +534,7 @@ function RichTextEditor({
533
534
  }, [imageModalOpen]);
534
535
  useEffect(() => {
535
536
  if (editorRef.current && value && value !== lastSynchronizedHtmlRef.current) {
536
- requestAnimationFrame(() => processExistingMedia(editorRef.current));
537
+ requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
537
538
  }
538
539
  }, [value]);
539
540
 
@@ -542,7 +543,7 @@ function RichTextEditor({
542
543
  if (!editable) {
543
544
  setEditorFocused(false);
544
545
  }
545
- processExistingMedia(editorRef.current);
546
+ syncProcessedMediaRef.current(editorRef.current);
546
547
  }, [editable]);
547
548
  useEffect(() => {
548
549
  if (!editorRef.current) return;
@@ -573,7 +574,7 @@ function RichTextEditor({
573
574
  if (editorRef.current && editorRef.current.innerHTML !== newContent) {
574
575
  editorRef.current.innerHTML = newContent;
575
576
  }
576
- requestAnimationFrame(() => processExistingMedia(editorRef.current));
577
+ requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
577
578
  updateMetrics();
578
579
  }
579
580
  } catch (e) {
@@ -588,10 +589,47 @@ function RichTextEditor({
588
589
  }
589
590
  }
590
591
  }, [value, initialEditable, updateMetrics]);
592
+ const LIST_BLOCK_MEDIA_SELECTOR = ".video-container, .image-container, table";
593
+ const isListItemEffectivelyEmpty = listItem => {
594
+ if (!listItem) return true;
595
+ const clone = listItem.cloneNode(true);
596
+ clone.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR).forEach(el => el.remove());
597
+ clone.querySelectorAll("br").forEach(el => el.remove());
598
+ return clone.textContent.replace(/[\u200B\u00A0\s]/g, "").length === 0;
599
+ };
600
+ const hoistBlockMediaOutOfListItems = container => {
601
+ if (!container) return false;
602
+ let changed = false;
603
+ container.querySelectorAll("ol, ul").forEach(list => {
604
+ const items = Array.from(list.children).filter(child => child.tagName === "LI");
605
+ items.forEach(listItem => {
606
+ const blockMedia = Array.from(listItem.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR));
607
+ if (blockMedia.length === 0) return;
608
+ const hadText = !isListItemEffectivelyEmpty(listItem);
609
+ blockMedia.forEach(media => {
610
+ listItem.removeChild(media);
611
+ if (list.parentNode) {
612
+ list.parentNode.insertBefore(media, list.nextSibling);
613
+ }
614
+ changed = true;
615
+ });
616
+ if (!hadText || isListItemEffectivelyEmpty(listItem)) {
617
+ listItem.remove();
618
+ changed = true;
619
+ }
620
+ });
621
+ if (list.children.length === 0 && list.parentNode) {
622
+ list.remove();
623
+ changed = true;
624
+ }
625
+ });
626
+ return changed;
627
+ };
591
628
  const processExistingMedia = container => {
592
- if (!container) return;
629
+ if (!container) return false;
593
630
  processExistingImages(container);
594
631
  processExistingVideos(container);
632
+ return hoistBlockMediaOutOfListItems(container);
595
633
  };
596
634
  const getCleanHtml = () => {
597
635
  if (!editorRef.current) return "";
@@ -607,6 +645,11 @@ function RichTextEditor({
607
645
  lastSynchronizedHtmlRef.current = next;
608
646
  onChange && onChange(next);
609
647
  }, [onChange]);
648
+ syncProcessedMediaRef.current = container => {
649
+ if (processExistingMedia(container)) {
650
+ triggerChange();
651
+ }
652
+ };
610
653
 
611
654
  // Helper to walk up DOM to find style tags or CSS style:
612
655
  const isParentStyle = (node, ...tagNames) => {
@@ -1126,8 +1169,10 @@ function RichTextEditor({
1126
1169
  }
1127
1170
  setVideoModalOpen(false);
1128
1171
  setVideoUrl("");
1129
- triggerChange && triggerChange();
1130
- requestAnimationFrame(() => processExistingMedia(editorRef.current));
1172
+ requestAnimationFrame(() => {
1173
+ processExistingMedia(editorRef.current);
1174
+ triggerChange();
1175
+ });
1131
1176
  } else {
1132
1177
  console.warn("Invalid Video URL or Platform not supported");
1133
1178
  }
@@ -1374,6 +1419,12 @@ function RichTextEditor({
1374
1419
  txt.innerHTML = html;
1375
1420
  return txt.value;
1376
1421
  };
1422
+ const isCursorAtStartOfListItem = (range, listItem) => {
1423
+ const prefixRange = document.createRange();
1424
+ prefixRange.setStart(listItem, 0);
1425
+ prefixRange.setEnd(range.startContainer, range.startOffset);
1426
+ return prefixRange.toString().replace(/[\u200B\u00A0\s]/g, "").length === 0;
1427
+ };
1377
1428
  const isCursorAtEndOfListItem = (range, listItem) => {
1378
1429
  const suffixRange = document.createRange();
1379
1430
  suffixRange.setStart(range.startContainer, range.startOffset);
@@ -1418,7 +1469,7 @@ function RichTextEditor({
1418
1469
  // If we're at the end of a list item, add a new one
1419
1470
  if (range.collapsed && isCursorAtEndOfListItem(range, listItem)) {
1420
1471
  // If it's empty, create a regular paragraph instead
1421
- if (listItem.textContent.replace(/\u200B/g, '').trim() === '') {
1472
+ if (isListItemEffectivelyEmpty(listItem)) {
1422
1473
  document.execCommand('insertHTML', false, '<div><br></div>');
1423
1474
  // Move the cursor to the new line
1424
1475
  const newRange = document.createRange();
@@ -1450,7 +1501,7 @@ function RichTextEditor({
1450
1501
  } else {
1451
1502
  list.appendChild(newItem);
1452
1503
  }
1453
- if (!newItem.textContent.replace(/\u200B/g, '').trim()) {
1504
+ if (isListItemEffectivelyEmpty(newItem)) {
1454
1505
  newItem.textContent = "";
1455
1506
  prepareListItemForTyping(newItem, selection);
1456
1507
  } else {
@@ -1468,6 +1519,54 @@ function RichTextEditor({
1468
1519
  triggerChange();
1469
1520
  return;
1470
1521
  }
1522
+ if (e.key === "Backspace") {
1523
+ var _node$closest, _node, _editorRef$current3;
1524
+ const selection = window.getSelection();
1525
+ if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1526
+ const range = selection.getRangeAt(0);
1527
+ if (!range.collapsed) return;
1528
+ let node = range.startContainer;
1529
+ if (node.nodeType === 3) {
1530
+ node = node.parentNode;
1531
+ }
1532
+ const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
1533
+ if (!listItem || !((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(listItem))) return;
1534
+ const list = listItem.parentNode;
1535
+ if (isListItemEffectivelyEmpty(listItem)) {
1536
+ e.preventDefault();
1537
+ const prevLi = listItem.previousElementSibling;
1538
+ const blockMedia = Array.from(listItem.querySelectorAll(LIST_BLOCK_MEDIA_SELECTOR));
1539
+ blockMedia.forEach(media => {
1540
+ var _list$parentNode;
1541
+ (_list$parentNode = list.parentNode) === null || _list$parentNode === void 0 || _list$parentNode.insertBefore(media, list.nextSibling);
1542
+ });
1543
+ listItem.remove();
1544
+ if (list.children.length === 0) {
1545
+ list.remove();
1546
+ }
1547
+ if ((prevLi === null || prevLi === void 0 ? void 0 : prevLi.tagName) === "LI") {
1548
+ const newRange = document.createRange();
1549
+ newRange.selectNodeContents(prevLi);
1550
+ newRange.collapse(false);
1551
+ selection.removeAllRanges();
1552
+ selection.addRange(newRange);
1553
+ }
1554
+ triggerChange();
1555
+ return;
1556
+ }
1557
+ if (isCursorAtStartOfListItem(range, listItem)) {
1558
+ const prevLi = listItem.previousElementSibling;
1559
+ if ((prevLi === null || prevLi === void 0 ? void 0 : prevLi.tagName) === "LI" && isListItemEffectivelyEmpty(prevLi)) {
1560
+ e.preventDefault();
1561
+ prevLi.remove();
1562
+ if (list.children.length === 0) {
1563
+ list.remove();
1564
+ }
1565
+ triggerChange();
1566
+ }
1567
+ }
1568
+ return;
1569
+ }
1471
1570
 
1472
1571
  // Handle Ctrl/Cmd + B/I/U for bold/italic/underline
1473
1572
  if ((e.ctrlKey || e.metaKey) && e.key === "b") {