@overlap/rte 1.0.15 → 1.0.16
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/hooks/useCheckbox.d.ts.map +1 -1
- package/dist/index.esm.js +160 -107
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +160 -107
- package/dist/index.js.map +1 -1
- package/dist/plugins/blockFormat.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -232,32 +232,6 @@ function findClosestListItem(node) {
|
|
|
232
232
|
: node;
|
|
233
233
|
return element?.closest("li") || null;
|
|
234
234
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Sets cursor position in a text node after an async DOM update.
|
|
237
|
-
*/
|
|
238
|
-
function setCursorInTextNode(textNode, position, editor) {
|
|
239
|
-
requestAnimationFrame(() => {
|
|
240
|
-
requestAnimationFrame(() => {
|
|
241
|
-
try {
|
|
242
|
-
const range = document.createRange();
|
|
243
|
-
const maxPos = textNode.textContent?.length || 0;
|
|
244
|
-
const safePos = Math.min(Math.max(0, position), maxPos);
|
|
245
|
-
range.setStart(textNode, safePos);
|
|
246
|
-
range.collapse(true);
|
|
247
|
-
const selection = window.getSelection();
|
|
248
|
-
if (selection) {
|
|
249
|
-
selection.removeAllRanges();
|
|
250
|
-
selection.addRange(range);
|
|
251
|
-
if (editor)
|
|
252
|
-
editor.focus();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
catch (_) {
|
|
256
|
-
// Silently fail - cursor positioning is best-effort
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
235
|
|
|
262
236
|
/**
|
|
263
237
|
* Pure checkbox utility functions for checkbox lists.
|
|
@@ -592,21 +566,39 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
592
566
|
}
|
|
593
567
|
return true;
|
|
594
568
|
}
|
|
595
|
-
// Normal case: create a new checkbox item
|
|
569
|
+
// Normal case: split content at cursor and create a new checkbox item
|
|
570
|
+
const afterRange = document.createRange();
|
|
571
|
+
afterRange.setStart(range.startContainer, range.startOffset);
|
|
572
|
+
if (listItem.lastChild) {
|
|
573
|
+
afterRange.setEndAfter(listItem.lastChild);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
afterRange.setEnd(listItem, listItem.childNodes.length);
|
|
577
|
+
}
|
|
578
|
+
const afterFragment = afterRange.extractContents();
|
|
596
579
|
const newLi = document.createElement("li");
|
|
597
580
|
updateListItemChecked(newLi, false);
|
|
598
|
-
const
|
|
599
|
-
|
|
581
|
+
const hasContent = afterFragment.textContent?.trim();
|
|
582
|
+
if (hasContent) {
|
|
583
|
+
newLi.appendChild(afterFragment);
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
newLi.appendChild(document.createTextNode(" "));
|
|
587
|
+
}
|
|
600
588
|
if (listItem.nextSibling) {
|
|
601
589
|
checkboxList.insertBefore(newLi, listItem.nextSibling);
|
|
602
590
|
}
|
|
603
591
|
else {
|
|
604
592
|
checkboxList.appendChild(newLi);
|
|
605
593
|
}
|
|
594
|
+
if (!listItem.firstChild) {
|
|
595
|
+
listItem.appendChild(document.createTextNode(" "));
|
|
596
|
+
}
|
|
606
597
|
if (editor)
|
|
607
598
|
ensureAllCheckboxes(editor);
|
|
599
|
+
const cursorNode = newLi.firstChild || newLi;
|
|
608
600
|
const newRange = document.createRange();
|
|
609
|
-
newRange.setStart(
|
|
601
|
+
newRange.setStart(cursorNode, 0);
|
|
610
602
|
newRange.collapse(true);
|
|
611
603
|
selection.removeAllRanges();
|
|
612
604
|
selection.addRange(newRange);
|
|
@@ -638,8 +630,16 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
638
630
|
isUpdatingRef.current = false;
|
|
639
631
|
return false;
|
|
640
632
|
}
|
|
641
|
-
//
|
|
642
|
-
|
|
633
|
+
// Resolve the element at the start of the selection as well,
|
|
634
|
+
// because when selecting across multiple list items the
|
|
635
|
+
// commonAncestorContainer is the editor root (above the list).
|
|
636
|
+
const startNode = range.startContainer;
|
|
637
|
+
const startElement = startNode.nodeType === Node.TEXT_NODE
|
|
638
|
+
? startNode.parentElement
|
|
639
|
+
: startNode;
|
|
640
|
+
// Already in a checkbox list? Remove it (convert back to bullet list).
|
|
641
|
+
const existingList = findClosestCheckboxList(element) ||
|
|
642
|
+
(startElement ? findClosestCheckboxList(startElement) : null);
|
|
643
643
|
if (existingList) {
|
|
644
644
|
existingList.classList.remove("rte-checkbox-list");
|
|
645
645
|
existingList
|
|
@@ -652,48 +652,65 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
652
652
|
isUpdatingRef.current = false;
|
|
653
653
|
return true;
|
|
654
654
|
}
|
|
655
|
-
//
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if (isValidBlockElement) {
|
|
670
|
-
const textContent = blockElement.textContent || "";
|
|
671
|
-
blockElement.parentElement.replaceChild(ul, blockElement);
|
|
672
|
-
const finalTextNode = li.firstChild;
|
|
673
|
-
if (finalTextNode) {
|
|
674
|
-
finalTextNode.textContent = textContent || " ";
|
|
675
|
-
const cursorPos = textContent ? textContent.length : 0;
|
|
676
|
-
setCursorInTextNode(finalTextNode, cursorPos, editor);
|
|
677
|
-
}
|
|
655
|
+
// Already in a <ul> (bullet list)? Convert in-place to checkbox.
|
|
656
|
+
const existingUl = element.closest("ul") ||
|
|
657
|
+
startElement?.closest("ul");
|
|
658
|
+
if (existingUl && editor.contains(existingUl)) {
|
|
659
|
+
existingUl.classList.add("rte-checkbox-list");
|
|
660
|
+
existingUl.querySelectorAll(":scope > li").forEach((li) => {
|
|
661
|
+
updateListItemChecked(li, false);
|
|
662
|
+
});
|
|
663
|
+
ensureAllCheckboxes(editor);
|
|
664
|
+
isUpdatingRef.current = false;
|
|
665
|
+
const content = getDomContent();
|
|
666
|
+
pushToHistory(content);
|
|
667
|
+
notifyChange(content);
|
|
668
|
+
return true;
|
|
678
669
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
670
|
+
// Already in an <ol> (numbered list)? Convert to <ul> first,
|
|
671
|
+
// then make it a checkbox list.
|
|
672
|
+
const existingOl = element.closest("ol") ||
|
|
673
|
+
startElement?.closest("ol");
|
|
674
|
+
if (existingOl && editor.contains(existingOl)) {
|
|
675
|
+
const ul = document.createElement("ul");
|
|
676
|
+
ul.classList.add("rte-checkbox-list");
|
|
677
|
+
while (existingOl.firstChild) {
|
|
678
|
+
ul.appendChild(existingOl.firstChild);
|
|
687
679
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
680
|
+
existingOl.parentNode?.replaceChild(ul, existingOl);
|
|
681
|
+
ul.querySelectorAll(":scope > li").forEach((li) => {
|
|
682
|
+
updateListItemChecked(li, false);
|
|
683
|
+
});
|
|
684
|
+
ensureAllCheckboxes(editor);
|
|
685
|
+
isUpdatingRef.current = false;
|
|
686
|
+
const content = getDomContent();
|
|
687
|
+
pushToHistory(content);
|
|
688
|
+
notifyChange(content);
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
// Not in any list: use the browser's insertUnorderedList command
|
|
692
|
+
// to properly handle single paragraphs and multi-paragraph selections,
|
|
693
|
+
// then convert the resulting <ul> to a checkbox list.
|
|
694
|
+
if (document.activeElement !== editor) {
|
|
695
|
+
editor.focus();
|
|
696
|
+
}
|
|
697
|
+
document.execCommand("insertUnorderedList", false);
|
|
698
|
+
// Find the <ul> the cursor is now inside
|
|
699
|
+
const sel = window.getSelection();
|
|
700
|
+
if (sel && sel.rangeCount > 0) {
|
|
701
|
+
const r = sel.getRangeAt(0);
|
|
702
|
+
const node = r.commonAncestorContainer.nodeType === Node.TEXT_NODE
|
|
703
|
+
? r.commonAncestorContainer.parentElement
|
|
704
|
+
: r.commonAncestorContainer;
|
|
705
|
+
const newUl = node?.closest("ul");
|
|
706
|
+
if (newUl && editor.contains(newUl)) {
|
|
707
|
+
newUl.classList.add("rte-checkbox-list");
|
|
708
|
+
newUl.querySelectorAll(":scope > li").forEach((li) => {
|
|
709
|
+
updateListItemChecked(li, false);
|
|
710
|
+
});
|
|
694
711
|
}
|
|
695
712
|
}
|
|
696
|
-
//
|
|
713
|
+
// Finalize: ensure attributes and save to history
|
|
697
714
|
setTimeout(() => {
|
|
698
715
|
if (!editor)
|
|
699
716
|
return;
|
|
@@ -2656,18 +2673,25 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
|
|
|
2656
2673
|
: container;
|
|
2657
2674
|
if (!element)
|
|
2658
2675
|
return undefined;
|
|
2676
|
+
// When the selection spans multiple blocks, commonAncestorContainer
|
|
2677
|
+
// may be the editor root. Fall back to startContainer to detect
|
|
2678
|
+
// formats from an element that is actually inside the content.
|
|
2679
|
+
const startNode = range.startContainer;
|
|
2680
|
+
const startEl = startNode.nodeType === Node.TEXT_NODE
|
|
2681
|
+
? startNode.parentElement
|
|
2682
|
+
: startNode;
|
|
2659
2683
|
const tagName = element.tagName.toLowerCase();
|
|
2660
2684
|
if (headings.includes(tagName))
|
|
2661
2685
|
return tagName;
|
|
2662
|
-
if (element.closest("pre"))
|
|
2686
|
+
if (element.closest("pre") || startEl?.closest("pre"))
|
|
2663
2687
|
return "code";
|
|
2664
|
-
if (element.closest("blockquote"))
|
|
2688
|
+
if (element.closest("blockquote") || startEl?.closest("blockquote"))
|
|
2665
2689
|
return "blockquote";
|
|
2666
|
-
if (findClosestCheckboxList(element))
|
|
2690
|
+
if (findClosestCheckboxList(element) || (startEl && findClosestCheckboxList(startEl)))
|
|
2667
2691
|
return "checkbox-list";
|
|
2668
|
-
if (element.closest("ul"))
|
|
2692
|
+
if (element.closest("ul") || startEl?.closest("ul"))
|
|
2669
2693
|
return "ul";
|
|
2670
|
-
if (element.closest("ol"))
|
|
2694
|
+
if (element.closest("ol") || startEl?.closest("ol"))
|
|
2671
2695
|
return "ol";
|
|
2672
2696
|
if (tagName === "p")
|
|
2673
2697
|
return "p";
|
|
@@ -2703,6 +2727,32 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
|
|
|
2703
2727
|
? container.parentElement
|
|
2704
2728
|
: container;
|
|
2705
2729
|
};
|
|
2730
|
+
// When the selection spans multiple blocks, commonAncestorContainer
|
|
2731
|
+
// is the editor root. Use startContainer to reach elements inside
|
|
2732
|
+
// the actual selected content.
|
|
2733
|
+
const getStartElement = () => {
|
|
2734
|
+
const sel = editor.getSelection();
|
|
2735
|
+
if (!sel || sel.rangeCount === 0)
|
|
2736
|
+
return null;
|
|
2737
|
+
const start = sel.getRangeAt(0).startContainer;
|
|
2738
|
+
return start.nodeType === Node.TEXT_NODE
|
|
2739
|
+
? start.parentElement
|
|
2740
|
+
: start;
|
|
2741
|
+
};
|
|
2742
|
+
const stripCheckboxAttributes = (list) => {
|
|
2743
|
+
list.classList.remove("rte-checkbox-list");
|
|
2744
|
+
list.querySelectorAll("li[role='checkbox']").forEach((li) => {
|
|
2745
|
+
li.removeAttribute("role");
|
|
2746
|
+
li.removeAttribute("tabIndex");
|
|
2747
|
+
li.removeAttribute("aria-checked");
|
|
2748
|
+
});
|
|
2749
|
+
};
|
|
2750
|
+
const findCheckboxInSelection = () => {
|
|
2751
|
+
const el = getCursorElement();
|
|
2752
|
+
const startEl = getStartElement();
|
|
2753
|
+
return ((el ? findClosestCheckboxList(el) : null) ||
|
|
2754
|
+
(startEl ? findClosestCheckboxList(startEl) : null));
|
|
2755
|
+
};
|
|
2706
2756
|
// Helper: merge all adjacent <pre> elements in the editor into one
|
|
2707
2757
|
const mergeAdjacentPre = () => {
|
|
2708
2758
|
const root = document.activeElement;
|
|
@@ -2726,20 +2776,12 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
|
|
|
2726
2776
|
// Helper: if cursor is inside a list, remove the list first
|
|
2727
2777
|
const escapeListIfNeeded = () => {
|
|
2728
2778
|
const el = getCursorElement();
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
const
|
|
2732
|
-
const
|
|
2733
|
-
const inOl = el.closest("ol");
|
|
2779
|
+
const startEl = getStartElement();
|
|
2780
|
+
const inCheckbox = findCheckboxInSelection();
|
|
2781
|
+
const inUl = el?.closest("ul") || startEl?.closest("ul");
|
|
2782
|
+
const inOl = el?.closest("ol") || startEl?.closest("ol");
|
|
2734
2783
|
if (inCheckbox) {
|
|
2735
|
-
inCheckbox
|
|
2736
|
-
inCheckbox
|
|
2737
|
-
.querySelectorAll("li[role='checkbox']")
|
|
2738
|
-
.forEach((li) => {
|
|
2739
|
-
li.removeAttribute("role");
|
|
2740
|
-
li.removeAttribute("tabIndex");
|
|
2741
|
-
li.removeAttribute("aria-checked");
|
|
2742
|
-
});
|
|
2784
|
+
stripCheckboxAttributes(inCheckbox);
|
|
2743
2785
|
editor.executeCommand("insertUnorderedList");
|
|
2744
2786
|
}
|
|
2745
2787
|
else if (inUl) {
|
|
@@ -2750,29 +2792,36 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
|
|
|
2750
2792
|
}
|
|
2751
2793
|
};
|
|
2752
2794
|
if (value === "checkbox-list") {
|
|
2753
|
-
const
|
|
2754
|
-
if (!el)
|
|
2755
|
-
return;
|
|
2756
|
-
const checkboxList = findClosestCheckboxList(el);
|
|
2795
|
+
const checkboxList = findCheckboxInSelection();
|
|
2757
2796
|
if (checkboxList) {
|
|
2758
|
-
checkboxList
|
|
2759
|
-
checkboxList
|
|
2760
|
-
.querySelectorAll("li[role='checkbox']")
|
|
2761
|
-
.forEach((li) => {
|
|
2762
|
-
li.removeAttribute("role");
|
|
2763
|
-
li.removeAttribute("tabIndex");
|
|
2764
|
-
li.removeAttribute("aria-checked");
|
|
2765
|
-
});
|
|
2797
|
+
stripCheckboxAttributes(checkboxList);
|
|
2766
2798
|
}
|
|
2767
2799
|
else {
|
|
2768
2800
|
editor.executeCommand("insertCheckboxList");
|
|
2769
2801
|
}
|
|
2770
2802
|
}
|
|
2771
2803
|
else if (value === "ul") {
|
|
2772
|
-
|
|
2804
|
+
const checkboxList = findCheckboxInSelection();
|
|
2805
|
+
if (checkboxList) {
|
|
2806
|
+
stripCheckboxAttributes(checkboxList);
|
|
2807
|
+
}
|
|
2808
|
+
else {
|
|
2809
|
+
editor.executeCommand("insertUnorderedList");
|
|
2810
|
+
}
|
|
2773
2811
|
}
|
|
2774
2812
|
else if (value === "ol") {
|
|
2775
|
-
|
|
2813
|
+
const checkboxList = findCheckboxInSelection();
|
|
2814
|
+
if (checkboxList) {
|
|
2815
|
+
stripCheckboxAttributes(checkboxList);
|
|
2816
|
+
const ol = document.createElement("ol");
|
|
2817
|
+
while (checkboxList.firstChild) {
|
|
2818
|
+
ol.appendChild(checkboxList.firstChild);
|
|
2819
|
+
}
|
|
2820
|
+
checkboxList.parentNode?.replaceChild(ol, checkboxList);
|
|
2821
|
+
}
|
|
2822
|
+
else {
|
|
2823
|
+
editor.executeCommand("insertOrderedList");
|
|
2824
|
+
}
|
|
2776
2825
|
}
|
|
2777
2826
|
else if (value === "blockquote") {
|
|
2778
2827
|
const el = getCursorElement();
|
|
@@ -2812,13 +2861,17 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
|
|
|
2812
2861
|
: container;
|
|
2813
2862
|
if (!element)
|
|
2814
2863
|
return false;
|
|
2864
|
+
const startNode = range.startContainer;
|
|
2865
|
+
const startEl = startNode.nodeType === Node.TEXT_NODE
|
|
2866
|
+
? startNode.parentElement
|
|
2867
|
+
: startNode;
|
|
2815
2868
|
const tagName = element.tagName.toLowerCase();
|
|
2816
2869
|
return (headings.includes(tagName) ||
|
|
2817
|
-
element.closest("pre") !== null ||
|
|
2818
|
-
element.closest("blockquote") !== null ||
|
|
2819
|
-
findClosestCheckboxList(element) !== null ||
|
|
2820
|
-
element.closest("ul") !== null ||
|
|
2821
|
-
element.closest("ol") !== null);
|
|
2870
|
+
element.closest("pre") !== null || startEl?.closest("pre") !== null ||
|
|
2871
|
+
element.closest("blockquote") !== null || startEl?.closest("blockquote") !== null ||
|
|
2872
|
+
findClosestCheckboxList(element) !== null || (startEl != null && findClosestCheckboxList(startEl) !== null) ||
|
|
2873
|
+
element.closest("ul") !== null || startEl?.closest("ul") !== null ||
|
|
2874
|
+
element.closest("ol") !== null || startEl?.closest("ol") !== null);
|
|
2822
2875
|
},
|
|
2823
2876
|
canExecute: () => true,
|
|
2824
2877
|
};
|