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