@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.js CHANGED
@@ -12187,7 +12187,15 @@ var defaultStarterModel = {
12187
12187
  headerSections: [],
12188
12188
  footerSections: [],
12189
12189
  paragraphStyles: [
12190
- { id: "Normal", name: "Body", isDefault: true },
12190
+ {
12191
+ id: "Normal",
12192
+ name: "Body",
12193
+ isDefault: true,
12194
+ // Word's blank-document default body typeface. Without this the body
12195
+ // default fell through to a heading font / Times New Roman and did not
12196
+ // match the "Calibri" shown in the toolbar.
12197
+ runStyle: { fontFamily: "Calibri", fontSizePt: 11 }
12198
+ },
12191
12199
  {
12192
12200
  id: "Heading1",
12193
12201
  name: "Heading 1",
@@ -12330,7 +12338,7 @@ function resolveDocumentInheritedFontFamily(model) {
12330
12338
  return cssFontFamily(defaultStyleFontFamily);
12331
12339
  }
12332
12340
  const paragraphStyleFontFamily = paragraphStyles.find(
12333
- (style) => Boolean(style.runStyle?.fontFamily?.trim())
12341
+ (style) => style.headingLevel === void 0 && Boolean(style.runStyle?.fontFamily?.trim())
12334
12342
  )?.runStyle?.fontFamily;
12335
12343
  if (paragraphStyleFontFamily) {
12336
12344
  return cssFontFamily(paragraphStyleFontFamily);
@@ -14565,7 +14573,17 @@ function nodeAlreadyEndsAtExplicitPageBoundary(node) {
14565
14573
  }
14566
14574
  return paragraphHasExplicitPageBreak2(node) || paragraphHasPageBreakBefore2(node) || sectionBreakAfterParagraphStartsNewPage(node);
14567
14575
  }
14576
+ var docxHardPageBreakStartNodeIndexesByModel = /* @__PURE__ */ new WeakMap();
14568
14577
  function collectDocxHardPageBreakStartNodeIndexes(model) {
14578
+ const cached = docxHardPageBreakStartNodeIndexesByModel.get(model);
14579
+ if (cached) {
14580
+ return cached;
14581
+ }
14582
+ const result = computeDocxHardPageBreakStartNodeIndexes(model);
14583
+ docxHardPageBreakStartNodeIndexesByModel.set(model, result);
14584
+ return result;
14585
+ }
14586
+ function computeDocxHardPageBreakStartNodeIndexes(model) {
14569
14587
  const breaks = collectTopLevelExplicitPageBreakStartNodeIndexes(model.nodes);
14570
14588
  const sections = resolveDocumentSectionsFromMetadata(model.metadata);
14571
14589
  for (let sectionIndex = 1; sectionIndex < sections.length; sectionIndex += 1) {
@@ -14589,7 +14607,17 @@ function collectDocxHardPageBreakStartNodeIndexes(model) {
14589
14607
  }
14590
14608
  return breaks;
14591
14609
  }
14610
+ var docxSectionStartPageBreakNodeIndexesByModel = /* @__PURE__ */ new WeakMap();
14592
14611
  function collectDocxSectionStartPageBreakNodeIndexes(model) {
14612
+ const cached = docxSectionStartPageBreakNodeIndexesByModel.get(model);
14613
+ if (cached) {
14614
+ return cached;
14615
+ }
14616
+ const result = computeDocxSectionStartPageBreakNodeIndexes(model);
14617
+ docxSectionStartPageBreakNodeIndexesByModel.set(model, result);
14618
+ return result;
14619
+ }
14620
+ function computeDocxSectionStartPageBreakNodeIndexes(model) {
14593
14621
  const breaks = /* @__PURE__ */ new Set();
14594
14622
  const sections = resolveDocumentSectionsFromMetadata(model.metadata);
14595
14623
  for (let sectionIndex = 1; sectionIndex < sections.length; sectionIndex += 1) {
@@ -29986,6 +30014,8 @@ function DocxEditorViewer({
29986
30014
  const fileDragDepthRef = React.useRef(0);
29987
30015
  const tableDraftLayoutRefreshRafRef = React.useRef(null);
29988
30016
  const activeRangeFlushFrameRef = React.useRef(null);
30017
+ const pendingEditableCaretRef = React.useRef(null);
30018
+ const pointerSelectionDragRef = React.useRef({ down: false, moved: false });
29989
30019
  const deferredCollapsedSelectionSyncTimeoutRef = React.useRef(
29990
30020
  null
29991
30021
  );
@@ -31171,6 +31201,7 @@ function DocxEditorViewer({
31171
31201
  const hasExternalVisiblePageRange = Number.isFinite(Number(visiblePageRange?.startPageIndex)) || Number.isFinite(Number(visiblePageRange?.endPageIndex));
31172
31202
  const [deferInternalPageVirtualization, setDeferInternalPageVirtualization] = React.useState(false);
31173
31203
  const [internalVirtualScrollElement, setInternalVirtualScrollElement] = React.useState(null);
31204
+ const [virtualizerMeasurementScale, setVirtualizerMeasurementScale] = React.useState(1);
31174
31205
  const [observedVisiblePageRange, setObservedVisiblePageRange] = React.useState(void 0);
31175
31206
  const pageVirtualizationSettleDelayMs = Math.max(
31176
31207
  0,
@@ -31220,6 +31251,19 @@ function DocxEditorViewer({
31220
31251
  nearestScrollableAncestor(viewerRootRef.current)
31221
31252
  );
31222
31253
  }, [editor.documentLoadNonce, pageCount, trackedChangesEnabled]);
31254
+ React.useLayoutEffect(() => {
31255
+ if (typeof window === "undefined") {
31256
+ return;
31257
+ }
31258
+ const rootElement = viewerRootRef.current;
31259
+ if (!rootElement) {
31260
+ return;
31261
+ }
31262
+ const nextScale = resolveEffectiveZoomScale(rootElement);
31263
+ setVirtualizerMeasurementScale(
31264
+ (current) => Math.abs(current - nextScale) < 1e-3 ? current : nextScale
31265
+ );
31266
+ });
31223
31267
  const internalPageVirtualizationEnabled = internalPageVirtualizationRequested && internalVirtualScrollElement !== null;
31224
31268
  const internalPageVirtualizationPending = typeof window !== "undefined" && internalPageVirtualizationRequested && internalVirtualScrollElement === null;
31225
31269
  const internalVirtualScrollUsesWindow = typeof document !== "undefined" && internalVirtualScrollElement !== null && (internalVirtualScrollElement === document.scrollingElement || internalVirtualScrollElement === document.documentElement || internalVirtualScrollElement === document.body);
@@ -31228,20 +31272,37 @@ function DocxEditorViewer({
31228
31272
  const estimateVirtualPageSize = React.useCallback(
31229
31273
  (pageIndex) => {
31230
31274
  const pageLayout = pageSectionInfoByIndex[pageIndex]?.layout ?? documentLayout;
31231
- return Math.max(1, pageLayout.pageHeightPx + DOC_PAGE_BREAK_GAP);
31275
+ return Math.max(
31276
+ 1,
31277
+ Math.round(
31278
+ (pageLayout.pageHeightPx + DOC_PAGE_BREAK_GAP) * virtualizerMeasurementScale
31279
+ )
31280
+ );
31232
31281
  },
31233
- [documentLayout, pageSectionInfoByIndex]
31282
+ [documentLayout, pageSectionInfoByIndex, virtualizerMeasurementScale]
31283
+ );
31284
+ const measureVirtualPageElement = React.useCallback(
31285
+ (element) => {
31286
+ const rect = element.getBoundingClientRect();
31287
+ return Math.max(
31288
+ 1,
31289
+ Math.round(rect.height + DOC_PAGE_BREAK_GAP * virtualizerMeasurementScale)
31290
+ );
31291
+ },
31292
+ [virtualizerMeasurementScale]
31234
31293
  );
31235
31294
  const internalElementPageVirtualizer = useVirtualizer({
31236
31295
  count: Math.max(1, pageCount),
31237
31296
  getScrollElement: () => internalElementPageVirtualizationEnabled ? internalVirtualScrollElement : null,
31238
31297
  estimateSize: estimateVirtualPageSize,
31298
+ measureElement: measureVirtualPageElement,
31239
31299
  overscan: pageVirtualizationOverscan
31240
31300
  });
31241
31301
  const internalWindowPageVirtualizer = useWindowVirtualizer({
31242
31302
  count: Math.max(1, pageCount),
31243
31303
  getScrollElement: () => internalWindowPageVirtualizationEnabled && typeof window !== "undefined" ? window : null,
31244
31304
  estimateSize: estimateVirtualPageSize,
31305
+ measureElement: measureVirtualPageElement,
31245
31306
  overscan: pageVirtualizationOverscan
31246
31307
  });
31247
31308
  const internalPageVirtualizer = internalVirtualScrollUsesWindow ? internalWindowPageVirtualizer : internalElementPageVirtualizer;
@@ -31751,7 +31812,8 @@ function DocxEditorViewer({
31751
31812
  internalPageVirtualizationEnabled,
31752
31813
  internalPageVirtualizer,
31753
31814
  pageCount,
31754
- trackedChangesEnabled
31815
+ trackedChangesEnabled,
31816
+ virtualizerMeasurementScale
31755
31817
  ]);
31756
31818
  React.useEffect(() => {
31757
31819
  const nextCurrentPage = clampNumber(
@@ -34002,14 +34064,62 @@ function DocxEditorViewer({
34002
34064
  });
34003
34065
  }, [resolveParagraphBoundaryFromSelectionPoint]);
34004
34066
  const setActiveRangeFromSelection = React.useCallback(() => {
34067
+ const session = editor.selectionSessionKind;
34068
+ const rootElement = viewerRootRef.current;
34069
+ const activeElement = document.activeElement;
34070
+ const editorHasFocus = Boolean(
34071
+ activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement && rootElement.contains(activeElement)
34072
+ );
34073
+ const liveSelection = window.getSelection();
34074
+ const anchorNode = liveSelection?.anchorNode ?? null;
34075
+ const collapsedElementAnchor = Boolean(
34076
+ liveSelection && liveSelection.isCollapsed && liveSelection.rangeCount > 0 && anchorNode && anchorNode.nodeType !== Node.TEXT_NODE
34077
+ );
34078
+ const selectionDropped = Boolean(
34079
+ editorHasFocus && (!liveSelection || liveSelection.rangeCount === 0)
34080
+ );
34081
+ if (collapsedElementAnchor || selectionDropped) {
34082
+ if (session === "keyboard" || session === "composition") {
34083
+ return;
34084
+ }
34085
+ const anchorElement = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement ?? null;
34086
+ const destroyedHost = anchorElement?.closest(
34087
+ "[data-docx-paragraph-host='true']"
34088
+ ) ?? (editorHasFocus ? activeElement.closest(
34089
+ "[data-docx-paragraph-host='true']"
34090
+ ) : null);
34091
+ const lastGoodRange = cloneTextRange(editor.activeTextRange);
34092
+ if (lastGoodRange && destroyedHost) {
34093
+ const normalizedLastGood = normalizeTextRange(lastGoodRange);
34094
+ const lastGoodHost = resolveParagraphHostElement(
34095
+ normalizedLastGood.start.location
34096
+ );
34097
+ if (lastGoodHost === destroyedHost) {
34098
+ setSelectionFromDocxBoundaries(
34099
+ normalizedLastGood.start,
34100
+ normalizedLastGood.end
34101
+ );
34102
+ }
34103
+ }
34104
+ return;
34105
+ }
34005
34106
  const range = resolveActiveRangeFromDomSelection();
34006
34107
  if (!range) {
34108
+ if (editorHasFocus && editor.activeTextRange) {
34109
+ return;
34110
+ }
34007
34111
  editor.setActiveTextRange(void 0);
34008
34112
  return;
34009
34113
  }
34010
34114
  clearTableCellSelection();
34011
34115
  editor.setActiveTextRange(range);
34012
- }, [clearTableCellSelection, editor, resolveActiveRangeFromDomSelection]);
34116
+ }, [
34117
+ clearTableCellSelection,
34118
+ editor,
34119
+ resolveActiveRangeFromDomSelection,
34120
+ resolveParagraphHostElement,
34121
+ setSelectionFromDocxBoundaries
34122
+ ]);
34013
34123
  const flushActiveRangeFromSelection = React.useCallback(() => {
34014
34124
  if (deferredCollapsedSelectionSyncTimeoutRef.current !== null) {
34015
34125
  window.clearTimeout(deferredCollapsedSelectionSyncTimeoutRef.current);
@@ -34023,6 +34133,73 @@ function DocxEditorViewer({
34023
34133
  setActiveRangeFromSelection();
34024
34134
  });
34025
34135
  }, [setActiveRangeFromSelection]);
34136
+ React.useLayoutEffect(() => {
34137
+ if (isReadOnly) {
34138
+ return;
34139
+ }
34140
+ const session = editor.selectionSessionKind;
34141
+ if (pointerSelectionDragRef.current.moved || session === "composition") {
34142
+ return;
34143
+ }
34144
+ const rootElement = viewerRootRef.current;
34145
+ if (!rootElement) {
34146
+ return;
34147
+ }
34148
+ const activeElement = document.activeElement;
34149
+ if (!(activeElement instanceof HTMLElement) || !activeElement.isContentEditable || !rootElement.contains(activeElement)) {
34150
+ return;
34151
+ }
34152
+ const focusedHost = activeElement.closest(
34153
+ "[data-docx-paragraph-host='true']"
34154
+ );
34155
+ if (!focusedHost) {
34156
+ return;
34157
+ }
34158
+ const selection = window.getSelection();
34159
+ if (!selection) {
34160
+ return;
34161
+ }
34162
+ const liveRange = selection.rangeCount > 0 ? selection.getRangeAt(0) : void 0;
34163
+ const anchorNode = selection.anchorNode;
34164
+ const selectionInsideHost = Boolean(
34165
+ liveRange && focusedHost.contains(liveRange.startContainer) && focusedHost.contains(liveRange.endContainer)
34166
+ );
34167
+ const anchorIsElement = Boolean(
34168
+ anchorNode && anchorNode.nodeType !== Node.TEXT_NODE
34169
+ );
34170
+ const selectionWasDestroyed = !liveRange || !selectionInsideHost || selection.isCollapsed && anchorIsElement;
34171
+ if (!selectionWasDestroyed) {
34172
+ return;
34173
+ }
34174
+ const focusedNodeIndexAttr = focusedHost.getAttribute(
34175
+ "data-docx-paragraph-node-index"
34176
+ );
34177
+ const focusedNodeIndex = focusedNodeIndexAttr != null ? Number.parseInt(focusedNodeIndexAttr, 10) : Number.NaN;
34178
+ const pendingCaret = pendingEditableCaretRef.current;
34179
+ if (pendingCaret && Number.isFinite(focusedNodeIndex) && pendingCaret.nodeIndex === focusedNodeIndex) {
34180
+ const textLength = editableTextFromElement(focusedHost).length;
34181
+ const safeStart = Math.max(0, Math.min(pendingCaret.start, textLength));
34182
+ const safeEnd = Math.max(
34183
+ safeStart,
34184
+ Math.min(pendingCaret.end, textLength)
34185
+ );
34186
+ setSelectionWithinElementByTextOffsets(focusedHost, safeStart, safeEnd);
34187
+ return;
34188
+ }
34189
+ if (session === "keyboard") {
34190
+ return;
34191
+ }
34192
+ const range = editor.activeTextRange;
34193
+ if (!range) {
34194
+ return;
34195
+ }
34196
+ const normalized = normalizeTextRange(range);
34197
+ const targetHost = resolveParagraphHostElement(normalized.start.location);
34198
+ if (!targetHost || targetHost !== focusedHost) {
34199
+ return;
34200
+ }
34201
+ setSelectionFromDocxBoundaries(normalized.start, normalized.end);
34202
+ });
34026
34203
  const selectionIsExpandedWithinElement = React.useCallback(
34027
34204
  (element) => {
34028
34205
  const selection = window.getSelection();
@@ -34476,6 +34653,28 @@ function DocxEditorViewer({
34476
34653
  const collapsedCaretInsideEditableHost = Boolean(
34477
34654
  selectionRange.collapsed && activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement.contains(activeElement) && activeElement.contains(selectionRange.startContainer)
34478
34655
  );
34656
+ const selectionWithinSingleEditableHost = Boolean(
34657
+ 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
34658
+ );
34659
+ if (selectionWithinSingleEditableHost && activeElement instanceof HTMLElement) {
34660
+ const host = activeElement.closest(
34661
+ "[data-docx-paragraph-host='true']"
34662
+ );
34663
+ const nodeIndexAttr = host?.getAttribute(
34664
+ "data-docx-paragraph-node-index"
34665
+ );
34666
+ const offsets = host ? selectionOffsetsWithinElement(host) : void 0;
34667
+ if (host && nodeIndexAttr != null && offsets) {
34668
+ const nodeIndex = Number.parseInt(nodeIndexAttr, 10);
34669
+ if (Number.isFinite(nodeIndex)) {
34670
+ pendingEditableCaretRef.current = {
34671
+ nodeIndex,
34672
+ start: offsets.start,
34673
+ end: offsets.end
34674
+ };
34675
+ }
34676
+ }
34677
+ }
34479
34678
  if (collapsedCaretInsideEditableHost) {
34480
34679
  scheduleDeferredCollapsedSelectionSync();
34481
34680
  return;
@@ -34487,6 +34686,27 @@ function DocxEditorViewer({
34487
34686
  document.removeEventListener("selectionchange", handleSelectionChange);
34488
34687
  };
34489
34688
  }, [flushActiveRangeFromSelection, scheduleDeferredCollapsedSelectionSync]);
34689
+ React.useEffect(() => {
34690
+ const onPointerDown = () => {
34691
+ pointerSelectionDragRef.current = { down: true, moved: false };
34692
+ };
34693
+ const onPointerMove = (event) => {
34694
+ if (pointerSelectionDragRef.current.down && event.buttons !== 0) {
34695
+ pointerSelectionDragRef.current.moved = true;
34696
+ }
34697
+ };
34698
+ const onPointerUp = () => {
34699
+ pointerSelectionDragRef.current = { down: false, moved: false };
34700
+ };
34701
+ window.addEventListener("pointerdown", onPointerDown, true);
34702
+ window.addEventListener("pointermove", onPointerMove, true);
34703
+ window.addEventListener("pointerup", onPointerUp, true);
34704
+ return () => {
34705
+ window.removeEventListener("pointerdown", onPointerDown, true);
34706
+ window.removeEventListener("pointermove", onPointerMove, true);
34707
+ window.removeEventListener("pointerup", onPointerUp, true);
34708
+ };
34709
+ }, []);
34490
34710
  const beginCrossNodeSelectionDrag = React.useCallback(
34491
34711
  (startBoundary, pointerId, startX, startY) => {
34492
34712
  tableSelectionDragRef.current = {
@@ -41914,6 +42134,18 @@ function DocxEditorViewer({
41914
42134
  ...leadingCoverLayoutSpacer ? {
41915
42135
  minHeight: `${estimateParagraphLineHeightPx(node, nodeDocGridLinePitchPx) + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX + LEADING_COVER_SPACER_EXTRA_HEIGHT_PX}px`
41916
42136
  } : void 0,
42137
+ // Carry the paragraph's run font on the editable host so text typed into
42138
+ // it (which is a bare, not-yet-committed text node, not a styled run
42139
+ // span) renders in the same font the committed run will use — i.e. the
42140
+ // font shown in the toolbar — instead of inheriting a generic default.
42141
+ // Rendered runs set their own font on their span, so this only affects
42142
+ // freshly typed/empty content. When the paragraph has no explicit run
42143
+ // font we leave it unset so the document default still applies.
42144
+ ...(() => {
42145
+ const runStyle = firstRunStyle(node);
42146
+ const hostFontFamily = cssFontFamily(runStyle?.fontFamily);
42147
+ return hostFontFamily ? { fontFamily: hostFontFamily } : {};
42148
+ })(),
41917
42149
  outline: "none"
41918
42150
  };
41919
42151
  let fallbackParagraphRuns;
@@ -42139,6 +42371,16 @@ function DocxEditorViewer({
42139
42371
  if (!editable) {
42140
42372
  return;
42141
42373
  }
42374
+ const mouseUpOffsets = selectionOffsetsWithinElement(
42375
+ event.currentTarget
42376
+ );
42377
+ if (mouseUpOffsets) {
42378
+ pendingEditableCaretRef.current = {
42379
+ nodeIndex,
42380
+ start: mouseUpOffsets.start,
42381
+ end: mouseUpOffsets.end
42382
+ };
42383
+ }
42142
42384
  if (selectionIsExpandedWithinElement(event.currentTarget)) {
42143
42385
  flushActiveRangeFromSelection();
42144
42386
  cancelPendingPointerSelectionReconcile();
@@ -42183,6 +42425,14 @@ function DocxEditorViewer({
42183
42425
  nodeIndex,
42184
42426
  event.currentTarget.innerHTML
42185
42427
  );
42428
+ const typedOffsets = selectionOffsetsWithinElement(
42429
+ event.currentTarget
42430
+ );
42431
+ pendingEditableCaretRef.current = typedOffsets ? {
42432
+ nodeIndex,
42433
+ start: typedOffsets.start,
42434
+ end: typedOffsets.end
42435
+ } : null;
42186
42436
  },
42187
42437
  onCompositionStart: () => {
42188
42438
  if (!editable) {