@arbidocs/blocks 0.4.98 → 0.4.100

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 (58) hide show
  1. package/dist/agents/index.cjs +207 -207
  2. package/dist/agents/index.js +2 -2
  3. package/dist/{chunk-LZ5AFAWC.js → chunk-6HZNFOHW.js} +3 -3
  4. package/dist/{chunk-LZ5AFAWC.js.map → chunk-6HZNFOHW.js.map} +1 -1
  5. package/dist/{chunk-FIEAWH4K.js → chunk-72PQ42YI.js} +1040 -88
  6. package/dist/chunk-72PQ42YI.js.map +1 -0
  7. package/dist/{chunk-MIMVRMKC.cjs → chunk-B7P73VHX.cjs} +1040 -83
  8. package/dist/chunk-B7P73VHX.cjs.map +1 -0
  9. package/dist/{chunk-6B2AWE3I.js → chunk-E2QWWLK5.js} +3 -3
  10. package/dist/{chunk-6B2AWE3I.js.map → chunk-E2QWWLK5.js.map} +1 -1
  11. package/dist/{chunk-MUYZP5FB.cjs → chunk-IE55TWIG.cjs} +5 -5
  12. package/dist/{chunk-MUYZP5FB.cjs.map → chunk-IE55TWIG.cjs.map} +1 -1
  13. package/dist/{chunk-5NQIBJPP.js → chunk-NJX774HB.js} +3 -3
  14. package/dist/{chunk-5NQIBJPP.js.map → chunk-NJX774HB.js.map} +1 -1
  15. package/dist/{chunk-DDYXFALG.cjs → chunk-NW3LFDZL.cjs} +3 -3
  16. package/dist/{chunk-DDYXFALG.cjs.map → chunk-NW3LFDZL.cjs.map} +1 -1
  17. package/dist/{chunk-VJO66VVF.cjs → chunk-O24WPHLD.cjs} +5 -5
  18. package/dist/{chunk-VJO66VVF.cjs.map → chunk-O24WPHLD.cjs.map} +1 -1
  19. package/dist/connect/docx.cjs +6 -6
  20. package/dist/connect/docx.js +4 -4
  21. package/dist/connect/index.cjs +3 -3
  22. package/dist/connect/index.js +2 -2
  23. package/dist/docx/NativeDocxEditor.d.ts +6 -1
  24. package/dist/docx/definitions/fragment.d.ts +3 -1
  25. package/dist/docx/definitions/port.d.ts +6 -2
  26. package/dist/docx/index.cjs +51 -31
  27. package/dist/docx/index.d.ts +3 -0
  28. package/dist/docx/index.js +3 -3
  29. package/dist/docx/layout/GenerateImage.d.ts +50 -0
  30. package/dist/docx/layout/LayoutRibbon.d.ts +32 -0
  31. package/dist/docx/layout/LayoutTabs.d.ts +20 -0
  32. package/dist/docx/layout/PictureRibbon.d.ts +23 -0
  33. package/dist/docx/layout/TableKeys.d.ts +25 -0
  34. package/dist/docx/layout/TableRibbon.d.ts +11 -0
  35. package/dist/docx/layout/caption.d.ts +38 -0
  36. package/dist/docx/layout/presets.d.ts +40 -0
  37. package/dist/docx/structural/trackedOrNoted.d.ts +51 -0
  38. package/dist/docx/structural/useTrackedOrNoted.d.ts +5 -0
  39. package/dist/facts/index.cjs +1 -1
  40. package/dist/facts/index.js +1 -1
  41. package/dist/files/index.cjs +1 -1
  42. package/dist/files/index.js +1 -1
  43. package/dist/home/index.cjs +1 -1
  44. package/dist/home/index.js +1 -1
  45. package/dist/index.cjs +253 -253
  46. package/dist/index.js +3 -3
  47. package/dist/shell/index.cjs +1 -1
  48. package/dist/shell/index.js +1 -1
  49. package/dist/studio/index.cjs +1 -1
  50. package/dist/studio/index.js +1 -1
  51. package/dist/workflow-canvas/index.cjs +9 -9
  52. package/dist/workflow-canvas/index.js +2 -2
  53. package/package.json +5 -4
  54. package/src/docx/README.md +22 -0
  55. package/src/docx/layout/layout.css +120 -0
  56. package/src/docx/styles.css +2 -0
  57. package/dist/chunk-FIEAWH4K.js.map +0 -1
  58. package/dist/chunk-MIMVRMKC.cjs.map +0 -1
@@ -12,6 +12,7 @@ var lucideReact = require('lucide-react');
12
12
  var jsxRuntime = require('react/jsx-runtime');
13
13
  var reactDom = require('react-dom');
14
14
  var template = require('@arbidocs/coedit/template');
15
+ var editor = require('@docx-editor.dev/core/editor');
15
16
  var server = require('react-dom/server');
16
17
  var Y = require('yjs');
17
18
  var coedit = require('@arbidocs/coedit');
@@ -1285,8 +1286,6 @@ function DocxMentions({
1285
1286
  }
1286
1287
  ) });
1287
1288
  }
1288
-
1289
- // src/docx/definitions/fragment.ts
1290
1289
  var DEFINED_TERM_STYLE = "DefinedTerm";
1291
1290
  var FRAGMENT_STYLES = chunkAOFATOT3_cjs.stylesXml(
1292
1291
  "",
@@ -1319,12 +1318,13 @@ function base64(bytes) {
1319
1318
  binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1320
1319
  return btoa(binary);
1321
1320
  }
1322
- function fragmentHtml(body, covered2) {
1323
- const bytes = chunkAOFATOT3_cjs.docxPackage({
1321
+ function fragmentHtml(body, covered2, revision) {
1322
+ const untracked = chunkAOFATOT3_cjs.docxPackage({
1324
1323
  document: chunkAOFATOT3_cjs.documentXml(body),
1325
1324
  styles: FRAGMENT_STYLES,
1326
1325
  title: "Defined terms"
1327
1326
  });
1327
+ const bytes = revision ? template.trackInsertion(untracked, revision, covered2) : untracked;
1328
1328
  return `<div data-docx-fragment="${base64(bytes)}" data-docx-fragment-end="${covered2 ? "covered" : "open"}"></div>`;
1329
1329
  }
1330
1330
 
@@ -1581,7 +1581,7 @@ function readParagraph(node, topLevel, bookmarks) {
1581
1581
  links
1582
1582
  };
1583
1583
  }
1584
- function editorPort(editor) {
1584
+ function editorPort(editor, revision) {
1585
1585
  const surface = () => {
1586
1586
  const s = editor.surface;
1587
1587
  if (!s) throw new Error("The document is not open.");
@@ -1600,7 +1600,7 @@ function editorPort(editor) {
1600
1600
  surface().setSelection({ anchor: from, head: to });
1601
1601
  surface().deleteSelection();
1602
1602
  },
1603
- paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2) }).ok,
1603
+ paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2, revision) }).ok,
1604
1604
  splitParagraph: () => surface().splitParagraph()
1605
1605
  };
1606
1606
  }
@@ -1643,6 +1643,70 @@ function holdCaret(editor, timeoutMs = 5e3) {
1643
1643
  const offSelection = editor.on("selectionChange", restore);
1644
1644
  const timer = setTimeout(stop, timeoutMs);
1645
1645
  }
1646
+
1647
+ // src/docx/structural/trackedOrNoted.ts
1648
+ var revisionDate = (at = /* @__PURE__ */ new Date()) => at.toISOString().replace(/\.\d{3}Z$/, "Z");
1649
+ function trackedOrNoted(port, { label, author, insert, inserted = Boolean }) {
1650
+ const mode = port.mode();
1651
+ if (mode === "viewing") throw new Error("This document is read only.");
1652
+ if (mode !== "suggesting") return { result: insert(null), how: "direct" };
1653
+ const revision = { author, date: revisionDate() };
1654
+ const before = port.caret();
1655
+ const tracked = port.trackedBy(revision);
1656
+ port.setMode("editing");
1657
+ let result;
1658
+ try {
1659
+ result = insert(revision);
1660
+ } finally {
1661
+ port.setMode("suggesting");
1662
+ }
1663
+ if (!inserted(result) || port.trackedBy(revision) > tracked)
1664
+ return { result, how: inserted(result) ? "tracked" : "direct" };
1665
+ const after = port.caret();
1666
+ if (before && after && port.comment(before, after, label)) return { result, how: "noted" };
1667
+ return { result, how: "direct" };
1668
+ }
1669
+ var attr2 = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
1670
+ function structuralPort(editor) {
1671
+ return {
1672
+ mode: () => editor.getEditingMode(),
1673
+ setMode: (mode) => void editor.setEditingMode(mode),
1674
+ caret: () => {
1675
+ const head = editor.surface?.state().selection?.head;
1676
+ return head ? { paragraphId: head.paragraphId, offset: head.offset } : null;
1677
+ },
1678
+ // Read from the story's tree: the review pane merges adjacent insertions by one author into
1679
+ // one card, so a card's date need not be this insert's.
1680
+ trackedBy: ({ author, date }) => {
1681
+ const root = editor.surface?.session.part().root;
1682
+ let count = 0;
1683
+ const visit = (node) => {
1684
+ if (node.localName === "ins" && attr2(node, "author") === author && attr2(node, "date") === date)
1685
+ count++;
1686
+ node.children?.forEach(visit);
1687
+ };
1688
+ if (root) visit(root);
1689
+ return count;
1690
+ },
1691
+ comment: (from, to, text) => {
1692
+ const surface = editor.surface;
1693
+ if (!surface) return false;
1694
+ surface.setSelection({ anchor: from, head: to });
1695
+ const ok2 = editor.addComment(text).ok;
1696
+ surface.setSelection({ anchor: to, head: to });
1697
+ return ok2;
1698
+ }
1699
+ };
1700
+ }
1701
+ var DocxAuthorContext = React3.createContext("You");
1702
+ function useTrackedOrNoted() {
1703
+ const editor = react.useDocxEditor();
1704
+ const author = React3.useContext(DocxAuthorContext);
1705
+ return React3.useCallback(
1706
+ (spec) => editor ? trackedOrNoted(structuralPort(editor), { ...spec, author }) : void 0,
1707
+ [editor, author]
1708
+ );
1709
+ }
1646
1710
  var DEFINED_TERM_TRIGGER = "@@";
1647
1711
  var TRIGGER_QUERY = /(?:^|[\s([{"“‘'])@@([^@\n\t]{0,40})$/;
1648
1712
  var MAX_GROUPS = 8;
@@ -1686,6 +1750,7 @@ function DefinedTermsProvider({
1686
1750
  labels
1687
1751
  }) {
1688
1752
  const editor = react.useDocxEditor();
1753
+ const track = useTrackedOrNoted();
1689
1754
  const l = React3.useMemo(() => ({ ...DEFAULT_DEFINED_TERMS_LABELS, ...labels }), [labels]);
1690
1755
  const [sources, setSources] = React3.useState(null);
1691
1756
  const loadedAt = React3.useRef(0);
@@ -1742,35 +1807,46 @@ function DefinedTermsProvider({
1742
1807
  refreshRef.current = refresh;
1743
1808
  const firstHover = React3.useCallback(() => void refreshRef.current(), []);
1744
1809
  const caretRect = () => container().querySelector(".docx-editor-one-surface__caret")?.getBoundingClientRect();
1745
- const run = React3.useCallback(
1810
+ const attempt = React3.useCallback(
1746
1811
  (fn) => {
1747
- if (!editor) return void 0;
1748
1812
  try {
1749
- return withDirectEditing(editor, fn);
1813
+ return fn();
1750
1814
  } catch (error) {
1751
1815
  notify?.(error instanceof Error ? error.message : "Could not change the document.", "error");
1752
1816
  return void 0;
1753
1817
  }
1754
1818
  },
1755
- [editor, notify]
1819
+ [notify]
1820
+ );
1821
+ const run = React3.useCallback(
1822
+ (fn) => editor ? attempt(() => withDirectEditing(editor, fn)) : void 0,
1823
+ [editor, attempt]
1756
1824
  );
1757
1825
  const pick = React3.useCallback(
1758
1826
  (row) => {
1759
1827
  if (!editor || !picker || !row) return;
1760
1828
  const port = editorPort(editor);
1761
- const result = run(() => {
1829
+ const placed = run(() => {
1762
1830
  if (picker.mode === "search" && picker.caret) port.setCaret(picker.caret);
1763
- return insertDefinedTerm(port, row.definition, {
1764
- replace: picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0
1765
- });
1831
+ const caret = port.caret();
1832
+ if (!caret) throw new Error("Place the cursor where the term should go.");
1833
+ const typed = picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0;
1834
+ if (typed) port.deleteRange({ ...caret, offset: Math.max(0, caret.offset - typed) }, caret);
1835
+ return true;
1766
1836
  });
1837
+ const result = placed ? attempt(
1838
+ () => track({
1839
+ label: `Inserted defined term: ${row.definition.term}`,
1840
+ insert: (revision) => insertDefinedTerm(editorPort(editor, revision), row.definition)
1841
+ })?.result
1842
+ ) : void 0;
1767
1843
  setPicker(null);
1768
1844
  editor.surface?.focus();
1769
1845
  if (result) holdCaret(editor);
1770
1846
  if (result?.addedDefinition)
1771
1847
  notify?.(`\u201C${row.definition.term}\u201D added to the Definitions section.`, "success");
1772
1848
  },
1773
- [editor, picker, run, notify]
1849
+ [editor, picker, run, attempt, track, notify]
1774
1850
  );
1775
1851
  const openPicker = React3.useCallback(
1776
1852
  (anchor) => {
@@ -2174,6 +2250,7 @@ function HouseTemplateProvider({
2174
2250
  children
2175
2251
  }) {
2176
2252
  const editor = react.useDocxEditor();
2253
+ const track = useTrackedOrNoted();
2177
2254
  const [template$1, setTemplate] = React3.useState(builtinHouseTemplate);
2178
2255
  const [divergence, setDivergence] = React3.useState(null);
2179
2256
  const [basedOnTemplate, setBasedOnTemplate] = React3.useState(false);
@@ -2214,11 +2291,18 @@ function HouseTemplateProvider({
2214
2291
  const fragment = template.buildingBlockFragment(template$1.bytes, name);
2215
2292
  if (!fragment) return false;
2216
2293
  try {
2217
- const result = withDirectEditing(
2218
- editor,
2219
- () => editor.exec({ type: "paste", text: "", html: template.fragmentClipboardHtml(fragment) })
2220
- );
2221
- if (!result.ok) throw new Error(result.reason ?? "The block could not be inserted here.");
2294
+ const result = track({
2295
+ label: `Inserted Quick Part: ${name}`,
2296
+ insert: (revision) => editor.exec({
2297
+ type: "paste",
2298
+ text: "",
2299
+ html: template.fragmentClipboardHtml(
2300
+ revision ? template.trackInsertion(fragment, revision, true) : fragment
2301
+ )
2302
+ }),
2303
+ inserted: (r) => r.ok
2304
+ })?.result;
2305
+ if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2222
2306
  refresh();
2223
2307
  return true;
2224
2308
  } catch (failure) {
@@ -2229,7 +2313,7 @@ function HouseTemplateProvider({
2229
2313
  return false;
2230
2314
  }
2231
2315
  },
2232
- [editor, editable, template$1, notify, refresh]
2316
+ [editor, editable, template$1, notify, refresh, track]
2233
2317
  );
2234
2318
  const setAsHouseTemplate = React3.useMemo(
2235
2319
  () => host?.setFromDocument && documentId ? async () => {
@@ -2272,6 +2356,241 @@ function HouseTemplateProvider({
2272
2356
  );
2273
2357
  return /* @__PURE__ */ jsxRuntime.jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2274
2358
  }
2359
+ function RibbonMenu({
2360
+ label,
2361
+ icon,
2362
+ children,
2363
+ className,
2364
+ testId,
2365
+ title
2366
+ }) {
2367
+ const [open, setOpen] = React3.useState(false);
2368
+ const element = React3.useRef(null);
2369
+ React3.useEffect(() => {
2370
+ if (!open) return;
2371
+ const dismiss = (event) => {
2372
+ if (!element.current?.contains(event.target)) setOpen(false);
2373
+ };
2374
+ const escape = (event) => {
2375
+ if (event.key === "Escape") setOpen(false);
2376
+ };
2377
+ document.addEventListener("pointerdown", dismiss);
2378
+ document.addEventListener("keydown", escape);
2379
+ return () => {
2380
+ document.removeEventListener("pointerdown", dismiss);
2381
+ document.removeEventListener("keydown", escape);
2382
+ };
2383
+ }, [open]);
2384
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2385
+ /* @__PURE__ */ jsxRuntime.jsx(
2386
+ "button",
2387
+ {
2388
+ type: "button",
2389
+ className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2390
+ "aria-label": label,
2391
+ title: title ?? label,
2392
+ "aria-expanded": open,
2393
+ "data-testid": testId,
2394
+ onMouseDown: (event) => event.preventDefault(),
2395
+ onClick: () => setOpen((value) => !value),
2396
+ children: icon
2397
+ }
2398
+ ),
2399
+ open && /* @__PURE__ */ jsxRuntime.jsx(
2400
+ "div",
2401
+ {
2402
+ className: "native-docx-ribbon-popover",
2403
+ role: "group",
2404
+ "aria-label": label,
2405
+ onMouseDown: (event) => {
2406
+ if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2407
+ event.preventDefault();
2408
+ },
2409
+ children: typeof children === "function" ? children(() => setOpen(false)) : children
2410
+ }
2411
+ )
2412
+ ] });
2413
+ }
2414
+ var titleOf = (prompt) => {
2415
+ const line = prompt.trim().split("\n")[0];
2416
+ return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
2417
+ };
2418
+ function rememberCaret(editor) {
2419
+ const surface = editor.surface;
2420
+ const head = surface?.state().selection?.head;
2421
+ if (!surface || !head) return null;
2422
+ return { paraId: surface.session.paraIdOf(head.paragraphId), ...head };
2423
+ }
2424
+ var mounted = [];
2425
+ var running = 0;
2426
+ var listeners = /* @__PURE__ */ new Set();
2427
+ var setRunning = (delta) => {
2428
+ running += delta;
2429
+ listeners.forEach((listener) => listener());
2430
+ };
2431
+ var subscribe = (listener) => {
2432
+ listeners.add(listener);
2433
+ return () => void listeners.delete(listener);
2434
+ };
2435
+ function insertionTarget(origin, caret, editors = mounted) {
2436
+ if (editors.includes(origin) && origin.surface) return origin;
2437
+ const paraId = caret?.paraId;
2438
+ if (!paraId) return null;
2439
+ for (let i = editors.length - 1; i >= 0; i--) {
2440
+ const session = editors[i].surface?.session;
2441
+ if (session?.nodeIdOf(paraId)) return editors[i];
2442
+ }
2443
+ return null;
2444
+ }
2445
+ function restoreCaret(editor, caret) {
2446
+ const surface = editor.surface;
2447
+ if (!surface || !caret) return;
2448
+ const paragraphId = caret.paraId && surface.session.nodeIdOf(caret.paraId) || caret.paragraphId;
2449
+ if (!surface.session.paragraphIds().includes(paragraphId)) return;
2450
+ const point = { paragraphId, offset: caret.offset };
2451
+ surface.setSelection({ anchor: point, head: point });
2452
+ }
2453
+ async function insertGeneratedImage(editor$1, bytes, prompt, caret) {
2454
+ const image = react.normalizeImageBytes(bytes);
2455
+ if (!image.ok) throw new Error("The generated image is not in a format Word can embed.");
2456
+ let result;
2457
+ for (let attempt = 0; attempt < 3 && !result?.ok; attempt++) {
2458
+ editor$1.focus();
2459
+ restoreCaret(editor$1, caret);
2460
+ result = await editor.executeImageCommand(editor$1, {
2461
+ type: "insertImage",
2462
+ data: image.bytes,
2463
+ mime: image.mime,
2464
+ widthPoints: image.widthPoints,
2465
+ heightPoints: image.heightPoints,
2466
+ description: prompt.trim(),
2467
+ title: titleOf(prompt)
2468
+ });
2469
+ if (!result.ok && result.reason !== "stale drawing selection") break;
2470
+ }
2471
+ if (!result?.ok) throw new Error(result?.reason || "The picture could not be inserted here.");
2472
+ }
2473
+ var AUTO = "";
2474
+ function useGenerateImageTarget() {
2475
+ const editor = react.useDocxEditor();
2476
+ const loaded2 = react.useEditorState((snapshot) => !snapshot.isLoading);
2477
+ React3.useEffect(() => {
2478
+ if (!editor || !loaded2) return;
2479
+ mounted.push(editor);
2480
+ return () => void mounted.splice(mounted.indexOf(editor), 1);
2481
+ }, [editor, loaded2]);
2482
+ }
2483
+ function GenerateImageButton({ generation }) {
2484
+ const editor = react.useDocxEditor();
2485
+ const [prompt, setPrompt] = React3.useState("");
2486
+ const [aspectRatio, setAspectRatio] = React3.useState(AUTO);
2487
+ const [busy, setBusy] = React3.useState(false);
2488
+ const [error, setError] = React3.useState("");
2489
+ const abort = React3.useRef(null);
2490
+ const close = React3.useRef(() => {
2491
+ });
2492
+ const editable = react.useEditorState((snapshot) => snapshot.editable);
2493
+ const generating = React3.useSyncExternalStore(subscribe, () => running > 0);
2494
+ async function run() {
2495
+ if (!editor || !prompt.trim() || busy) return;
2496
+ const caret = rememberCaret(editor);
2497
+ const controller = new AbortController();
2498
+ abort.current = controller;
2499
+ setBusy(true);
2500
+ setRunning(1);
2501
+ setError("");
2502
+ try {
2503
+ const bytes = await generation.generate(prompt.trim(), {
2504
+ aspectRatio: aspectRatio || void 0,
2505
+ signal: controller.signal
2506
+ });
2507
+ if (controller.signal.aborted) return;
2508
+ const target = insertionTarget(editor, caret);
2509
+ if (!target) throw new Error("The document was closed before the generated image was ready.");
2510
+ await insertGeneratedImage(target, bytes, prompt, caret);
2511
+ setPrompt("");
2512
+ close.current();
2513
+ generation.notify?.("Generated image inserted", "success");
2514
+ } catch (failure) {
2515
+ if (controller.signal.aborted) return;
2516
+ const message = failure instanceof Error ? failure.message : String(failure);
2517
+ setError(message);
2518
+ generation.notify?.(message, "error");
2519
+ } finally {
2520
+ abort.current = null;
2521
+ setRunning(-1);
2522
+ setBusy(false);
2523
+ }
2524
+ }
2525
+ if (!editable && !generating) return null;
2526
+ return /* @__PURE__ */ jsxRuntime.jsx(
2527
+ RibbonMenu,
2528
+ {
2529
+ label: generating ? "Generating image\u2026" : "Generate image",
2530
+ icon: generating ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "native-docx-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { size: 16 }),
2531
+ testId: "docx-generate-image",
2532
+ children: (dismiss) => {
2533
+ close.current = dismiss;
2534
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2535
+ "form",
2536
+ {
2537
+ className: "native-docx-form native-docx-generate-image",
2538
+ onSubmit: (event) => {
2539
+ event.preventDefault();
2540
+ void run();
2541
+ },
2542
+ children: [
2543
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
2544
+ "Describe the image",
2545
+ /* @__PURE__ */ jsxRuntime.jsx(
2546
+ "textarea",
2547
+ {
2548
+ autoFocus: true,
2549
+ rows: 3,
2550
+ value: prompt,
2551
+ disabled: busy,
2552
+ placeholder: "A watercolour of a lighthouse at dawn",
2553
+ "data-testid": "docx-generate-image-prompt",
2554
+ onChange: (event) => setPrompt(event.target.value),
2555
+ onKeyDown: (event) => {
2556
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run();
2557
+ }
2558
+ }
2559
+ )
2560
+ ] }),
2561
+ generation.aspectRatios && generation.aspectRatios.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
2562
+ "Aspect ratio",
2563
+ /* @__PURE__ */ jsxRuntime.jsxs(
2564
+ "select",
2565
+ {
2566
+ value: aspectRatio,
2567
+ disabled: busy,
2568
+ onChange: (event) => setAspectRatio(event.target.value),
2569
+ children: [
2570
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: AUTO, children: "Auto" }),
2571
+ generation.aspectRatios.map((ratio) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
2572
+ ]
2573
+ }
2574
+ )
2575
+ ] }),
2576
+ busy && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "data-testid": "docx-generate-image-status", children: "Generating\u2026 the picture is inserted where your cursor was. You can keep writing." }),
2577
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error }),
2578
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-form-actions", children: busy ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => abort.current?.abort(), children: "Cancel" }) : /* @__PURE__ */ jsxRuntime.jsx(
2579
+ "button",
2580
+ {
2581
+ type: "submit",
2582
+ disabled: !prompt.trim(),
2583
+ "data-testid": "docx-generate-image-submit",
2584
+ children: "Generate"
2585
+ }
2586
+ ) })
2587
+ ]
2588
+ }
2589
+ );
2590
+ }
2591
+ }
2592
+ );
2593
+ }
2275
2594
  function wordDraftHtml(markdown) {
2276
2595
  const template = document.createElement("template");
2277
2596
  template.innerHTML = server.renderToStaticMarkup(/* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { children: markdown }));
@@ -2359,61 +2678,6 @@ var WordDraftStream = class {
2359
2678
  }
2360
2679
  }
2361
2680
  };
2362
- function RibbonMenu({
2363
- label,
2364
- icon,
2365
- children,
2366
- className,
2367
- testId,
2368
- title
2369
- }) {
2370
- const [open, setOpen] = React3.useState(false);
2371
- const element = React3.useRef(null);
2372
- React3.useEffect(() => {
2373
- if (!open) return;
2374
- const dismiss = (event) => {
2375
- if (!element.current?.contains(event.target)) setOpen(false);
2376
- };
2377
- const escape = (event) => {
2378
- if (event.key === "Escape") setOpen(false);
2379
- };
2380
- document.addEventListener("pointerdown", dismiss);
2381
- document.addEventListener("keydown", escape);
2382
- return () => {
2383
- document.removeEventListener("pointerdown", dismiss);
2384
- document.removeEventListener("keydown", escape);
2385
- };
2386
- }, [open]);
2387
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2388
- /* @__PURE__ */ jsxRuntime.jsx(
2389
- "button",
2390
- {
2391
- type: "button",
2392
- className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2393
- "aria-label": label,
2394
- title: title ?? label,
2395
- "aria-expanded": open,
2396
- "data-testid": testId,
2397
- onMouseDown: (event) => event.preventDefault(),
2398
- onClick: () => setOpen((value) => !value),
2399
- children: icon
2400
- }
2401
- ),
2402
- open && /* @__PURE__ */ jsxRuntime.jsx(
2403
- "div",
2404
- {
2405
- className: "native-docx-ribbon-popover",
2406
- role: "group",
2407
- "aria-label": label,
2408
- onMouseDown: (event) => {
2409
- if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2410
- event.preventDefault();
2411
- },
2412
- children: typeof children === "function" ? children(() => setOpen(false)) : children
2413
- }
2414
- )
2415
- ] });
2416
- }
2417
2681
 
2418
2682
  // src/docx/wordStyles.ts
2419
2683
  var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
@@ -2703,6 +2967,679 @@ function WordShortcuts({ scope }) {
2703
2967
  }, [scope]);
2704
2968
  return null;
2705
2969
  }
2970
+
2971
+ // src/docx/layout/presets.ts
2972
+ var TWIPS_PER_INCH = 1440;
2973
+ var MARGIN_PRESETS = [
2974
+ { id: "normal", label: "Normal", top: 1440, right: 1440, bottom: 1440, left: 1440 },
2975
+ { id: "narrow", label: "Narrow", top: 720, right: 720, bottom: 720, left: 720 },
2976
+ { id: "moderate", label: "Moderate", top: 1440, right: 1080, bottom: 1440, left: 1080 },
2977
+ { id: "wide", label: "Wide", top: 1440, right: 2880, bottom: 1440, left: 2880 }
2978
+ ];
2979
+ var PAPER_SIZES = [
2980
+ { id: "letter", label: "Letter", width: 12240, height: 15840 },
2981
+ { id: "legal", label: "Legal", width: 12240, height: 20160 },
2982
+ { id: "a4", label: "A4", width: 11906, height: 16838 },
2983
+ { id: "a5", label: "A5", width: 8391, height: 11906 },
2984
+ { id: "executive", label: "Executive", width: 10440, height: 15120 }
2985
+ ];
2986
+ var TOLERANCE = 20;
2987
+ var near = (a, b) => Math.abs(a - b) <= TOLERANCE;
2988
+ function matchMargins(setup) {
2989
+ if (!setup) return null;
2990
+ const m = setup.marginsTwips;
2991
+ return MARGIN_PRESETS.find(
2992
+ (p) => near(p.top, m.top) && near(p.right, m.right) && near(p.bottom, m.bottom) && near(p.left, m.left)
2993
+ )?.id ?? null;
2994
+ }
2995
+ function matchPaper(setup) {
2996
+ if (!setup) return null;
2997
+ const short = Math.min(setup.pageWidthTwips, setup.pageHeightTwips);
2998
+ const long = Math.max(setup.pageWidthTwips, setup.pageHeightTwips);
2999
+ return PAPER_SIZES.find((p) => near(p.width, short) && near(p.height, long))?.id ?? null;
3000
+ }
3001
+ function orientedSize(paper, orientation) {
3002
+ return orientation === "landscape" ? { pageWidthTwips: paper.height, pageHeightTwips: paper.width } : { pageWidthTwips: paper.width, pageHeightTwips: paper.height };
3003
+ }
3004
+ function inches(twips) {
3005
+ return `${Number((twips / TWIPS_PER_INCH).toFixed(2))}"`;
3006
+ }
3007
+ function paperDescription(paper) {
3008
+ return `${inches(paper.width)} \xD7 ${inches(paper.height)}`;
3009
+ }
3010
+ var COLUMNS_UNSUPPORTED = "Text columns are not available in this editor yet. Documents that already use columns display and save them unchanged.";
3011
+ function RibbonGallery({
3012
+ label,
3013
+ choices,
3014
+ current,
3015
+ onPick,
3016
+ close,
3017
+ testId
3018
+ }) {
3019
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-gallery", role: "menu", "aria-label": label, "data-testid": testId, children: choices.map((choice) => /* @__PURE__ */ jsxRuntime.jsxs(
3020
+ "button",
3021
+ {
3022
+ type: "button",
3023
+ role: "menuitemradio",
3024
+ "aria-checked": current === choice.id,
3025
+ disabled: choice.disabled,
3026
+ title: choice.title,
3027
+ "data-choice": choice.id,
3028
+ onClick: () => {
3029
+ onPick(choice.id);
3030
+ close();
3031
+ },
3032
+ children: [
3033
+ choice.icon,
3034
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-gallery-text", children: [
3035
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-gallery-label", children: choice.label }),
3036
+ choice.detail && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-gallery-detail", children: choice.detail })
3037
+ ] })
3038
+ ]
3039
+ },
3040
+ choice.id
3041
+ )) });
3042
+ }
3043
+ var marginDetail = (p) => `Top ${inches(p.top)} \xB7 Bottom ${inches(p.bottom)} \xB7 Left ${inches(p.left)} \xB7 Right ${inches(p.right)}`;
3044
+ function Margins({ setup, apply, openDialog }) {
3045
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Margins", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Scaling, { size: 16 }), testId: "docx-layout-margins", children: (close) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3046
+ /* @__PURE__ */ jsxRuntime.jsx(
3047
+ RibbonGallery,
3048
+ {
3049
+ label: "Margins",
3050
+ current: matchMargins(setup),
3051
+ close,
3052
+ choices: MARGIN_PRESETS.map((p) => ({
3053
+ id: p.id,
3054
+ label: p.label,
3055
+ detail: marginDetail(p)
3056
+ })),
3057
+ onPick: (id) => {
3058
+ const p = MARGIN_PRESETS.find((m) => m.id === id);
3059
+ apply({
3060
+ marginTopTwips: p.top,
3061
+ marginRightTwips: p.right,
3062
+ marginBottomTwips: p.bottom,
3063
+ marginLeftTwips: p.left,
3064
+ scope: "section"
3065
+ });
3066
+ }
3067
+ }
3068
+ ),
3069
+ /* @__PURE__ */ jsxRuntime.jsx(
3070
+ "button",
3071
+ {
3072
+ type: "button",
3073
+ className: "native-docx-gallery-more",
3074
+ onClick: () => {
3075
+ close();
3076
+ openDialog();
3077
+ },
3078
+ children: "Custom margins\u2026"
3079
+ }
3080
+ )
3081
+ ] }) });
3082
+ }
3083
+ function Orientation({ setup, apply }) {
3084
+ return /* @__PURE__ */ jsxRuntime.jsx(
3085
+ RibbonMenu,
3086
+ {
3087
+ label: "Orientation",
3088
+ icon: setup?.orientation === "landscape" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RectangleHorizontal, { size: 16 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RectangleVertical, { size: 16 }),
3089
+ testId: "docx-layout-orientation",
3090
+ children: (close) => /* @__PURE__ */ jsxRuntime.jsx(
3091
+ RibbonGallery,
3092
+ {
3093
+ label: "Orientation",
3094
+ current: setup?.orientation,
3095
+ close,
3096
+ choices: [
3097
+ { id: "portrait", label: "Portrait", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RectangleVertical, { size: 16 }) },
3098
+ { id: "landscape", label: "Landscape", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RectangleHorizontal, { size: 16 }) }
3099
+ ],
3100
+ onPick: (id) => apply({ orientation: id, scope: "section" })
3101
+ }
3102
+ )
3103
+ }
3104
+ );
3105
+ }
3106
+ function Size({ setup, apply, openDialog }) {
3107
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Size", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 16 }), testId: "docx-layout-size", children: (close) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3108
+ /* @__PURE__ */ jsxRuntime.jsx(
3109
+ RibbonGallery,
3110
+ {
3111
+ label: "Size",
3112
+ current: matchPaper(setup),
3113
+ close,
3114
+ choices: PAPER_SIZES.map((p) => ({
3115
+ id: p.id,
3116
+ label: p.label,
3117
+ detail: paperDescription(p)
3118
+ })),
3119
+ onPick: (id) => apply({
3120
+ ...orientedSize(
3121
+ PAPER_SIZES.find((p) => p.id === id),
3122
+ setup?.orientation
3123
+ ),
3124
+ scope: "section"
3125
+ })
3126
+ }
3127
+ ),
3128
+ /* @__PURE__ */ jsxRuntime.jsx(
3129
+ "button",
3130
+ {
3131
+ type: "button",
3132
+ className: "native-docx-gallery-more",
3133
+ onClick: () => {
3134
+ close();
3135
+ openDialog();
3136
+ },
3137
+ children: "More paper sizes\u2026"
3138
+ }
3139
+ )
3140
+ ] }) });
3141
+ }
3142
+ var COLUMN_BREAK = { type: "insertBreak", kind: "column" };
3143
+ function Breaks() {
3144
+ const page = react.useEditorCommand("insert.pageBreak");
3145
+ const nextPage = react.useEditorCommand("insert.sectionBreakNextPage");
3146
+ const continuous = react.useEditorCommand("insert.sectionBreakContinuous");
3147
+ const column = react.useEditorCommand(COLUMN_BREAK);
3148
+ const rows = [
3149
+ { id: "page", label: "Page", detail: "Start the next page here", command: page },
3150
+ { id: "column", label: "Column", detail: "Start the next column here", command: column },
3151
+ {
3152
+ id: "nextPage",
3153
+ label: "Next Page",
3154
+ detail: "Section break: new section on the next page",
3155
+ command: nextPage
3156
+ },
3157
+ {
3158
+ id: "continuous",
3159
+ label: "Continuous",
3160
+ detail: "Section break: new section on the same page",
3161
+ command: continuous
3162
+ }
3163
+ ];
3164
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Breaks", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SeparatorHorizontal, { size: 16 }), testId: "docx-layout-breaks", children: (close) => /* @__PURE__ */ jsxRuntime.jsx(
3165
+ RibbonGallery,
3166
+ {
3167
+ label: "Breaks",
3168
+ close,
3169
+ choices: rows.map((row) => ({
3170
+ id: row.id,
3171
+ label: row.label,
3172
+ detail: row.detail,
3173
+ disabled: !row.command.isEnabled,
3174
+ title: row.command.disabledReason ?? void 0
3175
+ })),
3176
+ onPick: (id) => void rows.find((row) => row.id === id)?.command.execute()
3177
+ }
3178
+ ) });
3179
+ }
3180
+ function LayoutButtons() {
3181
+ const { pageSetup, apply } = react.usePageSetup();
3182
+ const [dialog, setDialog] = React3.useState(false);
3183
+ const props = { setup: pageSetup, apply, openDialog: () => setDialog(true) };
3184
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3185
+ /* @__PURE__ */ jsxRuntime.jsx(Margins, { ...props }),
3186
+ /* @__PURE__ */ jsxRuntime.jsx(Orientation, { ...props }),
3187
+ /* @__PURE__ */ jsxRuntime.jsx(Size, { ...props }),
3188
+ /* @__PURE__ */ jsxRuntime.jsx(
3189
+ react.DocxEditor.Toolbar.Action,
3190
+ {
3191
+ label: "Columns",
3192
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Columns2, { size: 16 }),
3193
+ disabled: true,
3194
+ disabledReason: COLUMNS_UNSUPPORTED,
3195
+ className: "native-docx-layout-columns"
3196
+ }
3197
+ ),
3198
+ /* @__PURE__ */ jsxRuntime.jsx(Breaks, {}),
3199
+ /* @__PURE__ */ jsxRuntime.jsx(
3200
+ react.DocxEditor.Toolbar.Action,
3201
+ {
3202
+ label: "Page Setup\u2026",
3203
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings2, { size: 16 }),
3204
+ onSelect: () => setDialog(true),
3205
+ className: "native-docx-layout-page-setup"
3206
+ }
3207
+ ),
3208
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.PageSetupDialog, { open: dialog, onClose: () => setDialog(false) })
3209
+ ] });
3210
+ }
3211
+ var ROW_ABOVE = { type: "insertRow", where: "above" };
3212
+ var ROW_BELOW = { type: "insertRow", where: "below" };
3213
+ var COLUMN_LEFT = { type: "insertColumn", where: "left" };
3214
+ var COLUMN_RIGHT = { type: "insertColumn", where: "right" };
3215
+ var MERGE = { type: "mergeCells" };
3216
+ var SPLIT = { type: "splitCell", rows: 1, cols: 2 };
3217
+ var HEADER_ROW = { type: "toggleHeaderRow" };
3218
+ var DELETE_ROW = { type: "deleteRow" };
3219
+ var DELETE_COLUMN = { type: "deleteColumn" };
3220
+ var DELETE_TABLE = { type: "deleteTable" };
3221
+ var ALIGN_TOP = { type: "setTableCellVerticalAlignment", alignment: "top" };
3222
+ var ALIGN_CENTER = { type: "setTableCellVerticalAlignment", alignment: "center" };
3223
+ var ALIGN_BOTTOM = { type: "setTableCellVerticalAlignment", alignment: "bottom" };
3224
+ function useRows(rows) {
3225
+ return rows.map((row) => ({ ...row, state: react.useEditorCommand(row.command) }));
3226
+ }
3227
+ function CommandGallery({
3228
+ label,
3229
+ icon,
3230
+ testId,
3231
+ rows
3232
+ }) {
3233
+ const bound = useRows(rows);
3234
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label, icon, testId, children: (close) => /* @__PURE__ */ jsxRuntime.jsx(
3235
+ RibbonGallery,
3236
+ {
3237
+ label,
3238
+ close,
3239
+ choices: bound.map((row) => ({
3240
+ id: row.id,
3241
+ label: row.label,
3242
+ icon: row.icon,
3243
+ disabled: !row.state.isEnabled,
3244
+ title: row.state.disabledReason ?? void 0
3245
+ })),
3246
+ onPick: (id) => void bound.find((row) => row.id === id)?.state.execute()
3247
+ }
3248
+ ) });
3249
+ }
3250
+ function CommandAction({
3251
+ command,
3252
+ label,
3253
+ icon,
3254
+ testId
3255
+ }) {
3256
+ const state = react.useEditorCommand(command);
3257
+ return /* @__PURE__ */ jsxRuntime.jsx(
3258
+ react.DocxEditor.Toolbar.Action,
3259
+ {
3260
+ label,
3261
+ icon,
3262
+ active: state.isActive,
3263
+ disabled: !state.isEnabled,
3264
+ disabledReason: state.disabledReason ?? void 0,
3265
+ onSelect: () => void state.execute(),
3266
+ className: `native-docx-table-${testId}`
3267
+ }
3268
+ );
3269
+ }
3270
+ function TableButtons() {
3271
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3272
+ /* @__PURE__ */ jsxRuntime.jsx(
3273
+ CommandAction,
3274
+ {
3275
+ command: ROW_ABOVE,
3276
+ label: "Insert row above",
3277
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BetweenHorizontalStart, { size: 16 }),
3278
+ testId: "row-above"
3279
+ }
3280
+ ),
3281
+ /* @__PURE__ */ jsxRuntime.jsx(
3282
+ CommandAction,
3283
+ {
3284
+ command: ROW_BELOW,
3285
+ label: "Insert row below",
3286
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BetweenHorizontalEnd, { size: 16 }),
3287
+ testId: "row-below"
3288
+ }
3289
+ ),
3290
+ /* @__PURE__ */ jsxRuntime.jsx(
3291
+ CommandAction,
3292
+ {
3293
+ command: COLUMN_LEFT,
3294
+ label: "Insert column left",
3295
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BetweenVerticalStart, { size: 16 }),
3296
+ testId: "column-left"
3297
+ }
3298
+ ),
3299
+ /* @__PURE__ */ jsxRuntime.jsx(
3300
+ CommandAction,
3301
+ {
3302
+ command: COLUMN_RIGHT,
3303
+ label: "Insert column right",
3304
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BetweenVerticalEnd, { size: 16 }),
3305
+ testId: "column-right"
3306
+ }
3307
+ ),
3308
+ /* @__PURE__ */ jsxRuntime.jsx(
3309
+ CommandGallery,
3310
+ {
3311
+ label: "Delete",
3312
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 16 }),
3313
+ testId: "docx-table-delete",
3314
+ rows: [
3315
+ { id: "row", label: "Delete row", command: DELETE_ROW },
3316
+ { id: "column", label: "Delete column", command: DELETE_COLUMN },
3317
+ { id: "table", label: "Delete table", command: DELETE_TABLE }
3318
+ ]
3319
+ }
3320
+ ),
3321
+ /* @__PURE__ */ jsxRuntime.jsx(
3322
+ CommandAction,
3323
+ {
3324
+ command: MERGE,
3325
+ label: "Merge cells",
3326
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Merge, { size: 16 }),
3327
+ testId: "merge"
3328
+ }
3329
+ ),
3330
+ /* @__PURE__ */ jsxRuntime.jsx(CommandAction, { command: SPLIT, label: "Split cell", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Split, { size: 16 }), testId: "split" }),
3331
+ /* @__PURE__ */ jsxRuntime.jsx(
3332
+ CommandAction,
3333
+ {
3334
+ command: HEADER_ROW,
3335
+ label: "Repeat header row",
3336
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Heading, { size: 16 }),
3337
+ testId: "header-row"
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsxRuntime.jsx(
3341
+ CommandGallery,
3342
+ {
3343
+ label: "Cell alignment",
3344
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignVerticalJustifyCenter, { size: 16 }),
3345
+ testId: "docx-table-align",
3346
+ rows: [
3347
+ {
3348
+ id: "top",
3349
+ label: "Top",
3350
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignVerticalJustifyStart, { size: 16 }),
3351
+ command: ALIGN_TOP
3352
+ },
3353
+ {
3354
+ id: "center",
3355
+ label: "Center",
3356
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignVerticalJustifyCenter, { size: 16 }),
3357
+ command: ALIGN_CENTER
3358
+ },
3359
+ {
3360
+ id: "bottom",
3361
+ label: "Bottom",
3362
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignVerticalJustifyEnd, { size: 16 }),
3363
+ command: ALIGN_BOTTOM
3364
+ }
3365
+ ]
3366
+ }
3367
+ ),
3368
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableBorderTarget, {}),
3369
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableBorderStyle, {}),
3370
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableBorderWidth, {}),
3371
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableBorderColor, {}),
3372
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableCellFill, {})
3373
+ ] });
3374
+ }
3375
+ var CAPTION_LABELS = ["Figure", "Table", "Equation"];
3376
+ var CAPTION_STYLES = chunkAOFATOT3_cjs.stylesXml(
3377
+ "",
3378
+ '<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style><w:style w:type="paragraph" w:styleId="Caption"><w:name w:val="caption"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:uiPriority w:val="35"/><w:unhideWhenUsed/><w:qFormat/><w:pPr><w:spacing w:after="200" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:i/><w:iCs/><w:color w:val="44546A"/><w:sz w:val="18"/><w:szCs w:val="18"/></w:rPr></w:style>'
3379
+ );
3380
+ function seqIdentifier(instruction) {
3381
+ const match = /^\s*SEQ\s+("[^"]+"|\S+)/i.exec(instruction);
3382
+ return match ? match[1].replace(/"/g, "") : null;
3383
+ }
3384
+ function countSeqFields(root, label, throughId) {
3385
+ let count = 0;
3386
+ let done = false;
3387
+ const target = label.toLowerCase();
3388
+ const visit = (node) => {
3389
+ if (done) return;
3390
+ if (node.localName === "fldSimple") {
3391
+ const instr = node.attributes?.find((a) => a.localName === "instr")?.value ?? "";
3392
+ if (seqIdentifier(instr)?.toLowerCase() === target) count++;
3393
+ } else if (node.localName === "instrText") {
3394
+ const text = (node.children ?? []).map((c) => c.value ?? "").join("");
3395
+ if (seqIdentifier(text)?.toLowerCase() === target) count++;
3396
+ }
3397
+ node.children?.forEach(visit);
3398
+ if (throughId && node.id === throughId) done = true;
3399
+ };
3400
+ visit(root);
3401
+ return count;
3402
+ }
3403
+ function captionParagraphXml(label, number, text) {
3404
+ const run = (value) => `<w:r><w:t xml:space="preserve">${chunkAOFATOT3_cjs.esc(value)}</w:t></w:r>`;
3405
+ const rest = text.trim() ? `: ${text.trim()}` : "";
3406
+ return `<w:p><w:pPr><w:pStyle w:val="Caption"/></w:pPr>${run(`${label} `)}<w:fldSimple w:instr=" SEQ ${chunkAOFATOT3_cjs.esc(label)} \\* ARABIC "><w:r><w:t>${number}</w:t></w:r></w:fldSimple>${rest ? run(rest) : ""}</w:p>`;
3407
+ }
3408
+ function base642(bytes) {
3409
+ let binary = "";
3410
+ for (let i = 0; i < bytes.length; i += 32768)
3411
+ binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
3412
+ return btoa(binary);
3413
+ }
3414
+ function captionFragmentHtml(label, number, text, revision) {
3415
+ const untracked = chunkAOFATOT3_cjs.docxPackage({
3416
+ document: chunkAOFATOT3_cjs.documentXml(captionParagraphXml(label, number, text)),
3417
+ styles: CAPTION_STYLES,
3418
+ title: "Caption"
3419
+ });
3420
+ const bytes = revision ? template.trackInsertion(untracked, revision, true) : untracked;
3421
+ return `<div data-docx-fragment="${base642(bytes)}" data-docx-fragment-end="covered"></div>`;
3422
+ }
3423
+ var TRANSFORMS = Object.fromEntries(
3424
+ ["rotateCW", "rotateCCW", "flipH", "flipV"].map((action) => [
3425
+ action,
3426
+ { type: "transformImage", action }
3427
+ ])
3428
+ );
3429
+ function TransformAction({
3430
+ action,
3431
+ label,
3432
+ icon
3433
+ }) {
3434
+ const state = react.useEditorCommand(TRANSFORMS[action]);
3435
+ return /* @__PURE__ */ jsxRuntime.jsx(
3436
+ react.DocxEditor.Toolbar.Action,
3437
+ {
3438
+ label,
3439
+ icon,
3440
+ disabled: !state.isEnabled,
3441
+ disabledReason: state.disabledReason ?? void 0,
3442
+ onSelect: () => void state.execute(),
3443
+ className: `native-docx-picture-${action}`
3444
+ }
3445
+ );
3446
+ }
3447
+ function insertCaption(editor, label, text, track) {
3448
+ const surface = editor.surface;
3449
+ const selection = surface?.state().selection;
3450
+ if (!surface || !selection) return null;
3451
+ const { paragraphId } = selection.head;
3452
+ const number = countSeqFields(
3453
+ surface.session.part().root,
3454
+ label,
3455
+ paragraphId
3456
+ ) + 1;
3457
+ const ids = surface.session.paragraphIds();
3458
+ const next = ids[ids.indexOf(paragraphId) + 1];
3459
+ const caption = `${label} ${number}${text.trim() ? `: ${text.trim()}` : ""}`;
3460
+ const outcome = track({
3461
+ label: `Inserted caption: ${caption}`,
3462
+ inserted: (inserted) => inserted !== null,
3463
+ insert: (revision) => {
3464
+ if (next) {
3465
+ surface.setSelection({
3466
+ anchor: { paragraphId: next, offset: 0 },
3467
+ head: { paragraphId: next, offset: 0 }
3468
+ });
3469
+ } else {
3470
+ editor.exec({ type: "selectAll" });
3471
+ const end = surface.state().selection?.head;
3472
+ if (!end) return null;
3473
+ surface.setSelection({ anchor: end, head: end });
3474
+ surface.splitParagraph();
3475
+ }
3476
+ const ok2 = editor.exec({
3477
+ type: "paste",
3478
+ text: caption,
3479
+ html: captionFragmentHtml(label, number, text, revision)
3480
+ }).ok;
3481
+ return ok2 ? number : null;
3482
+ }
3483
+ });
3484
+ return outcome?.result ?? null;
3485
+ }
3486
+ function CaptionButton({ defaultLabel = "Figure" }) {
3487
+ const editor = react.useDocxEditor();
3488
+ const track = useTrackedOrNoted();
3489
+ const [label, setLabel] = React3.useState(defaultLabel);
3490
+ const [text, setText] = React3.useState("");
3491
+ const [error, setError] = React3.useState("");
3492
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Insert caption", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Captions, { size: 16 }), testId: "docx-insert-caption", children: (close) => /* @__PURE__ */ jsxRuntime.jsxs(
3493
+ "form",
3494
+ {
3495
+ className: "native-docx-form",
3496
+ onSubmit: (event) => {
3497
+ event.preventDefault();
3498
+ if (!editor) return;
3499
+ try {
3500
+ if (insertCaption(editor, label, text, track) === null)
3501
+ return setError("The caption could not be inserted here.");
3502
+ setText("");
3503
+ setError("");
3504
+ close();
3505
+ editor.focus();
3506
+ } catch (failure) {
3507
+ setError(failure instanceof Error ? failure.message : String(failure));
3508
+ }
3509
+ },
3510
+ children: [
3511
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
3512
+ "Label",
3513
+ /* @__PURE__ */ jsxRuntime.jsx(
3514
+ "select",
3515
+ {
3516
+ value: label,
3517
+ onChange: (event) => setLabel(event.target.value),
3518
+ children: CAPTION_LABELS.map((value) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: value }, value))
3519
+ }
3520
+ )
3521
+ ] }),
3522
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
3523
+ "Caption",
3524
+ /* @__PURE__ */ jsxRuntime.jsx(
3525
+ "input",
3526
+ {
3527
+ autoFocus: true,
3528
+ value: text,
3529
+ placeholder: "Optional text after the number",
3530
+ "data-testid": "docx-caption-text",
3531
+ onChange: (event) => setText(event.target.value)
3532
+ }
3533
+ )
3534
+ ] }),
3535
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error }),
3536
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", "data-testid": "docx-caption-insert", children: "Insert" })
3537
+ ]
3538
+ }
3539
+ ) });
3540
+ }
3541
+ function PictureButtons() {
3542
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3543
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.ImageWrap, {}),
3544
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.ImageProperties, {}),
3545
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.ImageAltText, {}),
3546
+ /* @__PURE__ */ jsxRuntime.jsx(TransformAction, { action: "rotateCW", label: "Rotate right 90\xB0", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCw, { size: 16 }) }),
3547
+ /* @__PURE__ */ jsxRuntime.jsx(TransformAction, { action: "rotateCCW", label: "Rotate left 90\xB0", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 16 }) }),
3548
+ /* @__PURE__ */ jsxRuntime.jsx(
3549
+ TransformAction,
3550
+ {
3551
+ action: "flipH",
3552
+ label: "Flip horizontal",
3553
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlipHorizontal2, { size: 16 })
3554
+ }
3555
+ ),
3556
+ /* @__PURE__ */ jsxRuntime.jsx(TransformAction, { action: "flipV", label: "Flip vertical", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlipVertical2, { size: 16 }) }),
3557
+ /* @__PURE__ */ jsxRuntime.jsx(CaptionButton, {})
3558
+ ] });
3559
+ }
3560
+ var LAYOUT_TAB = "Layout";
3561
+ var TABLE_TAB = "Table";
3562
+ var PICTURE_TAB = "Picture";
3563
+ function useContextualTabs(tab) {
3564
+ const inTable = react.useEditorState((snapshot) => snapshot.table !== null);
3565
+ const onPicture = react.useEditorState((snapshot) => snapshot.image !== null);
3566
+ const tabs = [...inTable ? [TABLE_TAB] : [], ...onPicture ? [PICTURE_TAB] : []];
3567
+ const contextual = tab === TABLE_TAB || tab === PICTURE_TAB;
3568
+ return { tabs, shown: contextual && !tabs.includes(tab) ? "Home" : tab };
3569
+ }
3570
+ function LayoutTabContent({ tab }) {
3571
+ if (tab === LAYOUT_TAB) return /* @__PURE__ */ jsxRuntime.jsx(LayoutButtons, {});
3572
+ if (tab === TABLE_TAB) return /* @__PURE__ */ jsxRuntime.jsx(TableButtons, {});
3573
+ if (tab === PICTURE_TAB) return /* @__PURE__ */ jsxRuntime.jsx(PictureButtons, {});
3574
+ return null;
3575
+ }
3576
+ var contains = (node, id) => node.id === id || (node.children ?? []).some((child) => contains(child, id));
3577
+ function firstParagraph(node) {
3578
+ for (const child of node.children ?? []) {
3579
+ if (child.kind === "paragraph" && child.id) return child.id;
3580
+ if (child.localName === "tbl") continue;
3581
+ const nested = firstParagraph(child);
3582
+ if (nested) return nested;
3583
+ }
3584
+ return null;
3585
+ }
3586
+ function tableCells(root, paragraphId) {
3587
+ let table = null;
3588
+ const find = (node) => {
3589
+ if (node.localName === "tbl" && contains(node, paragraphId)) table = node;
3590
+ for (const child of node.children ?? []) if (contains(child, paragraphId)) find(child);
3591
+ };
3592
+ find(root);
3593
+ if (!table) return null;
3594
+ const cells = [];
3595
+ let index = -1;
3596
+ const rows = (node) => (node.children ?? []).flatMap(
3597
+ (child) => child.localName === "tr" ? [child] : child.localName === "tbl" ? [] : rows(child)
3598
+ );
3599
+ for (const row of rows(table))
3600
+ for (const cell of (row.children ?? []).filter((child) => child.localName === "tc")) {
3601
+ const first = firstParagraph(cell);
3602
+ if (!first) continue;
3603
+ if (contains(cell, paragraphId)) index = cells.length;
3604
+ cells.push(first);
3605
+ }
3606
+ return index < 0 ? null : { cells, index };
3607
+ }
3608
+ function TableTabKeys({ scope }) {
3609
+ const editor = react.useDocxEditor();
3610
+ const latest = React3.useRef(editor);
3611
+ latest.current = editor;
3612
+ React3.useEffect(() => {
3613
+ const element = scope.current;
3614
+ if (!element) return;
3615
+ const onKeyDown = (event) => {
3616
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return;
3617
+ const editor2 = latest.current;
3618
+ const surface = editor2?.surface;
3619
+ const head = surface?.state().selection?.head;
3620
+ if (!editor2 || !surface || !head) return;
3621
+ const root = () => surface.session.part().root;
3622
+ const table = tableCells(root(), head.paragraphId);
3623
+ if (!table) return;
3624
+ event.preventDefault();
3625
+ event.stopPropagation();
3626
+ let target = table.cells[table.index + (event.shiftKey ? -1 : 1)];
3627
+ if (!target && !event.shiftKey && editor2.exec({ type: "insertRow", where: "below" }).ok) {
3628
+ const grown = tableCells(
3629
+ root(),
3630
+ surface.state().selection?.head.paragraphId ?? head.paragraphId
3631
+ );
3632
+ target = grown?.cells[table.cells.length] ?? "";
3633
+ }
3634
+ if (!target) return;
3635
+ const point = { paragraphId: target, offset: 0 };
3636
+ surface.setSelection({ anchor: point, head: point });
3637
+ };
3638
+ element.addEventListener("keydown", onKeyDown, true);
3639
+ return () => element.removeEventListener("keydown", onKeyDown, true);
3640
+ }, [scope]);
3641
+ return null;
3642
+ }
2706
3643
  function QuickParts() {
2707
3644
  const house = useHouseTemplate();
2708
3645
  if (!house || house.blocks.length === 0) return null;
@@ -2873,17 +3810,23 @@ function DefinedTermButtons() {
2873
3810
  }
2874
3811
  function DocxRibbon({
2875
3812
  onSave,
2876
- badge
3813
+ badge,
3814
+ imageGeneration
2877
3815
  }) {
2878
- const [tab, setTab] = React3.useState("Home");
3816
+ const [selected, setTab] = React3.useState("Home");
3817
+ const contextual = useContextualTabs(selected);
3818
+ useGenerateImageTarget();
3819
+ const tab = contextual.shown;
2879
3820
  const t = react.useChromeTranslate();
2880
3821
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-ribbon", children: [
2881
3822
  /* @__PURE__ */ jsxRuntime.jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
2882
3823
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Home" }),
2883
3824
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Insert" }),
2884
3825
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Design" }),
3826
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: LAYOUT_TAB }),
2885
3827
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "References" }),
2886
- /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Review" })
3828
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Review" }),
3829
+ contextual.tabs.map((name) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: name }, name))
2887
3830
  ] }),
2888
3831
  /* @__PURE__ */ jsxRuntime.jsxs(react.DocxEditor.Toolbar, { t, preset: false, onSave, children: [
2889
3832
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Undo, {}),
@@ -2914,10 +3857,14 @@ function DocxRibbon({
2914
3857
  tab === "Insert" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2915
3858
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableInsert, {}),
2916
3859
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.ImageInsert, {}),
3860
+ imageGeneration && /* @__PURE__ */ jsxRuntime.jsx(GenerateImageButton, { generation: imageGeneration }),
3861
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Button, { slotId: "insert.pageBreak" }),
2917
3862
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Link, {}),
2918
3863
  /* @__PURE__ */ jsxRuntime.jsx(DefinedTermButtons, {}),
2919
3864
  /* @__PURE__ */ jsxRuntime.jsx(QuickParts, {})
2920
3865
  ] }),
3866
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableCellFill, { hidden: true }),
3867
+ /* @__PURE__ */ jsxRuntime.jsx(LayoutTabContent, { tab }),
2921
3868
  tab === "Design" && /* @__PURE__ */ jsxRuntime.jsx(HouseTemplatePanel, {}),
2922
3869
  tab === "References" && /* @__PURE__ */ jsxRuntime.jsx(NoteButtons, {}),
2923
3870
  tab === "Review" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -3264,7 +4211,7 @@ function NativeDocxEditor({
3264
4211
  if (change.source !== "load") changed();
3265
4212
  },
3266
4213
  onFontError: (e) => setError(e.message),
3267
- children: /* @__PURE__ */ jsxRuntime.jsx(
4214
+ children: /* @__PURE__ */ jsxRuntime.jsx(DocxAuthorContext.Provider, { value: ready.author, children: /* @__PURE__ */ jsxRuntime.jsx(
3268
4215
  DefinedTermsProvider,
3269
4216
  {
3270
4217
  load: adapter.definitions,
@@ -3293,6 +4240,10 @@ function NativeDocxEditor({
3293
4240
  DocxRibbon,
3294
4241
  {
3295
4242
  onSave: () => void saveVersion(),
4243
+ imageGeneration: adapter.imageGeneration && {
4244
+ notify: adapter.notify,
4245
+ ...adapter.imageGeneration
4246
+ },
3296
4247
  badge: (
3297
4248
  // Says the document is shared live, in place of a title row above the ribbon.
3298
4249
  ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
@@ -3300,6 +4251,7 @@ function NativeDocxEditor({
3300
4251
  }
3301
4252
  ),
3302
4253
  !stream && /* @__PURE__ */ jsxRuntime.jsx(WordShortcuts, { scope: stageRef }),
4254
+ !stream && /* @__PURE__ */ jsxRuntime.jsx(TableTabKeys, { scope: stageRef }),
3303
4255
  /* @__PURE__ */ jsxRuntime.jsxs(
3304
4256
  "div",
3305
4257
  {
@@ -3337,7 +4289,7 @@ function NativeDocxEditor({
3337
4289
  }
3338
4290
  )
3339
4291
  }
3340
- )
4292
+ ) })
3341
4293
  }
3342
4294
  ) }, retry) }),
3343
4295
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-actions", children: [
@@ -3436,11 +4388,13 @@ exports.BUILTIN_TEMPLATE_NAME = BUILTIN_TEMPLATE_NAME;
3436
4388
  exports.DEFAULT_DEFINED_TERMS_LABELS = DEFAULT_DEFINED_TERMS_LABELS;
3437
4389
  exports.DEFINED_TERM_TRIGGER = DEFINED_TERM_TRIGGER;
3438
4390
  exports.DefinedTermsProvider = DefinedTermsProvider;
4391
+ exports.DocxAuthorContext = DocxAuthorContext;
3439
4392
  exports.DocxCommentActivity = DocxCommentActivity;
3440
4393
  exports.DocxEditorReview = DocxEditorReview;
3441
4394
  exports.DocxMentions = DocxMentions;
3442
4395
  exports.DocxParticipants = DocxParticipants;
3443
4396
  exports.DocxRibbon = DocxRibbon;
4397
+ exports.GenerateImageButton = GenerateImageButton;
3444
4398
  exports.HouseTemplateProvider = HouseTemplateProvider;
3445
4399
  exports.NativeDocxEditor = NativeDocxEditor;
3446
4400
  exports.builtinHouseTemplate = builtinHouseTemplate;
@@ -3452,12 +4406,15 @@ exports.loadDocxFonts = loadDocxFonts;
3452
4406
  exports.normalizeDocxExport = normalizeDocxExport;
3453
4407
  exports.resolveHouseTemplate = resolveHouseTemplate;
3454
4408
  exports.reviewModule = reviewModule;
4409
+ exports.structuralPort = structuralPort;
3455
4410
  exports.templateFileName = templateFileName;
4411
+ exports.trackedOrNoted = trackedOrNoted;
3456
4412
  exports.updateDefinitions = updateDefinitions;
3457
4413
  exports.useDefinedTerms = useDefinedTerms;
3458
4414
  exports.useHouseTemplate = useHouseTemplate;
3459
4415
  exports.useReview = useReview;
3460
4416
  exports.useReviewItem = useReviewItem;
4417
+ exports.useTrackedOrNoted = useTrackedOrNoted;
3461
4418
  exports.withDirectEditing = withDirectEditing;
3462
- //# sourceMappingURL=chunk-MIMVRMKC.cjs.map
3463
- //# sourceMappingURL=chunk-MIMVRMKC.cjs.map
4419
+ //# sourceMappingURL=chunk-B7P73VHX.cjs.map
4420
+ //# sourceMappingURL=chunk-B7P73VHX.cjs.map