@extend-ai/react-docx 0.6.5 → 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
  );
@@ -34034,14 +34064,62 @@ function DocxEditorViewer({
34034
34064
  });
34035
34065
  }, [resolveParagraphBoundaryFromSelectionPoint]);
34036
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
+ }
34037
34106
  const range = resolveActiveRangeFromDomSelection();
34038
34107
  if (!range) {
34108
+ if (editorHasFocus && editor.activeTextRange) {
34109
+ return;
34110
+ }
34039
34111
  editor.setActiveTextRange(void 0);
34040
34112
  return;
34041
34113
  }
34042
34114
  clearTableCellSelection();
34043
34115
  editor.setActiveTextRange(range);
34044
- }, [clearTableCellSelection, editor, resolveActiveRangeFromDomSelection]);
34116
+ }, [
34117
+ clearTableCellSelection,
34118
+ editor,
34119
+ resolveActiveRangeFromDomSelection,
34120
+ resolveParagraphHostElement,
34121
+ setSelectionFromDocxBoundaries
34122
+ ]);
34045
34123
  const flushActiveRangeFromSelection = React.useCallback(() => {
34046
34124
  if (deferredCollapsedSelectionSyncTimeoutRef.current !== null) {
34047
34125
  window.clearTimeout(deferredCollapsedSelectionSyncTimeoutRef.current);
@@ -34055,6 +34133,73 @@ function DocxEditorViewer({
34055
34133
  setActiveRangeFromSelection();
34056
34134
  });
34057
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
+ });
34058
34203
  const selectionIsExpandedWithinElement = React.useCallback(
34059
34204
  (element) => {
34060
34205
  const selection = window.getSelection();
@@ -34508,6 +34653,28 @@ function DocxEditorViewer({
34508
34653
  const collapsedCaretInsideEditableHost = Boolean(
34509
34654
  selectionRange.collapsed && activeElement instanceof HTMLElement && activeElement.isContentEditable && rootElement.contains(activeElement) && activeElement.contains(selectionRange.startContainer)
34510
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
+ }
34511
34678
  if (collapsedCaretInsideEditableHost) {
34512
34679
  scheduleDeferredCollapsedSelectionSync();
34513
34680
  return;
@@ -34519,6 +34686,27 @@ function DocxEditorViewer({
34519
34686
  document.removeEventListener("selectionchange", handleSelectionChange);
34520
34687
  };
34521
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
+ }, []);
34522
34710
  const beginCrossNodeSelectionDrag = React.useCallback(
34523
34711
  (startBoundary, pointerId, startX, startY) => {
34524
34712
  tableSelectionDragRef.current = {
@@ -41946,6 +42134,18 @@ function DocxEditorViewer({
41946
42134
  ...leadingCoverLayoutSpacer ? {
41947
42135
  minHeight: `${estimateParagraphLineHeightPx(node, nodeDocGridLinePitchPx) + EMPTY_PARAGRAPH_EXTRA_HEIGHT_PX + LEADING_COVER_SPACER_EXTRA_HEIGHT_PX}px`
41948
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
+ })(),
41949
42149
  outline: "none"
41950
42150
  };
41951
42151
  let fallbackParagraphRuns;
@@ -42171,6 +42371,16 @@ function DocxEditorViewer({
42171
42371
  if (!editable) {
42172
42372
  return;
42173
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
+ }
42174
42384
  if (selectionIsExpandedWithinElement(event.currentTarget)) {
42175
42385
  flushActiveRangeFromSelection();
42176
42386
  cancelPendingPointerSelectionReconcile();
@@ -42215,6 +42425,14 @@ function DocxEditorViewer({
42215
42425
  nodeIndex,
42216
42426
  event.currentTarget.innerHTML
42217
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;
42218
42436
  },
42219
42437
  onCompositionStart: () => {
42220
42438
  if (!editable) {