docxodus 9.3.0 → 9.5.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 (48) hide show
  1. package/dist/editor.bundle.js +588 -114
  2. package/dist/editor.d.ts +87 -11
  3. package/dist/editor.d.ts.map +1 -1
  4. package/dist/editor.js +317 -100
  5. package/dist/editor.js.map +1 -1
  6. package/dist/embed.bundle.js +621 -115
  7. package/dist/embed.iife.js +621 -115
  8. package/dist/index.d.ts +4 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/page-geometry.d.ts +74 -0
  13. package/dist/page-geometry.d.ts.map +1 -0
  14. package/dist/page-geometry.js +105 -0
  15. package/dist/page-geometry.js.map +1 -0
  16. package/dist/pagination.bundle.js +8 -6
  17. package/dist/pagination.d.ts +2 -25
  18. package/dist/pagination.d.ts.map +1 -1
  19. package/dist/pagination.js +2 -45
  20. package/dist/pagination.js.map +1 -1
  21. package/dist/ribbon-chrome.d.ts +1 -1
  22. package/dist/ribbon-chrome.d.ts.map +1 -1
  23. package/dist/ribbon-chrome.js +18 -18
  24. package/dist/ribbon.js +2 -0
  25. package/dist/ribbon.js.map +1 -1
  26. package/dist/session.bundle.js +25 -1
  27. package/dist/session.d.ts +20 -2
  28. package/dist/session.d.ts.map +1 -1
  29. package/dist/session.js +23 -1
  30. package/dist/session.js.map +1 -1
  31. package/dist/types.d.ts +8 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/dist/viewport.d.ts +80 -0
  35. package/dist/viewport.d.ts.map +1 -0
  36. package/dist/viewport.js +139 -0
  37. package/dist/viewport.js.map +1 -0
  38. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  39. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  40. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  41. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  42. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  43. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  44. package/dist/wasm/_framework/dotnet.boot.js +5 -5
  45. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  46. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  47. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  48. package/package.json +1 -1
@@ -491,7 +491,9 @@ var DocxSession = class {
491
491
  * Table row/column editing, addressed by a cell-paragraph anchor (e.g. one returned from
492
492
  * {@link insertTable}'s `created`). Insert clones the reference row/column's widths and starts
493
493
  * empty (`created` lists the new cell-paragraph anchors); delete of the last row/column removes
494
- * the whole table. v1 assumes a rectangular grid (no horizontal cell merges).
494
+ * the whole table. All four are grid-aware: inserting across a merge extends it, deleting
495
+ * through one narrows it, and deleting a vertical merge's lead row promotes the next row to
496
+ * carry it — the grid is never left ragged.
495
497
  */
496
498
  insertTableRow(cellAnchorId, position) {
497
499
  return JSON.parse(this.wasm.InsertTableRow(this.handle, cellAnchorId, position));
@@ -505,6 +507,28 @@ var DocxSession = class {
505
507
  deleteTableColumn(cellAnchorId) {
506
508
  return JSON.parse(this.wasm.DeleteTableColumn(this.handle, cellAnchorId));
507
509
  }
510
+ /**
511
+ * Merge the rectangle of cells anchored at `cellAnchorId` running `rowSpan` rows down ×
512
+ * `colSpan` cells right (Word's *Merge Cells*): `w:gridSpan` for the horizontal extent,
513
+ * `w:vMerge` restart/continue for the vertical one. The rectangle must tile the same whole grid
514
+ * columns in every row it covers and must not clip a vertical merge entering from above or
515
+ * continuing below — a partial overlap fails with `invalid_table_merge` instead of tearing the
516
+ * grid. `content` decides what happens to the absorbed cells' content (default `"append"`).
517
+ */
518
+ mergeCells(cellAnchorId, rowSpan, colSpan, content = "append") {
519
+ return JSON.parse(
520
+ this.wasm.MergeCells(this.handle, cellAnchorId, rowSpan, colSpan, content)
521
+ );
522
+ }
523
+ /**
524
+ * Split the merged cell at `cellAnchorId` back into unit cells, dropping its `w:gridSpan` and
525
+ * `w:vMerge` markup and restoring one cell per grid column (each taking its `w:tblGrid` width).
526
+ * Addressing a vertical-merge continuation unmerges the whole run. A cell with no merge markup
527
+ * fails with `invalid_table_merge`.
528
+ */
529
+ unmergeCells(cellAnchorId) {
530
+ return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
531
+ }
508
532
  /**
509
533
  * Table styling, addressed by a cell-paragraph anchor — the post-insert counterpart of
510
534
  * {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
@@ -1304,20 +1328,18 @@ function formatPageNumber(value, format) {
1304
1328
  return (renderer ?? RENDERERS.decimal)(value);
1305
1329
  }
1306
1330
 
1307
- // src/pagination.ts
1331
+ // src/page-geometry.ts
1308
1332
  var DEFAULT_PAGE_WIDTH = 612;
1309
1333
  var DEFAULT_PAGE_HEIGHT = 792;
1310
1334
  var DEFAULT_MARGIN = 72;
1311
- var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1312
- var MIN_BODY_CONTENT_HEIGHT = 72;
1335
+ var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1313
1336
  function pxToPt(px) {
1314
1337
  return px * 0.75;
1315
1338
  }
1316
1339
  function ptToPx(pt) {
1317
1340
  return pt / 0.75;
1318
1341
  }
1319
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1320
- function parseDimensions(section) {
1342
+ function parseSectionDimensions(section) {
1321
1343
  const pageWidth = parseFloat(section.dataset.pageWidth || "") || DEFAULT_PAGE_WIDTH;
1322
1344
  const pageHeight = parseFloat(section.dataset.pageHeight || "") || DEFAULT_PAGE_HEIGHT;
1323
1345
  const contentWidth = parseFloat(section.dataset.contentWidth || "") || pageWidth - 2 * DEFAULT_MARGIN;
@@ -1341,6 +1363,42 @@ function parseDimensions(section) {
1341
1363
  footerHeight
1342
1364
  };
1343
1365
  }
1366
+ function sectionWrappers(root) {
1367
+ const sections = Array.from(root.querySelectorAll("[data-section-index]"));
1368
+ return sections.length > 0 ? sections : [root];
1369
+ }
1370
+ var MIN_FIT_SCALE = 0.25;
1371
+ function fitScale(availablePx, naturalPt, max = 1) {
1372
+ if (!(availablePx > 0) || !(naturalPt > 0)) return max;
1373
+ const naturalPx = ptToPx(naturalPt);
1374
+ if (naturalPx <= availablePx) return max;
1375
+ return Math.max(MIN_FIT_SCALE, Math.min(max, availablePx / naturalPx));
1376
+ }
1377
+ function applyZoom(el, scale, naturalPt) {
1378
+ if (scale === 1) {
1379
+ el.style.removeProperty("zoom");
1380
+ el.style.removeProperty("transform");
1381
+ el.style.removeProperty("transform-origin");
1382
+ el.style.removeProperty("margin-right");
1383
+ el.style.removeProperty("margin-bottom");
1384
+ return;
1385
+ }
1386
+ const zoomSupported = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("zoom", "0.5");
1387
+ if (zoomSupported) {
1388
+ el.style.zoom = String(scale);
1389
+ return;
1390
+ }
1391
+ el.style.transform = `scale(${scale})`;
1392
+ el.style.transformOrigin = "top left";
1393
+ if (naturalPt) {
1394
+ el.style.marginRight = `-${ptToPx(naturalPt.width * (1 - scale))}px`;
1395
+ el.style.marginBottom = `-${ptToPx(naturalPt.height * (1 - scale))}px`;
1396
+ }
1397
+ }
1398
+
1399
+ // src/pagination.ts
1400
+ var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1401
+ var MIN_BODY_CONTENT_HEIGHT = 72;
1344
1402
  var PaginationEngine = class {
1345
1403
  /**
1346
1404
  * Creates a new pagination engine.
@@ -1386,7 +1444,7 @@ var PaginationEngine = class {
1386
1444
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1387
1445
  for (const section of sectionsToProcess) {
1388
1446
  const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1389
- const dims = parseDimensions(section);
1447
+ const dims = parseSectionDimensions(section);
1390
1448
  this.stagingElement.style.visibility = "hidden";
1391
1449
  this.stagingElement.style.position = "absolute";
1392
1450
  this.stagingElement.style.left = "-9999px";
@@ -2684,6 +2742,114 @@ function paginateHtml(html, container, options = {}) {
2684
2742
  return engine.paginate();
2685
2743
  }
2686
2744
 
2745
+ // src/viewport.ts
2746
+ var DocumentViewport = class {
2747
+ constructor(host, options = {}) {
2748
+ this.root = null;
2749
+ /** Natural page size in points — the widest section's PAGE box, which is what must fit. */
2750
+ this.natural = { width: 0, height: 0 };
2751
+ this.observer = null;
2752
+ this.host = host;
2753
+ this.options = {
2754
+ columnWidth: options.columnWidth ?? "section",
2755
+ fitToWidth: options.fitToWidth ?? true,
2756
+ scale: options.scale ?? 1
2757
+ };
2758
+ }
2759
+ /**
2760
+ * Adopt a freshly mounted document root (a continuous flow, or the paginated page stack).
2761
+ * Safe to call on every remount; the previous root is released first.
2762
+ *
2763
+ * `applySectionGeometry` is false for the paginated view, which already builds real page
2764
+ * boxes at the section's dimensions — there the viewport contributes only the fit zoom.
2765
+ */
2766
+ attach(root, applySectionGeometry) {
2767
+ this.release();
2768
+ this.root = root;
2769
+ this.natural = applySectionGeometry ? this.stampSections(root) : this.measurePages(root);
2770
+ this.refresh();
2771
+ if (typeof ResizeObserver !== "undefined") {
2772
+ this.observer = new ResizeObserver(() => this.refresh());
2773
+ this.observer.observe(this.host);
2774
+ }
2775
+ }
2776
+ /** Recompute the fit zoom against the host's current width. */
2777
+ refresh() {
2778
+ if (!this.root) return;
2779
+ const scale = this.scale;
2780
+ applyZoom(this.root, scale, this.natural);
2781
+ this.host.style.setProperty(
2782
+ "--docx-sheet-width",
2783
+ this.natural.width > 0 ? `${ptToPx(this.natural.width) * scale}px` : "100%"
2784
+ );
2785
+ }
2786
+ /** The zoom currently applied (1 = 100%). Reported by the ribbon's anchor rail. */
2787
+ get scale() {
2788
+ if (!this.root) return this.options.scale;
2789
+ return this.options.fitToWidth ? fitScale(this.availableWidthPx(), this.natural.width, this.options.scale) : this.options.scale;
2790
+ }
2791
+ dispose() {
2792
+ this.release();
2793
+ this.root = null;
2794
+ }
2795
+ release() {
2796
+ this.observer?.disconnect();
2797
+ this.observer = null;
2798
+ if (this.root) applyZoom(this.root, 1);
2799
+ this.host.style.removeProperty("--docx-sheet-width");
2800
+ }
2801
+ /** The host's content box, which is the space a page has to fit into. */
2802
+ availableWidthPx() {
2803
+ const style = typeof getComputedStyle === "function" ? getComputedStyle(this.host) : null;
2804
+ const padding = style ? (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0) : 0;
2805
+ return Math.max(0, this.host.clientWidth - padding);
2806
+ }
2807
+ /**
2808
+ * Give each section wrapper its `w:sectPr` geometry: the authored text column, guttered by
2809
+ * the authored margins. The wrapper then measures exactly one page wide, which is what the
2810
+ * sheet chrome paints and what the fit zoom scales.
2811
+ */
2812
+ stampSections(root) {
2813
+ const sections = sectionWrappers(root);
2814
+ if (this.options.columnWidth === "fluid") {
2815
+ root.style.removeProperty("width");
2816
+ for (const section of sections) {
2817
+ section.style.removeProperty("width");
2818
+ section.style.removeProperty("padding-left");
2819
+ section.style.removeProperty("padding-right");
2820
+ }
2821
+ return { width: 0, height: 0 };
2822
+ }
2823
+ let widest = 0;
2824
+ for (const section of sections) {
2825
+ const dims = parseSectionDimensions(section);
2826
+ section.style.width = `${dims.contentWidth}pt`;
2827
+ section.style.paddingLeft = `${dims.marginLeft}pt`;
2828
+ section.style.paddingRight = `${dims.marginRight}pt`;
2829
+ section.style.boxSizing = "content-box";
2830
+ section.style.marginLeft = "auto";
2831
+ section.style.marginRight = "auto";
2832
+ widest = Math.max(widest, dims.pageWidth);
2833
+ }
2834
+ if (widest > 0 && sections[0] !== root) root.style.width = `${widest}pt`;
2835
+ return { width: widest, height: 0 };
2836
+ }
2837
+ /**
2838
+ * The paginated view's page boxes are already page-sized — `pagination.ts` writes each
2839
+ * box's `width` in points — so the widest of those is the natural width. Reading the
2840
+ * inline width rather than the laid-out box keeps this independent of the per-box zoom
2841
+ * pagination may itself have applied.
2842
+ */
2843
+ measurePages(root) {
2844
+ let widest = 0;
2845
+ for (const box of Array.from(root.children)) {
2846
+ const declared = /^([\d.]+)pt$/.exec(box.style.width || "");
2847
+ widest = Math.max(widest, declared ? parseFloat(declared[1]) : pxToPt(box.offsetWidth));
2848
+ }
2849
+ return { width: widest, height: 0 };
2850
+ }
2851
+ };
2852
+
2687
2853
  // src/editor-headerfooter.ts
2688
2854
  var PAGE_FORMAT_LABELS = [
2689
2855
  { value: "", label: "Format\u2026" },
@@ -4772,6 +4938,146 @@ function draggable(args) {
4772
4938
  return once(cleanup);
4773
4939
  }
4774
4940
 
4941
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js
4942
+ function ownKeys4(e, r) {
4943
+ var t = Object.keys(e);
4944
+ if (Object.getOwnPropertySymbols) {
4945
+ var o = Object.getOwnPropertySymbols(e);
4946
+ r && (o = o.filter(function(r2) {
4947
+ return Object.getOwnPropertyDescriptor(e, r2).enumerable;
4948
+ })), t.push.apply(t, o);
4949
+ }
4950
+ return t;
4951
+ }
4952
+ function _objectSpread4(e) {
4953
+ for (var r = 1; r < arguments.length; r++) {
4954
+ var t = null != arguments[r] ? arguments[r] : {};
4955
+ r % 2 ? ownKeys4(Object(t), true).forEach(function(r2) {
4956
+ _defineProperty(e, r2, t[r2]);
4957
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys4(Object(t)).forEach(function(r2) {
4958
+ Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
4959
+ });
4960
+ }
4961
+ return e;
4962
+ }
4963
+ function defaultOffset() {
4964
+ return {
4965
+ x: 0,
4966
+ y: 0
4967
+ };
4968
+ }
4969
+ function setCustomNativeDragPreview(_ref) {
4970
+ var render = _ref.render, nativeSetDragImage = _ref.nativeSetDragImage, _ref$getOffset = _ref.getOffset, getOffset = _ref$getOffset === void 0 ? defaultOffset : _ref$getOffset;
4971
+ var container = document.createElement("div");
4972
+ if (supportsPopover()) {
4973
+ container.setAttribute("popover", "manual");
4974
+ }
4975
+ Object.assign(container.style, _objectSpread4(_objectSpread4({
4976
+ // Ensuring we don't cause reflow when adding the element to the page
4977
+ // Using `position:fixed` rather than `position:absolute` so we are
4978
+ // positioned on the current viewport.
4979
+ // `position:fixed` also creates a new stacking context, so we don't need to do that here
4980
+ position: "fixed"
4981
+ }, supportsPopover() ? (
4982
+ // needs to come first as it has 'inset: unset' which
4983
+ // needs to be overridden by our top / left values
4984
+ popoverResetUserAgentStyles
4985
+ ) : {
4986
+ // Fallback: using maximum possible z-index so that this element
4987
+ // will always be on top of other positioned content.
4988
+ zIndex: maxZIndex
4989
+ }), {}, {
4990
+ // According to `mdn`, the element can be offscreen:
4991
+ // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage#imgelement
4992
+ //
4993
+ // However, that information does not appear in the specs:
4994
+ // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-setdragimage-dev
4995
+ //
4996
+ // If the element is _completely_ offscreen, Safari@17.1 will cancel the drag
4997
+ top: 0,
4998
+ left: 0,
4999
+ // Avoiding any additional events caused by the new element (being super safe)
5000
+ pointerEvents: "none"
5001
+ }));
5002
+ document.body.append(container);
5003
+ if (supportsPopover()) {
5004
+ container.showPopover();
5005
+ }
5006
+ var unmount = render({
5007
+ container
5008
+ });
5009
+ queueMicrotask(function() {
5010
+ var previewOffset = getOffset({
5011
+ container
5012
+ });
5013
+ if (isSafari()) {
5014
+ var rect = container.getBoundingClientRect();
5015
+ if (rect.width === 0) {
5016
+ return;
5017
+ }
5018
+ container.style.left = "-".concat(rect.width - 1e-4, "px");
5019
+ }
5020
+ nativeSetDragImage === null || nativeSetDragImage === void 0 || nativeSetDragImage(container, previewOffset.x, previewOffset.y);
5021
+ });
5022
+ function cleanup() {
5023
+ unbindMonitor();
5024
+ unmount === null || unmount === void 0 || unmount();
5025
+ document.body.removeChild(container);
5026
+ }
5027
+ var unbindMonitor = monitorForElements({
5028
+ // Remove portal in the dragstart event so that the user will never see it
5029
+ onDragStart: cleanup,
5030
+ // Backup: remove portal when the drop finishes (this would be an error case)
5031
+ onDrop: cleanup
5032
+ });
5033
+ }
5034
+
5035
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/util/is-safari-on-ios.js
5036
+ var isSafariOnIOS = once(function isSafariOnIOS2() {
5037
+ if (false) {
5038
+ return false;
5039
+ }
5040
+ return isSafari() && "ontouchend" in document;
5041
+ });
5042
+
5043
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/center-under-pointer.js
5044
+ var centerUnderPointer = function centerUnderPointer2(_ref) {
5045
+ var container = _ref.container;
5046
+ var rect = container.getBoundingClientRect();
5047
+ return {
5048
+ x: rect.width / 2,
5049
+ y: rect.height / 2
5050
+ };
5051
+ };
5052
+
5053
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/pointer-outside-of-preview.js
5054
+ function pointerOutsideOfPreview(point) {
5055
+ return function getOffset(_ref) {
5056
+ var container = _ref.container;
5057
+ if (isSafariOnIOS() || isAndroid()) {
5058
+ return centerUnderPointer({
5059
+ container
5060
+ });
5061
+ }
5062
+ Object.assign(container.style, {
5063
+ borderInlineStart: "".concat(point.x, " solid transparent"),
5064
+ borderTop: "".concat(point.y, " solid transparent")
5065
+ });
5066
+ var computed = window.getComputedStyle(container);
5067
+ if (computed.direction === "rtl") {
5068
+ var box = container.getBoundingClientRect();
5069
+ return {
5070
+ x: box.width,
5071
+ y: 0
5072
+ };
5073
+ }
5074
+ return {
5075
+ x: 0,
5076
+ y: 0
5077
+ };
5078
+ };
5079
+ }
5080
+
4775
5081
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/shared/engagement-history.js
4776
5082
  var ledger2 = /* @__PURE__ */ new Map();
4777
5083
  var requested = /* @__PURE__ */ new Set();
@@ -4905,7 +5211,7 @@ function addScrollableAttribute(element) {
4905
5211
  }
4906
5212
 
4907
5213
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/shared/configuration.js
4908
- function ownKeys4(e, r) {
5214
+ function ownKeys5(e, r) {
4909
5215
  var t = Object.keys(e);
4910
5216
  if (Object.getOwnPropertySymbols) {
4911
5217
  var o = Object.getOwnPropertySymbols(e);
@@ -4915,12 +5221,12 @@ function ownKeys4(e, r) {
4915
5221
  }
4916
5222
  return t;
4917
5223
  }
4918
- function _objectSpread4(e) {
5224
+ function _objectSpread5(e) {
4919
5225
  for (var r = 1; r < arguments.length; r++) {
4920
5226
  var t = null != arguments[r] ? arguments[r] : {};
4921
- r % 2 ? ownKeys4(Object(t), true).forEach(function(r2) {
5227
+ r % 2 ? ownKeys5(Object(t), true).forEach(function(r2) {
4922
5228
  _defineProperty(e, r2, t[r2]);
4923
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys4(Object(t)).forEach(function(r2) {
5229
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys5(Object(t)).forEach(function(r2) {
4924
5230
  Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
4925
5231
  });
4926
5232
  }
@@ -4955,7 +5261,7 @@ var maxPixelScrollPerSecond = {
4955
5261
  };
4956
5262
  function getInternalConfig(provided) {
4957
5263
  var _provided$maxScrollSp;
4958
- return _objectSpread4(_objectSpread4({}, baseConfig), {}, {
5264
+ return _objectSpread5(_objectSpread5({}, baseConfig), {}, {
4959
5265
  // only allowing limited control over the config at this stage
4960
5266
  maxPixelScrollPerSecond: maxPixelScrollPerSecond[(_provided$maxScrollSp = provided === null || provided === void 0 ? void 0 : provided.maxScrollSpeed) !== null && _provided$maxScrollSp !== void 0 ? _provided$maxScrollSp : "standard"]
4961
5267
  });
@@ -5439,7 +5745,7 @@ function tryScroll(_ref3) {
5439
5745
  }
5440
5746
 
5441
5747
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/over-element/make-api.js
5442
- function ownKeys5(e, r) {
5748
+ function ownKeys6(e, r) {
5443
5749
  var t = Object.keys(e);
5444
5750
  if (Object.getOwnPropertySymbols) {
5445
5751
  var o = Object.getOwnPropertySymbols(e);
@@ -5449,12 +5755,12 @@ function ownKeys5(e, r) {
5449
5755
  }
5450
5756
  return t;
5451
5757
  }
5452
- function _objectSpread5(e) {
5758
+ function _objectSpread6(e) {
5453
5759
  for (var r = 1; r < arguments.length; r++) {
5454
5760
  var t = null != arguments[r] ? arguments[r] : {};
5455
- r % 2 ? ownKeys5(Object(t), true).forEach(function(r2) {
5761
+ r % 2 ? ownKeys6(Object(t), true).forEach(function(r2) {
5456
5762
  _defineProperty(e, r2, t[r2]);
5457
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys5(Object(t)).forEach(function(r2) {
5763
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys6(Object(t)).forEach(function(r2) {
5458
5764
  Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
5459
5765
  });
5460
5766
  }
@@ -5493,7 +5799,7 @@ function makeApi(_ref) {
5493
5799
  }
5494
5800
  function autoScrollWindow() {
5495
5801
  var args = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
5496
- var unique = _objectSpread5({}, args);
5802
+ var unique = _objectSpread6({}, args);
5497
5803
  windowRegistry.add(unique);
5498
5804
  function cleanup() {
5499
5805
  windowRegistry.delete(unique);
@@ -5646,6 +5952,11 @@ function needsRemount(diff, newUnits, oldKinds, threshold = 40) {
5646
5952
  var EDITABLE_TAGS = /* @__PURE__ */ new Set(["P", "H1", "H2", "H3", "H4", "H5", "H6"]);
5647
5953
  var BLOCK_DRAG_TYPE = "docxodus-block";
5648
5954
  var blockDragStyledDocuments = /* @__PURE__ */ new WeakSet();
5955
+ function blockPreviewText(unit) {
5956
+ if (unit.tagName === "TABLE") return "Table";
5957
+ const text = (unit.textContent ?? "").trim().replace(/\s+/g, " ");
5958
+ return text.length > 48 ? `${text.slice(0, 48)}\u2026` : text;
5959
+ }
5649
5960
  function ensureBlockDragStyles(doc) {
5650
5961
  if (blockDragStyledDocuments.has(doc)) return;
5651
5962
  blockDragStyledDocuments.add(doc);
@@ -5660,10 +5971,28 @@ function ensureBlockDragStyles(doc) {
5660
5971
  }
5661
5972
  .docx-block-handle:hover, .docx-block-handle:focus-visible { color: #344054; border-color: #98a2b3; outline: none; }
5662
5973
  .docx-block-handle[aria-pressed="true"] { color: #175cd3; border-color: #84adff; background: #eff8ff; }
5663
- .docx-block-handle.docx-block-dragging { cursor: grabbing; opacity: .78; }
5974
+ .docx-block-handle.docx-block-dragging { cursor: grabbing; opacity: .35; }
5975
+ /* The block being carried. Dimming it is the "a drag is happening" signal that survives the
5976
+ pointer being anywhere on screen \u2014 the drop line only says where, not what. */
5977
+ .docx-block-drag-source { opacity: .38; transition: opacity 120ms ease-out; }
5978
+ /* Positioned by transform so tracking the pointer costs no layout. Flipping display none\u2192block
5979
+ restarts the fade \u2014 one cheap entry animation per appearance, none while it tracks. */
5664
5980
  .docx-block-drop-indicator {
5665
- position: fixed; z-index: 2147482999; display: none; height: 3px; pointer-events: none;
5666
- border-radius: 999px; background: #2e90fa; box-shadow: 0 0 0 1px rgba(255,255,255,.85);
5981
+ position: fixed; top: 0; left: 0; z-index: 2147482999; display: none; height: 0;
5982
+ pointer-events: none; border-top: 2px solid #2e90fa;
5983
+ filter: drop-shadow(0 1px 2px rgba(46,144,250,.5));
5984
+ animation: docx-block-drop-in 110ms ease-out;
5985
+ }
5986
+ .docx-block-drop-indicator::before {
5987
+ content: ""; position: absolute; top: -5px; left: -2px; width: 8px; height: 8px;
5988
+ border-radius: 50%; background: #2e90fa;
5989
+ }
5990
+ @keyframes docx-block-drop-in { from { opacity: 0; } to { opacity: 1; } }
5991
+ .docx-block-drag-preview {
5992
+ max-width: 320px; padding: 6px 10px; border: 1px solid #b2ddff; border-radius: 6px;
5993
+ background: #eff8ff; color: #175cd3; box-shadow: 0 6px 16px rgba(16,24,40,.18);
5994
+ font: 500 13px/1.35 system-ui, sans-serif; white-space: nowrap; overflow: hidden;
5995
+ text-overflow: ellipsis;
5667
5996
  }
5668
5997
  .docx-block-move-menu {
5669
5998
  position: fixed; z-index: 2147483001; display: none; min-width: 150px; padding: 5px;
@@ -6051,12 +6380,22 @@ var DocxEditor = class _DocxEditor {
6051
6380
  this.blockMoveLive = null;
6052
6381
  this.blockDragSource = null;
6053
6382
  this.blockDragCleanup = [];
6054
- this.blockDragTargetCleanup = [];
6383
+ /** Block boxes measured at drag start — see `BlockDropZone`. Empty when no drag is in flight. */
6384
+ this.dropZones = [];
6385
+ /** Combined scroll offset when `dropZones` was measured, and the scroller measured against. */
6386
+ this.dropZoneOrigin = 0;
6387
+ this.dropZoneScroller = null;
6055
6388
  this.blockDragging = false;
6056
6389
  this.blockDragPointerDown = false;
6057
6390
  /** Anchors the current drag source may legally move next to, per the engine's own rules.
6058
6391
  * Null when the bridge predates ValidMoveTargets — then every block is offered, as before. */
6059
6392
  this.blockMoveTargets = null;
6393
+ /** Memoized `ValidMoveTargets` answers, keyed by source anchor and dropped whenever an edit
6394
+ * lands. The legal-target set is a property of the document, so hovering back and forth over
6395
+ * the same blocks between edits must not re-ask the engine. */
6396
+ this.blockMoveTargetCache = /* @__PURE__ */ new Map();
6397
+ /** Cancels the pending idle prefetch of the hovered block's targets — see `showBlockHandle`. */
6398
+ this.blockMoveTargetPrefetch = null;
6060
6399
  /** Why the last move was refused, verbatim from the engine — diagnostics, not announcement copy. */
6061
6400
  this.lastMoveError = null;
6062
6401
  /**
@@ -6174,6 +6513,11 @@ var DocxEditor = class _DocxEditor {
6174
6513
  this.handle = handle;
6175
6514
  this.options = options;
6176
6515
  this.editRoot = container;
6516
+ this.viewport = new DocumentViewport(container, {
6517
+ columnWidth: options.columnWidth,
6518
+ fitToWidth: options.fitToWidth,
6519
+ scale: options.scale
6520
+ });
6177
6521
  if (typeof document !== "undefined") {
6178
6522
  document.addEventListener("selectionchange", this.onSelectionChange);
6179
6523
  document.addEventListener("mousedown", this.onMouseDown, true);
@@ -6256,6 +6600,8 @@ var DocxEditor = class _DocxEditor {
6256
6600
  editable: options.editable ?? true,
6257
6601
  paginated: options.paginated ?? false,
6258
6602
  scale: options.scale ?? 1,
6603
+ columnWidth: options.columnWidth ?? "section",
6604
+ fitToWidth: options.fitToWidth ?? true,
6259
6605
  headerFooter: options.headerFooter ?? false,
6260
6606
  blockDrag: options.blockDrag ?? false,
6261
6607
  trackedChanges: options.trackedChanges ?? 0 /* Accept */,
@@ -6312,6 +6658,7 @@ var DocxEditor = class _DocxEditor {
6312
6658
  }
6313
6659
  this.clearDragSelection();
6314
6660
  this.teardownBlockDrag();
6661
+ this.viewport.dispose();
6315
6662
  this.exports.DocxSessionBridge.CloseSession(this.handle);
6316
6663
  }
6317
6664
  /**
@@ -6329,6 +6676,14 @@ var DocxEditor = class _DocxEditor {
6329
6676
  get root() {
6330
6677
  return this.container;
6331
6678
  }
6679
+ /**
6680
+ * The zoom the viewport is currently applying (1 = 100%). Below 1 the page is wider than the
6681
+ * host and has been scaled to fit rather than reflowed — the honest thing to show a user who
6682
+ * is wondering why a phone shows the whole page.
6683
+ */
6684
+ get zoom() {
6685
+ return this.viewport.scale;
6686
+ }
6332
6687
  /**
6333
6688
  * The live `DocxSession` handle backing this editor — the model of record.
6334
6689
  *
@@ -6357,8 +6712,7 @@ var DocxEditor = class _DocxEditor {
6357
6712
  return true;
6358
6713
  }
6359
6714
  const destination = res.created?.[0] ?? res.modified?.[0];
6360
- if (this.renderTrackedChanges) this.remount();
6361
- else this.reconcile();
6715
+ this.reconcile();
6362
6716
  const moved = destination ? this.bodyUnitNodes().find((el) => el.getAttribute("data-anchor") === destination.unid) : null;
6363
6717
  if (moved) {
6364
6718
  moved.classList.add("docx-block-move-flash");
@@ -6383,7 +6737,12 @@ var DocxEditor = class _DocxEditor {
6383
6737
  const node = target instanceof Node ? target : null;
6384
6738
  const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
6385
6739
  if (!el || !this.editRoot.contains(el)) return null;
6386
- return this.bodyUnitNodes().find((unit) => unit === el || unit.contains(el)) ?? null;
6740
+ let unit = null;
6741
+ for (let candidate = el.closest("[data-anchor]"); candidate && this.editRoot.contains(candidate); candidate = candidate.parentElement?.closest("[data-anchor]") ?? null) {
6742
+ unit = candidate;
6743
+ }
6744
+ if (!unit || unit.closest("section.footnotes, section.endnotes")) return null;
6745
+ return unit;
6387
6746
  }
6388
6747
  isMovableBlockUnit(unit) {
6389
6748
  if (!unit || !this.anchorIdOf(unit)) return false;
@@ -6405,18 +6764,18 @@ var DocxEditor = class _DocxEditor {
6405
6764
  if (!handle || !this.isMovableBlockUnit(unit)) return;
6406
6765
  const changed = unit !== this.blockDragSource;
6407
6766
  this.blockDragSource = unit;
6408
- if (changed) this.refreshBlockMoveTargets(unit);
6409
- if (this.blockMoveTargets?.size === 0) {
6767
+ const known = this.blockMoveTargetsFor(unit, { cachedOnly: true });
6768
+ if (known?.size === 0) {
6410
6769
  handle.style.display = "none";
6411
6770
  return;
6412
6771
  }
6772
+ if (changed && known === void 0) this.prefetchBlockMoveTargets(unit);
6413
6773
  const rect = unit.getBoundingClientRect();
6414
6774
  handle.style.display = "flex";
6415
6775
  handle.style.left = `${Math.max(4, rect.left - 32)}px`;
6416
6776
  handle.style.top = `${Math.max(4, rect.top + (unit.tagName === "TABLE" ? 6 : Math.max(0, (rect.height - 28) / 2)))}px`;
6417
- const preview = (unit.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 48);
6777
+ const preview = blockPreviewText(unit);
6418
6778
  handle.setAttribute("aria-label", preview ? `Move block: ${preview}` : "Move block");
6419
- if (changed) this.refreshBlockDropTargets();
6420
6779
  }
6421
6780
  hideBlockHandle() {
6422
6781
  if (this.blockDragging || this.blockMoveMenu?.style.display === "block") return;
@@ -6426,14 +6785,33 @@ var DocxEditor = class _DocxEditor {
6426
6785
  const source = this.currentBlockDragSource();
6427
6786
  if (source && this.blockDragHandle?.style.display !== "none") this.showBlockHandle(source);
6428
6787
  }
6429
- showDropIndicator(target, position) {
6788
+ /** Draw the drop line on `zone`'s requested edge, or take it away when there is no target. */
6789
+ paintDropIndicator(data) {
6430
6790
  const indicator = this.blockDropIndicator;
6791
+ const zone = data.zone;
6431
6792
  if (!indicator) return;
6432
- const rect = this.unitWrapperOf(target).getBoundingClientRect();
6793
+ if (!zone) {
6794
+ this.hideDropIndicator();
6795
+ return;
6796
+ }
6797
+ const y = Math.round(this.dropEdgeY(zone, data.position === "after" ? "after" : "before") + this.dropZoneShift()) - 1;
6798
+ indicator.style.transform = `translate3d(${Math.round(zone.left)}px, ${y}px, 0)`;
6799
+ indicator.style.width = `${Math.max(24, Math.round(zone.width))}px`;
6433
6800
  indicator.style.display = "block";
6434
- indicator.style.left = `${rect.left}px`;
6435
- indicator.style.top = `${position === "before" ? rect.top - 1 : rect.bottom - 1}px`;
6436
- indicator.style.width = `${Math.max(24, rect.width)}px`;
6801
+ }
6802
+ /**
6803
+ * Where to draw the line for an insertion on `position` of `zone` — the MIDDLE of the gap to
6804
+ * the neighbour on that side, not the zone's own border-box edge. A paragraph's `w:spacing`
6805
+ * becomes a CSS margin, which sits outside the box, so drawing on the edge underlines the
6806
+ * block's last line instead of reading as a gap between two blocks. Falls back to the raw edge
6807
+ * at the ends of the flow, and degrades to the same value when blocks are contiguous.
6808
+ */
6809
+ dropEdgeY(zone, position) {
6810
+ const neighbour = this.dropZones[zone.index + (position === "after" ? 1 : -1)];
6811
+ if (!neighbour) {
6812
+ return position === "after" ? zone.bottom + zone.marginAfter / 2 : zone.top - zone.marginBefore / 2;
6813
+ }
6814
+ return position === "after" ? (zone.bottom + neighbour.top) / 2 : (neighbour.bottom + zone.top) / 2;
6437
6815
  }
6438
6816
  hideDropIndicator() {
6439
6817
  if (this.blockDropIndicator) this.blockDropIndicator.style.display = "none";
@@ -6453,19 +6831,52 @@ var DocxEditor = class _DocxEditor {
6453
6831
  * behaviour this replaces. Null (no bridge support) keeps the previous offer-everything path.
6454
6832
  */
6455
6833
  refreshBlockMoveTargets(source) {
6834
+ this.blockMoveTargets = source ? this.blockMoveTargetsFor(source) ?? null : null;
6835
+ }
6836
+ /**
6837
+ * This block's legal destinations, from the memo when it is there. Returns `undefined` — not
6838
+ * `null` — for "not asked yet", so a caller can tell an unknown answer from the engine's
6839
+ * "no bridge support, offer everything" one.
6840
+ */
6841
+ blockMoveTargetsFor(source, options = {}) {
6456
6842
  const bridge = this.exports.DocxSessionBridge;
6457
- const sourceId = source ? this.anchorIdOf(source) : null;
6458
- if (!sourceId || typeof bridge.ValidMoveTargets !== "function") {
6459
- this.blockMoveTargets = null;
6460
- return;
6461
- }
6843
+ const sourceId = this.anchorIdOf(source);
6844
+ if (!sourceId || typeof bridge.ValidMoveTargets !== "function") return null;
6845
+ if (this.blockMoveTargetCache.has(sourceId)) return this.blockMoveTargetCache.get(sourceId);
6846
+ if (options.cachedOnly) return void 0;
6847
+ let targets;
6462
6848
  try {
6463
- const targets = JSON.parse(bridge.ValidMoveTargets(this.handle, sourceId));
6464
- this.blockMoveTargets = new Map(
6465
- targets.map((t) => [t.anchorId, { before: t.before, after: t.after }])
6466
- );
6849
+ const parsed = JSON.parse(bridge.ValidMoveTargets(this.handle, sourceId));
6850
+ targets = new Map(parsed.map((t) => [t.anchorId, { before: t.before, after: t.after }]));
6467
6851
  } catch {
6468
- this.blockMoveTargets = null;
6852
+ targets = null;
6853
+ }
6854
+ this.blockMoveTargetCache.set(sourceId, targets);
6855
+ return targets;
6856
+ }
6857
+ /**
6858
+ * Ask for the hovered block's destinations off the interaction path, and hide the handle if
6859
+ * the answer comes back empty and that block is still the one under the pointer. The handle
6860
+ * therefore appears immediately on hover and withdraws a beat later on the rare immovable
6861
+ * block, instead of every hover paying for the query up front.
6862
+ */
6863
+ prefetchBlockMoveTargets(source) {
6864
+ const view = this.container.ownerDocument.defaultView;
6865
+ if (!view) return;
6866
+ this.blockMoveTargetPrefetch?.();
6867
+ const run = () => {
6868
+ this.blockMoveTargetPrefetch = null;
6869
+ if (this.closed || !source.isConnected || this.blockDragSource !== source) return;
6870
+ if (this.blockMoveTargetsFor(source)?.size === 0 && this.blockDragHandle)
6871
+ this.blockDragHandle.style.display = "none";
6872
+ };
6873
+ const idle = view;
6874
+ if (idle.requestIdleCallback && idle.cancelIdleCallback) {
6875
+ const id = idle.requestIdleCallback(run, { timeout: 500 });
6876
+ this.blockMoveTargetPrefetch = () => idle.cancelIdleCallback(id);
6877
+ } else {
6878
+ const id = view.setTimeout(run, 0);
6879
+ this.blockMoveTargetPrefetch = () => view.clearTimeout(id);
6469
6880
  }
6470
6881
  }
6471
6882
  /**
@@ -6480,45 +6891,75 @@ var DocxEditor = class _DocxEditor {
6480
6891
  if (!sides) return false;
6481
6892
  return position ? sides[position] : sides.before || sides.after;
6482
6893
  }
6894
+ /** Measure every movable block once, at drag start. See `BlockDropZone`. */
6895
+ captureDropZones() {
6896
+ const view = this.container.ownerDocument.defaultView;
6897
+ this.dropZoneScroller = this.scrollContainer();
6898
+ this.dropZoneOrigin = this.scrollOffsetSum();
6899
+ this.dropZones = [];
6900
+ const boxes = [];
6901
+ for (const unit of this.bodyUnitNodes()) {
6902
+ const anchorId = this.isMovableBlockUnit(unit) ? this.anchorIdOf(unit) : null;
6903
+ if (!anchorId) continue;
6904
+ const box = this.unitWrapperOf(unit);
6905
+ const rect = box.getBoundingClientRect();
6906
+ boxes.push(box);
6907
+ this.dropZones.push({
6908
+ unit,
6909
+ anchorId,
6910
+ index: this.dropZones.length,
6911
+ top: rect.top,
6912
+ bottom: rect.bottom,
6913
+ left: rect.left,
6914
+ width: rect.width,
6915
+ marginBefore: 0,
6916
+ marginAfter: 0
6917
+ });
6918
+ }
6919
+ const ends = new Set([0, this.dropZones.length - 1].filter((i) => i >= 0 && i < boxes.length));
6920
+ for (const i of ends) {
6921
+ const style = view?.getComputedStyle(boxes[i]);
6922
+ this.dropZones[i].marginBefore = parseFloat(style?.marginTop ?? "0") || 0;
6923
+ this.dropZones[i].marginAfter = parseFloat(style?.marginBottom ?? "0") || 0;
6924
+ }
6925
+ }
6926
+ scrollOffsetSum() {
6927
+ const view = this.container.ownerDocument.defaultView;
6928
+ return (view?.scrollY ?? 0) + (this.dropZoneScroller?.scrollTop ?? 0);
6929
+ }
6930
+ /** How far the measured boxes have travelled since capture, from scrolling (drag autoscroll). */
6931
+ dropZoneShift() {
6932
+ return this.dropZoneOrigin - this.scrollOffsetSum();
6933
+ }
6483
6934
  /**
6484
- * The side of `unit` a drop at `clientY` should land on: the half the pointer is in, snapped to
6485
- * the other side when only that one is legal. Snapping rather than refusing keeps a reachable
6486
- * target usable — the illegal side is usually illegal only because a section break or a
6487
- * cross-block range sits between the two blocks on that side.
6935
+ * Where a drop at `clientY` lands, or null when nothing there is legal.
6936
+ *
6937
+ * Resolution is by VERTICAL GEOMETRY over the measured blocks, not by which element the pointer
6938
+ * is over: the drag handle floats in the page margin, so a drag straight down the gutter — the
6939
+ * natural gesture — never crosses a paragraph box, and element hit testing gave those drags no
6940
+ * indicator and no drop at all. The nearest block by vertical distance is the target; the half
6941
+ * the pointer is in picks the side, snapped to the other side when only that one is legal
6942
+ * (a section break or a cross-block range usually makes exactly one side illegal). When neither
6943
+ * side is legal — the pointer is in a region this block cannot reach — there is no drop, and
6944
+ * nothing is drawn.
6488
6945
  */
6489
- dropPositionFor(unit, clientY) {
6490
- const rect = unit.getBoundingClientRect();
6491
- const preferred = clientY < rect.top + rect.height / 2 ? "before" : "after";
6492
- if (this.isValidMoveTarget(unit, preferred)) return preferred;
6493
- const other = preferred === "before" ? "after" : "before";
6494
- return this.isValidMoveTarget(unit, other) ? other : preferred;
6495
- }
6496
- refreshBlockDropTargets() {
6497
- for (const cleanup of this.blockDragTargetCleanup.splice(0)) cleanup();
6498
- if (!this.blockDragHandle || this.options.paginated) return;
6499
- for (const unit of this.bodyUnitNodes().filter((el) => this.isMovableBlockUnit(el))) {
6500
- this.blockDragTargetCleanup.push(dropTargetForElements({
6501
- element: unit,
6502
- // A target the engine would refuse is not a drop target at all, so Pragmatic never
6503
- // fires onDragEnter for it and no indicator is drawn over it.
6504
- canDrop: ({ source }) => source.data.type === BLOCK_DRAG_TYPE && source.data.sourceAnchorId !== this.anchorIdOf(unit) && this.isValidMoveTarget(unit),
6505
- getData: ({ input }) => ({
6506
- type: BLOCK_DRAG_TYPE,
6507
- targetAnchorId: this.anchorIdOf(unit),
6508
- position: this.dropPositionFor(unit, input.clientY),
6509
- targetElement: unit
6510
- }),
6511
- onDragEnter: ({ self }) => {
6512
- const pos = self.data.position === "after" ? "after" : "before";
6513
- this.showDropIndicator(unit, pos);
6514
- },
6515
- onDrag: ({ self }) => {
6516
- const pos = self.data.position === "after" ? "after" : "before";
6517
- this.showDropIndicator(unit, pos);
6518
- },
6519
- onDragLeave: () => this.hideDropIndicator()
6520
- }));
6946
+ resolveDropAt(clientY) {
6947
+ const y = clientY - this.dropZoneShift();
6948
+ let best = null;
6949
+ let bestGap = Infinity;
6950
+ for (const zone of this.dropZones) {
6951
+ const gap = y < zone.top ? zone.top - y : y > zone.bottom ? y - zone.bottom : 0;
6952
+ if (gap < bestGap) {
6953
+ best = zone;
6954
+ bestGap = gap;
6955
+ }
6956
+ if (gap === 0) break;
6521
6957
  }
6958
+ if (!best || best.unit === this.blockDragSource) return null;
6959
+ const preferred = y < (best.top + best.bottom) / 2 ? "before" : "after";
6960
+ if (this.isValidMoveTarget(best.unit, preferred)) return { zone: best, position: preferred };
6961
+ const other = preferred === "before" ? "after" : "before";
6962
+ return this.isValidMoveTarget(best.unit, other) ? { zone: best, position: other } : null;
6522
6963
  }
6523
6964
  closeBlockMoveMenu(restoreFocus = false) {
6524
6965
  if (!this.blockMoveMenu || !this.blockDragHandle) return;
@@ -6555,7 +6996,8 @@ var DocxEditor = class _DocxEditor {
6555
6996
  const index = units.indexOf(source);
6556
6997
  if (index < 0) return null;
6557
6998
  const position = action === "up" || action === "top" ? "before" : "after";
6558
- const candidates = units.filter((el) => el !== source && this.isValidMoveTarget(el, position)).filter((el) => position === "before" ? units.indexOf(el) < index : units.indexOf(el) > index);
6999
+ const side = position === "before" ? units.slice(0, index) : units.slice(index + 1);
7000
+ const candidates = side.filter((el) => this.isValidMoveTarget(el, position));
6559
7001
  if (candidates.length === 0) return null;
6560
7002
  if (action === "up") return { target: candidates[candidates.length - 1], position };
6561
7003
  if (action === "down") return { target: candidates[0], position };
@@ -6692,20 +7134,50 @@ var DocxEditor = class _DocxEditor {
6692
7134
  const source = this.currentBlockDragSource();
6693
7135
  return { type: BLOCK_DRAG_TYPE, sourceAnchorId: source ? this.anchorIdOf(source) : void 0 };
6694
7136
  },
7137
+ // The browser would otherwise ghost the 26px grip, which says nothing about what is moving.
7138
+ onGenerateDragPreview: ({ nativeSetDragImage }) => {
7139
+ const source = this.currentBlockDragSource();
7140
+ setCustomNativeDragPreview({
7141
+ nativeSetDragImage,
7142
+ getOffset: pointerOutsideOfPreview({ x: "14px", y: "10px" }),
7143
+ render: ({ container }) => {
7144
+ const chip = doc.createElement("div");
7145
+ chip.className = "docx-block-drag-preview";
7146
+ chip.textContent = source && blockPreviewText(source) || "Move block";
7147
+ container.appendChild(chip);
7148
+ return () => chip.remove();
7149
+ }
7150
+ });
7151
+ },
6695
7152
  onDragStart: () => {
7153
+ const source = this.currentBlockDragSource();
6696
7154
  this.blockDragging = true;
6697
7155
  handle.classList.add("docx-block-dragging");
7156
+ source?.classList.add("docx-block-drag-source");
6698
7157
  this.closeBlockMoveMenu();
6699
- this.refreshBlockMoveTargets(this.currentBlockDragSource());
6700
- this.refreshBlockDropTargets();
7158
+ this.refreshBlockMoveTargets(source);
7159
+ this.captureDropZones();
6701
7160
  },
6702
7161
  onDrop: () => {
6703
7162
  this.blockDragging = false;
6704
7163
  this.blockDragPointerDown = false;
7164
+ this.dropZones = [];
6705
7165
  handle.classList.remove("docx-block-dragging");
7166
+ doc.querySelectorAll(".docx-block-drag-source").forEach((el) => el.classList.remove("docx-block-drag-source"));
6706
7167
  this.hideDropIndicator();
6707
7168
  }
6708
7169
  }));
7170
+ this.blockDragCleanup.push(dropTargetForElements({
7171
+ element: this.editRoot,
7172
+ canDrop: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
7173
+ getData: ({ input }) => {
7174
+ const hit = this.resolveDropAt(input.clientY);
7175
+ return hit ? { type: BLOCK_DRAG_TYPE, targetAnchorId: hit.zone.anchorId, position: hit.position, zone: hit.zone } : { type: BLOCK_DRAG_TYPE };
7176
+ },
7177
+ onDragEnter: ({ self }) => this.paintDropIndicator(self.data),
7178
+ onDrag: ({ self }) => this.paintDropIndicator(self.data),
7179
+ onDragLeave: () => this.hideDropIndicator()
7180
+ }));
6709
7181
  this.blockDragCleanup.push(monitorForElements({
6710
7182
  canMonitor: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
6711
7183
  onDrop: ({ source, location: location2 }) => {
@@ -6732,10 +7204,13 @@ var DocxEditor = class _DocxEditor {
6732
7204
  canScroll: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
6733
7205
  getAllowedAxis: () => "vertical"
6734
7206
  }));
6735
- this.refreshBlockDropTargets();
6736
7207
  }
6737
7208
  teardownBlockDrag() {
6738
- for (const cleanup of this.blockDragTargetCleanup.splice(0)) cleanup();
7209
+ this.blockMoveTargetPrefetch?.();
7210
+ this.blockMoveTargetPrefetch = null;
7211
+ this.blockMoveTargetCache.clear();
7212
+ this.dropZones = [];
7213
+ this.dropZoneScroller = null;
6739
7214
  for (const cleanup of this.blockDragCleanup.splice(0)) cleanup();
6740
7215
  this.blockDragHandle?.remove();
6741
7216
  this.blockDropIndicator?.remove();
@@ -6824,17 +7299,17 @@ var DocxEditor = class _DocxEditor {
6824
7299
  bodyRoot.after(this.region.footerBand);
6825
7300
  this.region.refreshAll();
6826
7301
  }
6827
- /** Continuous (non-paginated) mount: inject the converter's styles + body, wire blocks. */
7302
+ /**
7303
+ * Continuous (non-paginated) mount: inject the converter's styles + body, wire blocks.
7304
+ *
7305
+ * The body always gets its own `.docx-body-flow` wrapper — not only when bands are docked.
7306
+ * It is the sheet: the element the viewport gives page geometry to and zooms, and the one
7307
+ * the bands dock around. Without it the container would have to be both the scrolling host
7308
+ * and the scaled page, which are different boxes.
7309
+ */
6828
7310
  mountHtml(fullHtml) {
6829
7311
  const parsed = new DOMParser().parseFromString(fullHtml, "text/html");
6830
7312
  const styles = Array.from(parsed.querySelectorAll("style")).map((s) => s.outerHTML).join("");
6831
- if (!this.region) {
6832
- this.container.innerHTML = styles + parsed.body.innerHTML;
6833
- this.editRoot = this.container;
6834
- if (this.options.editable) this.wireBlocks(this.container);
6835
- this.stampPlanState();
6836
- return;
6837
- }
6838
7313
  this.container.innerHTML = styles;
6839
7314
  const flow = document.createElement("div");
6840
7315
  flow.className = "docx-body-flow";
@@ -6843,7 +7318,8 @@ var DocxEditor = class _DocxEditor {
6843
7318
  this.editRoot = flow;
6844
7319
  if (this.options.editable) this.wireBlocks(flow);
6845
7320
  this.stampPlanState();
6846
- this.dockBands(flow);
7321
+ if (this.region) this.dockBands(flow);
7322
+ this.viewport.attach(flow, true);
6847
7323
  }
6848
7324
  /** Paginated mount: flow blocks into page boxes via pagination.ts, wire the page clones. */
6849
7325
  mountPaginated(fullHtml) {
@@ -6864,6 +7340,7 @@ var DocxEditor = class _DocxEditor {
6864
7340
  this.editRoot = pageRoot;
6865
7341
  if (this.options.editable) this.wireBlocks(pageRoot);
6866
7342
  if (this.region) this.dockBands(target);
7343
+ this.viewport.attach(pageRoot, false);
6867
7344
  }
6868
7345
  wireBlocks(root) {
6869
7346
  root.querySelectorAll("[data-anchor]").forEach((el) => this.wireBlock(el));
@@ -7252,11 +7729,22 @@ var DocxEditor = class _DocxEditor {
7252
7729
  }
7253
7730
  parseEdit(json) {
7254
7731
  try {
7255
- return JSON.parse(json);
7732
+ const result = JSON.parse(json);
7733
+ if (result.success) this.invalidateBlockMoveTargets();
7734
+ return result;
7256
7735
  } catch {
7257
7736
  return { success: false };
7258
7737
  }
7259
7738
  }
7739
+ /**
7740
+ * Drop the memoized `ValidMoveTargets` answers. Which blocks a block may move next to is a
7741
+ * fact about the DOCUMENT, so it survives hovering but not editing — and the two places a
7742
+ * document changes are `parseEdit` (every mutation that returns an `EditResult`) and
7743
+ * undo/redo, which return a bare boolean and so cannot go through it.
7744
+ */
7745
+ invalidateBlockMoveTargets() {
7746
+ this.blockMoveTargetCache.clear();
7747
+ }
7260
7748
  // ─── M5: formatting commands (ribbon) ────────────────────────────────
7261
7749
  // ─── Multi-block selection helpers (format a whole stack of paragraphs at once) ──────
7262
7750
  /**
@@ -7810,12 +8298,16 @@ var DocxEditor = class _DocxEditor {
7810
8298
  /** Undo the last edit (incremental repaint; falls back to a full re-render). */
7811
8299
  undo() {
7812
8300
  if (this.closed) return;
7813
- if (this.exports.DocxSessionBridge.Undo(this.handle)) this.reconcile();
8301
+ if (!this.exports.DocxSessionBridge.Undo(this.handle)) return;
8302
+ this.invalidateBlockMoveTargets();
8303
+ this.reconcile();
7814
8304
  }
7815
8305
  /** Redo the last undone edit (incremental repaint; falls back to a full re-render). */
7816
8306
  redo() {
7817
8307
  if (this.closed) return;
7818
- if (this.exports.DocxSessionBridge.Redo(this.handle)) this.reconcile();
8308
+ if (!this.exports.DocxSessionBridge.Redo(this.handle)) return;
8309
+ this.invalidateBlockMoveTargets();
8310
+ this.reconcile();
7819
8311
  }
7820
8312
  // ─── Header/footer region commands (no-ops unless `headerFooter` is on) ───────────────
7821
8313
  /**
@@ -8044,7 +8536,11 @@ var DocxEditor = class _DocxEditor {
8044
8536
  const oldTokens = oldNodes.map(_DocxEditor.domTokenOf);
8045
8537
  const oldKinds = oldNodes.map(_DocxEditor.domKindOf);
8046
8538
  const bodyDiff = diffUnits(oldTokens, plan.body);
8047
- if (needsRemount(bodyDiff, plan.body, oldKinds)) return this.bail("needsRemount (li change or churn)");
8539
+ if (needsRemount(bodyDiff, plan.body, oldKinds)) {
8540
+ return this.bail(
8541
+ `needsRemount (li change or churn): +${bodyDiff.added.length} -${bodyDiff.removed.length} ~${bodyDiff.substituted.length} moved=${bodyDiff.moved.length} of ${plan.body.length}`
8542
+ );
8543
+ }
8048
8544
  const fnState = this.notesDiff("footnotes", plan.footnotes);
8049
8545
  const enState = this.notesDiff("endnotes", plan.endnotes);
8050
8546
  if (fnState === null || enState === null) return this.bail("notes container unstampable/missing");
@@ -8653,14 +9149,7 @@ var RIBBON_CSS = `
8653
9149
  SHOULD win.
8654
9150
  (No backticks in this file's comments: the stylesheet is a template literal.) */
8655
9151
  .dxr-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
8656
- .dxr[data-chrome] .dxr-surface { max-width: 920px; margin: 26px auto; padding: 0 16px 96px; }
8657
- .dxr-surface[data-view="continuous"] > * { background: var(--dxr-sheet); }
8658
- .dxr[data-chrome] .dxr-surface[data-view="continuous"] {
8659
- padding: 56px 72px;
8660
- border-radius: 3px;
8661
- background: var(--dxr-sheet);
8662
- box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
8663
- }
9152
+ .dxr[data-chrome] .dxr-surface { margin: 26px auto; padding: 0 16px 96px; }
8664
9153
  .dxr-surface [contenteditable="true"]:focus {
8665
9154
  outline: 2px solid var(--dxr-accent);
8666
9155
  outline-offset: 2px;
@@ -8672,7 +9161,10 @@ var RIBBON_CSS = `
8672
9161
  so they dock as their own regions. Styled from the same tokens as the ribbon
8673
9162
  so the surface reads as one instrument rather than two apps. */
8674
9163
  .dxr-surface .docx-hf-band {
8675
- margin: 0 0 14px;
9164
+ /* Docked outside the zoomed sheet, so it takes the page's on-screen width from the
9165
+ custom property the viewport publishes rather than stretching to the whole surface. */
9166
+ width: min(100%, var(--docx-sheet-width, 100%));
9167
+ margin: 0 auto 14px;
8676
9168
  padding: 9px 72px 13px;
8677
9169
  border: 1px solid var(--dxr-rule);
8678
9170
  border-left: 2px solid #c2ccd9;
@@ -8680,7 +9172,7 @@ var RIBBON_CSS = `
8680
9172
  background: var(--dxr-sheet);
8681
9173
  }
8682
9174
  .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
8683
- .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px 0 0; }
9175
+ .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px auto 0; }
8684
9176
  .dxr-surface .docx-hf-chrome {
8685
9177
  display: flex;
8686
9178
  gap: 8px;
@@ -8735,13 +9227,15 @@ var RIBBON_CSS = `
8735
9227
  text-transform: none;
8736
9228
  }
8737
9229
  .dxr-surface .docx-hf-band[data-hf-inherited] { border-style: dashed; }
8738
- .dxr[data-chrome] .dxr-surface[data-view="continuous"]:has(.docx-body-flow) {
8739
- padding: 0;
8740
- background: transparent;
8741
- box-shadow: none;
8742
- }
9230
+ /* The sheet IS the page. The editor's viewport sizes .docx-body-flow to the section's page
9231
+ width and its section wrappers to the authored text column, so the horizontal gutters here
9232
+ are the document's own w:sectPr margins, not a padding this chrome invents; only the
9233
+ vertical breathing room is ours. Centering is left to margin:auto so a page the viewport
9234
+ has zoomed to fit stays centered at its scaled width. */
8743
9235
  .dxr[data-chrome] .dxr-surface[data-view="continuous"] .docx-body-flow {
8744
- padding: 56px 72px;
9236
+ max-width: 100%;
9237
+ margin: 0 auto;
9238
+ padding: 56px 0;
8745
9239
  border-radius: 3px;
8746
9240
  background: var(--dxr-sheet);
8747
9241
  box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
@@ -8820,9 +9314,11 @@ var RIBBON_CSS = `
8820
9314
  .dxr[data-chrome="compact"] .dxr-note { display: none; }
8821
9315
  .dxr[data-chrome="compact"] .dxr-rail { display: none; }
8822
9316
  .dxr[data-chrome="compact"] .dxr-hint { display: none; }
9317
+ /* Compact trims the chrome around the page, never the page: the document's own column width
9318
+ is what the viewport's fit-to-width zoom scales, so a phone shows a whole smaller page
9319
+ instead of a narrower one that breaks its lines somewhere Word never would. */
8823
9320
  .dxr[data-chrome="compact"] .dxr-surface { margin: 12px auto; padding: 0 10px 64px; }
8824
- .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] { padding: 22px 18px; }
8825
- .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 18px; }
9321
+ .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 0; }
8826
9322
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-band { padding: 9px 18px 13px; }
8827
9323
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-chrome,
8828
9324
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-warning { margin-left: 0; }
@@ -9572,6 +10068,8 @@ var RibbonSurface = class {
9572
10068
  fabricateClasses: this.options.fabricateClasses,
9573
10069
  editable: this.options.editable,
9574
10070
  scale: this.options.scale,
10071
+ columnWidth: this.options.columnWidth,
10072
+ fitToWidth: this.options.fitToWidth,
9575
10073
  onEdit: this.options.onEdit,
9576
10074
  onMove: this.options.onMove,
9577
10075
  paginated,
@@ -11543,8 +12041,12 @@ export {
11543
12041
  ComparisonLogLevel,
11544
12042
  ConflictResolution,
11545
12043
  ContextBoundary,
12044
+ DEFAULT_MARGIN,
12045
+ DEFAULT_PAGE_HEIGHT,
12046
+ DEFAULT_PAGE_WIDTH,
11546
12047
  DiffFormat,
11547
12048
  DocumentElementType,
12049
+ DocumentViewport,
11548
12050
  DocxDiffFormatComparison,
11549
12051
  DocxDiffRevisionGranularity,
11550
12052
  DocxEditor,
@@ -11590,6 +12092,7 @@ export {
11590
12092
  findElementsByType,
11591
12093
  findMovePair,
11592
12094
  findTextOccurrences,
12095
+ fitScale,
11593
12096
  generateAnnotationCss,
11594
12097
  generateAnnotationVisibilityCss,
11595
12098
  getAnnotations,
@@ -11613,7 +12116,10 @@ export {
11613
12116
  mountRibbon,
11614
12117
  openDocxSession2 as openDocxSession,
11615
12118
  paginateHtml,
12119
+ parseSectionDimensions,
11616
12120
  projectAnnotationsOntoHtml,
12121
+ ptToPx,
12122
+ pxToPt,
11617
12123
  removeAnnotation,
11618
12124
  removeAnnotationFromHtml,
11619
12125
  renderBlockHtml,