@extend-ai/react-docx 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -12297,7 +12297,15 @@ var defaultStarterModel = {
12297
12297
  headerSections: [],
12298
12298
  footerSections: [],
12299
12299
  paragraphStyles: [
12300
- { id: "Normal", name: "Body", isDefault: true },
12300
+ {
12301
+ id: "Normal",
12302
+ name: "Body",
12303
+ isDefault: true,
12304
+ // Word's blank-document default body typeface. Without this the body
12305
+ // default fell through to a heading font / Times New Roman and did not
12306
+ // match the "Calibri" shown in the toolbar.
12307
+ runStyle: { fontFamily: "Calibri", fontSizePt: 11 }
12308
+ },
12301
12309
  {
12302
12310
  id: "Heading1",
12303
12311
  name: "Heading 1",
@@ -12440,7 +12448,7 @@ function resolveDocumentInheritedFontFamily(model) {
12440
12448
  return cssFontFamily(defaultStyleFontFamily);
12441
12449
  }
12442
12450
  const paragraphStyleFontFamily = paragraphStyles.find(
12443
- (style) => Boolean(style.runStyle?.fontFamily?.trim())
12451
+ (style) => style.headingLevel === void 0 && Boolean(style.runStyle?.fontFamily?.trim())
12444
12452
  )?.runStyle?.fontFamily;
12445
12453
  if (paragraphStyleFontFamily) {
12446
12454
  return cssFontFamily(paragraphStyleFontFamily);
@@ -14675,7 +14683,17 @@ function nodeAlreadyEndsAtExplicitPageBoundary(node) {
14675
14683
  }
14676
14684
  return paragraphHasExplicitPageBreak2(node) || paragraphHasPageBreakBefore2(node) || sectionBreakAfterParagraphStartsNewPage(node);
14677
14685
  }
14686
+ var docxHardPageBreakStartNodeIndexesByModel = /* @__PURE__ */ new WeakMap();
14678
14687
  function collectDocxHardPageBreakStartNodeIndexes(model) {
14688
+ const cached = docxHardPageBreakStartNodeIndexesByModel.get(model);
14689
+ if (cached) {
14690
+ return cached;
14691
+ }
14692
+ const result = computeDocxHardPageBreakStartNodeIndexes(model);
14693
+ docxHardPageBreakStartNodeIndexesByModel.set(model, result);
14694
+ return result;
14695
+ }
14696
+ function computeDocxHardPageBreakStartNodeIndexes(model) {
14679
14697
  const breaks = collectTopLevelExplicitPageBreakStartNodeIndexes(model.nodes);
14680
14698
  const sections = resolveDocumentSectionsFromMetadata(model.metadata);
14681
14699
  for (let sectionIndex = 1; sectionIndex < sections.length; sectionIndex += 1) {
@@ -14699,7 +14717,17 @@ function collectDocxHardPageBreakStartNodeIndexes(model) {
14699
14717
  }
14700
14718
  return breaks;
14701
14719
  }
14720
+ var docxSectionStartPageBreakNodeIndexesByModel = /* @__PURE__ */ new WeakMap();
14702
14721
  function collectDocxSectionStartPageBreakNodeIndexes(model) {
14722
+ const cached = docxSectionStartPageBreakNodeIndexesByModel.get(model);
14723
+ if (cached) {
14724
+ return cached;
14725
+ }
14726
+ const result = computeDocxSectionStartPageBreakNodeIndexes(model);
14727
+ docxSectionStartPageBreakNodeIndexesByModel.set(model, result);
14728
+ return result;
14729
+ }
14730
+ function computeDocxSectionStartPageBreakNodeIndexes(model) {
14703
14731
  const breaks = /* @__PURE__ */ new Set();
14704
14732
  const sections = resolveDocumentSectionsFromMetadata(model.metadata);
14705
14733
  for (let sectionIndex = 1; sectionIndex < sections.length; sectionIndex += 1) {
@@ -30096,6 +30124,8 @@ function DocxEditorViewer({
30096
30124
  const fileDragDepthRef = React.useRef(0);
30097
30125
  const tableDraftLayoutRefreshRafRef = React.useRef(null);
30098
30126
  const activeRangeFlushFrameRef = React.useRef(null);
30127
+ const pendingEditableCaretRef = React.useRef(null);
30128
+ const pointerSelectionDragRef = React.useRef({ down: false, moved: false });
30099
30129
  const deferredCollapsedSelectionSyncTimeoutRef = React.useRef(
30100
30130
  null
30101
30131
  );
@@ -31281,6 +31311,7 @@ function DocxEditorViewer({
31281
31311
  const hasExternalVisiblePageRange = Number.isFinite(Number(visiblePageRange?.startPageIndex)) || Number.isFinite(Number(visiblePageRange?.endPageIndex));
31282
31312
  const [deferInternalPageVirtualization, setDeferInternalPageVirtualization] = React.useState(false);
31283
31313
  const [internalVirtualScrollElement, setInternalVirtualScrollElement] = React.useState(null);
31314
+ const [virtualizerMeasurementScale, setVirtualizerMeasurementScale] = React.useState(1);
31284
31315
  const [observedVisiblePageRange, setObservedVisiblePageRange] = React.useState(void 0);
31285
31316
  const pageVirtualizationSettleDelayMs = Math.max(
31286
31317
  0,
@@ -31330,6 +31361,19 @@ function DocxEditorViewer({
31330
31361
  nearestScrollableAncestor(viewerRootRef.current)
31331
31362
  );
31332
31363
  }, [editor.documentLoadNonce, pageCount, trackedChangesEnabled]);
31364
+ React.useLayoutEffect(() => {
31365
+ if (typeof window === "undefined") {
31366
+ return;
31367
+ }
31368
+ const rootElement = viewerRootRef.current;
31369
+ if (!rootElement) {
31370
+ return;
31371
+ }
31372
+ const nextScale = resolveEffectiveZoomScale(rootElement);
31373
+ setVirtualizerMeasurementScale(
31374
+ (current) => Math.abs(current - nextScale) < 1e-3 ? current : nextScale
31375
+ );
31376
+ });
31333
31377
  const internalPageVirtualizationEnabled = internalPageVirtualizationRequested && internalVirtualScrollElement !== null;
31334
31378
  const internalPageVirtualizationPending = typeof window !== "undefined" && internalPageVirtualizationRequested && internalVirtualScrollElement === null;
31335
31379
  const internalVirtualScrollUsesWindow = typeof document !== "undefined" && internalVirtualScrollElement !== null && (internalVirtualScrollElement === document.scrollingElement || internalVirtualScrollElement === document.documentElement || internalVirtualScrollElement === document.body);
@@ -31338,20 +31382,37 @@ function DocxEditorViewer({
31338
31382
  const estimateVirtualPageSize = React.useCallback(
31339
31383
  (pageIndex) => {
31340
31384
  const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? documentLayout;
31341
- return Math.max(1, pageLayout.pageHeightPx + DOC_PAGE_BREAK_GAP);
31385
+ return Math.max(
31386
+ 1,
31387
+ Math.round(
31388
+ (pageLayout.pageHeightPx + DOC_PAGE_BREAK_GAP) * virtualizerMeasurementScale
31389
+ )
31390
+ );
31342
31391
  },
31343
- [documentLayout, pageSectionInfoByIndex]
31392
+ [documentLayout, pageSectionInfoByIndex, virtualizerMeasurementScale]
31393
+ );
31394
+ const measureVirtualPageElement = React.useCallback(
31395
+ (element) => {
31396
+ const rect = element.getBoundingClientRect();
31397
+ return Math.max(
31398
+ 1,
31399
+ Math.round(rect.height + DOC_PAGE_BREAK_GAP * virtualizerMeasurementScale)
31400
+ );
31401
+ },
31402
+ [virtualizerMeasurementScale]
31344
31403
  );
31345
31404
  const internalElementPageVirtualizer = (0, import_react_virtual.useVirtualizer)({
31346
31405
  count: Math.max(1, pageCount),
31347
31406
  getScrollElement: () => internalElementPageVirtualizationEnabled ? internalVirtualScrollElement : null,
31348
31407
  estimateSize: estimateVirtualPageSize,
31408
+ measureElement: measureVirtualPageElement,
31349
31409
  overscan: pageVirtualizationOverscan
31350
31410
  });
31351
31411
  const internalWindowPageVirtualizer = (0, import_react_virtual.useWindowVirtualizer)({
31352
31412
  count: Math.max(1, pageCount),
31353
31413
  getScrollElement: () => internalWindowPageVirtualizationEnabled && typeof window !== "undefined" ? window : null,
31354
31414
  estimateSize: estimateVirtualPageSize,
31415
+ measureElement: measureVirtualPageElement,
31355
31416
  overscan: pageVirtualizationOverscan
31356
31417
  });
31357
31418
  const internalPageVirtualizer = internalVirtualScrollUsesWindow ? internalWindowPageVirtualizer : internalElementPageVirtualizer;
@@ -31861,7 +31922,8 @@ function DocxEditorViewer({
31861
31922
  internalPageVirtualizationEnabled,
31862
31923
  internalPageVirtualizer,
31863
31924
  pageCount,
31864
- trackedChangesEnabled
31925
+ trackedChangesEnabled,
31926
+ virtualizerMeasurementScale
31865
31927
  ]);
31866
31928
  React.useEffect(() => {
31867
31929
  const nextCurrentPage = clampNumber(
@@ -34112,14 +34174,62 @@ function DocxEditorViewer({
34112
34174
  });
34113
34175
  }, [resolveParagraphBoundaryFromSelectionPoint]);
34114
34176
  const setActiveRangeFromSelection = React.useCallback(() => {
34177
+ const session = editor.selectionSessionKind;
34178
+ const rootElement = viewerRootRef.current;
34179
+ const activeElement = document.activeElement;
34180
+ const editorHasFocus = Boolean(
34181
+ activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement && rootElement.contains(activeElement)
34182
+ );
34183
+ const liveSelection = window.getSelection();
34184
+ const anchorNode = liveSelection?.anchorNode ?? null;
34185
+ const collapsedElementAnchor = Boolean(
34186
+ liveSelection && liveSelection.isCollapsed && liveSelection.rangeCount > 0 && anchorNode && anchorNode.nodeType !== Node.TEXT_NODE
34187
+ );
34188
+ const selectionDropped = Boolean(
34189
+ editorHasFocus && (!liveSelection || liveSelection.rangeCount === 0)
34190
+ );
34191
+ if (collapsedElementAnchor || selectionDropped) {
34192
+ if (session === "keyboard" || session === "composition") {
34193
+ return;
34194
+ }
34195
+ const anchorElement = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement ?? null;
34196
+ const destroyedHost = anchorElement?.closest(
34197
+ "[data-docx-paragraph-host='true']"
34198
+ ) ?? (editorHasFocus ? activeElement.closest(
34199
+ "[data-docx-paragraph-host='true']"
34200
+ ) : null);
34201
+ const lastGoodRange = cloneTextRange(editor.activeTextRange);
34202
+ if (lastGoodRange && destroyedHost) {
34203
+ const normalizedLastGood = normalizeTextRange(lastGoodRange);
34204
+ const lastGoodHost = resolveParagraphHostElement(
34205
+ normalizedLastGood.start.location
34206
+ );
34207
+ if (lastGoodHost === destroyedHost) {
34208
+ setSelectionFromDocxBoundaries(
34209
+ normalizedLastGood.start,
34210
+ normalizedLastGood.end
34211
+ );
34212
+ }
34213
+ }
34214
+ return;
34215
+ }
34115
34216
  const range = resolveActiveRangeFromDomSelection();
34116
34217
  if (!range) {
34218
+ if (editorHasFocus && editor.activeTextRange) {
34219
+ return;
34220
+ }
34117
34221
  editor.setActiveTextRange(void 0);
34118
34222
  return;
34119
34223
  }
34120
34224
  clearTableCellSelection();
34121
34225
  editor.setActiveTextRange(range);
34122
- }, [clearTableCellSelection, editor, resolveActiveRangeFromDomSelection]);
34226
+ }, [
34227
+ clearTableCellSelection,
34228
+ editor,
34229
+ resolveActiveRangeFromDomSelection,
34230
+ resolveParagraphHostElement,
34231
+ setSelectionFromDocxBoundaries
34232
+ ]);
34123
34233
  const flushActiveRangeFromSelection = React.useCallback(() => {
34124
34234
  if (deferredCollapsedSelectionSyncTimeoutRef.current !== null) {
34125
34235
  window.clearTimeout(deferredCollapsedSelectionSyncTimeoutRef.current);
@@ -34133,6 +34243,73 @@ function DocxEditorViewer({
34133
34243
  setActiveRangeFromSelection();
34134
34244
  });
34135
34245
  }, [setActiveRangeFromSelection]);
34246
+ React.useLayoutEffect(() => {
34247
+ if (isReadOnly) {
34248
+ return;
34249
+ }
34250
+ const session = editor.selectionSessionKind;
34251
+ if (pointerSelectionDragRef.current.moved || session === "composition") {
34252
+ return;
34253
+ }
34254
+ const rootElement = viewerRootRef.current;
34255
+ if (!rootElement) {
34256
+ return;
34257
+ }
34258
+ const activeElement = document.activeElement;
34259
+ if (!(activeElement instanceof HTMLElement) || !activeElement.isContentEditable || !rootElement.contains(activeElement)) {
34260
+ return;
34261
+ }
34262
+ const focusedHost = activeElement.closest(
34263
+ "[data-docx-paragraph-host='true']"
34264
+ );
34265
+ if (!focusedHost) {
34266
+ return;
34267
+ }
34268
+ const selection = window.getSelection();
34269
+ if (!selection) {
34270
+ return;
34271
+ }
34272
+ const liveRange = selection.rangeCount > 0 ? selection.getRangeAt(0) : void 0;
34273
+ const anchorNode = selection.anchorNode;
34274
+ const selectionInsideHost = Boolean(
34275
+ liveRange && focusedHost.contains(liveRange.startContainer) && focusedHost.contains(liveRange.endContainer)
34276
+ );
34277
+ const anchorIsElement = Boolean(
34278
+ anchorNode && anchorNode.nodeType !== Node.TEXT_NODE
34279
+ );
34280
+ const selectionWasDestroyed = !liveRange || !selectionInsideHost || selection.isCollapsed && anchorIsElement;
34281
+ if (!selectionWasDestroyed) {
34282
+ return;
34283
+ }
34284
+ const focusedNodeIndexAttr = focusedHost.getAttribute(
34285
+ "data-docx-paragraph-node-index"
34286
+ );
34287
+ const focusedNodeIndex = focusedNodeIndexAttr != null ? Number.parseInt(focusedNodeIndexAttr, 10) : Number.NaN;
34288
+ const pendingCaret = pendingEditableCaretRef.current;
34289
+ if (pendingCaret && Number.isFinite(focusedNodeIndex) && pendingCaret.nodeIndex === focusedNodeIndex) {
34290
+ const textLength = editableTextFromElement(focusedHost).length;
34291
+ const safeStart = Math.max(0, Math.min(pendingCaret.start, textLength));
34292
+ const safeEnd = Math.max(
34293
+ safeStart,
34294
+ Math.min(pendingCaret.end, textLength)
34295
+ );
34296
+ setSelectionWithinElementByTextOffsets(focusedHost, safeStart, safeEnd);
34297
+ return;
34298
+ }
34299
+ if (session === "keyboard") {
34300
+ return;
34301
+ }
34302
+ const range = editor.activeTextRange;
34303
+ if (!range) {
34304
+ return;
34305
+ }
34306
+ const normalized = normalizeTextRange(range);
34307
+ const targetHost = resolveParagraphHostElement(normalized.start.location);
34308
+ if (!targetHost || targetHost !== focusedHost) {
34309
+ return;
34310
+ }
34311
+ setSelectionFromDocxBoundaries(normalized.start, normalized.end);
34312
+ });
34136
34313
  const selectionIsExpandedWithinElement = React.useCallback(
34137
34314
  (element) => {
34138
34315
  const selection = window.getSelection();
@@ -34586,6 +34763,28 @@ function DocxEditorViewer({
34586
34763
  const collapsedCaretInsideEditableHost = Boolean(
34587
34764
  selectionRange.collapsed && activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement.contains(activeElement) && activeElement.contains(selectionRange.startContainer)
34588
34765
  );
34766
+ const selectionWithinSingleEditableHost = Boolean(
34767
+ activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement.contains(activeElement) && activeElement.contains(selectionRange.startContainer) && activeElement.contains(selectionRange.endContainer) && selectionRange.startContainer.nodeType === Node.TEXT_NODE && selectionRange.endContainer.nodeType === Node.TEXT_NODE
34768
+ );
34769
+ if (selectionWithinSingleEditableHost && activeElement instanceof HTMLElement) {
34770
+ const host = activeElement.closest(
34771
+ "[data-docx-paragraph-host='true']"
34772
+ );
34773
+ const nodeIndexAttr = host?.getAttribute(
34774
+ "data-docx-paragraph-node-index"
34775
+ );
34776
+ const offsets = host ? selectionOffsetsWithinElement(host) : void 0;
34777
+ if (host && nodeIndexAttr != null && offsets) {
34778
+ const nodeIndex = Number.parseInt(nodeIndexAttr, 10);
34779
+ if (Number.isFinite(nodeIndex)) {
34780
+ pendingEditableCaretRef.current = {
34781
+ nodeIndex,
34782
+ start: offsets.start,
34783
+ end: offsets.end
34784
+ };
34785
+ }
34786
+ }
34787
+ }
34589
34788
  if (collapsedCaretInsideEditableHost) {
34590
34789
  scheduleDeferredCollapsedSelectionSync();
34591
34790
  return;
@@ -34597,6 +34796,27 @@ function DocxEditorViewer({
34597
34796
  document.removeEventListener("selectionchange", handleSelectionChange);
34598
34797
  };
34599
34798
  }, [flushActiveRangeFromSelection, scheduleDeferredCollapsedSelectionSync]);
34799
+ React.useEffect(() => {
34800
+ const onPointerDown = () => {
34801
+ pointerSelectionDragRef.current = { down: true, moved: false };
34802
+ };
34803
+ const onPointerMove = (event) => {
34804
+ if (pointerSelectionDragRef.current.down && event.buttons !== 0) {
34805
+ pointerSelectionDragRef.current.moved = true;
34806
+ }
34807
+ };
34808
+ const onPointerUp = () => {
34809
+ pointerSelectionDragRef.current = { down: false, moved: false };
34810
+ };
34811
+ window.addEventListener("pointerdown", onPointerDown, true);
34812
+ window.addEventListener("pointermove", onPointerMove, true);
34813
+ window.addEventListener("pointerup", onPointerUp, true);
34814
+ return () => {
34815
+ window.removeEventListener("pointerdown", onPointerDown, true);
34816
+ window.removeEventListener("pointermove", onPointerMove, true);
34817
+ window.removeEventListener("pointerup", onPointerUp, true);
34818
+ };
34819
+ }, []);
34600
34820
  const beginCrossNodeSelectionDrag = React.useCallback(
34601
34821
  (startBoundary, pointerId, startX, startY) => {
34602
34822
  tableSelectionDragRef.current = {
@@ -42024,6 +42244,18 @@ function DocxEditorViewer({
42024
42244
  ...leadingCoverLayoutSpacer ? {
42025
42245
  minHeight: `${estimateParagraphLineHeightPx(node, nodeDocGridLinePitchPx) + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX + LEADING_COVER_SPACER_EXTRA_HEIGHT_PX}px`
42026
42246
  } : void 0,
42247
+ // Carry the paragraph's run font on the editable host so text typed into
42248
+ // it (which is a bare, not-yet-committed text node, not a styled run
42249
+ // span) renders in the same font the committed run will use — i.e. the
42250
+ // font shown in the toolbar — instead of inheriting a generic default.
42251
+ // Rendered runs set their own font on their span, so this only affects
42252
+ // freshly typed/empty content. When the paragraph has no explicit run
42253
+ // font we leave it unset so the document default still applies.
42254
+ ...(() => {
42255
+ const runStyle = firstRunStyle(node);
42256
+ const hostFontFamily = cssFontFamily(runStyle?.fontFamily);
42257
+ return hostFontFamily ? { fontFamily: hostFontFamily } : {};
42258
+ })(),
42027
42259
  outline: "none"
42028
42260
  };
42029
42261
  let fallbackParagraphRuns;
@@ -42249,6 +42481,16 @@ function DocxEditorViewer({
42249
42481
  if (!editable) {
42250
42482
  return;
42251
42483
  }
42484
+ const mouseUpOffsets = selectionOffsetsWithinElement(
42485
+ event.currentTarget
42486
+ );
42487
+ if (mouseUpOffsets) {
42488
+ pendingEditableCaretRef.current = {
42489
+ nodeIndex,
42490
+ start: mouseUpOffsets.start,
42491
+ end: mouseUpOffsets.end
42492
+ };
42493
+ }
42252
42494
  if (selectionIsExpandedWithinElement(event.currentTarget)) {
42253
42495
  flushActiveRangeFromSelection();
42254
42496
  cancelPendingPointerSelectionReconcile();
@@ -42293,6 +42535,14 @@ function DocxEditorViewer({
42293
42535
  nodeIndex,
42294
42536
  event.currentTarget.innerHTML
42295
42537
  );
42538
+ const typedOffsets = selectionOffsetsWithinElement(
42539
+ event.currentTarget
42540
+ );
42541
+ pendingEditableCaretRef.current = typedOffsets ? {
42542
+ nodeIndex,
42543
+ start: typedOffsets.start,
42544
+ end: typedOffsets.end
42545
+ } : null;
42296
42546
  },
42297
42547
  onCompositionStart: () => {
42298
42548
  if (!editable) {