docxodus 9.4.0 → 9.6.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.
@@ -132,8 +132,12 @@ var Docxodus = (() => {
132
132
  ComparisonLogLevel: () => ComparisonLogLevel,
133
133
  ConflictResolution: () => ConflictResolution,
134
134
  ContextBoundary: () => ContextBoundary,
135
+ DEFAULT_MARGIN: () => DEFAULT_MARGIN,
136
+ DEFAULT_PAGE_HEIGHT: () => DEFAULT_PAGE_HEIGHT,
137
+ DEFAULT_PAGE_WIDTH: () => DEFAULT_PAGE_WIDTH,
135
138
  DiffFormat: () => DiffFormat,
136
139
  DocumentElementType: () => DocumentElementType,
140
+ DocumentViewport: () => DocumentViewport,
137
141
  DocxDiffFormatComparison: () => DocxDiffFormatComparison,
138
142
  DocxDiffRevisionGranularity: () => DocxDiffRevisionGranularity,
139
143
  DocxEditor: () => DocxEditor,
@@ -179,6 +183,7 @@ var Docxodus = (() => {
179
183
  findElementsByType: () => findElementsByType,
180
184
  findMovePair: () => findMovePair,
181
185
  findTextOccurrences: () => findTextOccurrences,
186
+ fitScale: () => fitScale,
182
187
  generateAnnotationCss: () => generateAnnotationCss,
183
188
  generateAnnotationVisibilityCss: () => generateAnnotationVisibilityCss,
184
189
  getAnnotations: () => getAnnotations,
@@ -202,7 +207,10 @@ var Docxodus = (() => {
202
207
  mountRibbon: () => mountRibbon,
203
208
  openDocxSession: () => openDocxSession2,
204
209
  paginateHtml: () => paginateHtml,
210
+ parseSectionDimensions: () => parseSectionDimensions,
205
211
  projectAnnotationsOntoHtml: () => projectAnnotationsOntoHtml,
212
+ ptToPx: () => ptToPx,
213
+ pxToPt: () => pxToPt,
206
214
  removeAnnotation: () => removeAnnotation,
207
215
  removeAnnotationFromHtml: () => removeAnnotationFromHtml,
208
216
  renderBlockHtml: () => renderBlockHtml,
@@ -1438,20 +1446,18 @@ var Docxodus = (() => {
1438
1446
  return (renderer ?? RENDERERS.decimal)(value);
1439
1447
  }
1440
1448
 
1441
- // src/pagination.ts
1449
+ // src/page-geometry.ts
1442
1450
  var DEFAULT_PAGE_WIDTH = 612;
1443
1451
  var DEFAULT_PAGE_HEIGHT = 792;
1444
1452
  var DEFAULT_MARGIN = 72;
1445
- var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1446
- var MIN_BODY_CONTENT_HEIGHT = 72;
1453
+ var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1447
1454
  function pxToPt(px) {
1448
1455
  return px * 0.75;
1449
1456
  }
1450
1457
  function ptToPx(pt) {
1451
1458
  return pt / 0.75;
1452
1459
  }
1453
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1454
- function parseDimensions(section) {
1460
+ function parseSectionDimensions(section) {
1455
1461
  const pageWidth = parseFloat(section.dataset.pageWidth || "") || DEFAULT_PAGE_WIDTH;
1456
1462
  const pageHeight = parseFloat(section.dataset.pageHeight || "") || DEFAULT_PAGE_HEIGHT;
1457
1463
  const contentWidth = parseFloat(section.dataset.contentWidth || "") || pageWidth - 2 * DEFAULT_MARGIN;
@@ -1475,6 +1481,42 @@ var Docxodus = (() => {
1475
1481
  footerHeight
1476
1482
  };
1477
1483
  }
1484
+ function sectionWrappers(root) {
1485
+ const sections = Array.from(root.querySelectorAll("[data-section-index]"));
1486
+ return sections.length > 0 ? sections : [root];
1487
+ }
1488
+ var MIN_FIT_SCALE = 0.25;
1489
+ function fitScale(availablePx, naturalPt, max = 1) {
1490
+ if (!(availablePx > 0) || !(naturalPt > 0)) return max;
1491
+ const naturalPx = ptToPx(naturalPt);
1492
+ if (naturalPx <= availablePx) return max;
1493
+ return Math.max(MIN_FIT_SCALE, Math.min(max, availablePx / naturalPx));
1494
+ }
1495
+ function applyZoom(el, scale, naturalPt) {
1496
+ if (scale === 1) {
1497
+ el.style.removeProperty("zoom");
1498
+ el.style.removeProperty("transform");
1499
+ el.style.removeProperty("transform-origin");
1500
+ el.style.removeProperty("margin-right");
1501
+ el.style.removeProperty("margin-bottom");
1502
+ return;
1503
+ }
1504
+ const zoomSupported = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("zoom", "0.5");
1505
+ if (zoomSupported) {
1506
+ el.style.zoom = String(scale);
1507
+ return;
1508
+ }
1509
+ el.style.transform = `scale(${scale})`;
1510
+ el.style.transformOrigin = "top left";
1511
+ if (naturalPt) {
1512
+ el.style.marginRight = `-${ptToPx(naturalPt.width * (1 - scale))}px`;
1513
+ el.style.marginBottom = `-${ptToPx(naturalPt.height * (1 - scale))}px`;
1514
+ }
1515
+ }
1516
+
1517
+ // src/pagination.ts
1518
+ var MAX_FOOTNOTE_AREA_RATIO = 0.6;
1519
+ var MIN_BODY_CONTENT_HEIGHT = 72;
1478
1520
  var PaginationEngine = class {
1479
1521
  /**
1480
1522
  * Creates a new pagination engine.
@@ -1520,7 +1562,7 @@ var Docxodus = (() => {
1520
1562
  const sectionsToProcess = sections.length > 0 ? Array.from(sections) : [this.stagingElement];
1521
1563
  for (const section of sectionsToProcess) {
1522
1564
  const sectionIndex = parseInt(section.dataset.sectionIndex || "0", 10);
1523
- const dims = parseDimensions(section);
1565
+ const dims = parseSectionDimensions(section);
1524
1566
  this.stagingElement.style.visibility = "hidden";
1525
1567
  this.stagingElement.style.position = "absolute";
1526
1568
  this.stagingElement.style.left = "-9999px";
@@ -2818,6 +2860,114 @@ var Docxodus = (() => {
2818
2860
  return engine.paginate();
2819
2861
  }
2820
2862
 
2863
+ // src/viewport.ts
2864
+ var DocumentViewport = class {
2865
+ constructor(host, options = {}) {
2866
+ this.root = null;
2867
+ /** Natural page size in points — the widest section's PAGE box, which is what must fit. */
2868
+ this.natural = { width: 0, height: 0 };
2869
+ this.observer = null;
2870
+ this.host = host;
2871
+ this.options = {
2872
+ columnWidth: options.columnWidth ?? "section",
2873
+ fitToWidth: options.fitToWidth ?? true,
2874
+ scale: options.scale ?? 1
2875
+ };
2876
+ }
2877
+ /**
2878
+ * Adopt a freshly mounted document root (a continuous flow, or the paginated page stack).
2879
+ * Safe to call on every remount; the previous root is released first.
2880
+ *
2881
+ * `applySectionGeometry` is false for the paginated view, which already builds real page
2882
+ * boxes at the section's dimensions — there the viewport contributes only the fit zoom.
2883
+ */
2884
+ attach(root, applySectionGeometry) {
2885
+ this.release();
2886
+ this.root = root;
2887
+ this.natural = applySectionGeometry ? this.stampSections(root) : this.measurePages(root);
2888
+ this.refresh();
2889
+ if (typeof ResizeObserver !== "undefined") {
2890
+ this.observer = new ResizeObserver(() => this.refresh());
2891
+ this.observer.observe(this.host);
2892
+ }
2893
+ }
2894
+ /** Recompute the fit zoom against the host's current width. */
2895
+ refresh() {
2896
+ if (!this.root) return;
2897
+ const scale = this.scale;
2898
+ applyZoom(this.root, scale, this.natural);
2899
+ this.host.style.setProperty(
2900
+ "--docx-sheet-width",
2901
+ this.natural.width > 0 ? `${ptToPx(this.natural.width) * scale}px` : "100%"
2902
+ );
2903
+ }
2904
+ /** The zoom currently applied (1 = 100%). Reported by the ribbon's anchor rail. */
2905
+ get scale() {
2906
+ if (!this.root) return this.options.scale;
2907
+ return this.options.fitToWidth ? fitScale(this.availableWidthPx(), this.natural.width, this.options.scale) : this.options.scale;
2908
+ }
2909
+ dispose() {
2910
+ this.release();
2911
+ this.root = null;
2912
+ }
2913
+ release() {
2914
+ this.observer?.disconnect();
2915
+ this.observer = null;
2916
+ if (this.root) applyZoom(this.root, 1);
2917
+ this.host.style.removeProperty("--docx-sheet-width");
2918
+ }
2919
+ /** The host's content box, which is the space a page has to fit into. */
2920
+ availableWidthPx() {
2921
+ const style = typeof getComputedStyle === "function" ? getComputedStyle(this.host) : null;
2922
+ const padding = style ? (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0) : 0;
2923
+ return Math.max(0, this.host.clientWidth - padding);
2924
+ }
2925
+ /**
2926
+ * Give each section wrapper its `w:sectPr` geometry: the authored text column, guttered by
2927
+ * the authored margins. The wrapper then measures exactly one page wide, which is what the
2928
+ * sheet chrome paints and what the fit zoom scales.
2929
+ */
2930
+ stampSections(root) {
2931
+ const sections = sectionWrappers(root);
2932
+ if (this.options.columnWidth === "fluid") {
2933
+ root.style.removeProperty("width");
2934
+ for (const section of sections) {
2935
+ section.style.removeProperty("width");
2936
+ section.style.removeProperty("padding-left");
2937
+ section.style.removeProperty("padding-right");
2938
+ }
2939
+ return { width: 0, height: 0 };
2940
+ }
2941
+ let widest = 0;
2942
+ for (const section of sections) {
2943
+ const dims = parseSectionDimensions(section);
2944
+ section.style.width = `${dims.contentWidth}pt`;
2945
+ section.style.paddingLeft = `${dims.marginLeft}pt`;
2946
+ section.style.paddingRight = `${dims.marginRight}pt`;
2947
+ section.style.boxSizing = "content-box";
2948
+ section.style.marginLeft = "auto";
2949
+ section.style.marginRight = "auto";
2950
+ widest = Math.max(widest, dims.pageWidth);
2951
+ }
2952
+ if (widest > 0 && sections[0] !== root) root.style.width = `${widest}pt`;
2953
+ return { width: widest, height: 0 };
2954
+ }
2955
+ /**
2956
+ * The paginated view's page boxes are already page-sized — `pagination.ts` writes each
2957
+ * box's `width` in points — so the widest of those is the natural width. Reading the
2958
+ * inline width rather than the laid-out box keeps this independent of the per-box zoom
2959
+ * pagination may itself have applied.
2960
+ */
2961
+ measurePages(root) {
2962
+ let widest = 0;
2963
+ for (const box of Array.from(root.children)) {
2964
+ const declared = /^([\d.]+)pt$/.exec(box.style.width || "");
2965
+ widest = Math.max(widest, declared ? parseFloat(declared[1]) : pxToPt(box.offsetWidth));
2966
+ }
2967
+ return { width: widest, height: 0 };
2968
+ }
2969
+ };
2970
+
2821
2971
  // src/editor-headerfooter.ts
2822
2972
  var PAGE_FORMAT_LABELS = [
2823
2973
  { value: "", label: "Format\u2026" },
@@ -4906,6 +5056,146 @@ var Docxodus = (() => {
4906
5056
  return once(cleanup);
4907
5057
  }
4908
5058
 
5059
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js
5060
+ function ownKeys4(e, r) {
5061
+ var t = Object.keys(e);
5062
+ if (Object.getOwnPropertySymbols) {
5063
+ var o = Object.getOwnPropertySymbols(e);
5064
+ r && (o = o.filter(function(r2) {
5065
+ return Object.getOwnPropertyDescriptor(e, r2).enumerable;
5066
+ })), t.push.apply(t, o);
5067
+ }
5068
+ return t;
5069
+ }
5070
+ function _objectSpread4(e) {
5071
+ for (var r = 1; r < arguments.length; r++) {
5072
+ var t = null != arguments[r] ? arguments[r] : {};
5073
+ r % 2 ? ownKeys4(Object(t), true).forEach(function(r2) {
5074
+ _defineProperty(e, r2, t[r2]);
5075
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys4(Object(t)).forEach(function(r2) {
5076
+ Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
5077
+ });
5078
+ }
5079
+ return e;
5080
+ }
5081
+ function defaultOffset() {
5082
+ return {
5083
+ x: 0,
5084
+ y: 0
5085
+ };
5086
+ }
5087
+ function setCustomNativeDragPreview(_ref) {
5088
+ var render = _ref.render, nativeSetDragImage = _ref.nativeSetDragImage, _ref$getOffset = _ref.getOffset, getOffset = _ref$getOffset === void 0 ? defaultOffset : _ref$getOffset;
5089
+ var container = document.createElement("div");
5090
+ if (supportsPopover()) {
5091
+ container.setAttribute("popover", "manual");
5092
+ }
5093
+ Object.assign(container.style, _objectSpread4(_objectSpread4({
5094
+ // Ensuring we don't cause reflow when adding the element to the page
5095
+ // Using `position:fixed` rather than `position:absolute` so we are
5096
+ // positioned on the current viewport.
5097
+ // `position:fixed` also creates a new stacking context, so we don't need to do that here
5098
+ position: "fixed"
5099
+ }, supportsPopover() ? (
5100
+ // needs to come first as it has 'inset: unset' which
5101
+ // needs to be overridden by our top / left values
5102
+ popoverResetUserAgentStyles
5103
+ ) : {
5104
+ // Fallback: using maximum possible z-index so that this element
5105
+ // will always be on top of other positioned content.
5106
+ zIndex: maxZIndex
5107
+ }), {}, {
5108
+ // According to `mdn`, the element can be offscreen:
5109
+ // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage#imgelement
5110
+ //
5111
+ // However, that information does not appear in the specs:
5112
+ // https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-setdragimage-dev
5113
+ //
5114
+ // If the element is _completely_ offscreen, Safari@17.1 will cancel the drag
5115
+ top: 0,
5116
+ left: 0,
5117
+ // Avoiding any additional events caused by the new element (being super safe)
5118
+ pointerEvents: "none"
5119
+ }));
5120
+ document.body.append(container);
5121
+ if (supportsPopover()) {
5122
+ container.showPopover();
5123
+ }
5124
+ var unmount = render({
5125
+ container
5126
+ });
5127
+ queueMicrotask(function() {
5128
+ var previewOffset = getOffset({
5129
+ container
5130
+ });
5131
+ if (isSafari()) {
5132
+ var rect = container.getBoundingClientRect();
5133
+ if (rect.width === 0) {
5134
+ return;
5135
+ }
5136
+ container.style.left = "-".concat(rect.width - 1e-4, "px");
5137
+ }
5138
+ nativeSetDragImage === null || nativeSetDragImage === void 0 || nativeSetDragImage(container, previewOffset.x, previewOffset.y);
5139
+ });
5140
+ function cleanup() {
5141
+ unbindMonitor();
5142
+ unmount === null || unmount === void 0 || unmount();
5143
+ document.body.removeChild(container);
5144
+ }
5145
+ var unbindMonitor = monitorForElements({
5146
+ // Remove portal in the dragstart event so that the user will never see it
5147
+ onDragStart: cleanup,
5148
+ // Backup: remove portal when the drop finishes (this would be an error case)
5149
+ onDrop: cleanup
5150
+ });
5151
+ }
5152
+
5153
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/util/is-safari-on-ios.js
5154
+ var isSafariOnIOS = once(function isSafariOnIOS2() {
5155
+ if (false) {
5156
+ return false;
5157
+ }
5158
+ return isSafari() && "ontouchend" in document;
5159
+ });
5160
+
5161
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/center-under-pointer.js
5162
+ var centerUnderPointer = function centerUnderPointer2(_ref) {
5163
+ var container = _ref.container;
5164
+ var rect = container.getBoundingClientRect();
5165
+ return {
5166
+ x: rect.width / 2,
5167
+ y: rect.height / 2
5168
+ };
5169
+ };
5170
+
5171
+ // node_modules/@atlaskit/pragmatic-drag-and-drop/dist/esm/public-utils/element/custom-native-drag-preview/pointer-outside-of-preview.js
5172
+ function pointerOutsideOfPreview(point) {
5173
+ return function getOffset(_ref) {
5174
+ var container = _ref.container;
5175
+ if (isSafariOnIOS() || isAndroid()) {
5176
+ return centerUnderPointer({
5177
+ container
5178
+ });
5179
+ }
5180
+ Object.assign(container.style, {
5181
+ borderInlineStart: "".concat(point.x, " solid transparent"),
5182
+ borderTop: "".concat(point.y, " solid transparent")
5183
+ });
5184
+ var computed = window.getComputedStyle(container);
5185
+ if (computed.direction === "rtl") {
5186
+ var box = container.getBoundingClientRect();
5187
+ return {
5188
+ x: box.width,
5189
+ y: 0
5190
+ };
5191
+ }
5192
+ return {
5193
+ x: 0,
5194
+ y: 0
5195
+ };
5196
+ };
5197
+ }
5198
+
4909
5199
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/shared/engagement-history.js
4910
5200
  var ledger2 = /* @__PURE__ */ new Map();
4911
5201
  var requested = /* @__PURE__ */ new Set();
@@ -5039,7 +5329,7 @@ var Docxodus = (() => {
5039
5329
  }
5040
5330
 
5041
5331
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/shared/configuration.js
5042
- function ownKeys4(e, r) {
5332
+ function ownKeys5(e, r) {
5043
5333
  var t = Object.keys(e);
5044
5334
  if (Object.getOwnPropertySymbols) {
5045
5335
  var o = Object.getOwnPropertySymbols(e);
@@ -5049,12 +5339,12 @@ var Docxodus = (() => {
5049
5339
  }
5050
5340
  return t;
5051
5341
  }
5052
- function _objectSpread4(e) {
5342
+ function _objectSpread5(e) {
5053
5343
  for (var r = 1; r < arguments.length; r++) {
5054
5344
  var t = null != arguments[r] ? arguments[r] : {};
5055
- r % 2 ? ownKeys4(Object(t), true).forEach(function(r2) {
5345
+ r % 2 ? ownKeys5(Object(t), true).forEach(function(r2) {
5056
5346
  _defineProperty(e, r2, t[r2]);
5057
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys4(Object(t)).forEach(function(r2) {
5347
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys5(Object(t)).forEach(function(r2) {
5058
5348
  Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
5059
5349
  });
5060
5350
  }
@@ -5089,7 +5379,7 @@ var Docxodus = (() => {
5089
5379
  };
5090
5380
  function getInternalConfig(provided) {
5091
5381
  var _provided$maxScrollSp;
5092
- return _objectSpread4(_objectSpread4({}, baseConfig), {}, {
5382
+ return _objectSpread5(_objectSpread5({}, baseConfig), {}, {
5093
5383
  // only allowing limited control over the config at this stage
5094
5384
  maxPixelScrollPerSecond: maxPixelScrollPerSecond[(_provided$maxScrollSp = provided === null || provided === void 0 ? void 0 : provided.maxScrollSpeed) !== null && _provided$maxScrollSp !== void 0 ? _provided$maxScrollSp : "standard"]
5095
5385
  });
@@ -5573,7 +5863,7 @@ var Docxodus = (() => {
5573
5863
  }
5574
5864
 
5575
5865
  // node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll/dist/esm/over-element/make-api.js
5576
- function ownKeys5(e, r) {
5866
+ function ownKeys6(e, r) {
5577
5867
  var t = Object.keys(e);
5578
5868
  if (Object.getOwnPropertySymbols) {
5579
5869
  var o = Object.getOwnPropertySymbols(e);
@@ -5583,12 +5873,12 @@ var Docxodus = (() => {
5583
5873
  }
5584
5874
  return t;
5585
5875
  }
5586
- function _objectSpread5(e) {
5876
+ function _objectSpread6(e) {
5587
5877
  for (var r = 1; r < arguments.length; r++) {
5588
5878
  var t = null != arguments[r] ? arguments[r] : {};
5589
- r % 2 ? ownKeys5(Object(t), true).forEach(function(r2) {
5879
+ r % 2 ? ownKeys6(Object(t), true).forEach(function(r2) {
5590
5880
  _defineProperty(e, r2, t[r2]);
5591
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys5(Object(t)).forEach(function(r2) {
5881
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys6(Object(t)).forEach(function(r2) {
5592
5882
  Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
5593
5883
  });
5594
5884
  }
@@ -5627,7 +5917,7 @@ var Docxodus = (() => {
5627
5917
  }
5628
5918
  function autoScrollWindow() {
5629
5919
  var args = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
5630
- var unique = _objectSpread5({}, args);
5920
+ var unique = _objectSpread6({}, args);
5631
5921
  windowRegistry.add(unique);
5632
5922
  function cleanup() {
5633
5923
  windowRegistry.delete(unique);
@@ -5780,6 +6070,11 @@ var Docxodus = (() => {
5780
6070
  var EDITABLE_TAGS = /* @__PURE__ */ new Set(["P", "H1", "H2", "H3", "H4", "H5", "H6"]);
5781
6071
  var BLOCK_DRAG_TYPE = "docxodus-block";
5782
6072
  var blockDragStyledDocuments = /* @__PURE__ */ new WeakSet();
6073
+ function blockPreviewText(unit) {
6074
+ if (unit.tagName === "TABLE") return "Table";
6075
+ const text = (unit.textContent ?? "").trim().replace(/\s+/g, " ");
6076
+ return text.length > 48 ? `${text.slice(0, 48)}\u2026` : text;
6077
+ }
5783
6078
  function ensureBlockDragStyles(doc) {
5784
6079
  if (blockDragStyledDocuments.has(doc)) return;
5785
6080
  blockDragStyledDocuments.add(doc);
@@ -5794,10 +6089,28 @@ var Docxodus = (() => {
5794
6089
  }
5795
6090
  .docx-block-handle:hover, .docx-block-handle:focus-visible { color: #344054; border-color: #98a2b3; outline: none; }
5796
6091
  .docx-block-handle[aria-pressed="true"] { color: #175cd3; border-color: #84adff; background: #eff8ff; }
5797
- .docx-block-handle.docx-block-dragging { cursor: grabbing; opacity: .78; }
6092
+ .docx-block-handle.docx-block-dragging { cursor: grabbing; opacity: .35; }
6093
+ /* The block being carried. Dimming it is the "a drag is happening" signal that survives the
6094
+ pointer being anywhere on screen \u2014 the drop line only says where, not what. */
6095
+ .docx-block-drag-source { opacity: .38; transition: opacity 120ms ease-out; }
6096
+ /* Positioned by transform so tracking the pointer costs no layout. Flipping display none\u2192block
6097
+ restarts the fade \u2014 one cheap entry animation per appearance, none while it tracks. */
5798
6098
  .docx-block-drop-indicator {
5799
- position: fixed; z-index: 2147482999; display: none; height: 3px; pointer-events: none;
5800
- border-radius: 999px; background: #2e90fa; box-shadow: 0 0 0 1px rgba(255,255,255,.85);
6099
+ position: fixed; top: 0; left: 0; z-index: 2147482999; display: none; height: 0;
6100
+ pointer-events: none; border-top: 2px solid #2e90fa;
6101
+ filter: drop-shadow(0 1px 2px rgba(46,144,250,.5));
6102
+ animation: docx-block-drop-in 110ms ease-out;
6103
+ }
6104
+ .docx-block-drop-indicator::before {
6105
+ content: ""; position: absolute; top: -5px; left: -2px; width: 8px; height: 8px;
6106
+ border-radius: 50%; background: #2e90fa;
6107
+ }
6108
+ @keyframes docx-block-drop-in { from { opacity: 0; } to { opacity: 1; } }
6109
+ .docx-block-drag-preview {
6110
+ max-width: 320px; padding: 6px 10px; border: 1px solid #b2ddff; border-radius: 6px;
6111
+ background: #eff8ff; color: #175cd3; box-shadow: 0 6px 16px rgba(16,24,40,.18);
6112
+ font: 500 13px/1.35 system-ui, sans-serif; white-space: nowrap; overflow: hidden;
6113
+ text-overflow: ellipsis;
5801
6114
  }
5802
6115
  .docx-block-move-menu {
5803
6116
  position: fixed; z-index: 2147483001; display: none; min-width: 150px; padding: 5px;
@@ -6185,7 +6498,11 @@ var Docxodus = (() => {
6185
6498
  this.blockMoveLive = null;
6186
6499
  this.blockDragSource = null;
6187
6500
  this.blockDragCleanup = [];
6188
- this.blockDragTargetCleanup = [];
6501
+ /** Block boxes measured at drag start — see `BlockDropZone`. Empty when no drag is in flight. */
6502
+ this.dropZones = [];
6503
+ /** Combined scroll offset when `dropZones` was measured, and the scroller measured against. */
6504
+ this.dropZoneOrigin = 0;
6505
+ this.dropZoneScroller = null;
6189
6506
  this.blockDragging = false;
6190
6507
  this.blockDragPointerDown = false;
6191
6508
  /** Anchors the current drag source may legally move next to, per the engine's own rules.
@@ -6314,6 +6631,11 @@ var Docxodus = (() => {
6314
6631
  this.handle = handle;
6315
6632
  this.options = options;
6316
6633
  this.editRoot = container;
6634
+ this.viewport = new DocumentViewport(container, {
6635
+ columnWidth: options.columnWidth,
6636
+ fitToWidth: options.fitToWidth,
6637
+ scale: options.scale
6638
+ });
6317
6639
  if (typeof document !== "undefined") {
6318
6640
  document.addEventListener("selectionchange", this.onSelectionChange);
6319
6641
  document.addEventListener("mousedown", this.onMouseDown, true);
@@ -6396,6 +6718,8 @@ var Docxodus = (() => {
6396
6718
  editable: options.editable ?? true,
6397
6719
  paginated: options.paginated ?? false,
6398
6720
  scale: options.scale ?? 1,
6721
+ columnWidth: options.columnWidth ?? "section",
6722
+ fitToWidth: options.fitToWidth ?? true,
6399
6723
  headerFooter: options.headerFooter ?? false,
6400
6724
  blockDrag: options.blockDrag ?? false,
6401
6725
  trackedChanges: options.trackedChanges ?? 0 /* Accept */,
@@ -6452,6 +6776,7 @@ var Docxodus = (() => {
6452
6776
  }
6453
6777
  this.clearDragSelection();
6454
6778
  this.teardownBlockDrag();
6779
+ this.viewport.dispose();
6455
6780
  this.exports.DocxSessionBridge.CloseSession(this.handle);
6456
6781
  }
6457
6782
  /**
@@ -6469,6 +6794,14 @@ var Docxodus = (() => {
6469
6794
  get root() {
6470
6795
  return this.container;
6471
6796
  }
6797
+ /**
6798
+ * The zoom the viewport is currently applying (1 = 100%). Below 1 the page is wider than the
6799
+ * host and has been scaled to fit rather than reflowed — the honest thing to show a user who
6800
+ * is wondering why a phone shows the whole page.
6801
+ */
6802
+ get zoom() {
6803
+ return this.viewport.scale;
6804
+ }
6472
6805
  /**
6473
6806
  * The live `DocxSession` handle backing this editor — the model of record.
6474
6807
  *
@@ -6479,6 +6812,19 @@ var Docxodus = (() => {
6479
6812
  get sessionHandle() {
6480
6813
  return this.handle;
6481
6814
  }
6815
+ /**
6816
+ * Repaint from the live session after it was mutated OUTSIDE the editor's own
6817
+ * commands — a host driving {@link sessionHandle} directly (an agent pipeline,
6818
+ * `raw.replaceXml`, a batch import). Continuous mode patches incrementally from
6819
+ * the render plan (a Unid-preserving single-block mutation repaints just that
6820
+ * block); paginated mode — or anything the reconciler cannot prove — remounts.
6821
+ * The editor cannot observe external mutations, so the host owns calling this
6822
+ * once per mutation batch.
6823
+ */
6824
+ refresh() {
6825
+ this.assertOpen();
6826
+ this.reconcile();
6827
+ }
6482
6828
  /** Move one top-level body block relative to another and repaint from the live session. */
6483
6829
  moveBlock(sourceAnchorId, targetAnchorId, position) {
6484
6830
  this.assertOpen();
@@ -6559,7 +6905,7 @@ var Docxodus = (() => {
6559
6905
  handle.style.display = "flex";
6560
6906
  handle.style.left = `${Math.max(4, rect.left - 32)}px`;
6561
6907
  handle.style.top = `${Math.max(4, rect.top + (unit.tagName === "TABLE" ? 6 : Math.max(0, (rect.height - 28) / 2)))}px`;
6562
- const preview = (unit.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 48);
6908
+ const preview = blockPreviewText(unit);
6563
6909
  handle.setAttribute("aria-label", preview ? `Move block: ${preview}` : "Move block");
6564
6910
  }
6565
6911
  hideBlockHandle() {
@@ -6570,14 +6916,33 @@ var Docxodus = (() => {
6570
6916
  const source = this.currentBlockDragSource();
6571
6917
  if (source && this.blockDragHandle?.style.display !== "none") this.showBlockHandle(source);
6572
6918
  }
6573
- showDropIndicator(target, position) {
6919
+ /** Draw the drop line on `zone`'s requested edge, or take it away when there is no target. */
6920
+ paintDropIndicator(data) {
6574
6921
  const indicator = this.blockDropIndicator;
6922
+ const zone = data.zone;
6575
6923
  if (!indicator) return;
6576
- const rect = this.unitWrapperOf(target).getBoundingClientRect();
6924
+ if (!zone) {
6925
+ this.hideDropIndicator();
6926
+ return;
6927
+ }
6928
+ const y = Math.round(this.dropEdgeY(zone, data.position === "after" ? "after" : "before") + this.dropZoneShift()) - 1;
6929
+ indicator.style.transform = `translate3d(${Math.round(zone.left)}px, ${y}px, 0)`;
6930
+ indicator.style.width = `${Math.max(24, Math.round(zone.width))}px`;
6577
6931
  indicator.style.display = "block";
6578
- indicator.style.left = `${rect.left}px`;
6579
- indicator.style.top = `${position === "before" ? rect.top - 1 : rect.bottom - 1}px`;
6580
- indicator.style.width = `${Math.max(24, rect.width)}px`;
6932
+ }
6933
+ /**
6934
+ * Where to draw the line for an insertion on `position` of `zone` — the MIDDLE of the gap to
6935
+ * the neighbour on that side, not the zone's own border-box edge. A paragraph's `w:spacing`
6936
+ * becomes a CSS margin, which sits outside the box, so drawing on the edge underlines the
6937
+ * block's last line instead of reading as a gap between two blocks. Falls back to the raw edge
6938
+ * at the ends of the flow, and degrades to the same value when blocks are contiguous.
6939
+ */
6940
+ dropEdgeY(zone, position) {
6941
+ const neighbour = this.dropZones[zone.index + (position === "after" ? 1 : -1)];
6942
+ if (!neighbour) {
6943
+ return position === "after" ? zone.bottom + zone.marginAfter / 2 : zone.top - zone.marginBefore / 2;
6944
+ }
6945
+ return position === "after" ? (zone.bottom + neighbour.top) / 2 : (neighbour.bottom + zone.top) / 2;
6581
6946
  }
6582
6947
  hideDropIndicator() {
6583
6948
  if (this.blockDropIndicator) this.blockDropIndicator.style.display = "none";
@@ -6657,45 +7022,75 @@ var Docxodus = (() => {
6657
7022
  if (!sides) return false;
6658
7023
  return position ? sides[position] : sides.before || sides.after;
6659
7024
  }
7025
+ /** Measure every movable block once, at drag start. See `BlockDropZone`. */
7026
+ captureDropZones() {
7027
+ const view = this.container.ownerDocument.defaultView;
7028
+ this.dropZoneScroller = this.scrollContainer();
7029
+ this.dropZoneOrigin = this.scrollOffsetSum();
7030
+ this.dropZones = [];
7031
+ const boxes = [];
7032
+ for (const unit of this.bodyUnitNodes()) {
7033
+ const anchorId = this.isMovableBlockUnit(unit) ? this.anchorIdOf(unit) : null;
7034
+ if (!anchorId) continue;
7035
+ const box = this.unitWrapperOf(unit);
7036
+ const rect = box.getBoundingClientRect();
7037
+ boxes.push(box);
7038
+ this.dropZones.push({
7039
+ unit,
7040
+ anchorId,
7041
+ index: this.dropZones.length,
7042
+ top: rect.top,
7043
+ bottom: rect.bottom,
7044
+ left: rect.left,
7045
+ width: rect.width,
7046
+ marginBefore: 0,
7047
+ marginAfter: 0
7048
+ });
7049
+ }
7050
+ const ends = new Set([0, this.dropZones.length - 1].filter((i) => i >= 0 && i < boxes.length));
7051
+ for (const i of ends) {
7052
+ const style = view?.getComputedStyle(boxes[i]);
7053
+ this.dropZones[i].marginBefore = parseFloat(style?.marginTop ?? "0") || 0;
7054
+ this.dropZones[i].marginAfter = parseFloat(style?.marginBottom ?? "0") || 0;
7055
+ }
7056
+ }
7057
+ scrollOffsetSum() {
7058
+ const view = this.container.ownerDocument.defaultView;
7059
+ return (view?.scrollY ?? 0) + (this.dropZoneScroller?.scrollTop ?? 0);
7060
+ }
7061
+ /** How far the measured boxes have travelled since capture, from scrolling (drag autoscroll). */
7062
+ dropZoneShift() {
7063
+ return this.dropZoneOrigin - this.scrollOffsetSum();
7064
+ }
6660
7065
  /**
6661
- * The side of `unit` a drop at `clientY` should land on: the half the pointer is in, snapped to
6662
- * the other side when only that one is legal. Snapping rather than refusing keeps a reachable
6663
- * target usable — the illegal side is usually illegal only because a section break or a
6664
- * cross-block range sits between the two blocks on that side.
6665
- */
6666
- dropPositionFor(unit, clientY) {
6667
- const rect = unit.getBoundingClientRect();
6668
- const preferred = clientY < rect.top + rect.height / 2 ? "before" : "after";
6669
- if (this.isValidMoveTarget(unit, preferred)) return preferred;
6670
- const other = preferred === "before" ? "after" : "before";
6671
- return this.isValidMoveTarget(unit, other) ? other : preferred;
6672
- }
6673
- refreshBlockDropTargets() {
6674
- for (const cleanup of this.blockDragTargetCleanup.splice(0)) cleanup();
6675
- if (!this.blockDragHandle || this.options.paginated) return;
6676
- for (const unit of this.bodyUnitNodes().filter((el) => this.isMovableBlockUnit(el))) {
6677
- this.blockDragTargetCleanup.push(dropTargetForElements({
6678
- element: unit,
6679
- // A target the engine would refuse is not a drop target at all, so Pragmatic never
6680
- // fires onDragEnter for it and no indicator is drawn over it.
6681
- canDrop: ({ source }) => source.data.type === BLOCK_DRAG_TYPE && source.data.sourceAnchorId !== this.anchorIdOf(unit) && this.isValidMoveTarget(unit),
6682
- getData: ({ input }) => ({
6683
- type: BLOCK_DRAG_TYPE,
6684
- targetAnchorId: this.anchorIdOf(unit),
6685
- position: this.dropPositionFor(unit, input.clientY),
6686
- targetElement: unit
6687
- }),
6688
- onDragEnter: ({ self }) => {
6689
- const pos = self.data.position === "after" ? "after" : "before";
6690
- this.showDropIndicator(unit, pos);
6691
- },
6692
- onDrag: ({ self }) => {
6693
- const pos = self.data.position === "after" ? "after" : "before";
6694
- this.showDropIndicator(unit, pos);
6695
- },
6696
- onDragLeave: () => this.hideDropIndicator()
6697
- }));
7066
+ * Where a drop at `clientY` lands, or null when nothing there is legal.
7067
+ *
7068
+ * Resolution is by VERTICAL GEOMETRY over the measured blocks, not by which element the pointer
7069
+ * is over: the drag handle floats in the page margin, so a drag straight down the gutter — the
7070
+ * natural gesture — never crosses a paragraph box, and element hit testing gave those drags no
7071
+ * indicator and no drop at all. The nearest block by vertical distance is the target; the half
7072
+ * the pointer is in picks the side, snapped to the other side when only that one is legal
7073
+ * (a section break or a cross-block range usually makes exactly one side illegal). When neither
7074
+ * side is legal — the pointer is in a region this block cannot reach — there is no drop, and
7075
+ * nothing is drawn.
7076
+ */
7077
+ resolveDropAt(clientY) {
7078
+ const y = clientY - this.dropZoneShift();
7079
+ let best = null;
7080
+ let bestGap = Infinity;
7081
+ for (const zone of this.dropZones) {
7082
+ const gap = y < zone.top ? zone.top - y : y > zone.bottom ? y - zone.bottom : 0;
7083
+ if (gap < bestGap) {
7084
+ best = zone;
7085
+ bestGap = gap;
7086
+ }
7087
+ if (gap === 0) break;
6698
7088
  }
7089
+ if (!best || best.unit === this.blockDragSource) return null;
7090
+ const preferred = y < (best.top + best.bottom) / 2 ? "before" : "after";
7091
+ if (this.isValidMoveTarget(best.unit, preferred)) return { zone: best, position: preferred };
7092
+ const other = preferred === "before" ? "after" : "before";
7093
+ return this.isValidMoveTarget(best.unit, other) ? { zone: best, position: other } : null;
6699
7094
  }
6700
7095
  closeBlockMoveMenu(restoreFocus = false) {
6701
7096
  if (!this.blockMoveMenu || !this.blockDragHandle) return;
@@ -6870,20 +7265,50 @@ var Docxodus = (() => {
6870
7265
  const source = this.currentBlockDragSource();
6871
7266
  return { type: BLOCK_DRAG_TYPE, sourceAnchorId: source ? this.anchorIdOf(source) : void 0 };
6872
7267
  },
7268
+ // The browser would otherwise ghost the 26px grip, which says nothing about what is moving.
7269
+ onGenerateDragPreview: ({ nativeSetDragImage }) => {
7270
+ const source = this.currentBlockDragSource();
7271
+ setCustomNativeDragPreview({
7272
+ nativeSetDragImage,
7273
+ getOffset: pointerOutsideOfPreview({ x: "14px", y: "10px" }),
7274
+ render: ({ container }) => {
7275
+ const chip = doc.createElement("div");
7276
+ chip.className = "docx-block-drag-preview";
7277
+ chip.textContent = source && blockPreviewText(source) || "Move block";
7278
+ container.appendChild(chip);
7279
+ return () => chip.remove();
7280
+ }
7281
+ });
7282
+ },
6873
7283
  onDragStart: () => {
7284
+ const source = this.currentBlockDragSource();
6874
7285
  this.blockDragging = true;
6875
7286
  handle.classList.add("docx-block-dragging");
7287
+ source?.classList.add("docx-block-drag-source");
6876
7288
  this.closeBlockMoveMenu();
6877
- this.refreshBlockMoveTargets(this.currentBlockDragSource());
6878
- this.refreshBlockDropTargets();
7289
+ this.refreshBlockMoveTargets(source);
7290
+ this.captureDropZones();
6879
7291
  },
6880
7292
  onDrop: () => {
6881
7293
  this.blockDragging = false;
6882
7294
  this.blockDragPointerDown = false;
7295
+ this.dropZones = [];
6883
7296
  handle.classList.remove("docx-block-dragging");
7297
+ doc.querySelectorAll(".docx-block-drag-source").forEach((el) => el.classList.remove("docx-block-drag-source"));
6884
7298
  this.hideDropIndicator();
6885
7299
  }
6886
7300
  }));
7301
+ this.blockDragCleanup.push(dropTargetForElements({
7302
+ element: this.editRoot,
7303
+ canDrop: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
7304
+ getData: ({ input }) => {
7305
+ const hit = this.resolveDropAt(input.clientY);
7306
+ return hit ? { type: BLOCK_DRAG_TYPE, targetAnchorId: hit.zone.anchorId, position: hit.position, zone: hit.zone } : { type: BLOCK_DRAG_TYPE };
7307
+ },
7308
+ onDragEnter: ({ self }) => this.paintDropIndicator(self.data),
7309
+ onDrag: ({ self }) => this.paintDropIndicator(self.data),
7310
+ onDragLeave: () => this.hideDropIndicator()
7311
+ }));
6887
7312
  this.blockDragCleanup.push(monitorForElements({
6888
7313
  canMonitor: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
6889
7314
  onDrop: ({ source, location: location2 }) => {
@@ -6910,13 +7335,13 @@ var Docxodus = (() => {
6910
7335
  canScroll: ({ source }) => source.data.type === BLOCK_DRAG_TYPE,
6911
7336
  getAllowedAxis: () => "vertical"
6912
7337
  }));
6913
- this.refreshBlockDropTargets();
6914
7338
  }
6915
7339
  teardownBlockDrag() {
6916
7340
  this.blockMoveTargetPrefetch?.();
6917
7341
  this.blockMoveTargetPrefetch = null;
6918
7342
  this.blockMoveTargetCache.clear();
6919
- for (const cleanup of this.blockDragTargetCleanup.splice(0)) cleanup();
7343
+ this.dropZones = [];
7344
+ this.dropZoneScroller = null;
6920
7345
  for (const cleanup of this.blockDragCleanup.splice(0)) cleanup();
6921
7346
  this.blockDragHandle?.remove();
6922
7347
  this.blockDropIndicator?.remove();
@@ -7005,17 +7430,17 @@ var Docxodus = (() => {
7005
7430
  bodyRoot.after(this.region.footerBand);
7006
7431
  this.region.refreshAll();
7007
7432
  }
7008
- /** Continuous (non-paginated) mount: inject the converter's styles + body, wire blocks. */
7433
+ /**
7434
+ * Continuous (non-paginated) mount: inject the converter's styles + body, wire blocks.
7435
+ *
7436
+ * The body always gets its own `.docx-body-flow` wrapper — not only when bands are docked.
7437
+ * It is the sheet: the element the viewport gives page geometry to and zooms, and the one
7438
+ * the bands dock around. Without it the container would have to be both the scrolling host
7439
+ * and the scaled page, which are different boxes.
7440
+ */
7009
7441
  mountHtml(fullHtml) {
7010
7442
  const parsed = new DOMParser().parseFromString(fullHtml, "text/html");
7011
7443
  const styles = Array.from(parsed.querySelectorAll("style")).map((s) => s.outerHTML).join("");
7012
- if (!this.region) {
7013
- this.container.innerHTML = styles + parsed.body.innerHTML;
7014
- this.editRoot = this.container;
7015
- if (this.options.editable) this.wireBlocks(this.container);
7016
- this.stampPlanState();
7017
- return;
7018
- }
7019
7444
  this.container.innerHTML = styles;
7020
7445
  const flow = document.createElement("div");
7021
7446
  flow.className = "docx-body-flow";
@@ -7024,7 +7449,8 @@ var Docxodus = (() => {
7024
7449
  this.editRoot = flow;
7025
7450
  if (this.options.editable) this.wireBlocks(flow);
7026
7451
  this.stampPlanState();
7027
- this.dockBands(flow);
7452
+ if (this.region) this.dockBands(flow);
7453
+ this.viewport.attach(flow, true);
7028
7454
  }
7029
7455
  /** Paginated mount: flow blocks into page boxes via pagination.ts, wire the page clones. */
7030
7456
  mountPaginated(fullHtml) {
@@ -7045,6 +7471,7 @@ var Docxodus = (() => {
7045
7471
  this.editRoot = pageRoot;
7046
7472
  if (this.options.editable) this.wireBlocks(pageRoot);
7047
7473
  if (this.region) this.dockBands(target);
7474
+ this.viewport.attach(pageRoot, false);
7048
7475
  }
7049
7476
  wireBlocks(root) {
7050
7477
  root.querySelectorAll("[data-anchor]").forEach((el) => this.wireBlock(el));
@@ -8853,14 +9280,7 @@ var Docxodus = (() => {
8853
9280
  SHOULD win.
8854
9281
  (No backticks in this file's comments: the stylesheet is a template literal.) */
8855
9282
  .dxr-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
8856
- .dxr[data-chrome] .dxr-surface { max-width: 920px; margin: 26px auto; padding: 0 16px 96px; }
8857
- .dxr-surface[data-view="continuous"] > * { background: var(--dxr-sheet); }
8858
- .dxr[data-chrome] .dxr-surface[data-view="continuous"] {
8859
- padding: 56px 72px;
8860
- border-radius: 3px;
8861
- background: var(--dxr-sheet);
8862
- box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
8863
- }
9283
+ .dxr[data-chrome] .dxr-surface { margin: 26px auto; padding: 0 16px 96px; }
8864
9284
  .dxr-surface [contenteditable="true"]:focus {
8865
9285
  outline: 2px solid var(--dxr-accent);
8866
9286
  outline-offset: 2px;
@@ -8872,7 +9292,10 @@ var Docxodus = (() => {
8872
9292
  so they dock as their own regions. Styled from the same tokens as the ribbon
8873
9293
  so the surface reads as one instrument rather than two apps. */
8874
9294
  .dxr-surface .docx-hf-band {
8875
- margin: 0 0 14px;
9295
+ /* Docked outside the zoomed sheet, so it takes the page's on-screen width from the
9296
+ custom property the viewport publishes rather than stretching to the whole surface. */
9297
+ width: min(100%, var(--docx-sheet-width, 100%));
9298
+ margin: 0 auto 14px;
8876
9299
  padding: 9px 72px 13px;
8877
9300
  border: 1px solid var(--dxr-rule);
8878
9301
  border-left: 2px solid #c2ccd9;
@@ -8880,7 +9303,7 @@ var Docxodus = (() => {
8880
9303
  background: var(--dxr-sheet);
8881
9304
  }
8882
9305
  .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
8883
- .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px 0 0; }
9306
+ .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px auto 0; }
8884
9307
  .dxr-surface .docx-hf-chrome {
8885
9308
  display: flex;
8886
9309
  gap: 8px;
@@ -8935,13 +9358,15 @@ var Docxodus = (() => {
8935
9358
  text-transform: none;
8936
9359
  }
8937
9360
  .dxr-surface .docx-hf-band[data-hf-inherited] { border-style: dashed; }
8938
- .dxr[data-chrome] .dxr-surface[data-view="continuous"]:has(.docx-body-flow) {
8939
- padding: 0;
8940
- background: transparent;
8941
- box-shadow: none;
8942
- }
9361
+ /* The sheet IS the page. The editor's viewport sizes .docx-body-flow to the section's page
9362
+ width and its section wrappers to the authored text column, so the horizontal gutters here
9363
+ are the document's own w:sectPr margins, not a padding this chrome invents; only the
9364
+ vertical breathing room is ours. Centering is left to margin:auto so a page the viewport
9365
+ has zoomed to fit stays centered at its scaled width. */
8943
9366
  .dxr[data-chrome] .dxr-surface[data-view="continuous"] .docx-body-flow {
8944
- padding: 56px 72px;
9367
+ max-width: 100%;
9368
+ margin: 0 auto;
9369
+ padding: 56px 0;
8945
9370
  border-radius: 3px;
8946
9371
  background: var(--dxr-sheet);
8947
9372
  box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
@@ -9020,9 +9445,11 @@ var Docxodus = (() => {
9020
9445
  .dxr[data-chrome="compact"] .dxr-note { display: none; }
9021
9446
  .dxr[data-chrome="compact"] .dxr-rail { display: none; }
9022
9447
  .dxr[data-chrome="compact"] .dxr-hint { display: none; }
9448
+ /* Compact trims the chrome around the page, never the page: the document's own column width
9449
+ is what the viewport's fit-to-width zoom scales, so a phone shows a whole smaller page
9450
+ instead of a narrower one that breaks its lines somewhere Word never would. */
9023
9451
  .dxr[data-chrome="compact"] .dxr-surface { margin: 12px auto; padding: 0 10px 64px; }
9024
- .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] { padding: 22px 18px; }
9025
- .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 18px; }
9452
+ .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 0; }
9026
9453
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-band { padding: 9px 18px 13px; }
9027
9454
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-chrome,
9028
9455
  .dxr[data-chrome="compact"] .dxr-surface .docx-hf-warning { margin-left: 0; }
@@ -9772,6 +10199,8 @@ var Docxodus = (() => {
9772
10199
  fabricateClasses: this.options.fabricateClasses,
9773
10200
  editable: this.options.editable,
9774
10201
  scale: this.options.scale,
10202
+ columnWidth: this.options.columnWidth,
10203
+ fitToWidth: this.options.fitToWidth,
9775
10204
  onEdit: this.options.onEdit,
9776
10205
  onMove: this.options.onMove,
9777
10206
  paginated,