docxodus 12.1.0 → 12.3.0

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.
Files changed (87) hide show
  1. package/README.md +14 -4
  2. package/dist/editor.bundle.js +137 -23
  3. package/dist/editor.d.ts +25 -3
  4. package/dist/editor.d.ts.map +1 -1
  5. package/dist/editor.js +132 -13
  6. package/dist/editor.js.map +1 -1
  7. package/dist/embed.bundle.js +1163 -48
  8. package/dist/embed.d.ts +30 -1
  9. package/dist/embed.d.ts.map +1 -1
  10. package/dist/embed.iife.js +1163 -48
  11. package/dist/embed.js +124 -13
  12. package/dist/embed.js.map +1 -1
  13. package/dist/export-assets.json +33 -33
  14. package/dist/history-checkpoints.d.ts +48 -0
  15. package/dist/history-checkpoints.d.ts.map +1 -0
  16. package/dist/history-checkpoints.js +100 -0
  17. package/dist/history-checkpoints.js.map +1 -0
  18. package/dist/history-controls.d.ts +30 -0
  19. package/dist/history-controls.d.ts.map +1 -0
  20. package/dist/history-controls.js +333 -0
  21. package/dist/history-controls.js.map +1 -0
  22. package/dist/history-indexeddb.d.ts +12 -0
  23. package/dist/history-indexeddb.d.ts.map +1 -0
  24. package/dist/history-indexeddb.js +154 -0
  25. package/dist/history-indexeddb.js.map +1 -0
  26. package/dist/history.d.ts +265 -0
  27. package/dist/history.d.ts.map +1 -0
  28. package/dist/history.js +307 -0
  29. package/dist/history.js.map +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +23 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/ribbon-chrome.d.ts +2 -2
  35. package/dist/ribbon-chrome.d.ts.map +1 -1
  36. package/dist/ribbon-chrome.js +6 -4
  37. package/dist/ribbon-chrome.js.map +1 -1
  38. package/dist/ribbon.js +42 -9
  39. package/dist/ribbon.js.map +1 -1
  40. package/dist/session.js +1 -1
  41. package/dist/session.js.map +1 -1
  42. package/dist/types.d.ts +2 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js.map +1 -1
  45. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
  46. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
  47. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  48. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
  49. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  50. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  51. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  52. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  53. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  54. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  55. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  56. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  57. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  58. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  59. package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
  60. package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
  61. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  62. package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
  63. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  64. package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
  65. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  66. package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
  67. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  68. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  69. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  70. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  71. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  72. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  73. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  74. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  75. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  76. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  77. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  78. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  79. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  80. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  81. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  82. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  83. package/dist/wasm/_framework/dotnet.boot.js +21 -21
  84. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  85. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  86. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  87. package/package.json +6 -3
package/dist/editor.js CHANGED
@@ -614,16 +614,20 @@ function selectionSpanIn(block) {
614
614
  const span = trimmedSpan(block, { start: Math.min(start, end), length: Math.abs(end - start) });
615
615
  return span.length > 0 ? span : null;
616
616
  }
617
- /** Restore a content-text selection spanning [start, start+length) within `el` (skips markers). */
618
- function selectRange(el, start, length) {
619
- const sel = typeof window !== "undefined" ? window.getSelection() : null;
617
+ /**
618
+ * A DOM Range over the content text spanning [start, start+length) within `el` (skips markers),
619
+ * or null when the block is detached or the offsets do not resolve. Building the range is kept
620
+ * separate from selecting it so a caller that only wants to PAINT a span (find preview) does not
621
+ * have to touch the selection — and therefore focus — at all.
622
+ */
623
+ function contentRangeIn(el, start, length) {
620
624
  // The block may have been swapped out of the document by a re-render before this runs
621
625
  // (e.g. a focus-stealing toolbar control firing twice). addRange on a detached range
622
626
  // throws "the given range isn't in document" — skip rather than warn.
623
- if (!sel || !el.isConnected)
624
- return;
625
- el.focus();
626
- const range = document.createRange();
627
+ if (!el.isConnected)
628
+ return null;
629
+ const doc = el.ownerDocument;
630
+ const range = doc.createRange();
627
631
  const from = contentPositionIn(el, start);
628
632
  const to = contentPositionIn(el, start + length);
629
633
  try {
@@ -631,11 +635,66 @@ function selectRange(el, start, length) {
631
635
  range.setEnd(to.node, to.offset);
632
636
  }
633
637
  catch {
634
- return;
638
+ return null;
635
639
  }
640
+ return range;
641
+ }
642
+ /** Restore a content-text selection spanning [start, start+length) within `el` (skips markers). */
643
+ function selectRange(el, start, length) {
644
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
645
+ if (!sel || !el.isConnected)
646
+ return;
647
+ el.focus();
648
+ const range = contentRangeIn(el, start, length);
649
+ if (!range)
650
+ return;
636
651
  sel.removeAllRanges();
637
652
  sel.addRange(range);
638
653
  }
654
+ // ─── Find painting ──────────────────────────────────────────────────────────
655
+ //
656
+ // A find box has to show WHERE the hits are while the user is still typing into it, so it cannot
657
+ // express "the current match" as a selection: putting a caret in a contenteditable block focuses
658
+ // that block, and the next keystroke lands in the document instead of the search field. These
659
+ // highlights paint the same information with no focus and no DOM mutation — the CSS Custom
660
+ // Highlight API takes plain Ranges over the live text and styles them from the stylesheet.
661
+ /** Every match currently on screen. */
662
+ const FIND_HIGHLIGHT = "docxodus-find";
663
+ /** The one the find bar's counter is pointing at. */
664
+ const FIND_HIGHLIGHT_ACTIVE = "docxodus-find-active";
665
+ /** Ranges are cheap, but a pathological query ("e" in a book) is not worth painting in full. */
666
+ const FIND_PAINT_LIMIT = 500;
667
+ const findHighlightStyledDocuments = new WeakSet();
668
+ /**
669
+ * Which editor last painted into the shared registry. Only that instance may clear it, so a
670
+ * second surface on the page cannot wipe the first one's find painting when its own find bar
671
+ * closes. Two live searches still share one painting — the newer one wins, which is what a
672
+ * single visible "current match" should mean.
673
+ */
674
+ let findPaintOwner = null;
675
+ function ensureFindHighlightStyles(doc) {
676
+ if (findHighlightStyledDocuments.has(doc))
677
+ return;
678
+ findHighlightStyledDocuments.add(doc);
679
+ const style = doc.createElement("style");
680
+ style.dataset.docxodusFindHighlight = "true";
681
+ style.textContent = `
682
+ ::highlight(${FIND_HIGHLIGHT}) { background-color: rgba(15, 118, 110, .16); }
683
+ ::highlight(${FIND_HIGHLIGHT_ACTIVE}) { background-color: rgba(15, 118, 110, .38); }
684
+ `;
685
+ (doc.head ?? doc.documentElement).appendChild(style);
686
+ }
687
+ function highlightRegistry() {
688
+ if (typeof CSS === "undefined")
689
+ return null;
690
+ const registry = CSS.highlights;
691
+ const ctor = globalThis.Highlight;
692
+ return registry && typeof ctor === "function" ? registry : null;
693
+ }
694
+ function makeHighlight(ranges) {
695
+ const ctor = globalThis.Highlight;
696
+ return new ctor(...ranges);
697
+ }
639
698
  /**
640
699
  * True when `el`'s immediate parent is a paragraph-border `<div>` the full render wrapped it in
641
700
  * (CreateBorderDivs groups visibly-bordered paragraphs into a div). The body wrapper div has no
@@ -1021,6 +1080,7 @@ export class DocxEditor {
1021
1080
  document.removeEventListener("mouseup", this.onMouseUp, true);
1022
1081
  }
1023
1082
  this.clearDragSelection();
1083
+ this.clearFindMatches();
1024
1084
  this.teardownBlockDrag();
1025
1085
  this.gutter?.dispose();
1026
1086
  this.gutter = null;
@@ -3711,16 +3771,75 @@ export class DocxEditor {
3711
3771
  }
3712
3772
  return out;
3713
3773
  }
3714
- /** Select a match and scroll it into view. */
3774
+ /**
3775
+ * Select a match, focus its block and scroll it into view — the caret lands on the hit, so
3776
+ * typing continues in the document. A find box that is still being typed into wants
3777
+ * `showFindMatches` instead; this is the commit step (closing the bar, jumping in to edit).
3778
+ */
3715
3779
  selectMatch(match) {
3716
3780
  if (!match.block.isConnected)
3717
3781
  return;
3782
+ this.clearFindMatches();
3718
3783
  this.activeBlock = match.block;
3719
3784
  selectRange(match.block, match.start, match.length);
3720
3785
  match.block.scrollIntoView({ block: "center", behavior: "smooth" });
3721
3786
  }
3722
- /** Replace one match's text (formatting of the surrounding run is inherited). */
3723
- replaceMatch(match, replacement) {
3787
+ /**
3788
+ * Paint `matches` and scroll the one at `activeIndex` into view WITHOUT moving focus or the
3789
+ * caret. This is what a find field calls on every keystroke: the document shows where the hits
3790
+ * are and rides to the current one, while the keyboard stays in the search box.
3791
+ *
3792
+ * Falls back to the document selection where the CSS Custom Highlight API is missing — still
3793
+ * without focusing the block, so the next character typed goes to the search field either way.
3794
+ */
3795
+ showFindMatches(matches, activeIndex) {
3796
+ const active = matches[activeIndex];
3797
+ this.clearFindMatches();
3798
+ if (!active || !active.block.isConnected)
3799
+ return;
3800
+ this.activeBlock = active.block;
3801
+ const activeRange = contentRangeIn(active.block, active.start, active.length);
3802
+ const registry = highlightRegistry();
3803
+ if (registry && activeRange) {
3804
+ ensureFindHighlightStyles(this.container.ownerDocument);
3805
+ const rest = [];
3806
+ for (let i = 0; i < matches.length && rest.length < FIND_PAINT_LIMIT; i++) {
3807
+ if (i === activeIndex)
3808
+ continue;
3809
+ const range = contentRangeIn(matches[i].block, matches[i].start, matches[i].length);
3810
+ if (range)
3811
+ rest.push(range);
3812
+ }
3813
+ if (rest.length > 0)
3814
+ registry.set(FIND_HIGHLIGHT, makeHighlight(rest));
3815
+ registry.set(FIND_HIGHLIGHT_ACTIVE, makeHighlight([activeRange]));
3816
+ findPaintOwner = this;
3817
+ }
3818
+ else if (activeRange) {
3819
+ // No highlight registry (older Firefox/Safari): the document selection is the only paint
3820
+ // available. It is not registry-owned, so `clearFindMatches` leaves it — the next
3821
+ // `showFindMatches` or the closing `selectMatch` overwrites it.
3822
+ const sel = this.container.ownerDocument.defaultView?.getSelection();
3823
+ sel?.removeAllRanges();
3824
+ sel?.addRange(activeRange);
3825
+ }
3826
+ active.block.scrollIntoView({ block: "center", behavior: "smooth" });
3827
+ }
3828
+ /** Drop the find painting (closing the find bar, or committing a match to the caret). */
3829
+ clearFindMatches() {
3830
+ if (findPaintOwner !== this)
3831
+ return;
3832
+ const registry = highlightRegistry();
3833
+ registry?.delete(FIND_HIGHLIGHT);
3834
+ registry?.delete(FIND_HIGHLIGHT_ACTIVE);
3835
+ findPaintOwner = null;
3836
+ }
3837
+ /**
3838
+ * Replace one match's text (formatting of the surrounding run is inherited). `focus: false`
3839
+ * leaves the keyboard where it is — a Replace button pressed from the find bar must not drag
3840
+ * the caret into the document mid-search.
3841
+ */
3842
+ replaceMatch(match, replacement, options = {}) {
3724
3843
  const block = match.block;
3725
3844
  if (this.closed || !block.isConnected)
3726
3845
  return false;
@@ -3738,7 +3857,7 @@ export class DocxEditor {
3738
3857
  if (!res.success)
3739
3858
  return false;
3740
3859
  const fresh = this.swapBlock(block, unid, res.modified?.[0]);
3741
- if (fresh)
3860
+ if (fresh && options.focus !== false)
3742
3861
  selectRange(fresh, span.start, replacement.length);
3743
3862
  return true;
3744
3863
  }
@@ -3761,7 +3880,7 @@ export class DocxEditor {
3761
3880
  if (!current.isConnected)
3762
3881
  break;
3763
3882
  const before = current;
3764
- if (this.replaceMatch({ block: current, start: m.start, length: m.length }, replacement)) {
3883
+ if (this.replaceMatch({ block: current, start: m.start, length: m.length }, replacement, { focus: options.focus })) {
3765
3884
  count++;
3766
3885
  // swapBlock replaced the node; keep following it.
3767
3886
  current = this.activeBlock && this.activeBlock !== before ? this.activeBlock : current;