@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.
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckbox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACH,mBAAmB,EAInB,qBAAqB,EACxB,MAAM,mBAAmB,CAAC;AAS3B,UAAU,kBAAkB;IACxB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,EAAE,MAAM,aAAa,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EACxB,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,GAChB,EAAE,kBAAkB;;iCA4QJ,WAAW,KAAG,OAAO;+BApL1B,aAAa,KAAG,OAAO;6BAkFvB,aAAa,KAAG,OAAO;;EA0MlC"}
1
+ {"version":3,"file":"useCheckbox.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckbox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACH,mBAAmB,EAInB,qBAAqB,EACxB,MAAM,mBAAmB,CAAC;AAQ3B,UAAU,kBAAkB;IACxB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,EAAE,MAAM,aAAa,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EACxB,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,GAChB,EAAE,kBAAkB;;iCA+RJ,WAAW,KAAG,OAAO;+BAvM1B,aAAa,KAAG,OAAO;6BAkFvB,aAAa,KAAG,OAAO;;EA2PlC"}
package/dist/index.esm.js CHANGED
@@ -228,32 +228,6 @@ function findClosestListItem(node) {
228
228
  : node;
229
229
  return element?.closest("li") || null;
230
230
  }
231
- /**
232
- * Sets cursor position in a text node after an async DOM update.
233
- */
234
- function setCursorInTextNode(textNode, position, editor) {
235
- requestAnimationFrame(() => {
236
- requestAnimationFrame(() => {
237
- try {
238
- const range = document.createRange();
239
- const maxPos = textNode.textContent?.length || 0;
240
- const safePos = Math.min(Math.max(0, position), maxPos);
241
- range.setStart(textNode, safePos);
242
- range.collapse(true);
243
- const selection = window.getSelection();
244
- if (selection) {
245
- selection.removeAllRanges();
246
- selection.addRange(range);
247
- if (editor)
248
- editor.focus();
249
- }
250
- }
251
- catch (_) {
252
- // Silently fail - cursor positioning is best-effort
253
- }
254
- });
255
- });
256
- }
257
231
 
258
232
  /**
259
233
  * Pure checkbox utility functions for checkbox lists.
@@ -588,21 +562,39 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
588
562
  }
589
563
  return true;
590
564
  }
591
- // Normal case: create a new checkbox item
565
+ // Normal case: split content at cursor and create a new checkbox item
566
+ const afterRange = document.createRange();
567
+ afterRange.setStart(range.startContainer, range.startOffset);
568
+ if (listItem.lastChild) {
569
+ afterRange.setEndAfter(listItem.lastChild);
570
+ }
571
+ else {
572
+ afterRange.setEnd(listItem, listItem.childNodes.length);
573
+ }
574
+ const afterFragment = afterRange.extractContents();
592
575
  const newLi = document.createElement("li");
593
576
  updateListItemChecked(newLi, false);
594
- const textNode = document.createTextNode(" ");
595
- newLi.appendChild(textNode);
577
+ const hasContent = afterFragment.textContent?.trim();
578
+ if (hasContent) {
579
+ newLi.appendChild(afterFragment);
580
+ }
581
+ else {
582
+ newLi.appendChild(document.createTextNode(" "));
583
+ }
596
584
  if (listItem.nextSibling) {
597
585
  checkboxList.insertBefore(newLi, listItem.nextSibling);
598
586
  }
599
587
  else {
600
588
  checkboxList.appendChild(newLi);
601
589
  }
590
+ if (!listItem.firstChild) {
591
+ listItem.appendChild(document.createTextNode(" "));
592
+ }
602
593
  if (editor)
603
594
  ensureAllCheckboxes(editor);
595
+ const cursorNode = newLi.firstChild || newLi;
604
596
  const newRange = document.createRange();
605
- newRange.setStart(textNode, 0);
597
+ newRange.setStart(cursorNode, 0);
606
598
  newRange.collapse(true);
607
599
  selection.removeAllRanges();
608
600
  selection.addRange(newRange);
@@ -634,8 +626,16 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
634
626
  isUpdatingRef.current = false;
635
627
  return false;
636
628
  }
637
- // Already in a checkbox list? Remove it.
638
- const existingList = findClosestCheckboxList(element);
629
+ // Resolve the element at the start of the selection as well,
630
+ // because when selecting across multiple list items the
631
+ // commonAncestorContainer is the editor root (above the list).
632
+ const startNode = range.startContainer;
633
+ const startElement = startNode.nodeType === Node.TEXT_NODE
634
+ ? startNode.parentElement
635
+ : startNode;
636
+ // Already in a checkbox list? Remove it (convert back to bullet list).
637
+ const existingList = findClosestCheckboxList(element) ||
638
+ (startElement ? findClosestCheckboxList(startElement) : null);
639
639
  if (existingList) {
640
640
  existingList.classList.remove("rte-checkbox-list");
641
641
  existingList
@@ -648,48 +648,65 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
648
648
  isUpdatingRef.current = false;
649
649
  return true;
650
650
  }
651
- // Create new checkbox list
652
- const ul = document.createElement("ul");
653
- ul.classList.add("rte-checkbox-list");
654
- const li = document.createElement("li");
655
- updateListItemChecked(li, false);
656
- const textNode = document.createTextNode(" ");
657
- li.appendChild(textNode);
658
- ul.appendChild(li);
659
- // Find block element to replace
660
- const blockElement = element.closest("p, div, h1, h2, h3, h4, h5, h6, blockquote");
661
- const isValidBlockElement = blockElement &&
662
- blockElement !== editor &&
663
- editor.contains(blockElement) &&
664
- blockElement.parentElement;
665
- if (isValidBlockElement) {
666
- const textContent = blockElement.textContent || "";
667
- blockElement.parentElement.replaceChild(ul, blockElement);
668
- const finalTextNode = li.firstChild;
669
- if (finalTextNode) {
670
- finalTextNode.textContent = textContent || " ";
671
- const cursorPos = textContent ? textContent.length : 0;
672
- setCursorInTextNode(finalTextNode, cursorPos, editor);
673
- }
651
+ // Already in a <ul> (bullet list)? Convert in-place to checkbox.
652
+ const existingUl = element.closest("ul") ||
653
+ startElement?.closest("ul");
654
+ if (existingUl && editor.contains(existingUl)) {
655
+ existingUl.classList.add("rte-checkbox-list");
656
+ existingUl.querySelectorAll(":scope > li").forEach((li) => {
657
+ updateListItemChecked(li, false);
658
+ });
659
+ ensureAllCheckboxes(editor);
660
+ isUpdatingRef.current = false;
661
+ const content = getDomContent();
662
+ pushToHistory(content);
663
+ notifyChange(content);
664
+ return true;
674
665
  }
675
- else {
676
- try {
677
- range.deleteContents();
678
- range.insertNode(ul);
679
- const finalTextNode = li.firstChild;
680
- if (finalTextNode) {
681
- setCursorInTextNode(finalTextNode, 0, editor);
682
- }
666
+ // Already in an <ol> (numbered list)? Convert to <ul> first,
667
+ // then make it a checkbox list.
668
+ const existingOl = element.closest("ol") ||
669
+ startElement?.closest("ol");
670
+ if (existingOl && editor.contains(existingOl)) {
671
+ const ul = document.createElement("ul");
672
+ ul.classList.add("rte-checkbox-list");
673
+ while (existingOl.firstChild) {
674
+ ul.appendChild(existingOl.firstChild);
683
675
  }
684
- catch (_) {
685
- editor.appendChild(ul);
686
- const finalTextNode = li.firstChild;
687
- if (finalTextNode) {
688
- setCursorInTextNode(finalTextNode, 0, editor);
689
- }
676
+ existingOl.parentNode?.replaceChild(ul, existingOl);
677
+ ul.querySelectorAll(":scope > li").forEach((li) => {
678
+ updateListItemChecked(li, false);
679
+ });
680
+ ensureAllCheckboxes(editor);
681
+ isUpdatingRef.current = false;
682
+ const content = getDomContent();
683
+ pushToHistory(content);
684
+ notifyChange(content);
685
+ return true;
686
+ }
687
+ // Not in any list: use the browser's insertUnorderedList command
688
+ // to properly handle single paragraphs and multi-paragraph selections,
689
+ // then convert the resulting <ul> to a checkbox list.
690
+ if (document.activeElement !== editor) {
691
+ editor.focus();
692
+ }
693
+ document.execCommand("insertUnorderedList", false);
694
+ // Find the <ul> the cursor is now inside
695
+ const sel = window.getSelection();
696
+ if (sel && sel.rangeCount > 0) {
697
+ const r = sel.getRangeAt(0);
698
+ const node = r.commonAncestorContainer.nodeType === Node.TEXT_NODE
699
+ ? r.commonAncestorContainer.parentElement
700
+ : r.commonAncestorContainer;
701
+ const newUl = node?.closest("ul");
702
+ if (newUl && editor.contains(newUl)) {
703
+ newUl.classList.add("rte-checkbox-list");
704
+ newUl.querySelectorAll(":scope > li").forEach((li) => {
705
+ updateListItemChecked(li, false);
706
+ });
690
707
  }
691
708
  }
692
- // After insertion: ensure attributes and save to history
709
+ // Finalize: ensure attributes and save to history
693
710
  setTimeout(() => {
694
711
  if (!editor)
695
712
  return;
@@ -2652,18 +2669,25 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
2652
2669
  : container;
2653
2670
  if (!element)
2654
2671
  return undefined;
2672
+ // When the selection spans multiple blocks, commonAncestorContainer
2673
+ // may be the editor root. Fall back to startContainer to detect
2674
+ // formats from an element that is actually inside the content.
2675
+ const startNode = range.startContainer;
2676
+ const startEl = startNode.nodeType === Node.TEXT_NODE
2677
+ ? startNode.parentElement
2678
+ : startNode;
2655
2679
  const tagName = element.tagName.toLowerCase();
2656
2680
  if (headings.includes(tagName))
2657
2681
  return tagName;
2658
- if (element.closest("pre"))
2682
+ if (element.closest("pre") || startEl?.closest("pre"))
2659
2683
  return "code";
2660
- if (element.closest("blockquote"))
2684
+ if (element.closest("blockquote") || startEl?.closest("blockquote"))
2661
2685
  return "blockquote";
2662
- if (findClosestCheckboxList(element))
2686
+ if (findClosestCheckboxList(element) || (startEl && findClosestCheckboxList(startEl)))
2663
2687
  return "checkbox-list";
2664
- if (element.closest("ul"))
2688
+ if (element.closest("ul") || startEl?.closest("ul"))
2665
2689
  return "ul";
2666
- if (element.closest("ol"))
2690
+ if (element.closest("ol") || startEl?.closest("ol"))
2667
2691
  return "ol";
2668
2692
  if (tagName === "p")
2669
2693
  return "p";
@@ -2699,6 +2723,32 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
2699
2723
  ? container.parentElement
2700
2724
  : container;
2701
2725
  };
2726
+ // When the selection spans multiple blocks, commonAncestorContainer
2727
+ // is the editor root. Use startContainer to reach elements inside
2728
+ // the actual selected content.
2729
+ const getStartElement = () => {
2730
+ const sel = editor.getSelection();
2731
+ if (!sel || sel.rangeCount === 0)
2732
+ return null;
2733
+ const start = sel.getRangeAt(0).startContainer;
2734
+ return start.nodeType === Node.TEXT_NODE
2735
+ ? start.parentElement
2736
+ : start;
2737
+ };
2738
+ const stripCheckboxAttributes = (list) => {
2739
+ list.classList.remove("rte-checkbox-list");
2740
+ list.querySelectorAll("li[role='checkbox']").forEach((li) => {
2741
+ li.removeAttribute("role");
2742
+ li.removeAttribute("tabIndex");
2743
+ li.removeAttribute("aria-checked");
2744
+ });
2745
+ };
2746
+ const findCheckboxInSelection = () => {
2747
+ const el = getCursorElement();
2748
+ const startEl = getStartElement();
2749
+ return ((el ? findClosestCheckboxList(el) : null) ||
2750
+ (startEl ? findClosestCheckboxList(startEl) : null));
2751
+ };
2702
2752
  // Helper: merge all adjacent <pre> elements in the editor into one
2703
2753
  const mergeAdjacentPre = () => {
2704
2754
  const root = document.activeElement;
@@ -2722,20 +2772,12 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
2722
2772
  // Helper: if cursor is inside a list, remove the list first
2723
2773
  const escapeListIfNeeded = () => {
2724
2774
  const el = getCursorElement();
2725
- if (!el)
2726
- return;
2727
- const inCheckbox = findClosestCheckboxList(el);
2728
- const inUl = el.closest("ul");
2729
- const inOl = el.closest("ol");
2775
+ const startEl = getStartElement();
2776
+ const inCheckbox = findCheckboxInSelection();
2777
+ const inUl = el?.closest("ul") || startEl?.closest("ul");
2778
+ const inOl = el?.closest("ol") || startEl?.closest("ol");
2730
2779
  if (inCheckbox) {
2731
- inCheckbox.classList.remove("rte-checkbox-list");
2732
- inCheckbox
2733
- .querySelectorAll("li[role='checkbox']")
2734
- .forEach((li) => {
2735
- li.removeAttribute("role");
2736
- li.removeAttribute("tabIndex");
2737
- li.removeAttribute("aria-checked");
2738
- });
2780
+ stripCheckboxAttributes(inCheckbox);
2739
2781
  editor.executeCommand("insertUnorderedList");
2740
2782
  }
2741
2783
  else if (inUl) {
@@ -2746,29 +2788,36 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
2746
2788
  }
2747
2789
  };
2748
2790
  if (value === "checkbox-list") {
2749
- const el = getCursorElement();
2750
- if (!el)
2751
- return;
2752
- const checkboxList = findClosestCheckboxList(el);
2791
+ const checkboxList = findCheckboxInSelection();
2753
2792
  if (checkboxList) {
2754
- checkboxList.classList.remove("rte-checkbox-list");
2755
- checkboxList
2756
- .querySelectorAll("li[role='checkbox']")
2757
- .forEach((li) => {
2758
- li.removeAttribute("role");
2759
- li.removeAttribute("tabIndex");
2760
- li.removeAttribute("aria-checked");
2761
- });
2793
+ stripCheckboxAttributes(checkboxList);
2762
2794
  }
2763
2795
  else {
2764
2796
  editor.executeCommand("insertCheckboxList");
2765
2797
  }
2766
2798
  }
2767
2799
  else if (value === "ul") {
2768
- editor.executeCommand("insertUnorderedList");
2800
+ const checkboxList = findCheckboxInSelection();
2801
+ if (checkboxList) {
2802
+ stripCheckboxAttributes(checkboxList);
2803
+ }
2804
+ else {
2805
+ editor.executeCommand("insertUnorderedList");
2806
+ }
2769
2807
  }
2770
2808
  else if (value === "ol") {
2771
- editor.executeCommand("insertOrderedList");
2809
+ const checkboxList = findCheckboxInSelection();
2810
+ if (checkboxList) {
2811
+ stripCheckboxAttributes(checkboxList);
2812
+ const ol = document.createElement("ol");
2813
+ while (checkboxList.firstChild) {
2814
+ ol.appendChild(checkboxList.firstChild);
2815
+ }
2816
+ checkboxList.parentNode?.replaceChild(ol, checkboxList);
2817
+ }
2818
+ else {
2819
+ editor.executeCommand("insertOrderedList");
2820
+ }
2772
2821
  }
2773
2822
  else if (value === "blockquote") {
2774
2823
  const el = getCursorElement();
@@ -2808,13 +2857,17 @@ function createBlockFormatPlugin(headings = defaultHeadings$2, blockOptions = {}
2808
2857
  : container;
2809
2858
  if (!element)
2810
2859
  return false;
2860
+ const startNode = range.startContainer;
2861
+ const startEl = startNode.nodeType === Node.TEXT_NODE
2862
+ ? startNode.parentElement
2863
+ : startNode;
2811
2864
  const tagName = element.tagName.toLowerCase();
2812
2865
  return (headings.includes(tagName) ||
2813
- element.closest("pre") !== null ||
2814
- element.closest("blockquote") !== null ||
2815
- findClosestCheckboxList(element) !== null ||
2816
- element.closest("ul") !== null ||
2817
- element.closest("ol") !== null);
2866
+ element.closest("pre") !== null || startEl?.closest("pre") !== null ||
2867
+ element.closest("blockquote") !== null || startEl?.closest("blockquote") !== null ||
2868
+ findClosestCheckboxList(element) !== null || (startEl != null && findClosestCheckboxList(startEl) !== null) ||
2869
+ element.closest("ul") !== null || startEl?.closest("ul") !== null ||
2870
+ element.closest("ol") !== null || startEl?.closest("ol") !== null);
2818
2871
  },
2819
2872
  canExecute: () => true,
2820
2873
  };