@arbidocs/blocks 0.4.102 → 0.4.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/agents/index.cjs +207 -207
  2. package/dist/agents/index.js +2 -2
  3. package/dist/{chunk-AOFATOT3.cjs → chunk-2A4MDHJR.cjs} +7 -5
  4. package/dist/chunk-2A4MDHJR.cjs.map +1 -0
  5. package/dist/{chunk-G5JH2YMQ.js → chunk-4GFB5U3K.js} +3 -3
  6. package/dist/{chunk-G5JH2YMQ.js.map → chunk-4GFB5U3K.js.map} +1 -1
  7. package/dist/chunk-5FYU7ZIU.cjs +139 -0
  8. package/dist/chunk-5FYU7ZIU.cjs.map +1 -0
  9. package/dist/{chunk-Y5XW57U4.cjs → chunk-7LLLQUM7.cjs} +5 -5
  10. package/dist/{chunk-Y5XW57U4.cjs.map → chunk-7LLLQUM7.cjs.map} +1 -1
  11. package/dist/{chunk-D3QPYKZG.js → chunk-EPHGD4YV.js} +7 -6
  12. package/dist/chunk-EPHGD4YV.js.map +1 -0
  13. package/dist/chunk-HFZ45FD3.js +133 -0
  14. package/dist/chunk-HFZ45FD3.js.map +1 -0
  15. package/dist/{chunk-5NQIBJPP.js → chunk-NJX774HB.js} +3 -3
  16. package/dist/{chunk-5NQIBJPP.js.map → chunk-NJX774HB.js.map} +1 -1
  17. package/dist/{chunk-DDYXFALG.cjs → chunk-NW3LFDZL.cjs} +3 -3
  18. package/dist/{chunk-DDYXFALG.cjs.map → chunk-NW3LFDZL.cjs.map} +1 -1
  19. package/dist/{chunk-YGREW5TZ.js → chunk-PIT2YZCK.js} +3 -3
  20. package/dist/{chunk-YGREW5TZ.js.map → chunk-PIT2YZCK.js.map} +1 -1
  21. package/dist/{chunk-Z3G65XHZ.js → chunk-S55VDTX3.js} +3 -3
  22. package/dist/{chunk-Z3G65XHZ.js.map → chunk-S55VDTX3.js.map} +1 -1
  23. package/dist/{chunk-LENXDIMY.cjs → chunk-SKSE4OT6.cjs} +22 -22
  24. package/dist/{chunk-LENXDIMY.cjs.map → chunk-SKSE4OT6.cjs.map} +1 -1
  25. package/dist/{chunk-ZT2EBBSC.cjs → chunk-TP3FAP5J.cjs} +5 -5
  26. package/dist/{chunk-ZT2EBBSC.cjs.map → chunk-TP3FAP5J.cjs.map} +1 -1
  27. package/dist/{chunk-MYALCKDR.js → chunk-W2RTZOK3.js} +1533 -263
  28. package/dist/chunk-W2RTZOK3.js.map +1 -0
  29. package/dist/{chunk-FAGQTB3D.cjs → chunk-ZWWN4CUL.cjs} +1690 -408
  30. package/dist/chunk-ZWWN4CUL.cjs.map +1 -0
  31. package/dist/connect/docx.cjs +8 -7
  32. package/dist/connect/docx.cjs.map +1 -1
  33. package/dist/connect/docx.js +6 -5
  34. package/dist/connect/docx.js.map +1 -1
  35. package/dist/connect/index.cjs +12 -11
  36. package/dist/connect/index.cjs.map +1 -1
  37. package/dist/connect/index.js +5 -4
  38. package/dist/connect/index.js.map +1 -1
  39. package/dist/docx/LinkHoverCard.d.ts +24 -0
  40. package/dist/docx/NativeDocxEditor.d.ts +6 -0
  41. package/dist/docx/RibbonMenu.d.ts +4 -1
  42. package/dist/docx/RibbonOverflow.d.ts +27 -0
  43. package/dist/docx/WordReferences.d.ts +3 -2
  44. package/dist/docx/beforeSave.d.ts +9 -0
  45. package/dist/docx/definitions/fragment.d.ts +19 -1
  46. package/dist/docx/definitions/port.d.ts +43 -11
  47. package/dist/docx/index.cjs +85 -36
  48. package/dist/docx/index.cjs.map +1 -1
  49. package/dist/docx/index.d.ts +6 -0
  50. package/dist/docx/index.js +5 -4
  51. package/dist/docx/index.js.map +1 -1
  52. package/dist/docx/noteStyles.d.ts +46 -0
  53. package/dist/docx/overflowPlan.d.ts +15 -0
  54. package/dist/docx/references/CrossReferences.d.ts +97 -0
  55. package/dist/docx/references/EditorMenus.d.ts +10 -0
  56. package/dist/docx/references/commands.d.ts +119 -0
  57. package/dist/docx/references/numbering.d.ts +49 -0
  58. package/dist/docx/references/passages.d.ts +32 -0
  59. package/dist/docx/references/port.d.ts +20 -0
  60. package/dist/facts/index.cjs +22 -129
  61. package/dist/facts/index.cjs.map +1 -1
  62. package/dist/facts/index.js +3 -125
  63. package/dist/facts/index.js.map +1 -1
  64. package/dist/files/index.cjs +1 -1
  65. package/dist/files/index.js +1 -1
  66. package/dist/home/index.cjs +1 -1
  67. package/dist/home/index.js +1 -1
  68. package/dist/index.cjs +314 -314
  69. package/dist/index.js +8 -8
  70. package/dist/shell/index.cjs +1 -1
  71. package/dist/shell/index.js +1 -1
  72. package/dist/studio/index.cjs +1 -1
  73. package/dist/studio/index.js +1 -1
  74. package/dist/tags/ooxml.d.ts +4 -0
  75. package/dist/workflow-canvas/index.cjs +9 -9
  76. package/dist/workflow-canvas/index.js +2 -2
  77. package/node_modules/@arbidocs/coedit/package.json +1 -1
  78. package/package.json +4 -4
  79. package/src/docx/README.md +32 -0
  80. package/src/docx/styles.css +73 -5
  81. package/dist/chunk-AOFATOT3.cjs.map +0 -1
  82. package/dist/chunk-D3QPYKZG.js.map +0 -1
  83. package/dist/chunk-FAGQTB3D.cjs.map +0 -1
  84. package/dist/chunk-MYALCKDR.js.map +0 -1
@@ -1,15 +1,17 @@
1
1
  "use client";
2
- import { stylesXml, definitionBookmark, groupDefinitions, definitionLabels, filterTermGroups, esc, docxPackage, documentXml, definitionIdOfBookmark, compareTerms, parseDefinition } from './chunk-D3QPYKZG.js';
2
+ import { FactPassageItem } from './chunk-HFZ45FD3.js';
3
+ import { stylesXml, definitionBookmark, groupDefinitions, definitionLabels, filterTermGroups, esc, hyperlinkRelationship, docxPackage, documentXml, definitionIdOfBookmark, compareTerms, parseDefinition } from './chunk-EPHGD4YV.js';
3
4
  import { filterMentionCandidates, MentionMenu, findMentionQuery, listOptionId, extractMentions, useListKeyNav } from './chunk-AA3PT6KR.js';
4
5
  import { unzipSync, strFromU8, strToU8, zipSync } from 'fflate';
5
6
  import { collectReviewItems, revisionItemsOf } from '@docx-editor.dev/core/store';
6
- import React3, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
7
+ import React4, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
7
8
  import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useEditorState, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, normalizeImageBytes, usePageSetup, useEditorCommand } from '@docx-editor.dev/react';
8
- import { AiMarkdown, Dialog, DialogContent, DialogHeader, DialogTitle, cn, splitMarkdownBlocks } from '@arbidocs/react/ui';
9
- import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Loader2, Sparkles, Type, Ellipsis, RefreshCw, WifiOff, Download, Save, BookA, Blocks, Superscript, NotebookText, Columns2, Settings2, BetweenHorizontalStart, BetweenHorizontalEnd, BetweenVerticalStart, BetweenVerticalEnd, Merge, Split, Heading, AlignVerticalJustifyStart, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, RotateCw, FlipHorizontal2, FlipVertical2, Scaling, RectangleVertical, RectangleHorizontal, FileText, SeparatorHorizontal, Captions } from 'lucide-react';
9
+ import { AiMarkdown, Dialog, DialogContent, DialogHeader, DialogTitle, cn, DialogDescription, Button, DialogFooter, Tabs, TabsList, TabsTrigger, TabsContent, Input, splitMarkdownBlocks } from '@arbidocs/react/ui';
10
+ import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Loader2, Sparkles, Type, Pilcrow, UsersRound, RefreshCw, WifiOff, Download, Save, Ellipsis, BookA, Blocks, Link2, Superscript, NotebookText, RefreshCcw, Columns2, Settings2, BetweenHorizontalStart, BetweenHorizontalEnd, BetweenVerticalStart, BetweenVerticalEnd, Merge, Split, Heading, AlignVerticalJustifyStart, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, RotateCw, FlipHorizontal2, FlipVertical2, Scaling, RectangleVertical, RectangleHorizontal, FileText, SeparatorHorizontal, Captions } from 'lucide-react';
10
11
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
12
  import { createPortal } from 'react-dom';
12
13
  import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, trackInsertion, newFromTemplate } from '@arbidocs/coedit/template';
14
+ import { resolveNumberingLevel, resolveStoryListItems, buildStyleCascadeTable, withNumberingStyleLinks, buildNumberingIndex } from '@docx-editor.dev/core/layout';
13
15
  import { executeImageCommand } from '@docx-editor.dev/core/editor';
14
16
  import { renderToStaticMarkup } from 'react-dom/server';
15
17
  import * as Y from 'yjs';
@@ -896,7 +898,7 @@ function DocxCommentActivity() {
896
898
  const thread = review.items.filter(
897
899
  (entry) => entry.kind === "comment" && (entry.item.id === commentId || entry.parentId === commentId)
898
900
  );
899
- const statuses = Object.entries(activity).filter(([, run]) => run.commentId === commentId);
901
+ const statuses = Object.entries(activity).filter(([, run2]) => run2.commentId === commentId);
900
902
  const labels = {
901
903
  delivering: "Delivering mention\u2026",
902
904
  queued: "Queued\u2026",
@@ -908,18 +910,18 @@ function DocxCommentActivity() {
908
910
  unknown: "Status unavailable"
909
911
  };
910
912
  return /* @__PURE__ */ jsxs("div", { className: "native-docx-comment-extra", "data-review-selectable": "", children: [
911
- statuses.map(([id, run]) => /* @__PURE__ */ jsxs(
913
+ statuses.map(([id, run2]) => /* @__PURE__ */ jsxs(
912
914
  "div",
913
915
  {
914
916
  role: "status",
915
917
  "data-testid": "docx-comment-agent-status",
916
- "data-status": run.status,
918
+ "data-status": run2.status,
917
919
  className: "native-docx-comment-activity",
918
920
  children: [
919
- ["delivering", "queued", "in_progress"].includes(run.status) && /* @__PURE__ */ jsx("span", { className: "native-docx-agent-spinner", "aria-hidden": "true" }),
920
- run.label,
921
+ ["delivering", "queued", "in_progress"].includes(run2.status) && /* @__PURE__ */ jsx("span", { className: "native-docx-agent-spinner", "aria-hidden": "true" }),
922
+ run2.label,
921
923
  ": ",
922
- labels[run.status]
924
+ labels[run2.status]
923
925
  ]
924
926
  },
925
927
  id
@@ -947,10 +949,10 @@ function DocxCommentActivity() {
947
949
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: entry.author || "Reviewer" }),
948
950
  /* @__PURE__ */ jsx("div", { "data-testid": "docx-full-comment-text", children: /* @__PURE__ */ jsx(AiMarkdown, { allowHtml: false, children: commentText(entry) }) })
949
951
  ] }, entry.key)),
950
- statuses.map(([id, run]) => /* @__PURE__ */ jsxs("p", { role: "status", className: "text-sm", children: [
951
- run.label,
952
+ statuses.map(([id, run2]) => /* @__PURE__ */ jsxs("p", { role: "status", className: "text-sm", children: [
953
+ run2.label,
952
954
  ": ",
953
- labels[run.status]
955
+ labels[run2.status]
954
956
  ] }, id))
955
957
  ]
956
958
  }
@@ -1262,9 +1264,19 @@ function DocxMentions({
1262
1264
  ) });
1263
1265
  }
1264
1266
  var DEFINED_TERM_STYLE = "DefinedTerm";
1265
- var FRAGMENT_STYLES = stylesXml(
1267
+ var CROSS_REFERENCE_STYLE = "CrossReference";
1268
+ var BROKEN_CROSS_REFERENCE_STYLE = "CrossReferenceBroken";
1269
+ var CHARACTER_STYLES = {
1270
+ [DEFINED_TERM_STYLE]: `<w:style w:type="character" w:customStyle="1" w:styleId="${DEFINED_TERM_STYLE}"><w:name w:val="Defined Term"/><w:rPr><w:u w:val="dotted" w:color="7F7F7F"/></w:rPr></w:style>`,
1271
+ [CROSS_REFERENCE_STYLE]: `<w:style w:type="character" w:customStyle="1" w:styleId="${CROSS_REFERENCE_STYLE}"><w:name w:val="Cross Reference"/><w:rPr><w:color w:val="1F4E79"/><w:u w:val="dotted" w:color="8EA9C1"/></w:rPr></w:style>`,
1272
+ [BROKEN_CROSS_REFERENCE_STYLE]: `<w:style w:type="character" w:customStyle="1" w:styleId="${BROKEN_CROSS_REFERENCE_STYLE}"><w:name w:val="Cross Reference Broken"/><w:basedOn w:val="${CROSS_REFERENCE_STYLE}"/><w:rPr><w:color w:val="C00000"/><w:u w:val="wave" w:color="C00000"/></w:rPr></w:style>`
1273
+ };
1274
+ function stylesUsed(body) {
1275
+ return [...new Set([...body.matchAll(/<w:rStyle w:val="([^"]+)"/g)].map((m) => m[1]))];
1276
+ }
1277
+ var fragmentStyles = (ids) => stylesXml(
1266
1278
  "",
1267
- `<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style><w:style w:type="character" w:customStyle="1" w:styleId="${DEFINED_TERM_STYLE}"><w:name w:val="Defined Term"/><w:rPr><w:u w:val="dotted" w:color="7F7F7F"/></w:rPr></w:style>`
1279
+ `<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style>${ids.map((id) => CHARACTER_STYLES[id] ?? "").join("")}`
1268
1280
  );
1269
1281
  var textRun = (text, props = "") => `<w:r>${props ? `<w:rPr>${props}</w:rPr>` : ""}<w:t xml:space="preserve">${esc(text)}</w:t></w:r>`;
1270
1282
  function definitionTooltip(definition) {
@@ -1292,11 +1304,16 @@ function base64(bytes) {
1292
1304
  binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1293
1305
  return btoa(binary);
1294
1306
  }
1295
- function fragmentHtml(body, covered2, revision) {
1307
+ function fragmentHtml(body, covered2, {
1308
+ revision,
1309
+ relationships,
1310
+ styles = stylesUsed(body)
1311
+ } = {}) {
1296
1312
  const untracked = docxPackage({
1297
1313
  document: documentXml(body),
1298
- styles: FRAGMENT_STYLES,
1299
- title: "Defined terms"
1314
+ styles: fragmentStyles(styles),
1315
+ title: "Defined terms",
1316
+ relationships
1300
1317
  });
1301
1318
  const bytes = revision ? trackInsertion(untracked, revision, covered2) : untracked;
1302
1319
  return `<div data-docx-fragment="${base64(bytes)}" data-docx-fragment-end="${covered2 ? "covered" : "open"}"></div>`;
@@ -1395,9 +1412,93 @@ function definitionOfAnchor(anchor, definitions) {
1395
1412
  const id = definitionIdOfBookmark(anchor.replace(/^#/, ""));
1396
1413
  return id ? definitions.get(id) : void 0;
1397
1414
  }
1415
+ function LinkHoverCard({
1416
+ root,
1417
+ matches,
1418
+ resolve,
1419
+ onFirstHover,
1420
+ testId
1421
+ }) {
1422
+ const [card, setCard] = useState(null);
1423
+ const hovered = useRef(false);
1424
+ const latest = useRef({ matches, onFirstHover });
1425
+ latest.current = { matches, onFirstHover };
1426
+ useEffect(() => {
1427
+ if (!root) return;
1428
+ const over = (event) => {
1429
+ const link = event.target?.closest?.(
1430
+ "a.docx-hyperlink"
1431
+ );
1432
+ const href = link?.getAttribute("href") ?? "";
1433
+ if (!link || !href || !latest.current.matches(href, link)) return;
1434
+ if (!hovered.current) {
1435
+ hovered.current = true;
1436
+ latest.current.onFirstHover?.();
1437
+ }
1438
+ const title = link.getAttribute("title");
1439
+ if (title) {
1440
+ link.dataset.hoverTitle = title;
1441
+ link.removeAttribute("title");
1442
+ }
1443
+ const rect = link.getBoundingClientRect();
1444
+ setCard({
1445
+ left: rect.left,
1446
+ top: rect.bottom + 6,
1447
+ href,
1448
+ fallback: link.dataset.hoverTitle ?? ""
1449
+ });
1450
+ };
1451
+ const out = (event) => {
1452
+ const link = event.target?.closest?.("a.docx-hyperlink");
1453
+ if (link && !link.contains(event.relatedTarget)) setCard(null);
1454
+ };
1455
+ const hide = () => setCard(null);
1456
+ root.addEventListener("mouseover", over);
1457
+ root.addEventListener("mouseout", out);
1458
+ window.addEventListener("keydown", hide, true);
1459
+ window.addEventListener("pointerdown", hide, true);
1460
+ window.addEventListener("scroll", hide, true);
1461
+ return () => {
1462
+ root.removeEventListener("mouseover", over);
1463
+ root.removeEventListener("mouseout", out);
1464
+ window.removeEventListener("keydown", hide, true);
1465
+ window.removeEventListener("pointerdown", hide, true);
1466
+ window.removeEventListener("scroll", hide, true);
1467
+ };
1468
+ }, [root]);
1469
+ if (!card) return null;
1470
+ const content = resolve(card.href);
1471
+ if (!content && !card.fallback) return null;
1472
+ return createPortal(
1473
+ /* @__PURE__ */ jsx(
1474
+ "div",
1475
+ {
1476
+ role: "tooltip",
1477
+ className: "native-docx-link-card rounded-md border border-border bg-popover p-3 text-sm text-popover-foreground shadow-lg",
1478
+ style: {
1479
+ position: "fixed",
1480
+ left: Math.max(8, Math.min(card.left, window.innerWidth - 348)),
1481
+ top: card.top,
1482
+ width: 340,
1483
+ zIndex: 1e3,
1484
+ pointerEvents: "none"
1485
+ },
1486
+ "data-testid": testId,
1487
+ children: content ? /* @__PURE__ */ jsxs(Fragment, { children: [
1488
+ content.title && /* @__PURE__ */ jsx("p", { className: "font-medium", children: content.title }),
1489
+ /* @__PURE__ */ jsx("p", { className: content.title ? "mt-1 text-muted-foreground" : void 0, children: content.body }),
1490
+ content.footer && /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: content.footer })
1491
+ ] }) : /* @__PURE__ */ jsx("p", { children: card.fallback })
1492
+ }
1493
+ ),
1494
+ document.body
1495
+ );
1496
+ }
1398
1497
 
1399
1498
  // src/docx/definitions/port.ts
1499
+ var WATCHED_STYLES = /* @__PURE__ */ new Set([BROKEN_CROSS_REFERENCE_STYLE]);
1400
1500
  var attr = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
1501
+ var FIELD_ATOM = "\uFFFC";
1401
1502
  var HEADING_STYLE = /^(heading|berschrift|titre|t[ií]tulo|titolo|kop)\s*\d/i;
1402
1503
  function readParagraphs(root) {
1403
1504
  const out = [];
@@ -1424,8 +1525,44 @@ function readParagraph(node, topLevel, bookmarks) {
1424
1525
  const links = [];
1425
1526
  let styleId;
1426
1527
  let outline = false;
1528
+ let fieldDepth = 0;
1529
+ const marks = [];
1530
+ let runStyle;
1531
+ const fields = [];
1532
+ let current = null;
1533
+ const field = (c) => {
1534
+ const type = attr(c, "fldCharType");
1535
+ if (type === "begin") {
1536
+ if (fieldDepth === 0) {
1537
+ current = { offset: text.length, instruction: "", result: "", inResult: false };
1538
+ text += FIELD_ATOM;
1539
+ }
1540
+ fieldDepth++;
1541
+ } else if (type === "separate" && fieldDepth === 1 && current) current.inResult = true;
1542
+ else if (type === "end") {
1543
+ fieldDepth = Math.max(0, fieldDepth - 1);
1544
+ if (fieldDepth === 0 && current) {
1545
+ const { offset, instruction, result } = current;
1546
+ fields.push({ offset, instruction: instruction.trim(), result });
1547
+ current = null;
1548
+ }
1549
+ }
1550
+ };
1427
1551
  const walk = (n, link) => {
1428
1552
  for (const c of n.children ?? []) {
1553
+ if (c.localName === "fldChar") {
1554
+ field(c);
1555
+ continue;
1556
+ }
1557
+ if (c.localName === "fldSimple") {
1558
+ if (fieldDepth === 0) text += FIELD_ATOM;
1559
+ continue;
1560
+ }
1561
+ if (c.localName === "instrText") {
1562
+ if (fieldDepth === 1 && current)
1563
+ current.instruction += (c.children ?? []).map((t) => t.value ?? "").join("");
1564
+ continue;
1565
+ }
1429
1566
  if (c.kind === "paragraphProperties" || c.localName === "pPr") {
1430
1567
  for (const p of c.children ?? []) {
1431
1568
  if (p.localName === "pStyle") styleId = attr(p, "val");
@@ -1434,6 +1571,14 @@ function readParagraph(node, topLevel, bookmarks) {
1434
1571
  continue;
1435
1572
  }
1436
1573
  if (c.localName === "delText" || c.localName === "del" || c.localName === "rPr") continue;
1574
+ if (c.localName === "r") {
1575
+ const rStyle = c.children?.find((x) => x.localName === "rPr")?.children?.find((x) => x.localName === "rStyle");
1576
+ const watched = rStyle && attr(rStyle, "val");
1577
+ runStyle = watched && WATCHED_STYLES.has(watched) ? watched : void 0;
1578
+ walk(c, link);
1579
+ runStyle = void 0;
1580
+ continue;
1581
+ }
1437
1582
  if (c.localName === "bookmarkStart") {
1438
1583
  const name = attr(c, "name");
1439
1584
  if (name) bookmarks.push(name);
@@ -1447,11 +1592,22 @@ function readParagraph(node, topLevel, bookmarks) {
1447
1592
  continue;
1448
1593
  }
1449
1594
  if (c.kind === "textValue") {
1595
+ if (fieldDepth > 0) {
1596
+ const open = current;
1597
+ if (fieldDepth === 1 && open?.inResult) open.result += c.value ?? "";
1598
+ continue;
1599
+ }
1600
+ if (runStyle && c.value) {
1601
+ const last = marks[marks.length - 1];
1602
+ if (last && last.styleId === runStyle && last.offset + last.text.length === text.length)
1603
+ last.text += c.value;
1604
+ else marks.push({ styleId: runStyle, offset: text.length, text: c.value });
1605
+ }
1450
1606
  text += c.value ?? "";
1451
1607
  if (link) link.text += c.value ?? "";
1452
1608
  continue;
1453
1609
  }
1454
- if (c.localName === "tab") text += " ";
1610
+ if (c.localName === "tab" && fieldDepth === 0) text += " ";
1455
1611
  walk(c, link);
1456
1612
  }
1457
1613
  };
@@ -1463,9 +1619,20 @@ function readParagraph(node, topLevel, bookmarks) {
1463
1619
  styleId,
1464
1620
  topLevel,
1465
1621
  bookmarks,
1466
- links
1622
+ links,
1623
+ ...fields.length ? { fields } : {},
1624
+ ...marks.length ? { marks } : {}
1467
1625
  };
1468
1626
  }
1627
+ function documentStyleIds(root) {
1628
+ const ids = /* @__PURE__ */ new Set();
1629
+ for (const style of root?.children ?? [])
1630
+ if (style.localName === "style") {
1631
+ const id = attr(style, "styleId");
1632
+ if (id) ids.add(id);
1633
+ }
1634
+ return ids;
1635
+ }
1469
1636
  function editorPort(editor, revision) {
1470
1637
  const surface = () => {
1471
1638
  const s = editor.surface;
@@ -1485,8 +1652,17 @@ function editorPort(editor, revision) {
1485
1652
  surface().setSelection({ anchor: from, head: to });
1486
1653
  surface().deleteSelection();
1487
1654
  },
1488
- paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2, revision) }).ok,
1489
- splitParagraph: () => surface().splitParagraph()
1655
+ paste: (body, text, covered2, relationships) => {
1656
+ const defined = documentStyleIds(surface().session.stylesRoot());
1657
+ const styles = stylesUsed(body).filter((id) => !defined.has(id));
1658
+ return editor.exec({
1659
+ type: "paste",
1660
+ text,
1661
+ html: fragmentHtml(body, covered2, { revision, relationships, styles })
1662
+ }).ok;
1663
+ },
1664
+ splitParagraph: () => surface().splitParagraph(),
1665
+ typeText: (text) => surface().insertPlainText(text)
1490
1666
  };
1491
1667
  }
1492
1668
  function withDirectEditing(editor, fn) {
@@ -1499,34 +1675,68 @@ function withDirectEditing(editor, fn) {
1499
1675
  if (mode === "suggesting") editor.setEditingMode("suggesting");
1500
1676
  }
1501
1677
  }
1502
- function holdCaret(editor, timeoutMs = 5e3) {
1678
+ function holdCaret(editor, { settleMs = 600, maxMs = 2e3 } = {}) {
1503
1679
  const surface = editor.surface;
1504
1680
  const selection = surface?.state().selection;
1505
1681
  if (!surface || !selection) return;
1506
- const { paragraphId, offset } = selection.head;
1507
- const paraId = surface.session.paraIdOf(paragraphId);
1682
+ const { offset } = selection.head;
1683
+ const paraId = surface.session.paraIdOf(selection.head.paragraphId);
1508
1684
  if (!paraId) return;
1685
+ let typed = "";
1509
1686
  let done = false;
1687
+ let settle;
1510
1688
  const stop = () => {
1511
1689
  done = true;
1512
1690
  offChange();
1513
- offSelection();
1514
- clearTimeout(timer);
1691
+ clearTimeout(settle);
1692
+ clearTimeout(limit);
1693
+ window.removeEventListener("keydown", key, true);
1694
+ window.removeEventListener("pointerdown", click, true);
1515
1695
  };
1516
- const restore = () => {
1696
+ const finish = () => {
1517
1697
  if (done) return;
1518
- const now = surface.state().selection;
1519
- const node = surface.session.nodeIdOf(paraId);
1520
- if (!now || !node) return;
1521
- if (now.head.offset !== offset || now.head.paragraphId !== paragraphId) return stop();
1522
- if (node === paragraphId) return;
1523
1698
  stop();
1699
+ const node = surface.session.nodeIdOf(paraId);
1700
+ if (!node) return;
1524
1701
  const point = { paragraphId: node, offset };
1525
1702
  surface.setSelection({ anchor: point, head: point });
1703
+ if (typed) surface.insertPlainText(typed);
1704
+ };
1705
+ const key = (event) => {
1706
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
1707
+ event.preventDefault();
1708
+ event.stopPropagation();
1709
+ typed += event.key;
1710
+ return;
1711
+ }
1712
+ finish();
1713
+ };
1714
+ const click = () => {
1715
+ if (typed) finish();
1716
+ else stop();
1717
+ };
1718
+ const quiet = () => {
1719
+ clearTimeout(settle);
1720
+ settle = setTimeout(finish, settleMs);
1526
1721
  };
1527
- const offChange = editor.on("change", restore);
1528
- const offSelection = editor.on("selectionChange", restore);
1529
- const timer = setTimeout(stop, timeoutMs);
1722
+ const offChange = editor.on("change", quiet);
1723
+ window.addEventListener("keydown", key, true);
1724
+ window.addEventListener("pointerdown", click, true);
1725
+ settle = setTimeout(finish, settleMs);
1726
+ const limit = setTimeout(finish, maxMs);
1727
+ }
1728
+ function preservingCaret(editor, fn) {
1729
+ const surface = editor.surface;
1730
+ const before = surface?.state().selection?.head;
1731
+ const paraId = before && surface?.session.paraIdOf(before.paragraphId);
1732
+ const result = fn();
1733
+ const node = paraId && surface?.session.nodeIdOf(paraId);
1734
+ if (before && node) {
1735
+ const point = { paragraphId: node, offset: before.offset };
1736
+ surface.setSelection({ anchor: point, head: point });
1737
+ holdCaret(editor);
1738
+ }
1739
+ return result;
1530
1740
  }
1531
1741
 
1532
1742
  // src/docx/structural/trackedOrNoted.ts
@@ -1690,19 +1900,26 @@ function DefinedTermsProvider({
1690
1900
  },
1691
1901
  [notify]
1692
1902
  );
1693
- const run = useCallback(
1903
+ const run2 = useCallback(
1694
1904
  (fn) => editor ? attempt(() => withDirectEditing(editor, fn)) : void 0,
1695
1905
  [editor, attempt]
1696
1906
  );
1907
+ const typedTrigger = (port) => {
1908
+ if (picker?.mode !== "typing") return 0;
1909
+ const caret = port.caret();
1910
+ const paragraph = caret && port.paragraphs().find((p) => p.id === caret.paragraphId);
1911
+ const match = paragraph && TRIGGER_QUERY.exec(paragraph.text.slice(0, caret.offset));
1912
+ return match ? DEFINED_TERM_TRIGGER.length + match[1].length : 0;
1913
+ };
1697
1914
  const pick = useCallback(
1698
1915
  (row) => {
1699
1916
  if (!editor || !picker || !row) return;
1700
1917
  const port = editorPort(editor);
1701
- const placed = run(() => {
1918
+ const placed = run2(() => {
1702
1919
  if (picker.mode === "search" && picker.caret) port.setCaret(picker.caret);
1703
1920
  const caret = port.caret();
1704
1921
  if (!caret) throw new Error("Place the cursor where the term should go.");
1705
- const typed = picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0;
1922
+ const typed = typedTrigger(port);
1706
1923
  if (typed) port.deleteRange({ ...caret, offset: Math.max(0, caret.offset - typed) }, caret);
1707
1924
  return true;
1708
1925
  });
@@ -1718,7 +1935,7 @@ function DefinedTermsProvider({
1718
1935
  if (result?.addedDefinition)
1719
1936
  notify?.(`\u201C${row.definition.term}\u201D added to the Definitions section.`, "success");
1720
1937
  },
1721
- [editor, picker, run, attempt, track, notify]
1938
+ [editor, picker, run2, attempt, track, notify]
1722
1939
  );
1723
1940
  const openPicker = useCallback(
1724
1941
  (anchor) => {
@@ -1797,11 +2014,16 @@ function DefinedTermsProvider({
1797
2014
  /* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
1798
2015
  children,
1799
2016
  load && /* @__PURE__ */ jsx(
1800
- DefinedTermHover,
2017
+ LinkHoverCard,
1801
2018
  {
1802
2019
  root: viewer,
1803
- resolve: (anchor) => definitionOfAnchor(anchor, byId),
1804
- onFirstHover: firstHover
2020
+ matches: (href) => href.startsWith("#_dt_"),
2021
+ resolve: (href) => {
2022
+ const definition = definitionOfAnchor(href, byId);
2023
+ return definition ? { title: definition.term, body: definition.meaning, footer: sourceOf(definition) } : null;
2024
+ },
2025
+ onFirstHover: firstHover,
2026
+ testId: "docx-defined-term-card"
1805
2027
  }
1806
2028
  ),
1807
2029
  picker && createPortal(
@@ -1843,7 +2065,7 @@ function DefinedTermMenu({
1843
2065
  onClose,
1844
2066
  labels: l
1845
2067
  }) {
1846
- const listboxId = React3.useId();
2068
+ const listboxId = React4.useId();
1847
2069
  const element = useRef(null);
1848
2070
  const search = picker.mode === "search";
1849
2071
  useEffect(() => {
@@ -1954,83 +2176,6 @@ function DefinedTermMenu({
1954
2176
  }
1955
2177
  );
1956
2178
  }
1957
- function DefinedTermHover({
1958
- root,
1959
- resolve,
1960
- onFirstHover
1961
- }) {
1962
- const [card, setCard] = useState(null);
1963
- const hovered = useRef(false);
1964
- useEffect(() => {
1965
- if (!root) return;
1966
- const over = (event) => {
1967
- const link = event.target?.closest?.(
1968
- 'a.docx-hyperlink[data-docx-link-kind="internal"]'
1969
- );
1970
- const href = link?.getAttribute("href") ?? "";
1971
- if (!link || !href.startsWith("#_dt_")) return;
1972
- if (!hovered.current) {
1973
- hovered.current = true;
1974
- onFirstHover();
1975
- }
1976
- const title = link.getAttribute("title");
1977
- if (title) {
1978
- link.dataset.definition = title;
1979
- link.removeAttribute("title");
1980
- }
1981
- const rect = link.getBoundingClientRect();
1982
- setCard({
1983
- left: rect.left,
1984
- top: rect.bottom + 6,
1985
- anchor: href.slice(1),
1986
- fallback: link.dataset.definition ?? ""
1987
- });
1988
- };
1989
- const out = (event) => {
1990
- const link = event.target?.closest?.("a.docx-hyperlink");
1991
- if (link && !link.contains(event.relatedTarget)) setCard(null);
1992
- };
1993
- root.addEventListener("mouseover", over);
1994
- root.addEventListener("mouseout", out);
1995
- const hide = () => setCard(null);
1996
- window.addEventListener("keydown", hide, true);
1997
- window.addEventListener("pointerdown", hide, true);
1998
- window.addEventListener("scroll", hide, true);
1999
- return () => {
2000
- root.removeEventListener("mouseover", over);
2001
- root.removeEventListener("mouseout", out);
2002
- window.removeEventListener("keydown", hide, true);
2003
- window.removeEventListener("pointerdown", hide, true);
2004
- window.removeEventListener("scroll", hide, true);
2005
- };
2006
- }, [root, onFirstHover]);
2007
- if (!card) return null;
2008
- const definition = resolve(card.anchor);
2009
- return createPortal(
2010
- /* @__PURE__ */ jsx(
2011
- "div",
2012
- {
2013
- role: "tooltip",
2014
- className: "native-docx-term-card rounded-md border border-border bg-popover p-3 text-sm text-popover-foreground shadow-lg",
2015
- style: {
2016
- position: "fixed",
2017
- left: Math.max(8, Math.min(card.left, window.innerWidth - 348)),
2018
- top: card.top,
2019
- width: 340,
2020
- zIndex: 1e3,
2021
- pointerEvents: "none"
2022
- },
2023
- "data-testid": "docx-defined-term-card",
2024
- children: definition ? /* @__PURE__ */ jsxs(Fragment, { children: [
2025
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: definition.term }),
2026
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-muted-foreground", children: definition.meaning }),
2027
- sourceOf(definition) && /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: sourceOf(definition) })
2028
- ] }) : /* @__PURE__ */ jsx("p", { children: card.fallback })
2029
- }
2030
- ),
2031
- document.body
2032
- );
2033
- }
2034
2179
  var BUILTIN_TEMPLATE_NAME = "ARBI legal template";
2035
2180
  var templateFileName = (t) => /\.(dotx|dotm|docx|docm)$/i.test(t.name) ? t.name : `${t.name}.dotx`;
2036
2181
  var builtin;
@@ -2163,13 +2308,834 @@ function HouseTemplateProvider({
2163
2308
  );
2164
2309
  return /* @__PURE__ */ jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2165
2310
  }
2311
+
2312
+ // src/docx/references/numbering.ts
2313
+ var numbers = (level) => !!level && level.numFmt !== "bullet" && level.numFmt !== "none" && (level.lvlText ?? level.markerText ?? "").length > 0;
2314
+ function paragraphNumber(order, items, levelOf, paragraphId) {
2315
+ const item = items.get(paragraphId);
2316
+ if (!item || !numbers(item)) return null;
2317
+ const at = order.indexOf(paragraphId);
2318
+ const markerAt = (ilvl) => {
2319
+ if (ilvl === item.ilvl) return item.markerText;
2320
+ for (let i = at - 1; i >= 0; i--) {
2321
+ const other = items.get(order[i]);
2322
+ if (other && other.numId === item.numId && other.ilvl === ilvl) return other.markerText;
2323
+ }
2324
+ return null;
2325
+ };
2326
+ const chain = [];
2327
+ const texts = [];
2328
+ for (let u = item.ilvl; u >= 0; u--) {
2329
+ const level = levelOf(item.numId, u);
2330
+ if (!level || !numbers(level)) continue;
2331
+ if (u !== item.ilvl && texts.some((t) => t.includes(`%${u + 1}`))) continue;
2332
+ chain.push(u);
2333
+ texts.push(level.lvlText);
2334
+ }
2335
+ const value = chain.reverse().map((u) => markerAt(u) ?? "").join("");
2336
+ if (!value) return null;
2337
+ return value.length > 1 && value.endsWith(".") ? value.slice(0, -1) : value;
2338
+ }
2339
+ function listTargets(paragraphs, items, levelOf) {
2340
+ const order = paragraphs.map((p) => p.id);
2341
+ const out = [];
2342
+ for (const p of paragraphs) {
2343
+ const text = p.text.replace(//g, "").trim();
2344
+ if (!text) continue;
2345
+ const number = paragraphNumber(order, items, levelOf, p.id);
2346
+ const item = items.get(p.id);
2347
+ if (number !== null)
2348
+ out.push({
2349
+ paragraphId: p.id,
2350
+ kind: "numbered",
2351
+ number,
2352
+ marker: item?.markerText ?? "",
2353
+ text,
2354
+ heading: p.heading,
2355
+ level: item?.ilvl ?? 0
2356
+ });
2357
+ else if (p.heading)
2358
+ out.push({
2359
+ paragraphId: p.id,
2360
+ kind: "heading",
2361
+ number: null,
2362
+ marker: "",
2363
+ text,
2364
+ heading: true,
2365
+ level: 0
2366
+ });
2367
+ }
2368
+ return out;
2369
+ }
2370
+ function filterTargets(targets, query) {
2371
+ const q = query.trim().toLowerCase();
2372
+ if (!q) return [...targets];
2373
+ return targets.filter(
2374
+ (t) => (t.number ?? "").toLowerCase().startsWith(q) || t.text.toLowerCase().includes(q)
2375
+ );
2376
+ }
2377
+
2378
+ // src/docx/references/commands.ts
2379
+ var REF_BOOKMARK_PREFIX = "_Ref";
2380
+ function newRefBookmark(taken, random = Math.random) {
2381
+ for (; ; ) {
2382
+ const name = `${REF_BOOKMARK_PREFIX}${String(Math.floor(random() * 9e8) + 1e8)}`;
2383
+ if (!taken.has(name)) return name;
2384
+ }
2385
+ }
2386
+ var styled = (style) => style ? `<w:rPr><w:rStyle w:val="${style}"/></w:rPr>` : "";
2387
+ var run = (text, style) => `<w:r>${styled(style)}<w:t xml:space="preserve">${esc(text)}</w:t></w:r>`;
2388
+ function refFieldXml(instruction, cached) {
2389
+ const r = (inner) => `<w:r>${styled(CROSS_REFERENCE_STYLE)}${inner}</w:r>`;
2390
+ return `${r('<w:fldChar w:fldCharType="begin"/>')}${r(
2391
+ `<w:instrText xml:space="preserve"> ${esc(instruction)} </w:instrText>`
2392
+ )}${r('<w:fldChar w:fldCharType="separate"/>')}${run(cached, CROSS_REFERENCE_STYLE)}${r(
2393
+ '<w:fldChar w:fldCharType="end"/>'
2394
+ )}`;
2395
+ }
2396
+ var refTooltip = (value, mode) => mode === "number" ? `Cross-reference to clause ${value}` : `Cross-reference to \u201C${value}\u201D`;
2397
+ function crossReferenceXml(bookmark, mode, value) {
2398
+ return `<w:hyperlink w:anchor="${esc(bookmark)}" w:tooltip="${esc(
2399
+ refTooltip(value, mode)
2400
+ )}" w:history="1">${refFieldXml(refInstruction(bookmark, mode), value)}</w:hyperlink>`;
2401
+ }
2402
+ var refInstruction = (bookmark, mode) => mode === "number" ? `REF ${bookmark} \\r \\h` : `REF ${bookmark} \\h`;
2403
+ var refResult = (target, mode) => mode === "number" && target.number ? target.number : target.text;
2404
+ var allBookmarks = (port) => new Set(port.paragraphs().flatMap((p) => p.bookmarks));
2405
+ function ensureTargetBookmark(port, target, mode) {
2406
+ const paragraph = port.paragraphs().find((p) => p.id === target.paragraphId);
2407
+ if (!paragraph) throw new Error("That clause is no longer in the document.");
2408
+ const existing = paragraph.bookmarks.find((b) => b.startsWith(REF_BOOKMARK_PREFIX));
2409
+ if (existing && mode === "number") return existing;
2410
+ const name = newRefBookmark(allBookmarks(port));
2411
+ const start = `<w:bookmarkStart w:id="0" w:name="${name}"/>`;
2412
+ const end = '<w:bookmarkEnd w:id="0"/>';
2413
+ if (mode === "number") {
2414
+ port.setCaret({ paragraphId: paragraph.id, offset: 0 });
2415
+ if (!port.paste(`<w:p>${start}${end}</w:p>`, "", false))
2416
+ throw new Error("The cross-reference target could not be marked.");
2417
+ return name;
2418
+ }
2419
+ const runs = port.runsXml(paragraph.id);
2420
+ if (!runs) throw new Error("This heading cannot be referred to by its text; use its number.");
2421
+ port.deleteRange(
2422
+ { paragraphId: paragraph.id, offset: 0 },
2423
+ { paragraphId: paragraph.id, offset: paragraph.text.length }
2424
+ );
2425
+ if (!port.paste(`<w:p>${start}${runs}${end}</w:p>`, paragraph.text, false))
2426
+ throw new Error("The cross-reference target could not be marked.");
2427
+ return name;
2428
+ }
2429
+ function pastePoint(port, caret) {
2430
+ const paragraph = port.paragraphs().find((p) => p.id === caret.paragraphId);
2431
+ if (paragraph?.text[caret.offset - 1] !== FIELD_ATOM) return caret;
2432
+ port.setCaret(caret);
2433
+ port.typeText(" ");
2434
+ return { paragraphId: caret.paragraphId, offset: caret.offset + 1 };
2435
+ }
2436
+ function markCrossReferenceTarget(port, target, mode) {
2437
+ if (mode === "number" && !target.number) mode = "text";
2438
+ const bookmark = ensureTargetBookmark(port, target, mode);
2439
+ return { bookmark, mode, value: refResult(target, mode) };
2440
+ }
2441
+ function insertCrossReferenceField(port, { bookmark, mode, value }, caret) {
2442
+ caret = pastePoint(port, caret);
2443
+ port.setCaret(caret);
2444
+ if (!port.paste(`<w:p>${crossReferenceXml(bookmark, mode, value)}</w:p>`, value, false))
2445
+ throw new Error("The cross-reference could not be inserted.");
2446
+ port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + 1 });
2447
+ return { bookmark };
2448
+ }
2449
+ function insertCrossReference(port, target, mode, caret) {
2450
+ return insertCrossReferenceField(port, markCrossReferenceTarget(port, target, mode), caret);
2451
+ }
2452
+ var documentReferenceTooltip = (text, appName = "ARBI") => `Cross-reference: ${text} \u2014 opens in ${appName}`;
2453
+ function insertDocumentReference(port, reference, caret) {
2454
+ const text = reference.text.trim();
2455
+ if (!text) throw new Error("Enter the reference text.");
2456
+ const tooltip = documentReferenceTooltip(text, reference.appName).slice(0, 255);
2457
+ caret = pastePoint(port, caret);
2458
+ port.setCaret(caret);
2459
+ const link = `<w:p><w:hyperlink r:id="rIdRef1"${tooltip ? ` w:tooltip="${esc(tooltip)}"` : ""} w:history="1">${run(text, CROSS_REFERENCE_STYLE)}</w:hyperlink></w:p>`;
2460
+ if (!port.paste(link, text, false, hyperlinkRelationship("rIdRef1", reference.url)))
2461
+ throw new Error("The reference could not be inserted.");
2462
+ port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + text.length });
2463
+ }
2464
+ var REF_FIELD = /^REF\s+(\S+)((?:\s+\\[A-Za-z*]+(?:\s+MERGEFORMAT)?)*)\s*$/i;
2465
+ function staleCrossReferences(port) {
2466
+ const paragraphs = port.paragraphs();
2467
+ if (!paragraphs.some((p) => p.fields?.length)) return [];
2468
+ const items = port.listItems();
2469
+ const order = paragraphs.map((p) => p.id);
2470
+ const owner = /* @__PURE__ */ new Map();
2471
+ for (const p of paragraphs) for (const b of p.bookmarks) if (!owner.has(b)) owner.set(b, p.id);
2472
+ const out = [];
2473
+ for (const p of paragraphs)
2474
+ for (const field of p.fields ?? []) {
2475
+ const match = REF_FIELD.exec(field.instruction);
2476
+ if (!match || !match[1].startsWith(REF_BOOKMARK_PREFIX)) continue;
2477
+ const switches = match[2].toLowerCase();
2478
+ const mode = /\\[rwn]\b/.test(switches) ? "number" : "text";
2479
+ const target = owner.get(match[1]);
2480
+ const base = {
2481
+ paragraphId: p.id,
2482
+ offset: field.offset,
2483
+ instruction: field.instruction,
2484
+ cached: field.result,
2485
+ bookmark: match[1],
2486
+ mode
2487
+ };
2488
+ if (!target) {
2489
+ out.push({ ...base, value: null });
2490
+ continue;
2491
+ }
2492
+ const value = /\\[rwn]\b/.test(switches) ? switches.includes("\\r") ? paragraphNumber(order, items, port.levelOf, target) : null : (paragraphs.find((x) => x.id === target)?.text ?? "").replace(//g, "").trim();
2493
+ if (value && value !== field.result.trim()) out.push({ ...base, value });
2494
+ }
2495
+ return out;
2496
+ }
2497
+ function brokenCrossReferences(port) {
2498
+ return port.paragraphs().flatMap(
2499
+ (p) => (p.marks ?? []).filter((m) => m.styleId === BROKEN_CROSS_REFERENCE_STYLE).map((m) => ({ paragraphId: p.id, offset: m.offset, text: m.text }))
2500
+ );
2501
+ }
2502
+ function refreshCrossReferences(port) {
2503
+ const stale = staleCrossReferences(port).sort(
2504
+ (a, b) => a.paragraphId === b.paragraphId ? b.offset - a.offset : 0
2505
+ );
2506
+ let refreshed = 0;
2507
+ for (const ref of stale) {
2508
+ const paragraph = port.paragraphs().find((p) => p.id === ref.paragraphId);
2509
+ if (!paragraph || paragraph.text[ref.offset - 1] === FIELD_ATOM) continue;
2510
+ const at = { paragraphId: ref.paragraphId, offset: ref.offset };
2511
+ port.deleteRange(at, { paragraphId: ref.paragraphId, offset: ref.offset + 1 });
2512
+ port.setCaret(at);
2513
+ const text = ref.value ?? (ref.cached.trim() || ref.bookmark);
2514
+ const body = ref.value === null ? run(text, BROKEN_CROSS_REFERENCE_STYLE) : crossReferenceXml(ref.bookmark, ref.mode, ref.value);
2515
+ if (port.paste(`<w:p>${body}</w:p>`, text, false) && ref.value !== null) refreshed++;
2516
+ }
2517
+ return { refreshed, broken: brokenCrossReferences(port) };
2518
+ }
2519
+
2520
+ // src/docx/references/passages.ts
2521
+ function shortTitle(title) {
2522
+ const base = title.replace(/\.[A-Za-z0-9]{1,5}$/, "").trim();
2523
+ const defined = /\(\s*(?:the\s+)?["“'‘]([^"”'’]{1,30})["”'’]\s*\)/i.exec(base);
2524
+ return defined ? defined[1] : base;
2525
+ }
2526
+ function passageClause(text) {
2527
+ const first = text.trim().split("\n")[0] ?? "";
2528
+ const match = /^(?:#+\s*)?(?:\*\*)?(?:clause|section|article)?\s*(\d+(?:\.\d+)*)[.)]?(?:\*\*)?\s+\S/i.exec(
2529
+ first
2530
+ );
2531
+ return match ? match[1] : null;
2532
+ }
2533
+ function referenceText(title, passage) {
2534
+ const name = shortTitle(title);
2535
+ const clause = passageClause(passage.text);
2536
+ if (clause) return `${name}, clause ${clause}`;
2537
+ if (passage.page) return `${name}, p. ${passage.page}`;
2538
+ return name;
2539
+ }
2540
+ function passageExcerpt(text, max = 280) {
2541
+ const plain = text.replace(/[#*_`>|]+/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
2542
+ return plain.length > max ? `${plain.slice(0, max - 1)}\u2026` : plain;
2543
+ }
2544
+ function filterPassages(passages, query) {
2545
+ const q = query.trim().toLowerCase();
2546
+ return q ? passages.filter((p) => p.text.toLowerCase().includes(q)) : [...passages];
2547
+ }
2548
+ var RUN_PARTS = /* @__PURE__ */ new Set(["rPr", "t", "tab", "br", "lastRenderedPageBreak"]);
2549
+ var SKIPPED = /* @__PURE__ */ new Set(["pPr", "bookmarkStart", "bookmarkEnd", "proofErr", "permStart", "permEnd"]);
2550
+ function serialize(node) {
2551
+ if (node.kind === "textValue") return esc(node.value ?? "");
2552
+ const name = `${node.prefix ?? "w"}:${node.localName}`;
2553
+ const attrs = (node.attributes ?? []).map((a) => ` ${a.prefix ? `${a.prefix}:` : ""}${a.localName}="${esc(a.value)}"`).join("");
2554
+ const children = (node.children ?? []).map((c) => serialize(c)).join("");
2555
+ return children ? `<${name}${attrs}>${children}</${name}>` : `<${name}${attrs}/>`;
2556
+ }
2557
+ function paragraphRunsXml(paragraph) {
2558
+ const runs = [];
2559
+ for (const child of paragraph.children ?? []) {
2560
+ if (child.localName && SKIPPED.has(child.localName)) continue;
2561
+ if (child.localName !== "r") return null;
2562
+ if ((child.children ?? []).some((c) => !c.localName || !RUN_PARTS.has(c.localName))) return null;
2563
+ runs.push(serialize(child));
2564
+ }
2565
+ return runs.length ? runs.join("") : null;
2566
+ }
2567
+ function findNode2(node, id) {
2568
+ if (node.id === id) return node;
2569
+ for (const child of node.children ?? []) {
2570
+ const found = findNode2(child, id);
2571
+ if (found) return found;
2572
+ }
2573
+ return void 0;
2574
+ }
2575
+ function referencesPort(editor, revision) {
2576
+ const base = editorPort(editor, revision);
2577
+ const session = () => {
2578
+ const s = editor.surface?.session;
2579
+ if (!s) throw new Error("The document is not open.");
2580
+ return s;
2581
+ };
2582
+ const root = () => session().part().root;
2583
+ let numbering;
2584
+ let cascade;
2585
+ const index = () => {
2586
+ if (!numbering) {
2587
+ const s = session();
2588
+ cascade = buildStyleCascadeTable(s.stylesRoot(), void 0, s.settingsRoot());
2589
+ numbering = withNumberingStyleLinks(buildNumberingIndex(s.numberingRoot()), cascade);
2590
+ }
2591
+ return numbering;
2592
+ };
2593
+ return {
2594
+ ...base,
2595
+ listItems: () => {
2596
+ const body = root().children?.find((n) => n.kind === "body");
2597
+ const items = resolveStoryListItems(
2598
+ body?.children ?? [],
2599
+ index(),
2600
+ cascade
2601
+ );
2602
+ const out = /* @__PURE__ */ new Map();
2603
+ for (const [id, item] of items)
2604
+ out.set(id, {
2605
+ numId: item.numId,
2606
+ ilvl: item.ilvl,
2607
+ markerText: item.markerText,
2608
+ numFmt: item.numFmt
2609
+ });
2610
+ return out;
2611
+ },
2612
+ levelOf: (numId, ilvl) => {
2613
+ const level = resolveNumberingLevel(index(), numId, ilvl)?.level;
2614
+ return level ? { lvlText: level.lvlText, numFmt: level.numFmt } : null;
2615
+ },
2616
+ runsXml: (paragraphId) => {
2617
+ const node = findNode2(root(), paragraphId);
2618
+ return node ? paragraphRunsXml(node) : null;
2619
+ }
2620
+ };
2621
+ }
2622
+
2623
+ // src/docx/beforeSave.ts
2624
+ var hooks = /* @__PURE__ */ new WeakMap();
2625
+ function onBeforeSave(editor, fn) {
2626
+ let set = hooks.get(editor);
2627
+ if (!set) hooks.set(editor, set = /* @__PURE__ */ new Set());
2628
+ set.add(fn);
2629
+ return () => set.delete(fn);
2630
+ }
2631
+ function runBeforeSave(editor) {
2632
+ if (!editor) return;
2633
+ for (const fn of hooks.get(editor) ?? [])
2634
+ try {
2635
+ fn();
2636
+ } catch {
2637
+ }
2638
+ }
2639
+ var DEFAULT_CROSS_REFERENCES_LABELS = {
2640
+ insert: "Insert cross-reference",
2641
+ title: "Insert cross-reference",
2642
+ description: "Refer to a clause of this document, or to a passage of another document.",
2643
+ thisDocument: "This document",
2644
+ otherDocument: "Another document",
2645
+ filterTargets: "Find a clause or heading",
2646
+ noTargets: "This document has no numbered clauses or headings yet.",
2647
+ number: "Paragraph number",
2648
+ text: "Heading text",
2649
+ insertAs: "Insert",
2650
+ chooseDocument: "Choose a document",
2651
+ filterPassages: "Find a passage",
2652
+ loadingPassages: "Loading passages\u2026",
2653
+ noPassages: "No passage matches.",
2654
+ referenceText: "Reference text",
2655
+ insertButton: "Insert",
2656
+ cancel: "Cancel",
2657
+ clause: (number) => `Clause ${number}`,
2658
+ heading: "Heading",
2659
+ update: "Update cross-references",
2660
+ updated: "Cross-references updated",
2661
+ updateSummary: (refreshed) => refreshed ? `${refreshed} cross-reference${refreshed === 1 ? "" : "s"} brought up to date.` : "Every cross-reference is up to date.",
2662
+ brokenTitle: "Broken cross-references",
2663
+ brokenNone: "No cross-reference points at a deleted target.",
2664
+ brokenHelp: "Their target was deleted: the last text is kept and marked. Insert the reference again to repair it.",
2665
+ brokenCard: "Broken cross-reference",
2666
+ brokenCardBody: "Its target was deleted. Update cross-references to mark it.",
2667
+ show: "Show",
2668
+ close: "Close"
2669
+ };
2670
+ var CrossReferencesContext = createContext(null);
2671
+ var useCrossReferences = () => useContext(CrossReferencesContext);
2672
+ function CrossReferencesProvider({
2673
+ children,
2674
+ editable,
2675
+ references,
2676
+ documentId,
2677
+ notify,
2678
+ labels
2679
+ }) {
2680
+ const editor = useDocxEditor();
2681
+ const track = useTrackedOrNoted();
2682
+ const l = useMemo(() => ({ ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels }), [labels]);
2683
+ const [dialog, setDialog] = useState(
2684
+ null
2685
+ );
2686
+ const marker = useRef(null);
2687
+ const [viewer, setViewer] = useState(null);
2688
+ useEffect(() => setViewer(marker.current?.closest(".native-docx-viewer") ?? null), []);
2689
+ const openDialog = useCallback(() => {
2690
+ if (!editor || !editable) return;
2691
+ try {
2692
+ const port = referencesPort(editor);
2693
+ const caret = port.caret();
2694
+ if (!caret) return notify?.("Place the cursor where the reference should go.", "error");
2695
+ setDialog({ caret, targets: listTargets(port.paragraphs(), port.listItems(), port.levelOf) });
2696
+ } catch (error) {
2697
+ notify?.(error instanceof Error ? error.message : "Could not read the document.", "error");
2698
+ }
2699
+ }, [editor, editable, notify]);
2700
+ const run2 = useCallback(
2701
+ (label, prepare, insert) => {
2702
+ if (!editor) return;
2703
+ try {
2704
+ const prepared = withDirectEditing(editor, prepare);
2705
+ if (dialog) referencesPort(editor).setCaret(dialog.caret);
2706
+ track({ label, insert: (revision) => insert(prepared, revision), inserted: () => true });
2707
+ setDialog(null);
2708
+ editor.surface?.focus();
2709
+ holdCaret(editor);
2710
+ } catch (error) {
2711
+ notify?.(
2712
+ error instanceof Error ? error.message : "Could not insert the reference.",
2713
+ "error"
2714
+ );
2715
+ }
2716
+ },
2717
+ [editor, dialog, track, notify]
2718
+ );
2719
+ useEffect(() => {
2720
+ if (!editor || !editable) return;
2721
+ const refresh = () => {
2722
+ try {
2723
+ const port = referencesPort(editor);
2724
+ if (!port.paragraphs().some((p) => p.fields?.length)) return;
2725
+ preservingCaret(editor, () => withDirectEditing(editor, () => refreshCrossReferences(port)));
2726
+ } catch {
2727
+ }
2728
+ };
2729
+ const off = onBeforeSave(editor, refresh);
2730
+ const timer = setTimeout(refresh, 1500);
2731
+ return () => {
2732
+ off();
2733
+ clearTimeout(timer);
2734
+ };
2735
+ }, [editor, editable]);
2736
+ const [report, setReport] = useState(null);
2737
+ const update = useCallback(() => {
2738
+ if (!editor || !editable) return;
2739
+ try {
2740
+ const result = preservingCaret(
2741
+ editor,
2742
+ () => withDirectEditing(editor, () => refreshCrossReferences(referencesPort(editor)))
2743
+ );
2744
+ setReport(result);
2745
+ } catch (error) {
2746
+ notify?.(
2747
+ error instanceof Error ? error.message : "Could not update cross-references.",
2748
+ "error"
2749
+ );
2750
+ }
2751
+ }, [editor, editable, notify]);
2752
+ const [passages, setPassages] = useState({});
2753
+ const loadPassages = useCallback(
2754
+ (id) => {
2755
+ if (!references || passages[id]) return;
2756
+ setPassages((p) => ({ ...p, [id]: "loading" }));
2757
+ references.passages(id).then((list) => setPassages((p) => ({ ...p, [id]: list }))).catch(() => setPassages((p) => ({ ...p, [id]: [] })));
2758
+ },
2759
+ [references, passages]
2760
+ );
2761
+ useEffect(() => {
2762
+ if (!viewer || !references) return;
2763
+ const targetOf = (event) => {
2764
+ const link = event.target?.closest?.(
2765
+ 'a.docx-hyperlink[data-docx-link-kind="external"]'
2766
+ );
2767
+ if (!link || !viewer.contains(link)) return null;
2768
+ const mouse = event;
2769
+ if (mouse.button !== 0 || mouse.ctrlKey || mouse.metaKey || mouse.shiftKey) return null;
2770
+ return references.targetOf(link.getAttribute("href") ?? "");
2771
+ };
2772
+ const take = (event) => {
2773
+ const target = targetOf(event);
2774
+ if (!target) return;
2775
+ event.preventDefault();
2776
+ event.stopPropagation();
2777
+ if (event.type === "click") references.open(target);
2778
+ };
2779
+ const types = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"];
2780
+ for (const type of types) window.addEventListener(type, take, true);
2781
+ return () => {
2782
+ for (const type of types) window.removeEventListener(type, take, true);
2783
+ };
2784
+ }, [viewer, references]);
2785
+ const value = useMemo(
2786
+ () => editable ? { openDialog, update, labels: l } : null,
2787
+ [editable, openDialog, update, l]
2788
+ );
2789
+ const resolveInternal = (href) => {
2790
+ if (!editor) return null;
2791
+ const name = href.replace(/^#/, "");
2792
+ try {
2793
+ const port = referencesPort(editor);
2794
+ const paragraphs = port.paragraphs();
2795
+ const target = paragraphs.find((p) => p.bookmarks.includes(name));
2796
+ if (!target) return { title: l.brokenCard, body: l.brokenCardBody };
2797
+ const number = paragraphNumber(
2798
+ paragraphs.map((p) => p.id),
2799
+ port.listItems(),
2800
+ port.levelOf,
2801
+ target.id
2802
+ );
2803
+ return {
2804
+ title: number ? l.clause(number) : l.heading,
2805
+ body: passageExcerpt(target.text.replace(//g, ""))
2806
+ };
2807
+ } catch {
2808
+ return null;
2809
+ }
2810
+ };
2811
+ return /* @__PURE__ */ jsxs(CrossReferencesContext.Provider, { value, children: [
2812
+ /* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
2813
+ children,
2814
+ /* @__PURE__ */ jsx(
2815
+ LinkHoverCard,
2816
+ {
2817
+ root: viewer,
2818
+ matches: (href) => href.startsWith(`#${REF_BOOKMARK_PREFIX}`),
2819
+ resolve: resolveInternal,
2820
+ testId: "docx-cross-reference-card"
2821
+ }
2822
+ ),
2823
+ references && /* @__PURE__ */ jsx(
2824
+ LinkHoverCard,
2825
+ {
2826
+ root: viewer,
2827
+ matches: (href) => !!references.targetOf(href),
2828
+ resolve: (href) => {
2829
+ const target = references.targetOf(href);
2830
+ if (!target) return null;
2831
+ const list = passages[target.documentId];
2832
+ if (!list) loadPassages(target.documentId);
2833
+ const passage = Array.isArray(list) ? list.find((p) => p.chunkId === target.chunkId) : void 0;
2834
+ return passage ? {
2835
+ body: passageExcerpt(passage.text),
2836
+ footer: passage.page ? `p. ${passage.page}` : void 0
2837
+ } : null;
2838
+ },
2839
+ testId: "docx-cross-reference-card"
2840
+ }
2841
+ ),
2842
+ dialog && /* @__PURE__ */ jsx(
2843
+ CrossReferenceDialog,
2844
+ {
2845
+ targets: dialog.targets,
2846
+ references,
2847
+ documentId,
2848
+ passages,
2849
+ loadPassages,
2850
+ labels: l,
2851
+ onCancel: () => setDialog(null),
2852
+ onClosed: () => editor?.surface?.focus(),
2853
+ onInsertTarget: (target, mode) => run2(
2854
+ `Inserted cross-reference: ${refResult(target, mode)}`,
2855
+ () => markCrossReferenceTarget(referencesPort(editor), target, mode),
2856
+ (marked, revision) => insertCrossReferenceField(referencesPort(editor, revision), marked, dialog.caret)
2857
+ ),
2858
+ onInsertPassage: (target, text) => run2(
2859
+ `Inserted cross-reference: ${text}`,
2860
+ () => void 0,
2861
+ (_, revision) => insertDocumentReference(
2862
+ referencesPort(editor, revision),
2863
+ { text, url: references.linkFor(target), appName: references.appName },
2864
+ dialog.caret
2865
+ )
2866
+ )
2867
+ }
2868
+ ),
2869
+ /* @__PURE__ */ jsx(Dialog, { open: !!report, onOpenChange: (open) => !open && setReport(null), children: /* @__PURE__ */ jsxs(
2870
+ DialogContent,
2871
+ {
2872
+ className: "max-h-[85dvh] overflow-y-auto",
2873
+ "data-testid": "docx-cross-references-report",
2874
+ children: [
2875
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2876
+ /* @__PURE__ */ jsx(DialogTitle, { children: l.updated }),
2877
+ /* @__PURE__ */ jsx(DialogDescription, { children: report && l.updateSummary(report.refreshed) })
2878
+ ] }),
2879
+ report && /* @__PURE__ */ jsxs("section", { className: "space-y-1 text-sm", children: [
2880
+ /* @__PURE__ */ jsx("h3", { className: "font-medium", children: l.brokenTitle }),
2881
+ report.broken.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
2882
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenHelp }),
2883
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", "data-testid": "docx-cross-references-broken", children: report.broken.map((b) => /* @__PURE__ */ jsxs(
2884
+ "li",
2885
+ {
2886
+ className: "flex items-center justify-between gap-2",
2887
+ children: [
2888
+ /* @__PURE__ */ jsx("span", { className: "text-red-text", children: b.text }),
2889
+ /* @__PURE__ */ jsx(
2890
+ Button,
2891
+ {
2892
+ size: "sm",
2893
+ variant: "ghost",
2894
+ onClick: () => {
2895
+ const point = { paragraphId: b.paragraphId, offset: b.offset };
2896
+ editor?.surface?.setSelection({ anchor: point, head: point });
2897
+ editor?.surface?.revealParagraph(b.paragraphId);
2898
+ setReport(null);
2899
+ },
2900
+ children: l.show
2901
+ }
2902
+ )
2903
+ ]
2904
+ },
2905
+ `${b.paragraphId}:${b.offset}`
2906
+ )) })
2907
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenNone })
2908
+ ] }),
2909
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setReport(null), children: l.close }) })
2910
+ ]
2911
+ }
2912
+ ) })
2913
+ ] });
2914
+ }
2915
+ function CrossReferenceDialog({
2916
+ targets,
2917
+ references,
2918
+ documentId,
2919
+ passages,
2920
+ loadPassages,
2921
+ labels,
2922
+ onCancel,
2923
+ onInsertTarget,
2924
+ onInsertPassage,
2925
+ onClosed
2926
+ }) {
2927
+ const l = { ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels };
2928
+ const [tab, setTab] = useState("this");
2929
+ const [query, setQuery] = useState("");
2930
+ const [chosen, setChosen] = useState(null);
2931
+ const [mode, setMode] = useState("number");
2932
+ const shown = useMemo(() => filterTargets(targets, query), [targets, query]);
2933
+ const effectiveMode = chosen?.number ? mode : "text";
2934
+ const [documents, setDocuments] = useState(null);
2935
+ const [docId, setDocId] = useState(null);
2936
+ const [passageQuery, setPassageQuery] = useState("");
2937
+ const [passage, setPassage] = useState(null);
2938
+ const [text, setText] = useState("");
2939
+ useEffect(() => {
2940
+ if (tab !== "other" || !references || documents) return;
2941
+ references.documents().then((list2) => setDocuments(list2.filter((d) => d.id !== documentId))).catch(() => setDocuments([]));
2942
+ }, [tab, references, documents, documentId]);
2943
+ const chooseDocument = (id) => {
2944
+ setDocId(id);
2945
+ setPassage(null);
2946
+ setText("");
2947
+ loadPassages(id);
2948
+ };
2949
+ const title = documents?.find((d) => d.id === docId)?.title ?? "";
2950
+ const list = docId ? passages[docId] : void 0;
2951
+ const shownPassages = Array.isArray(list) ? filterPassages(list, passageQuery) : [];
2952
+ const canInsert = tab === "this" ? !!chosen : !!(docId && passage && text.trim());
2953
+ const insert = () => {
2954
+ if (tab === "this" && chosen) onInsertTarget(chosen, effectiveMode);
2955
+ if (tab === "other" && docId && passage)
2956
+ onInsertPassage(
2957
+ { documentId: docId, chunkId: passage.chunkId, page: passage.page ?? null },
2958
+ text
2959
+ );
2960
+ };
2961
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onCancel(), children: /* @__PURE__ */ jsxs(
2962
+ DialogContent,
2963
+ {
2964
+ className: "max-h-[85dvh] overflow-y-auto",
2965
+ "data-testid": "docx-cross-reference-dialog",
2966
+ onCloseAutoFocus: (event) => {
2967
+ if (!onClosed) return;
2968
+ event.preventDefault();
2969
+ onClosed();
2970
+ },
2971
+ children: [
2972
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2973
+ /* @__PURE__ */ jsx(DialogTitle, { children: l.title }),
2974
+ /* @__PURE__ */ jsx(DialogDescription, { children: l.description })
2975
+ ] }),
2976
+ /* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), children: [
2977
+ /* @__PURE__ */ jsxs(TabsList, { children: [
2978
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "this", "data-testid": "docx-cross-reference-this", children: l.thisDocument }),
2979
+ references && /* @__PURE__ */ jsx(TabsTrigger, { value: "other", "data-testid": "docx-cross-reference-other", children: l.otherDocument })
2980
+ ] }),
2981
+ /* @__PURE__ */ jsxs(TabsContent, { value: "this", className: "space-y-3", children: [
2982
+ /* @__PURE__ */ jsx(
2983
+ Input,
2984
+ {
2985
+ value: query,
2986
+ placeholder: l.filterTargets,
2987
+ "aria-label": l.filterTargets,
2988
+ onChange: (e) => setQuery(e.target.value),
2989
+ "data-testid": "docx-cross-reference-filter"
2990
+ }
2991
+ ),
2992
+ /* @__PURE__ */ jsxs(
2993
+ "div",
2994
+ {
2995
+ role: "listbox",
2996
+ "aria-label": l.filterTargets,
2997
+ className: "max-h-64 space-y-0.5 overflow-y-auto rounded-md border border-border p-1",
2998
+ children: [
2999
+ shown.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noTargets }),
3000
+ shown.map((t) => /* @__PURE__ */ jsxs(
3001
+ "button",
3002
+ {
3003
+ type: "button",
3004
+ role: "option",
3005
+ "aria-selected": chosen?.paragraphId === t.paragraphId,
3006
+ "data-testid": "docx-cross-reference-target",
3007
+ onClick: () => setChosen(t),
3008
+ onDoubleClick: () => onInsertTarget(t, t.number ? mode : "text"),
3009
+ className: cn(
3010
+ "flex w-full items-baseline gap-2 rounded px-2 py-1 text-left text-sm",
3011
+ chosen?.paragraphId === t.paragraphId ? "bg-accent" : "hover:bg-accent/50"
3012
+ ),
3013
+ style: { paddingLeft: 8 + t.level * 16 },
3014
+ children: [
3015
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: t.marker || l.heading }),
3016
+ /* @__PURE__ */ jsx("span", { className: cn("truncate", t.heading && "font-medium"), children: t.text })
3017
+ ]
3018
+ },
3019
+ t.paragraphId
3020
+ ))
3021
+ ]
3022
+ }
3023
+ ),
3024
+ chosen && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-sm", children: [
3025
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.insertAs }),
3026
+ chosen.number && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3027
+ /* @__PURE__ */ jsx(
3028
+ "input",
3029
+ {
3030
+ type: "radio",
3031
+ name: "cross-reference-mode",
3032
+ checked: effectiveMode === "number",
3033
+ onChange: () => setMode("number")
3034
+ }
3035
+ ),
3036
+ l.number
3037
+ ] }),
3038
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3039
+ /* @__PURE__ */ jsx(
3040
+ "input",
3041
+ {
3042
+ type: "radio",
3043
+ name: "cross-reference-mode",
3044
+ checked: effectiveMode === "text",
3045
+ onChange: () => setMode("text")
3046
+ }
3047
+ ),
3048
+ l.text
3049
+ ] }),
3050
+ /* @__PURE__ */ jsx(
3051
+ "span",
3052
+ {
3053
+ className: "ml-auto rounded bg-muted px-2 py-0.5",
3054
+ "data-testid": "docx-cross-reference-preview",
3055
+ children: refResult(chosen, effectiveMode)
3056
+ }
3057
+ )
3058
+ ] })
3059
+ ] }),
3060
+ references && /* @__PURE__ */ jsxs(TabsContent, { value: "other", className: "space-y-3", children: [
3061
+ references.renderDocumentPicker ? references.renderDocumentPicker(chooseDocument, docId) : /* @__PURE__ */ jsxs(
3062
+ "select",
3063
+ {
3064
+ className: "h-9 w-full rounded-md border border-border bg-background px-2 text-sm",
3065
+ "aria-label": l.chooseDocument,
3066
+ value: docId ?? "",
3067
+ onChange: (e) => e.target.value && chooseDocument(e.target.value),
3068
+ "data-testid": "docx-cross-reference-document",
3069
+ children: [
3070
+ /* @__PURE__ */ jsx("option", { value: "", children: l.chooseDocument }),
3071
+ (documents ?? []).map((d) => /* @__PURE__ */ jsx("option", { value: d.id, children: d.title }, d.id))
3072
+ ]
3073
+ }
3074
+ ),
3075
+ docId && /* @__PURE__ */ jsxs(Fragment, { children: [
3076
+ /* @__PURE__ */ jsx(
3077
+ Input,
3078
+ {
3079
+ value: passageQuery,
3080
+ placeholder: l.filterPassages,
3081
+ "aria-label": l.filterPassages,
3082
+ onChange: (e) => setPassageQuery(e.target.value),
3083
+ "data-testid": "docx-cross-reference-passage-filter"
3084
+ }
3085
+ ),
3086
+ /* @__PURE__ */ jsx(
3087
+ "div",
3088
+ {
3089
+ className: "max-h-64 space-y-1 overflow-y-auto",
3090
+ "data-testid": "docx-cross-reference-passages",
3091
+ children: list === "loading" || list === void 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.loadingPassages }) : shownPassages.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noPassages }) : shownPassages.map((p, i) => /* @__PURE__ */ jsx(
3092
+ FactPassageItem,
3093
+ {
3094
+ index: i,
3095
+ page: p.page,
3096
+ excerpt: passageExcerpt(p.text, 200),
3097
+ isViewing: passage?.chunkId === p.chunkId,
3098
+ onSelect: () => {
3099
+ setPassage(p);
3100
+ setText(referenceText(title, p));
3101
+ },
3102
+ testId: "docx-cross-reference-passage"
3103
+ },
3104
+ p.chunkId
3105
+ ))
3106
+ }
3107
+ ),
3108
+ passage && /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-sm", children: [
3109
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.referenceText }),
3110
+ /* @__PURE__ */ jsx(
3111
+ Input,
3112
+ {
3113
+ value: text,
3114
+ onChange: (e) => setText(e.target.value),
3115
+ "data-testid": "docx-cross-reference-text"
3116
+ }
3117
+ )
3118
+ ] })
3119
+ ] })
3120
+ ] })
3121
+ ] }),
3122
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
3123
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onCancel, children: l.cancel }),
3124
+ /* @__PURE__ */ jsx(Button, { disabled: !canInsert, onClick: insert, "data-testid": "docx-cross-reference-insert", children: l.insertButton })
3125
+ ] })
3126
+ ]
3127
+ }
3128
+ ) });
3129
+ }
2166
3130
  function RibbonMenu({
2167
3131
  label,
2168
3132
  icon,
2169
3133
  children,
2170
3134
  className,
2171
3135
  testId,
2172
- title
3136
+ title,
3137
+ keepMounted = false,
3138
+ popoverClassName
2173
3139
  }) {
2174
3140
  const [open, setOpen] = useState(false);
2175
3141
  const element = useRef(null);
@@ -2203,12 +3169,13 @@ function RibbonMenu({
2203
3169
  children: icon
2204
3170
  }
2205
3171
  ),
2206
- open && /* @__PURE__ */ jsx(
3172
+ (open || keepMounted) && /* @__PURE__ */ jsx(
2207
3173
  "div",
2208
3174
  {
2209
- className: "native-docx-ribbon-popover",
3175
+ className: ["native-docx-ribbon-popover", popoverClassName].filter(Boolean).join(" "),
2210
3176
  role: "group",
2211
3177
  "aria-label": label,
3178
+ hidden: !open,
2212
3179
  onMouseDown: (event) => {
2213
3180
  if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2214
3181
  event.preventDefault();
@@ -2298,7 +3265,7 @@ function GenerateImageButton({ generation }) {
2298
3265
  });
2299
3266
  const editable = useEditorState((snapshot) => snapshot.editable);
2300
3267
  const generating = useSyncExternalStore(subscribe, () => running > 0);
2301
- async function run() {
3268
+ async function run2() {
2302
3269
  if (!editor || !prompt.trim() || busy) return;
2303
3270
  const caret = rememberCaret(editor);
2304
3271
  const controller = new AbortController();
@@ -2344,7 +3311,7 @@ function GenerateImageButton({ generation }) {
2344
3311
  className: "native-docx-form native-docx-generate-image",
2345
3312
  onSubmit: (event) => {
2346
3313
  event.preventDefault();
2347
- void run();
3314
+ void run2();
2348
3315
  },
2349
3316
  children: [
2350
3317
  /* @__PURE__ */ jsxs("label", { children: [
@@ -2360,7 +3327,7 @@ function GenerateImageButton({ generation }) {
2360
3327
  "data-testid": "docx-generate-image-prompt",
2361
3328
  onChange: (event) => setPrompt(event.target.value),
2362
3329
  onKeyDown: (event) => {
2363
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run();
3330
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run2();
2364
3331
  }
2365
3332
  }
2366
3333
  )
@@ -2486,6 +3453,163 @@ var WordDraftStream = class {
2486
3453
  }
2487
3454
  };
2488
3455
 
3456
+ // src/docx/overflowPlan.ts
3457
+ function planOverflow(items, available, moreWidth) {
3458
+ const collapsed = /* @__PURE__ */ new Set();
3459
+ let used = items.reduce((sum, item) => sum + item.width, 0);
3460
+ if (used <= available) return collapsed;
3461
+ used += moreWidth;
3462
+ const order = items.map((item, index) => ({ item, index })).sort((a, b) => a.item.priority - b.item.priority || b.index - a.index);
3463
+ for (const { item } of order) {
3464
+ if (used <= available) break;
3465
+ collapsed.add(item.id);
3466
+ used -= item.width;
3467
+ }
3468
+ return collapsed;
3469
+ }
3470
+ var Overflow = createContext(null);
3471
+ var MORE_WIDTH = 32;
3472
+ var sameSet = (a, b) => a.size === b.size && [...a].every((id) => b.has(id));
3473
+ function RibbonOverflow({ tab, children }) {
3474
+ const row = useRef(null);
3475
+ const items = useRef(/* @__PURE__ */ new Map());
3476
+ const widths = useRef(/* @__PURE__ */ new Map());
3477
+ const [version, setVersion] = useState(0);
3478
+ const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
3479
+ const [slots, setSlots] = useState({});
3480
+ const [menu, setMenu] = useState([]);
3481
+ const register = useCallback((id, item) => {
3482
+ items.current.set(id, item);
3483
+ setVersion((v) => v + 1);
3484
+ return () => {
3485
+ if (items.current.get(id) === item) items.current.delete(id);
3486
+ setVersion((v) => v + 1);
3487
+ };
3488
+ }, []);
3489
+ const plan = useCallback(() => {
3490
+ const element = row.current;
3491
+ if (!element) return;
3492
+ const gap = parseFloat(getComputedStyle(element).columnGap) || 0;
3493
+ const ordered = Array.from(element.querySelectorAll("[data-ribbon-item]")).map((node) => node.dataset.ribbonItem).filter((id) => items.current.has(id));
3494
+ const measured = ordered.map((id) => {
3495
+ const { element: node, priority } = items.current.get(id);
3496
+ if (!node.hidden) {
3497
+ const width = node.getBoundingClientRect().width;
3498
+ widths.current.set(id, width ? width + gap : 0);
3499
+ }
3500
+ return { id, priority, width: widths.current.get(id) ?? 0 };
3501
+ });
3502
+ const next = planOverflow(measured, element.clientWidth, MORE_WIDTH);
3503
+ setCollapsed((current) => sameSet(current, next) ? current : next);
3504
+ const entries = ordered.filter((id) => next.has(id)).map((id) => ({ id, label: items.current.get(id).label }));
3505
+ setMenu(
3506
+ (current) => current.length === entries.length && current.every((entry, i) => entry.id === entries[i].id && entry.label === entries[i].label) ? current : entries
3507
+ );
3508
+ }, []);
3509
+ useLayoutEffect(plan, [plan, version, tab, collapsed]);
3510
+ useLayoutEffect(() => {
3511
+ const element = row.current;
3512
+ if (!element) return;
3513
+ const observer = new ResizeObserver(() => plan());
3514
+ observer.observe(element);
3515
+ for (const item of items.current.values()) observer.observe(item.element);
3516
+ return () => observer.disconnect();
3517
+ }, [plan, version]);
3518
+ const slotRef = useCallback(
3519
+ (id) => (node) => setSlots((current) => {
3520
+ if (node ? current[id] === node : !(id in current)) return current;
3521
+ const next = { ...current };
3522
+ if (node) next[id] = node;
3523
+ else delete next[id];
3524
+ return next;
3525
+ }),
3526
+ []
3527
+ );
3528
+ const context = useMemo(() => ({ collapsed, slots, register }), [collapsed, slots, register]);
3529
+ const shown = menu.filter((entry) => collapsed.has(entry.id));
3530
+ return /* @__PURE__ */ jsx(Overflow.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-row", ref: row, "data-testid": "docx-ribbon-row", children: [
3531
+ children,
3532
+ shown.length > 0 && /* @__PURE__ */ jsx(
3533
+ RibbonMenu,
3534
+ {
3535
+ label: "More",
3536
+ title: "More commands",
3537
+ icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }),
3538
+ testId: "docx-ribbon-more",
3539
+ keepMounted: true,
3540
+ popoverClassName: "native-docx-ribbon-more",
3541
+ children: (close) => /* @__PURE__ */ jsx(MoreRows, { entries: shown, slotRef, close })
3542
+ },
3543
+ tab
3544
+ )
3545
+ ] }) });
3546
+ }
3547
+ function MoreRows({
3548
+ entries,
3549
+ slotRef,
3550
+ close
3551
+ }) {
3552
+ const refs = useRef(/* @__PURE__ */ new Map());
3553
+ const refFor = (id) => {
3554
+ let ref = refs.current.get(id);
3555
+ if (!ref) refs.current.set(id, ref = slotRef(id));
3556
+ return ref;
3557
+ };
3558
+ const list = useRef(null);
3559
+ const latest = useRef(close);
3560
+ latest.current = close;
3561
+ useEffect(() => {
3562
+ const element = list.current;
3563
+ if (!element) return;
3564
+ const onClick = (event) => {
3565
+ const target = event.target;
3566
+ const row = target.closest(".native-docx-ribbon-more-row");
3567
+ if (!row) return;
3568
+ const control = row.querySelector(".native-docx-ribbon-more-control");
3569
+ if (!control.contains(target)) {
3570
+ control.querySelector("button:not([disabled])")?.click();
3571
+ return;
3572
+ }
3573
+ const button = target.closest("button");
3574
+ if (button && !button.disabled && !control.querySelector(".native-docx-ribbon-popover")?.contains(button) && !button.hasAttribute("aria-expanded") && !button.hasAttribute("aria-haspopup"))
3575
+ latest.current();
3576
+ };
3577
+ element.addEventListener("click", onClick, true);
3578
+ return () => element.removeEventListener("click", onClick, true);
3579
+ }, []);
3580
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-ribbon-more-list", ref: list, children: entries.map((entry) => /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-more-row", "data-ribbon-more": entry.id, children: [
3581
+ /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-control", ref: refFor(entry.id) }),
3582
+ /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-label", children: entry.label })
3583
+ ] }, entry.id)) });
3584
+ }
3585
+ function RibbonItem({
3586
+ id,
3587
+ label,
3588
+ priority = 0,
3589
+ children
3590
+ }) {
3591
+ const context = useContext(Overflow);
3592
+ const element = useRef(null);
3593
+ const register = context?.register;
3594
+ useLayoutEffect(() => {
3595
+ if (!register || !element.current) return;
3596
+ return register(id, { label, priority, element: element.current });
3597
+ }, [register, id, label, priority]);
3598
+ if (!context) return /* @__PURE__ */ jsx(Fragment, { children });
3599
+ const collapsed = context.collapsed.has(id);
3600
+ const slot = context.slots[id];
3601
+ return /* @__PURE__ */ jsx(
3602
+ "span",
3603
+ {
3604
+ ref: element,
3605
+ className: "native-docx-ribbon-item",
3606
+ "data-ribbon-item": id,
3607
+ hidden: collapsed || void 0,
3608
+ children: collapsed ? slot ? createPortal(children, slot) : null : children
3609
+ }
3610
+ );
3611
+ }
3612
+
2489
3613
  // src/docx/wordStyles.ts
2490
3614
  var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
2491
3615
  function findStyle(options, name) {
@@ -2671,18 +3795,87 @@ function StyleGallery() {
2671
3795
  }
2672
3796
  );
2673
3797
  }
3798
+ var W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
3799
+ var DEFAULT_PARAGRAPH_FONT = {
3800
+ id: "DefaultParagraphFont",
3801
+ type: "character",
3802
+ xml: '<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont"><w:name w:val="Default Paragraph Font"/><w:uiPriority w:val="1"/><w:semiHidden/><w:unhideWhenUsed/></w:style>'
3803
+ };
3804
+ function stylesOf(kind) {
3805
+ const Kind = kind === "footnote" ? "Footnote" : "Endnote";
3806
+ const text = `${Kind}Text`;
3807
+ return [
3808
+ {
3809
+ id: text,
3810
+ type: "paragraph",
3811
+ xml: `<w:style w:type="paragraph" w:styleId="${text}"><w:name w:val="${kind} text"/><w:basedOn w:val="Normal"/><w:link w:val="${text}Char"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:pPr><w:spacing w:after="0" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
3812
+ },
3813
+ {
3814
+ id: `${text}Char`,
3815
+ type: "character",
3816
+ xml: `<w:style w:type="character" w:customStyle="1" w:styleId="${text}Char"><w:name w:val="${Kind} Text Char"/><w:basedOn w:val="DefaultParagraphFont"/><w:link w:val="${text}"/><w:uiPriority w:val="99"/><w:semiHidden/><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
3817
+ },
3818
+ {
3819
+ id: `${Kind}Reference`,
3820
+ type: "character",
3821
+ xml: `<w:style w:type="character" w:styleId="${Kind}Reference"><w:name w:val="${kind} reference"/><w:basedOn w:val="DefaultParagraphFont"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:rPr><w:vertAlign w:val="superscript"/></w:rPr></w:style>`
3822
+ }
3823
+ ];
3824
+ }
3825
+ function noteTextStyle(styles, kind) {
3826
+ const paragraph = styles.filter((style) => style.type === "paragraph");
3827
+ const id = kind === "footnote" ? "FootnoteText" : "EndnoteText";
3828
+ return paragraph.find((style) => style.name.toLowerCase() === `${kind} text`)?.styleId ?? paragraph.find((style) => style.styleId === id)?.styleId;
3829
+ }
3830
+ function missingNoteStyles(styles, kind) {
3831
+ const has = (type, id) => styles.some((style) => style.type === type && style.styleId === id);
3832
+ const text = noteTextStyle(styles, kind);
3833
+ const missing = stylesOf(kind).filter(
3834
+ (style) => style.type === "paragraph" ? !text : !has("character", style.id)
3835
+ );
3836
+ if (!missing.length) return [];
3837
+ const base = has("character", DEFAULT_PARAGRAPH_FONT.id) ? [] : [DEFAULT_PARAGRAPH_FONT];
3838
+ return [...base, ...missing].map((style) => style.xml);
3839
+ }
3840
+ var CONTENT_TYPES = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/></Types>';
3841
+ function stylesOnlyClipboardHtml(styles) {
3842
+ const fragment = zipSync({
3843
+ "[Content_Types].xml": strToU8(CONTENT_TYPES),
3844
+ "_rels/.rels": strToU8(
3845
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>'
3846
+ ),
3847
+ "word/document.xml": strToU8(
3848
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3849
+ <w:document xmlns:w="${W}"><w:body><w:p/></w:body></w:document>`
3850
+ ),
3851
+ "word/_rels/document.xml.rels": strToU8(
3852
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>'
3853
+ ),
3854
+ "word/styles.xml": strToU8(
3855
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3856
+ <w:styles xmlns:w="${W}">${styles.join("")}</w:styles>`
3857
+ )
3858
+ });
3859
+ return fragmentClipboardHtml(fragment, false);
3860
+ }
3861
+ function ensureNoteStyles(editor, kind, direct = (fn) => fn()) {
3862
+ if (!editor.can({ type: "insertNote", noteKind: kind }).ok) return void 0;
3863
+ const missing = missingNoteStyles(editor.getDocumentStyles(), kind);
3864
+ if (missing.length)
3865
+ direct(() => editor.exec({ type: "paste", text: "", html: stylesOnlyClipboardHtml(missing) }));
3866
+ return noteTextStyle(editor.getDocumentStyles(), kind);
3867
+ }
2674
3868
  function useInsertNote(kind) {
2675
3869
  const command = useEditorCommand(kind === "footnote" ? "insert.footnote" : "insert.endnote");
2676
- const { options } = useParagraphStyle();
2677
3870
  const editor = useDocxEditor();
2678
3871
  const insert = () => {
3872
+ const text = editor && ensureNoteStyles(editor, kind, (fn) => withDirectEditing(editor, fn));
2679
3873
  if (!command.execute()) return false;
2680
- const text = findStyle(options, `${kind} text`);
2681
- if (text && editor)
2682
- withDirectEditing(
2683
- editor,
2684
- () => editor.exec({ type: "setParagraphStyle", styleId: text.styleId })
2685
- );
3874
+ if (editor) {
3875
+ if (text)
3876
+ withDirectEditing(editor, () => editor.exec({ type: "setParagraphStyle", styleId: text }));
3877
+ editor.exec({ type: "insertText", text: " " });
3878
+ }
2686
3879
  return true;
2687
3880
  };
2688
3881
  return { insert, isEnabled: command.isEnabled, disabledReason: command.disabledReason };
@@ -2694,7 +3887,7 @@ function NoteAction({
2694
3887
  icon
2695
3888
  }) {
2696
3889
  const note = useInsertNote(kind);
2697
- return /* @__PURE__ */ jsx(
3890
+ return /* @__PURE__ */ jsx(RibbonItem, { id: `references-${kind}`, label, children: /* @__PURE__ */ jsx(
2698
3891
  DocxEditor.Toolbar.Action,
2699
3892
  {
2700
3893
  label: `${label} (${shortcut})`,
@@ -2704,7 +3897,7 @@ function NoteAction({
2704
3897
  onSelect: () => void note.insert(),
2705
3898
  className: `native-docx-insert-${kind}`
2706
3899
  }
2707
- );
3900
+ ) });
2708
3901
  }
2709
3902
  function NoteButtons() {
2710
3903
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2774,6 +3967,65 @@ function WordShortcuts({ scope }) {
2774
3967
  }, [scope]);
2775
3968
  return null;
2776
3969
  }
3970
+ function CrossReferenceButton() {
3971
+ const references = useCrossReferences();
3972
+ if (!references) return null;
3973
+ return /* @__PURE__ */ jsx(
3974
+ "button",
3975
+ {
3976
+ type: "button",
3977
+ className: "native-docx-ribbon-trigger",
3978
+ "aria-label": references.labels.insert,
3979
+ title: references.labels.insert,
3980
+ "data-testid": "docx-insert-cross-reference",
3981
+ onMouseDown: (event) => event.preventDefault(),
3982
+ onClick: () => references.openDialog(),
3983
+ children: /* @__PURE__ */ jsx(Link2, { size: 16 })
3984
+ }
3985
+ );
3986
+ }
3987
+ function UpdateCrossReferencesButton() {
3988
+ const references = useCrossReferences();
3989
+ if (!references) return null;
3990
+ return /* @__PURE__ */ jsx(
3991
+ "button",
3992
+ {
3993
+ type: "button",
3994
+ className: "native-docx-ribbon-trigger",
3995
+ "aria-label": references.labels.update,
3996
+ title: references.labels.update,
3997
+ "data-testid": "docx-update-cross-references",
3998
+ onMouseDown: (event) => event.preventDefault(),
3999
+ onClick: () => references.update(),
4000
+ children: /* @__PURE__ */ jsx(RefreshCcw, { size: 16 })
4001
+ }
4002
+ );
4003
+ }
4004
+ function DocxContextMenuExtras() {
4005
+ const terms = useDefinedTerms();
4006
+ const references = useCrossReferences();
4007
+ if (!terms && !references) return null;
4008
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4009
+ /* @__PURE__ */ jsx(DocxEditor.ContextMenu.Separator, {}),
4010
+ terms && /* @__PURE__ */ jsx(
4011
+ DocxEditor.ContextMenu.Item,
4012
+ {
4013
+ label: "Insert defined term",
4014
+ icon: /* @__PURE__ */ jsx(BookA, { size: 14 }),
4015
+ shortcut: "@@",
4016
+ onSelect: () => terms.openPicker()
4017
+ }
4018
+ ),
4019
+ references && /* @__PURE__ */ jsx(
4020
+ DocxEditor.ContextMenu.Item,
4021
+ {
4022
+ label: references.labels.insert,
4023
+ icon: /* @__PURE__ */ jsx(Link2, { size: 14 }),
4024
+ onSelect: () => references.openDialog()
4025
+ }
4026
+ )
4027
+ ] });
4028
+ }
2777
4029
 
2778
4030
  // src/docx/layout/presets.ts
2779
4031
  var TWIPS_PER_INCH = 1440;
@@ -2989,10 +4241,10 @@ function LayoutButtons() {
2989
4241
  const [dialog, setDialog] = useState(false);
2990
4242
  const props = { setup: pageSetup, apply, openDialog: () => setDialog(true) };
2991
4243
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2992
- /* @__PURE__ */ jsx(Margins, { ...props }),
2993
- /* @__PURE__ */ jsx(Orientation, { ...props }),
2994
- /* @__PURE__ */ jsx(Size, { ...props }),
2995
- /* @__PURE__ */ jsx(
4244
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-margins", label: "Margins", children: /* @__PURE__ */ jsx(Margins, { ...props }) }),
4245
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-orientation", label: "Orientation", children: /* @__PURE__ */ jsx(Orientation, { ...props }) }),
4246
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-size", label: "Size", children: /* @__PURE__ */ jsx(Size, { ...props }) }),
4247
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-columns", label: "Columns", children: /* @__PURE__ */ jsx(
2996
4248
  DocxEditor.Toolbar.Action,
2997
4249
  {
2998
4250
  label: "Columns",
@@ -3001,9 +4253,9 @@ function LayoutButtons() {
3001
4253
  disabledReason: COLUMNS_UNSUPPORTED,
3002
4254
  className: "native-docx-layout-columns"
3003
4255
  }
3004
- ),
3005
- /* @__PURE__ */ jsx(Breaks, {}),
3006
- /* @__PURE__ */ jsx(
4256
+ ) }),
4257
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-breaks", label: "Breaks", children: /* @__PURE__ */ jsx(Breaks, {}) }),
4258
+ /* @__PURE__ */ jsx(RibbonItem, { id: "layout-page-setup", label: "Page Setup\u2026", children: /* @__PURE__ */ jsx(
3007
4259
  DocxEditor.Toolbar.Action,
3008
4260
  {
3009
4261
  label: "Page Setup\u2026",
@@ -3011,7 +4263,7 @@ function LayoutButtons() {
3011
4263
  onSelect: () => setDialog(true),
3012
4264
  className: "native-docx-layout-page-setup"
3013
4265
  }
3014
- ),
4266
+ ) }),
3015
4267
  /* @__PURE__ */ jsx(DocxEditor.PageSetupDialog, { open: dialog, onClose: () => setDialog(false) })
3016
4268
  ] });
3017
4269
  }
@@ -3038,7 +4290,7 @@ function CommandGallery({
3038
4290
  rows
3039
4291
  }) {
3040
4292
  const bound = useRows(rows);
3041
- return /* @__PURE__ */ jsx(RibbonMenu, { label, icon, testId, children: (close) => /* @__PURE__ */ jsx(
4293
+ return /* @__PURE__ */ jsx(RibbonItem, { id: testId, label, children: /* @__PURE__ */ jsx(RibbonMenu, { label, icon, testId, children: (close) => /* @__PURE__ */ jsx(
3042
4294
  RibbonGallery,
3043
4295
  {
3044
4296
  label,
@@ -3052,7 +4304,7 @@ function CommandGallery({
3052
4304
  })),
3053
4305
  onPick: (id) => void bound.find((row) => row.id === id)?.state.execute()
3054
4306
  }
3055
- ) });
4307
+ ) }) });
3056
4308
  }
3057
4309
  function CommandAction({
3058
4310
  command,
@@ -3061,7 +4313,7 @@ function CommandAction({
3061
4313
  testId
3062
4314
  }) {
3063
4315
  const state = useEditorCommand(command);
3064
- return /* @__PURE__ */ jsx(
4316
+ return /* @__PURE__ */ jsx(RibbonItem, { id: `table-${testId}`, label, children: /* @__PURE__ */ jsx(
3065
4317
  DocxEditor.Toolbar.Action,
3066
4318
  {
3067
4319
  label,
@@ -3072,7 +4324,7 @@ function CommandAction({
3072
4324
  onSelect: () => void state.execute(),
3073
4325
  className: `native-docx-table-${testId}`
3074
4326
  }
3075
- );
4327
+ ) });
3076
4328
  }
3077
4329
  function TableButtons() {
3078
4330
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3172,11 +4424,11 @@ function TableButtons() {
3172
4424
  ]
3173
4425
  }
3174
4426
  ),
3175
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderTarget, {}),
3176
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderStyle, {}),
3177
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderWidth, {}),
3178
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderColor, {}),
3179
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, {})
4427
+ /* @__PURE__ */ jsx(RibbonItem, { id: "table-border-target", label: "Borders", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderTarget, {}) }),
4428
+ /* @__PURE__ */ jsx(RibbonItem, { id: "table-border-style", label: "Border style", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderStyle, {}) }),
4429
+ /* @__PURE__ */ jsx(RibbonItem, { id: "table-border-width", label: "Border width", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderWidth, {}) }),
4430
+ /* @__PURE__ */ jsx(RibbonItem, { id: "table-border-color", label: "Border color", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderColor, {}) }),
4431
+ /* @__PURE__ */ jsx(RibbonItem, { id: "table-cell-fill", label: "Shading", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, {}) })
3180
4432
  ] });
3181
4433
  }
3182
4434
  var CAPTION_LABELS = ["Figure", "Table", "Equation"];
@@ -3208,9 +4460,9 @@ function countSeqFields(root, label, throughId) {
3208
4460
  return count;
3209
4461
  }
3210
4462
  function captionParagraphXml(label, number, text) {
3211
- const run = (value) => `<w:r><w:t xml:space="preserve">${esc(value)}</w:t></w:r>`;
4463
+ const run2 = (value) => `<w:r><w:t xml:space="preserve">${esc(value)}</w:t></w:r>`;
3212
4464
  const rest = text.trim() ? `: ${text.trim()}` : "";
3213
- return `<w:p><w:pPr><w:pStyle w:val="Caption"/></w:pPr>${run(`${label} `)}<w:fldSimple w:instr=" SEQ ${esc(label)} \\* ARABIC "><w:r><w:t>${number}</w:t></w:r></w:fldSimple>${rest ? run(rest) : ""}</w:p>`;
4465
+ return `<w:p><w:pPr><w:pStyle w:val="Caption"/></w:pPr>${run2(`${label} `)}<w:fldSimple w:instr=" SEQ ${esc(label)} \\* ARABIC "><w:r><w:t>${number}</w:t></w:r></w:fldSimple>${rest ? run2(rest) : ""}</w:p>`;
3214
4466
  }
3215
4467
  function base642(bytes) {
3216
4468
  let binary = "";
@@ -3239,7 +4491,7 @@ function TransformAction({
3239
4491
  icon
3240
4492
  }) {
3241
4493
  const state = useEditorCommand(TRANSFORMS[action]);
3242
- return /* @__PURE__ */ jsx(
4494
+ return /* @__PURE__ */ jsx(RibbonItem, { id: `picture-${action}`, label, children: /* @__PURE__ */ jsx(
3243
4495
  DocxEditor.Toolbar.Action,
3244
4496
  {
3245
4497
  label,
@@ -3249,7 +4501,7 @@ function TransformAction({
3249
4501
  onSelect: () => void state.execute(),
3250
4502
  className: `native-docx-picture-${action}`
3251
4503
  }
3252
- );
4504
+ ) });
3253
4505
  }
3254
4506
  function insertCaption(editor, label, text, track) {
3255
4507
  const surface = editor.surface;
@@ -3347,9 +4599,9 @@ function CaptionButton({ defaultLabel = "Figure" }) {
3347
4599
  }
3348
4600
  function PictureButtons() {
3349
4601
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3350
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageWrap, {}),
3351
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageProperties, {}),
3352
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageAltText, {}),
4602
+ /* @__PURE__ */ jsx(RibbonItem, { id: "picture-wrap", label: "Wrap text", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageWrap, {}) }),
4603
+ /* @__PURE__ */ jsx(RibbonItem, { id: "picture-properties", label: "Size and position", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageProperties, {}) }),
4604
+ /* @__PURE__ */ jsx(RibbonItem, { id: "picture-alt-text", label: "Alt text", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageAltText, {}) }),
3353
4605
  /* @__PURE__ */ jsx(TransformAction, { action: "rotateCW", label: "Rotate right 90\xB0", icon: /* @__PURE__ */ jsx(RotateCw, { size: 16 }) }),
3354
4606
  /* @__PURE__ */ jsx(TransformAction, { action: "rotateCCW", label: "Rotate left 90\xB0", icon: /* @__PURE__ */ jsx(RotateCcw, { size: 16 }) }),
3355
4607
  /* @__PURE__ */ jsx(
@@ -3361,7 +4613,7 @@ function PictureButtons() {
3361
4613
  }
3362
4614
  ),
3363
4615
  /* @__PURE__ */ jsx(TransformAction, { action: "flipV", label: "Flip vertical", icon: /* @__PURE__ */ jsx(FlipVertical2, { size: 16 }) }),
3364
- /* @__PURE__ */ jsx(CaptionButton, {})
4616
+ /* @__PURE__ */ jsx(RibbonItem, { id: "picture-caption", label: "Insert caption", children: /* @__PURE__ */ jsx(CaptionButton, {}) })
3365
4617
  ] });
3366
4618
  }
3367
4619
  var LAYOUT_TAB = "Layout";
@@ -3562,7 +4814,7 @@ function covered(room, vector, receipt) {
3562
4814
  }
3563
4815
  return true;
3564
4816
  }
3565
- var EditorBoundary = class extends React3.Component {
4817
+ var EditorBoundary = class extends React4.Component {
3566
4818
  state = {};
3567
4819
  static getDerivedStateFromError(error) {
3568
4820
  return { error };
@@ -3624,46 +4876,53 @@ function DocxRibbon({
3624
4876
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Undo, {}),
3625
4877
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Redo, {}),
3626
4878
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Save, {}),
3627
- tab === "Home" && /* @__PURE__ */ jsxs(Fragment, { children: [
3628
- /* @__PURE__ */ jsx(StyleGallery, {}),
3629
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Bold, {}),
3630
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Italic, {}),
3631
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Underline, {}),
3632
- /* @__PURE__ */ jsxs(RibbonMenu, { label: "Font", icon: /* @__PURE__ */ jsx(Type, { size: 16 }), children: [
3633
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontFamily, {}),
3634
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontSize, {}),
3635
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontColor, {}),
3636
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Highlight, {})
4879
+ /* @__PURE__ */ jsxs(RibbonOverflow, { tab, children: [
4880
+ tab === "Home" && /* @__PURE__ */ jsxs(Fragment, { children: [
4881
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-style", label: "Styles", priority: 80, children: /* @__PURE__ */ jsx(StyleGallery, {}) }),
4882
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-bold", label: "Bold", priority: 100, children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Bold, {}) }),
4883
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-italic", label: "Italic", priority: 95, children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Italic, {}) }),
4884
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-underline", label: "Underline", priority: 90, children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Underline, {}) }),
4885
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-font", label: "Font", priority: 60, children: /* @__PURE__ */ jsxs(RibbonMenu, { label: "Font", icon: /* @__PURE__ */ jsx(Type, { size: 16 }), children: [
4886
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontFamily, {}),
4887
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontSize, {}),
4888
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontColor, {}),
4889
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Highlight, {})
4890
+ ] }) }),
4891
+ /* @__PURE__ */ jsx(RibbonItem, { id: "home-more-formatting", label: "More formatting", priority: 50, children: /* @__PURE__ */ jsxs(RibbonMenu, { label: "More formatting", icon: /* @__PURE__ */ jsx(Pilcrow, { size: 16 }), children: [
4892
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Alignment, {}),
4893
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.BulletList, {}),
4894
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.NumberedList, {}),
4895
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.LineSpacing, {}),
4896
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Indent, {}),
4897
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Outdent, {}),
4898
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Strike, {}),
4899
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ClearFormatting, {})
4900
+ ] }) })
4901
+ ] }),
4902
+ tab === "Insert" && /* @__PURE__ */ jsxs(Fragment, { children: [
4903
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-table", label: "Table", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableInsert, {}) }),
4904
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-picture", label: "Picture", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageInsert, {}) }),
4905
+ imageGeneration && /* @__PURE__ */ jsx(RibbonItem, { id: "insert-generate-image", label: "Generate image", children: /* @__PURE__ */ jsx(GenerateImageButton, { generation: imageGeneration }) }),
4906
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-page-break", label: "Page break", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Button, { slotId: "insert.pageBreak" }) }),
4907
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-link", label: "Link", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {}) }),
4908
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-defined-term", label: "Defined term", children: /* @__PURE__ */ jsx(DefinedTermButtons, {}) }),
4909
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-quick-parts", label: "Quick Parts", children: /* @__PURE__ */ jsx(QuickParts, {}) }),
4910
+ /* @__PURE__ */ jsx(RibbonItem, { id: "insert-cross-reference", label: "Cross-reference", children: /* @__PURE__ */ jsx(CrossReferenceButton, {}) })
3637
4911
  ] }),
3638
- /* @__PURE__ */ jsxs(RibbonMenu, { label: "More formatting", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: [
3639
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Alignment, {}),
3640
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.BulletList, {}),
3641
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.NumberedList, {}),
3642
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.LineSpacing, {}),
3643
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Indent, {}),
3644
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Outdent, {}),
3645
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Strike, {}),
3646
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.ClearFormatting, {})
4912
+ /* @__PURE__ */ jsx(LayoutTabContent, { tab }),
4913
+ tab === "Design" && /* @__PURE__ */ jsx(RibbonItem, { id: "design-house-template", label: "House template", children: /* @__PURE__ */ jsx(HouseTemplatePanel, {}) }),
4914
+ tab === "References" && /* @__PURE__ */ jsxs(Fragment, { children: [
4915
+ /* @__PURE__ */ jsx(NoteButtons, {}),
4916
+ /* @__PURE__ */ jsx(RibbonItem, { id: "references-cross-reference", label: "Cross-reference", children: /* @__PURE__ */ jsx(CrossReferenceButton, {}) }),
4917
+ /* @__PURE__ */ jsx(RibbonItem, { id: "references-update-cross-references", label: "Update cross-references", children: /* @__PURE__ */ jsx(UpdateCrossReferencesButton, {}) })
4918
+ ] }),
4919
+ tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
4920
+ /* @__PURE__ */ jsx(RibbonItem, { id: "review-editing-mode", label: "Editing mode", priority: 10, children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.EditingMode, {}) }),
4921
+ /* @__PURE__ */ jsx(RibbonItem, { id: "review-comments", label: "Comments", children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Comments, {}) }),
4922
+ /* @__PURE__ */ jsx(RibbonItem, { id: "review-options", label: "Review options", children: /* @__PURE__ */ jsx(RibbonMenu, { label: "Review options", icon: /* @__PURE__ */ jsx(UsersRound, { size: 16 }), children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Reviewers, {}) }) })
3647
4923
  ] })
3648
4924
  ] }),
3649
- tab === "Insert" && /* @__PURE__ */ jsxs(Fragment, { children: [
3650
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableInsert, {}),
3651
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageInsert, {}),
3652
- imageGeneration && /* @__PURE__ */ jsx(GenerateImageButton, { generation: imageGeneration }),
3653
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Button, { slotId: "insert.pageBreak" }),
3654
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {}),
3655
- /* @__PURE__ */ jsx(DefinedTermButtons, {}),
3656
- /* @__PURE__ */ jsx(QuickParts, {})
3657
- ] }),
3658
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, { hidden: true }),
3659
- /* @__PURE__ */ jsx(LayoutTabContent, { tab }),
3660
- tab === "Design" && /* @__PURE__ */ jsx(HouseTemplatePanel, {}),
3661
- tab === "References" && /* @__PURE__ */ jsx(NoteButtons, {}),
3662
- tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
3663
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.EditingMode, {}),
3664
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.Comments, {}),
3665
- /* @__PURE__ */ jsx(RibbonMenu, { label: "Review options", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Reviewers, {}) })
3666
- ] })
4925
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, { hidden: true })
3667
4926
  ] }),
3668
4927
  badge && /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-badge", "data-testid": "docx-ribbon-badge", children: badge })
3669
4928
  ] });
@@ -3860,6 +5119,7 @@ function NativeDocxEditor({
3860
5119
  };
3861
5120
  }, [dirty]);
3862
5121
  async function exportDocument() {
5122
+ runBeforeSave(ref.current);
3863
5123
  const data = await ref.current?.save();
3864
5124
  if (!data) return;
3865
5125
  const url = URL.createObjectURL(
@@ -3886,6 +5146,7 @@ function NativeDocxEditor({
3886
5146
  async function saveVersion() {
3887
5147
  if (!ready || saving || stream) return;
3888
5148
  setSaving(true);
5149
+ runBeforeSave(ref.current);
3889
5150
  const atRevision = revision.current;
3890
5151
  try {
3891
5152
  const result = isDraft ? await (async () => {
@@ -4009,7 +5270,7 @@ function NativeDocxEditor({
4009
5270
  load: adapter.definitions,
4010
5271
  editable: !stream && !snapshot && ready.canEdit,
4011
5272
  notify: adapter.notify,
4012
- children: /* @__PURE__ */ jsxs(
5273
+ children: /* @__PURE__ */ jsx(
4013
5274
  HouseTemplateProvider,
4014
5275
  {
4015
5276
  enabled: !stream,
@@ -4017,67 +5278,76 @@ function NativeDocxEditor({
4017
5278
  documentId: isDraft || snapshot ? void 0 : documentId,
4018
5279
  editable: !stream && !snapshot && ready.canEdit,
4019
5280
  notify: adapter.notify,
4020
- children: [
4021
- /* @__PURE__ */ jsx(
4022
- DocxEditor.RevisionMarkup,
4023
- {
4024
- insertions: { mark: "underline", color: "blue" },
4025
- deletions: { mark: "strikethrough", color: "red" },
4026
- movedFrom: { mark: "doubleStrikethrough", color: "green" },
4027
- movedTo: { mark: "doubleUnderline", color: "green" },
4028
- trackMoves: true
4029
- }
4030
- ),
4031
- !stream && /* @__PURE__ */ jsx(
4032
- DocxRibbon,
4033
- {
4034
- onSave: () => void saveVersion(),
4035
- imageGeneration: adapter.imageGeneration && {
4036
- notify: adapter.notify,
4037
- ...adapter.imageGeneration
4038
- },
4039
- badge: (
4040
- // Says the document is shared live, in place of a title row above the ribbon.
4041
- ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
5281
+ children: /* @__PURE__ */ jsxs(
5282
+ CrossReferencesProvider,
5283
+ {
5284
+ editable: !stream && !snapshot && ready.canEdit,
5285
+ references: adapter.references,
5286
+ documentId,
5287
+ notify: adapter.notify,
5288
+ children: [
5289
+ /* @__PURE__ */ jsx(
5290
+ DocxEditor.RevisionMarkup,
5291
+ {
5292
+ insertions: { mark: "underline", color: "blue" },
5293
+ deletions: { mark: "strikethrough", color: "red" },
5294
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
5295
+ movedTo: { mark: "doubleUnderline", color: "green" },
5296
+ trackMoves: true
5297
+ }
5298
+ ),
5299
+ !stream && /* @__PURE__ */ jsx(
5300
+ DocxRibbon,
5301
+ {
5302
+ onSave: () => void saveVersion(),
5303
+ imageGeneration: adapter.imageGeneration && {
5304
+ notify: adapter.notify,
5305
+ ...adapter.imageGeneration
5306
+ },
5307
+ badge: (
5308
+ // Says the document is shared live, in place of a title row above the ribbon.
5309
+ ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
5310
+ )
5311
+ }
5312
+ ),
5313
+ !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
5314
+ !stream && /* @__PURE__ */ jsx(TableTabKeys, { scope: stageRef }),
5315
+ /* @__PURE__ */ jsxs(
5316
+ "div",
5317
+ {
5318
+ className: "native-docx-stage",
5319
+ ref: stageRef,
5320
+ onMouseDownCapture: (event) => {
5321
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
5322
+ event.preventDefault();
5323
+ },
5324
+ children: [
5325
+ /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
5326
+ /* @__PURE__ */ jsx(DocxEditor.Content, {}),
5327
+ /* @__PURE__ */ jsx(
5328
+ DocxMentions,
5329
+ {
5330
+ initialCommentId,
5331
+ mentions: adapter.mentions,
5332
+ notify: adapter.notify,
5333
+ getAgentStatus: adapter.getAgentStatus,
5334
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
5335
+ children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
5336
+ }
5337
+ ),
5338
+ /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
5339
+ /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
5340
+ /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
5341
+ /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
5342
+ /* @__PURE__ */ jsx(DocxEditor.ContextMenu, { children: /* @__PURE__ */ jsx(DocxContextMenuExtras, {}) })
5343
+ ] }),
5344
+ /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
5345
+ ]
5346
+ }
4042
5347
  )
4043
- }
4044
- ),
4045
- !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
4046
- !stream && /* @__PURE__ */ jsx(TableTabKeys, { scope: stageRef }),
4047
- /* @__PURE__ */ jsxs(
4048
- "div",
4049
- {
4050
- className: "native-docx-stage",
4051
- ref: stageRef,
4052
- onMouseDownCapture: (event) => {
4053
- if (event.button === 2 && ref.current?.getSelectionPlacement())
4054
- event.preventDefault();
4055
- },
4056
- children: [
4057
- /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
4058
- /* @__PURE__ */ jsx(DocxEditor.Content, {}),
4059
- /* @__PURE__ */ jsx(
4060
- DocxMentions,
4061
- {
4062
- initialCommentId,
4063
- mentions: adapter.mentions,
4064
- notify: adapter.notify,
4065
- getAgentStatus: adapter.getAgentStatus,
4066
- requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
4067
- children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
4068
- }
4069
- ),
4070
- /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
4071
- /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
4072
- /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
4073
- /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
4074
- /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
4075
- ] }),
4076
- /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
4077
- ]
4078
- }
4079
- )
4080
- ]
5348
+ ]
5349
+ }
5350
+ )
4081
5351
  }
4082
5352
  )
4083
5353
  }
@@ -4176,6 +5446,6 @@ async function loadFonts(baseUrl) {
4176
5446
  };
4177
5447
  }
4178
5448
 
4179
- export { BUILTIN_TEMPLATE_NAME, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxAuthorContext, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, GenerateImageButton, HouseTemplateProvider, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertDefinedTerm, loadDocxFonts, normalizeDocxExport, resolveHouseTemplate, reviewModule, structuralPort, templateFileName, trackedOrNoted, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
4180
- //# sourceMappingURL=chunk-MYALCKDR.js.map
4181
- //# sourceMappingURL=chunk-MYALCKDR.js.map
5449
+ export { BUILTIN_TEMPLATE_NAME, CrossReferenceDialog, CrossReferencesProvider, DEFAULT_CROSS_REFERENCES_LABELS, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxAuthorContext, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, GenerateImageButton, HouseTemplateProvider, LinkHoverCard, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertCrossReference, insertDefinedTerm, insertDocumentReference, listTargets, loadDocxFonts, normalizeDocxExport, paragraphNumber, referenceText, referencesPort, resolveHouseTemplate, reviewModule, shortTitle, structuralPort, templateFileName, trackedOrNoted, useCrossReferences, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
5450
+ //# sourceMappingURL=chunk-W2RTZOK3.js.map
5451
+ //# sourceMappingURL=chunk-W2RTZOK3.js.map