@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
@@ -3,13 +3,14 @@ import { stylesXml, definitionBookmark, DEFINITION_BOOKMARK_PREFIX, compareTerms
3
3
  import { filterMentionCandidates, MentionMenu, findMentionQuery, listOptionId, extractMentions, useListKeyNav } from './chunk-AA3PT6KR.js';
4
4
  import { unzipSync, strFromU8, strToU8, zipSync } from 'fflate';
5
5
  import { collectReviewItems, revisionItemsOf } from '@docx-editor.dev/core/store';
6
- import React3, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
7
- import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, useEditorCommand } from '@docx-editor.dev/react';
6
+ import React3, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
7
+ import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useEditorState, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, normalizeImageBytes, usePageSetup, useEditorCommand } from '@docx-editor.dev/react';
8
8
  import { AiMarkdown, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Button, DialogFooter, cn, splitMarkdownBlocks } from '@arbidocs/react/ui';
9
- import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Type, Ellipsis, RefreshCw, WifiOff, Download, Save, BookA, ListRestart, Blocks, Superscript, NotebookText } from 'lucide-react';
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, ListRestart, 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';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
11
  import { createPortal } from 'react-dom';
12
- import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, newFromTemplate } from '@arbidocs/coedit/template';
12
+ import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, trackInsertion, newFromTemplate } from '@arbidocs/coedit/template';
13
+ import { executeImageCommand } from '@docx-editor.dev/core/editor';
13
14
  import { renderToStaticMarkup } from 'react-dom/server';
14
15
  import * as Y from 'yjs';
15
16
  import { DOCUMENT_COLLABORATION_VERSIONS, collaborationModule } from '@arbidocs/coedit';
@@ -1260,8 +1261,6 @@ function DocxMentions({
1260
1261
  }
1261
1262
  ) });
1262
1263
  }
1263
-
1264
- // src/docx/definitions/fragment.ts
1265
1264
  var DEFINED_TERM_STYLE = "DefinedTerm";
1266
1265
  var FRAGMENT_STYLES = stylesXml(
1267
1266
  "",
@@ -1294,12 +1293,13 @@ function base64(bytes) {
1294
1293
  binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1295
1294
  return btoa(binary);
1296
1295
  }
1297
- function fragmentHtml(body, covered2) {
1298
- const bytes = docxPackage({
1296
+ function fragmentHtml(body, covered2, revision) {
1297
+ const untracked = docxPackage({
1299
1298
  document: documentXml(body),
1300
1299
  styles: FRAGMENT_STYLES,
1301
1300
  title: "Defined terms"
1302
1301
  });
1302
+ const bytes = revision ? trackInsertion(untracked, revision, covered2) : untracked;
1303
1303
  return `<div data-docx-fragment="${base64(bytes)}" data-docx-fragment-end="${covered2 ? "covered" : "open"}"></div>`;
1304
1304
  }
1305
1305
 
@@ -1556,7 +1556,7 @@ function readParagraph(node, topLevel, bookmarks) {
1556
1556
  links
1557
1557
  };
1558
1558
  }
1559
- function editorPort(editor) {
1559
+ function editorPort(editor, revision) {
1560
1560
  const surface = () => {
1561
1561
  const s = editor.surface;
1562
1562
  if (!s) throw new Error("The document is not open.");
@@ -1575,7 +1575,7 @@ function editorPort(editor) {
1575
1575
  surface().setSelection({ anchor: from, head: to });
1576
1576
  surface().deleteSelection();
1577
1577
  },
1578
- paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2) }).ok,
1578
+ paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2, revision) }).ok,
1579
1579
  splitParagraph: () => surface().splitParagraph()
1580
1580
  };
1581
1581
  }
@@ -1618,6 +1618,70 @@ function holdCaret(editor, timeoutMs = 5e3) {
1618
1618
  const offSelection = editor.on("selectionChange", restore);
1619
1619
  const timer = setTimeout(stop, timeoutMs);
1620
1620
  }
1621
+
1622
+ // src/docx/structural/trackedOrNoted.ts
1623
+ var revisionDate = (at = /* @__PURE__ */ new Date()) => at.toISOString().replace(/\.\d{3}Z$/, "Z");
1624
+ function trackedOrNoted(port, { label, author, insert, inserted = Boolean }) {
1625
+ const mode = port.mode();
1626
+ if (mode === "viewing") throw new Error("This document is read only.");
1627
+ if (mode !== "suggesting") return { result: insert(null), how: "direct" };
1628
+ const revision = { author, date: revisionDate() };
1629
+ const before = port.caret();
1630
+ const tracked = port.trackedBy(revision);
1631
+ port.setMode("editing");
1632
+ let result;
1633
+ try {
1634
+ result = insert(revision);
1635
+ } finally {
1636
+ port.setMode("suggesting");
1637
+ }
1638
+ if (!inserted(result) || port.trackedBy(revision) > tracked)
1639
+ return { result, how: inserted(result) ? "tracked" : "direct" };
1640
+ const after = port.caret();
1641
+ if (before && after && port.comment(before, after, label)) return { result, how: "noted" };
1642
+ return { result, how: "direct" };
1643
+ }
1644
+ var attr2 = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
1645
+ function structuralPort(editor) {
1646
+ return {
1647
+ mode: () => editor.getEditingMode(),
1648
+ setMode: (mode) => void editor.setEditingMode(mode),
1649
+ caret: () => {
1650
+ const head = editor.surface?.state().selection?.head;
1651
+ return head ? { paragraphId: head.paragraphId, offset: head.offset } : null;
1652
+ },
1653
+ // Read from the story's tree: the review pane merges adjacent insertions by one author into
1654
+ // one card, so a card's date need not be this insert's.
1655
+ trackedBy: ({ author, date }) => {
1656
+ const root = editor.surface?.session.part().root;
1657
+ let count = 0;
1658
+ const visit = (node) => {
1659
+ if (node.localName === "ins" && attr2(node, "author") === author && attr2(node, "date") === date)
1660
+ count++;
1661
+ node.children?.forEach(visit);
1662
+ };
1663
+ if (root) visit(root);
1664
+ return count;
1665
+ },
1666
+ comment: (from, to, text) => {
1667
+ const surface = editor.surface;
1668
+ if (!surface) return false;
1669
+ surface.setSelection({ anchor: from, head: to });
1670
+ const ok2 = editor.addComment(text).ok;
1671
+ surface.setSelection({ anchor: to, head: to });
1672
+ return ok2;
1673
+ }
1674
+ };
1675
+ }
1676
+ var DocxAuthorContext = createContext("You");
1677
+ function useTrackedOrNoted() {
1678
+ const editor = useDocxEditor();
1679
+ const author = useContext(DocxAuthorContext);
1680
+ return useCallback(
1681
+ (spec) => editor ? trackedOrNoted(structuralPort(editor), { ...spec, author }) : void 0,
1682
+ [editor, author]
1683
+ );
1684
+ }
1621
1685
  var DEFINED_TERM_TRIGGER = "@@";
1622
1686
  var TRIGGER_QUERY = /(?:^|[\s([{"“‘'])@@([^@\n\t]{0,40})$/;
1623
1687
  var MAX_GROUPS = 8;
@@ -1661,6 +1725,7 @@ function DefinedTermsProvider({
1661
1725
  labels
1662
1726
  }) {
1663
1727
  const editor = useDocxEditor();
1728
+ const track = useTrackedOrNoted();
1664
1729
  const l = useMemo(() => ({ ...DEFAULT_DEFINED_TERMS_LABELS, ...labels }), [labels]);
1665
1730
  const [sources, setSources] = useState(null);
1666
1731
  const loadedAt = useRef(0);
@@ -1717,35 +1782,46 @@ function DefinedTermsProvider({
1717
1782
  refreshRef.current = refresh;
1718
1783
  const firstHover = useCallback(() => void refreshRef.current(), []);
1719
1784
  const caretRect = () => container().querySelector(".docx-editor-one-surface__caret")?.getBoundingClientRect();
1720
- const run = useCallback(
1785
+ const attempt = useCallback(
1721
1786
  (fn) => {
1722
- if (!editor) return void 0;
1723
1787
  try {
1724
- return withDirectEditing(editor, fn);
1788
+ return fn();
1725
1789
  } catch (error) {
1726
1790
  notify?.(error instanceof Error ? error.message : "Could not change the document.", "error");
1727
1791
  return void 0;
1728
1792
  }
1729
1793
  },
1730
- [editor, notify]
1794
+ [notify]
1795
+ );
1796
+ const run = useCallback(
1797
+ (fn) => editor ? attempt(() => withDirectEditing(editor, fn)) : void 0,
1798
+ [editor, attempt]
1731
1799
  );
1732
1800
  const pick = useCallback(
1733
1801
  (row) => {
1734
1802
  if (!editor || !picker || !row) return;
1735
1803
  const port = editorPort(editor);
1736
- const result = run(() => {
1804
+ const placed = run(() => {
1737
1805
  if (picker.mode === "search" && picker.caret) port.setCaret(picker.caret);
1738
- return insertDefinedTerm(port, row.definition, {
1739
- replace: picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0
1740
- });
1806
+ const caret = port.caret();
1807
+ if (!caret) throw new Error("Place the cursor where the term should go.");
1808
+ const typed = picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0;
1809
+ if (typed) port.deleteRange({ ...caret, offset: Math.max(0, caret.offset - typed) }, caret);
1810
+ return true;
1741
1811
  });
1812
+ const result = placed ? attempt(
1813
+ () => track({
1814
+ label: `Inserted defined term: ${row.definition.term}`,
1815
+ insert: (revision) => insertDefinedTerm(editorPort(editor, revision), row.definition)
1816
+ })?.result
1817
+ ) : void 0;
1742
1818
  setPicker(null);
1743
1819
  editor.surface?.focus();
1744
1820
  if (result) holdCaret(editor);
1745
1821
  if (result?.addedDefinition)
1746
1822
  notify?.(`\u201C${row.definition.term}\u201D added to the Definitions section.`, "success");
1747
1823
  },
1748
- [editor, picker, run, notify]
1824
+ [editor, picker, run, attempt, track, notify]
1749
1825
  );
1750
1826
  const openPicker = useCallback(
1751
1827
  (anchor) => {
@@ -2149,6 +2225,7 @@ function HouseTemplateProvider({
2149
2225
  children
2150
2226
  }) {
2151
2227
  const editor = useDocxEditor();
2228
+ const track = useTrackedOrNoted();
2152
2229
  const [template, setTemplate] = useState(builtinHouseTemplate);
2153
2230
  const [divergence, setDivergence] = useState(null);
2154
2231
  const [basedOnTemplate, setBasedOnTemplate] = useState(false);
@@ -2189,11 +2266,18 @@ function HouseTemplateProvider({
2189
2266
  const fragment = buildingBlockFragment(template.bytes, name);
2190
2267
  if (!fragment) return false;
2191
2268
  try {
2192
- const result = withDirectEditing(
2193
- editor,
2194
- () => editor.exec({ type: "paste", text: "", html: fragmentClipboardHtml(fragment) })
2195
- );
2196
- if (!result.ok) throw new Error(result.reason ?? "The block could not be inserted here.");
2269
+ const result = track({
2270
+ label: `Inserted Quick Part: ${name}`,
2271
+ insert: (revision) => editor.exec({
2272
+ type: "paste",
2273
+ text: "",
2274
+ html: fragmentClipboardHtml(
2275
+ revision ? trackInsertion(fragment, revision, true) : fragment
2276
+ )
2277
+ }),
2278
+ inserted: (r) => r.ok
2279
+ })?.result;
2280
+ if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2197
2281
  refresh();
2198
2282
  return true;
2199
2283
  } catch (failure) {
@@ -2204,7 +2288,7 @@ function HouseTemplateProvider({
2204
2288
  return false;
2205
2289
  }
2206
2290
  },
2207
- [editor, editable, template, notify, refresh]
2291
+ [editor, editable, template, notify, refresh, track]
2208
2292
  );
2209
2293
  const setAsHouseTemplate = useMemo(
2210
2294
  () => host?.setFromDocument && documentId ? async () => {
@@ -2247,6 +2331,241 @@ function HouseTemplateProvider({
2247
2331
  );
2248
2332
  return /* @__PURE__ */ jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2249
2333
  }
2334
+ function RibbonMenu({
2335
+ label,
2336
+ icon,
2337
+ children,
2338
+ className,
2339
+ testId,
2340
+ title
2341
+ }) {
2342
+ const [open, setOpen] = useState(false);
2343
+ const element = useRef(null);
2344
+ useEffect(() => {
2345
+ if (!open) return;
2346
+ const dismiss = (event) => {
2347
+ if (!element.current?.contains(event.target)) setOpen(false);
2348
+ };
2349
+ const escape = (event) => {
2350
+ if (event.key === "Escape") setOpen(false);
2351
+ };
2352
+ document.addEventListener("pointerdown", dismiss);
2353
+ document.addEventListener("keydown", escape);
2354
+ return () => {
2355
+ document.removeEventListener("pointerdown", dismiss);
2356
+ document.removeEventListener("keydown", escape);
2357
+ };
2358
+ }, [open]);
2359
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2360
+ /* @__PURE__ */ jsx(
2361
+ "button",
2362
+ {
2363
+ type: "button",
2364
+ className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2365
+ "aria-label": label,
2366
+ title: title ?? label,
2367
+ "aria-expanded": open,
2368
+ "data-testid": testId,
2369
+ onMouseDown: (event) => event.preventDefault(),
2370
+ onClick: () => setOpen((value) => !value),
2371
+ children: icon
2372
+ }
2373
+ ),
2374
+ open && /* @__PURE__ */ jsx(
2375
+ "div",
2376
+ {
2377
+ className: "native-docx-ribbon-popover",
2378
+ role: "group",
2379
+ "aria-label": label,
2380
+ onMouseDown: (event) => {
2381
+ if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2382
+ event.preventDefault();
2383
+ },
2384
+ children: typeof children === "function" ? children(() => setOpen(false)) : children
2385
+ }
2386
+ )
2387
+ ] });
2388
+ }
2389
+ var titleOf = (prompt) => {
2390
+ const line = prompt.trim().split("\n")[0];
2391
+ return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
2392
+ };
2393
+ function rememberCaret(editor) {
2394
+ const surface = editor.surface;
2395
+ const head = surface?.state().selection?.head;
2396
+ if (!surface || !head) return null;
2397
+ return { paraId: surface.session.paraIdOf(head.paragraphId), ...head };
2398
+ }
2399
+ var mounted = [];
2400
+ var running = 0;
2401
+ var listeners = /* @__PURE__ */ new Set();
2402
+ var setRunning = (delta) => {
2403
+ running += delta;
2404
+ listeners.forEach((listener) => listener());
2405
+ };
2406
+ var subscribe = (listener) => {
2407
+ listeners.add(listener);
2408
+ return () => void listeners.delete(listener);
2409
+ };
2410
+ function insertionTarget(origin, caret, editors = mounted) {
2411
+ if (editors.includes(origin) && origin.surface) return origin;
2412
+ const paraId = caret?.paraId;
2413
+ if (!paraId) return null;
2414
+ for (let i = editors.length - 1; i >= 0; i--) {
2415
+ const session = editors[i].surface?.session;
2416
+ if (session?.nodeIdOf(paraId)) return editors[i];
2417
+ }
2418
+ return null;
2419
+ }
2420
+ function restoreCaret(editor, caret) {
2421
+ const surface = editor.surface;
2422
+ if (!surface || !caret) return;
2423
+ const paragraphId = caret.paraId && surface.session.nodeIdOf(caret.paraId) || caret.paragraphId;
2424
+ if (!surface.session.paragraphIds().includes(paragraphId)) return;
2425
+ const point = { paragraphId, offset: caret.offset };
2426
+ surface.setSelection({ anchor: point, head: point });
2427
+ }
2428
+ async function insertGeneratedImage(editor, bytes, prompt, caret) {
2429
+ const image = normalizeImageBytes(bytes);
2430
+ if (!image.ok) throw new Error("The generated image is not in a format Word can embed.");
2431
+ let result;
2432
+ for (let attempt = 0; attempt < 3 && !result?.ok; attempt++) {
2433
+ editor.focus();
2434
+ restoreCaret(editor, caret);
2435
+ result = await executeImageCommand(editor, {
2436
+ type: "insertImage",
2437
+ data: image.bytes,
2438
+ mime: image.mime,
2439
+ widthPoints: image.widthPoints,
2440
+ heightPoints: image.heightPoints,
2441
+ description: prompt.trim(),
2442
+ title: titleOf(prompt)
2443
+ });
2444
+ if (!result.ok && result.reason !== "stale drawing selection") break;
2445
+ }
2446
+ if (!result?.ok) throw new Error(result?.reason || "The picture could not be inserted here.");
2447
+ }
2448
+ var AUTO = "";
2449
+ function useGenerateImageTarget() {
2450
+ const editor = useDocxEditor();
2451
+ const loaded2 = useEditorState((snapshot) => !snapshot.isLoading);
2452
+ useEffect(() => {
2453
+ if (!editor || !loaded2) return;
2454
+ mounted.push(editor);
2455
+ return () => void mounted.splice(mounted.indexOf(editor), 1);
2456
+ }, [editor, loaded2]);
2457
+ }
2458
+ function GenerateImageButton({ generation }) {
2459
+ const editor = useDocxEditor();
2460
+ const [prompt, setPrompt] = useState("");
2461
+ const [aspectRatio, setAspectRatio] = useState(AUTO);
2462
+ const [busy, setBusy] = useState(false);
2463
+ const [error, setError] = useState("");
2464
+ const abort = useRef(null);
2465
+ const close = useRef(() => {
2466
+ });
2467
+ const editable = useEditorState((snapshot) => snapshot.editable);
2468
+ const generating = useSyncExternalStore(subscribe, () => running > 0);
2469
+ async function run() {
2470
+ if (!editor || !prompt.trim() || busy) return;
2471
+ const caret = rememberCaret(editor);
2472
+ const controller = new AbortController();
2473
+ abort.current = controller;
2474
+ setBusy(true);
2475
+ setRunning(1);
2476
+ setError("");
2477
+ try {
2478
+ const bytes = await generation.generate(prompt.trim(), {
2479
+ aspectRatio: aspectRatio || void 0,
2480
+ signal: controller.signal
2481
+ });
2482
+ if (controller.signal.aborted) return;
2483
+ const target = insertionTarget(editor, caret);
2484
+ if (!target) throw new Error("The document was closed before the generated image was ready.");
2485
+ await insertGeneratedImage(target, bytes, prompt, caret);
2486
+ setPrompt("");
2487
+ close.current();
2488
+ generation.notify?.("Generated image inserted", "success");
2489
+ } catch (failure) {
2490
+ if (controller.signal.aborted) return;
2491
+ const message = failure instanceof Error ? failure.message : String(failure);
2492
+ setError(message);
2493
+ generation.notify?.(message, "error");
2494
+ } finally {
2495
+ abort.current = null;
2496
+ setRunning(-1);
2497
+ setBusy(false);
2498
+ }
2499
+ }
2500
+ if (!editable && !generating) return null;
2501
+ return /* @__PURE__ */ jsx(
2502
+ RibbonMenu,
2503
+ {
2504
+ label: generating ? "Generating image\u2026" : "Generate image",
2505
+ icon: generating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "native-docx-spin" }) : /* @__PURE__ */ jsx(Sparkles, { size: 16 }),
2506
+ testId: "docx-generate-image",
2507
+ children: (dismiss) => {
2508
+ close.current = dismiss;
2509
+ return /* @__PURE__ */ jsxs(
2510
+ "form",
2511
+ {
2512
+ className: "native-docx-form native-docx-generate-image",
2513
+ onSubmit: (event) => {
2514
+ event.preventDefault();
2515
+ void run();
2516
+ },
2517
+ children: [
2518
+ /* @__PURE__ */ jsxs("label", { children: [
2519
+ "Describe the image",
2520
+ /* @__PURE__ */ jsx(
2521
+ "textarea",
2522
+ {
2523
+ autoFocus: true,
2524
+ rows: 3,
2525
+ value: prompt,
2526
+ disabled: busy,
2527
+ placeholder: "A watercolour of a lighthouse at dawn",
2528
+ "data-testid": "docx-generate-image-prompt",
2529
+ onChange: (event) => setPrompt(event.target.value),
2530
+ onKeyDown: (event) => {
2531
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run();
2532
+ }
2533
+ }
2534
+ )
2535
+ ] }),
2536
+ generation.aspectRatios && generation.aspectRatios.length > 0 && /* @__PURE__ */ jsxs("label", { children: [
2537
+ "Aspect ratio",
2538
+ /* @__PURE__ */ jsxs(
2539
+ "select",
2540
+ {
2541
+ value: aspectRatio,
2542
+ disabled: busy,
2543
+ onChange: (event) => setAspectRatio(event.target.value),
2544
+ children: [
2545
+ /* @__PURE__ */ jsx("option", { value: AUTO, children: "Auto" }),
2546
+ generation.aspectRatios.map((ratio) => /* @__PURE__ */ jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
2547
+ ]
2548
+ }
2549
+ )
2550
+ ] }),
2551
+ busy && /* @__PURE__ */ 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." }),
2552
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
2553
+ /* @__PURE__ */ jsx("div", { className: "native-docx-form-actions", children: busy ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => abort.current?.abort(), children: "Cancel" }) : /* @__PURE__ */ jsx(
2554
+ "button",
2555
+ {
2556
+ type: "submit",
2557
+ disabled: !prompt.trim(),
2558
+ "data-testid": "docx-generate-image-submit",
2559
+ children: "Generate"
2560
+ }
2561
+ ) })
2562
+ ]
2563
+ }
2564
+ );
2565
+ }
2566
+ }
2567
+ );
2568
+ }
2250
2569
  function wordDraftHtml(markdown) {
2251
2570
  const template = document.createElement("template");
2252
2571
  template.innerHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(AiMarkdown, { children: markdown }));
@@ -2334,61 +2653,6 @@ var WordDraftStream = class {
2334
2653
  }
2335
2654
  }
2336
2655
  };
2337
- function RibbonMenu({
2338
- label,
2339
- icon,
2340
- children,
2341
- className,
2342
- testId,
2343
- title
2344
- }) {
2345
- const [open, setOpen] = useState(false);
2346
- const element = useRef(null);
2347
- useEffect(() => {
2348
- if (!open) return;
2349
- const dismiss = (event) => {
2350
- if (!element.current?.contains(event.target)) setOpen(false);
2351
- };
2352
- const escape = (event) => {
2353
- if (event.key === "Escape") setOpen(false);
2354
- };
2355
- document.addEventListener("pointerdown", dismiss);
2356
- document.addEventListener("keydown", escape);
2357
- return () => {
2358
- document.removeEventListener("pointerdown", dismiss);
2359
- document.removeEventListener("keydown", escape);
2360
- };
2361
- }, [open]);
2362
- return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2363
- /* @__PURE__ */ jsx(
2364
- "button",
2365
- {
2366
- type: "button",
2367
- className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2368
- "aria-label": label,
2369
- title: title ?? label,
2370
- "aria-expanded": open,
2371
- "data-testid": testId,
2372
- onMouseDown: (event) => event.preventDefault(),
2373
- onClick: () => setOpen((value) => !value),
2374
- children: icon
2375
- }
2376
- ),
2377
- open && /* @__PURE__ */ jsx(
2378
- "div",
2379
- {
2380
- className: "native-docx-ribbon-popover",
2381
- role: "group",
2382
- "aria-label": label,
2383
- onMouseDown: (event) => {
2384
- if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2385
- event.preventDefault();
2386
- },
2387
- children: typeof children === "function" ? children(() => setOpen(false)) : children
2388
- }
2389
- )
2390
- ] });
2391
- }
2392
2656
 
2393
2657
  // src/docx/wordStyles.ts
2394
2658
  var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
@@ -2678,6 +2942,679 @@ function WordShortcuts({ scope }) {
2678
2942
  }, [scope]);
2679
2943
  return null;
2680
2944
  }
2945
+
2946
+ // src/docx/layout/presets.ts
2947
+ var TWIPS_PER_INCH = 1440;
2948
+ var MARGIN_PRESETS = [
2949
+ { id: "normal", label: "Normal", top: 1440, right: 1440, bottom: 1440, left: 1440 },
2950
+ { id: "narrow", label: "Narrow", top: 720, right: 720, bottom: 720, left: 720 },
2951
+ { id: "moderate", label: "Moderate", top: 1440, right: 1080, bottom: 1440, left: 1080 },
2952
+ { id: "wide", label: "Wide", top: 1440, right: 2880, bottom: 1440, left: 2880 }
2953
+ ];
2954
+ var PAPER_SIZES = [
2955
+ { id: "letter", label: "Letter", width: 12240, height: 15840 },
2956
+ { id: "legal", label: "Legal", width: 12240, height: 20160 },
2957
+ { id: "a4", label: "A4", width: 11906, height: 16838 },
2958
+ { id: "a5", label: "A5", width: 8391, height: 11906 },
2959
+ { id: "executive", label: "Executive", width: 10440, height: 15120 }
2960
+ ];
2961
+ var TOLERANCE = 20;
2962
+ var near = (a, b) => Math.abs(a - b) <= TOLERANCE;
2963
+ function matchMargins(setup) {
2964
+ if (!setup) return null;
2965
+ const m = setup.marginsTwips;
2966
+ return MARGIN_PRESETS.find(
2967
+ (p) => near(p.top, m.top) && near(p.right, m.right) && near(p.bottom, m.bottom) && near(p.left, m.left)
2968
+ )?.id ?? null;
2969
+ }
2970
+ function matchPaper(setup) {
2971
+ if (!setup) return null;
2972
+ const short = Math.min(setup.pageWidthTwips, setup.pageHeightTwips);
2973
+ const long = Math.max(setup.pageWidthTwips, setup.pageHeightTwips);
2974
+ return PAPER_SIZES.find((p) => near(p.width, short) && near(p.height, long))?.id ?? null;
2975
+ }
2976
+ function orientedSize(paper, orientation) {
2977
+ return orientation === "landscape" ? { pageWidthTwips: paper.height, pageHeightTwips: paper.width } : { pageWidthTwips: paper.width, pageHeightTwips: paper.height };
2978
+ }
2979
+ function inches(twips) {
2980
+ return `${Number((twips / TWIPS_PER_INCH).toFixed(2))}"`;
2981
+ }
2982
+ function paperDescription(paper) {
2983
+ return `${inches(paper.width)} \xD7 ${inches(paper.height)}`;
2984
+ }
2985
+ var COLUMNS_UNSUPPORTED = "Text columns are not available in this editor yet. Documents that already use columns display and save them unchanged.";
2986
+ function RibbonGallery({
2987
+ label,
2988
+ choices,
2989
+ current,
2990
+ onPick,
2991
+ close,
2992
+ testId
2993
+ }) {
2994
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-gallery", role: "menu", "aria-label": label, "data-testid": testId, children: choices.map((choice) => /* @__PURE__ */ jsxs(
2995
+ "button",
2996
+ {
2997
+ type: "button",
2998
+ role: "menuitemradio",
2999
+ "aria-checked": current === choice.id,
3000
+ disabled: choice.disabled,
3001
+ title: choice.title,
3002
+ "data-choice": choice.id,
3003
+ onClick: () => {
3004
+ onPick(choice.id);
3005
+ close();
3006
+ },
3007
+ children: [
3008
+ choice.icon,
3009
+ /* @__PURE__ */ jsxs("span", { className: "native-docx-gallery-text", children: [
3010
+ /* @__PURE__ */ jsx("span", { className: "native-docx-gallery-label", children: choice.label }),
3011
+ choice.detail && /* @__PURE__ */ jsx("span", { className: "native-docx-gallery-detail", children: choice.detail })
3012
+ ] })
3013
+ ]
3014
+ },
3015
+ choice.id
3016
+ )) });
3017
+ }
3018
+ var marginDetail = (p) => `Top ${inches(p.top)} \xB7 Bottom ${inches(p.bottom)} \xB7 Left ${inches(p.left)} \xB7 Right ${inches(p.right)}`;
3019
+ function Margins({ setup, apply, openDialog }) {
3020
+ return /* @__PURE__ */ jsx(RibbonMenu, { label: "Margins", icon: /* @__PURE__ */ jsx(Scaling, { size: 16 }), testId: "docx-layout-margins", children: (close) => /* @__PURE__ */ jsxs(Fragment, { children: [
3021
+ /* @__PURE__ */ jsx(
3022
+ RibbonGallery,
3023
+ {
3024
+ label: "Margins",
3025
+ current: matchMargins(setup),
3026
+ close,
3027
+ choices: MARGIN_PRESETS.map((p) => ({
3028
+ id: p.id,
3029
+ label: p.label,
3030
+ detail: marginDetail(p)
3031
+ })),
3032
+ onPick: (id) => {
3033
+ const p = MARGIN_PRESETS.find((m) => m.id === id);
3034
+ apply({
3035
+ marginTopTwips: p.top,
3036
+ marginRightTwips: p.right,
3037
+ marginBottomTwips: p.bottom,
3038
+ marginLeftTwips: p.left,
3039
+ scope: "section"
3040
+ });
3041
+ }
3042
+ }
3043
+ ),
3044
+ /* @__PURE__ */ jsx(
3045
+ "button",
3046
+ {
3047
+ type: "button",
3048
+ className: "native-docx-gallery-more",
3049
+ onClick: () => {
3050
+ close();
3051
+ openDialog();
3052
+ },
3053
+ children: "Custom margins\u2026"
3054
+ }
3055
+ )
3056
+ ] }) });
3057
+ }
3058
+ function Orientation({ setup, apply }) {
3059
+ return /* @__PURE__ */ jsx(
3060
+ RibbonMenu,
3061
+ {
3062
+ label: "Orientation",
3063
+ icon: setup?.orientation === "landscape" ? /* @__PURE__ */ jsx(RectangleHorizontal, { size: 16 }) : /* @__PURE__ */ jsx(RectangleVertical, { size: 16 }),
3064
+ testId: "docx-layout-orientation",
3065
+ children: (close) => /* @__PURE__ */ jsx(
3066
+ RibbonGallery,
3067
+ {
3068
+ label: "Orientation",
3069
+ current: setup?.orientation,
3070
+ close,
3071
+ choices: [
3072
+ { id: "portrait", label: "Portrait", icon: /* @__PURE__ */ jsx(RectangleVertical, { size: 16 }) },
3073
+ { id: "landscape", label: "Landscape", icon: /* @__PURE__ */ jsx(RectangleHorizontal, { size: 16 }) }
3074
+ ],
3075
+ onPick: (id) => apply({ orientation: id, scope: "section" })
3076
+ }
3077
+ )
3078
+ }
3079
+ );
3080
+ }
3081
+ function Size({ setup, apply, openDialog }) {
3082
+ return /* @__PURE__ */ jsx(RibbonMenu, { label: "Size", icon: /* @__PURE__ */ jsx(FileText, { size: 16 }), testId: "docx-layout-size", children: (close) => /* @__PURE__ */ jsxs(Fragment, { children: [
3083
+ /* @__PURE__ */ jsx(
3084
+ RibbonGallery,
3085
+ {
3086
+ label: "Size",
3087
+ current: matchPaper(setup),
3088
+ close,
3089
+ choices: PAPER_SIZES.map((p) => ({
3090
+ id: p.id,
3091
+ label: p.label,
3092
+ detail: paperDescription(p)
3093
+ })),
3094
+ onPick: (id) => apply({
3095
+ ...orientedSize(
3096
+ PAPER_SIZES.find((p) => p.id === id),
3097
+ setup?.orientation
3098
+ ),
3099
+ scope: "section"
3100
+ })
3101
+ }
3102
+ ),
3103
+ /* @__PURE__ */ jsx(
3104
+ "button",
3105
+ {
3106
+ type: "button",
3107
+ className: "native-docx-gallery-more",
3108
+ onClick: () => {
3109
+ close();
3110
+ openDialog();
3111
+ },
3112
+ children: "More paper sizes\u2026"
3113
+ }
3114
+ )
3115
+ ] }) });
3116
+ }
3117
+ var COLUMN_BREAK = { type: "insertBreak", kind: "column" };
3118
+ function Breaks() {
3119
+ const page = useEditorCommand("insert.pageBreak");
3120
+ const nextPage = useEditorCommand("insert.sectionBreakNextPage");
3121
+ const continuous = useEditorCommand("insert.sectionBreakContinuous");
3122
+ const column = useEditorCommand(COLUMN_BREAK);
3123
+ const rows = [
3124
+ { id: "page", label: "Page", detail: "Start the next page here", command: page },
3125
+ { id: "column", label: "Column", detail: "Start the next column here", command: column },
3126
+ {
3127
+ id: "nextPage",
3128
+ label: "Next Page",
3129
+ detail: "Section break: new section on the next page",
3130
+ command: nextPage
3131
+ },
3132
+ {
3133
+ id: "continuous",
3134
+ label: "Continuous",
3135
+ detail: "Section break: new section on the same page",
3136
+ command: continuous
3137
+ }
3138
+ ];
3139
+ return /* @__PURE__ */ jsx(RibbonMenu, { label: "Breaks", icon: /* @__PURE__ */ jsx(SeparatorHorizontal, { size: 16 }), testId: "docx-layout-breaks", children: (close) => /* @__PURE__ */ jsx(
3140
+ RibbonGallery,
3141
+ {
3142
+ label: "Breaks",
3143
+ close,
3144
+ choices: rows.map((row) => ({
3145
+ id: row.id,
3146
+ label: row.label,
3147
+ detail: row.detail,
3148
+ disabled: !row.command.isEnabled,
3149
+ title: row.command.disabledReason ?? void 0
3150
+ })),
3151
+ onPick: (id) => void rows.find((row) => row.id === id)?.command.execute()
3152
+ }
3153
+ ) });
3154
+ }
3155
+ function LayoutButtons() {
3156
+ const { pageSetup, apply } = usePageSetup();
3157
+ const [dialog, setDialog] = useState(false);
3158
+ const props = { setup: pageSetup, apply, openDialog: () => setDialog(true) };
3159
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3160
+ /* @__PURE__ */ jsx(Margins, { ...props }),
3161
+ /* @__PURE__ */ jsx(Orientation, { ...props }),
3162
+ /* @__PURE__ */ jsx(Size, { ...props }),
3163
+ /* @__PURE__ */ jsx(
3164
+ DocxEditor.Toolbar.Action,
3165
+ {
3166
+ label: "Columns",
3167
+ icon: /* @__PURE__ */ jsx(Columns2, { size: 16 }),
3168
+ disabled: true,
3169
+ disabledReason: COLUMNS_UNSUPPORTED,
3170
+ className: "native-docx-layout-columns"
3171
+ }
3172
+ ),
3173
+ /* @__PURE__ */ jsx(Breaks, {}),
3174
+ /* @__PURE__ */ jsx(
3175
+ DocxEditor.Toolbar.Action,
3176
+ {
3177
+ label: "Page Setup\u2026",
3178
+ icon: /* @__PURE__ */ jsx(Settings2, { size: 16 }),
3179
+ onSelect: () => setDialog(true),
3180
+ className: "native-docx-layout-page-setup"
3181
+ }
3182
+ ),
3183
+ /* @__PURE__ */ jsx(DocxEditor.PageSetupDialog, { open: dialog, onClose: () => setDialog(false) })
3184
+ ] });
3185
+ }
3186
+ var ROW_ABOVE = { type: "insertRow", where: "above" };
3187
+ var ROW_BELOW = { type: "insertRow", where: "below" };
3188
+ var COLUMN_LEFT = { type: "insertColumn", where: "left" };
3189
+ var COLUMN_RIGHT = { type: "insertColumn", where: "right" };
3190
+ var MERGE = { type: "mergeCells" };
3191
+ var SPLIT = { type: "splitCell", rows: 1, cols: 2 };
3192
+ var HEADER_ROW = { type: "toggleHeaderRow" };
3193
+ var DELETE_ROW = { type: "deleteRow" };
3194
+ var DELETE_COLUMN = { type: "deleteColumn" };
3195
+ var DELETE_TABLE = { type: "deleteTable" };
3196
+ var ALIGN_TOP = { type: "setTableCellVerticalAlignment", alignment: "top" };
3197
+ var ALIGN_CENTER = { type: "setTableCellVerticalAlignment", alignment: "center" };
3198
+ var ALIGN_BOTTOM = { type: "setTableCellVerticalAlignment", alignment: "bottom" };
3199
+ function useRows(rows) {
3200
+ return rows.map((row) => ({ ...row, state: useEditorCommand(row.command) }));
3201
+ }
3202
+ function CommandGallery({
3203
+ label,
3204
+ icon,
3205
+ testId,
3206
+ rows
3207
+ }) {
3208
+ const bound = useRows(rows);
3209
+ return /* @__PURE__ */ jsx(RibbonMenu, { label, icon, testId, children: (close) => /* @__PURE__ */ jsx(
3210
+ RibbonGallery,
3211
+ {
3212
+ label,
3213
+ close,
3214
+ choices: bound.map((row) => ({
3215
+ id: row.id,
3216
+ label: row.label,
3217
+ icon: row.icon,
3218
+ disabled: !row.state.isEnabled,
3219
+ title: row.state.disabledReason ?? void 0
3220
+ })),
3221
+ onPick: (id) => void bound.find((row) => row.id === id)?.state.execute()
3222
+ }
3223
+ ) });
3224
+ }
3225
+ function CommandAction({
3226
+ command,
3227
+ label,
3228
+ icon,
3229
+ testId
3230
+ }) {
3231
+ const state = useEditorCommand(command);
3232
+ return /* @__PURE__ */ jsx(
3233
+ DocxEditor.Toolbar.Action,
3234
+ {
3235
+ label,
3236
+ icon,
3237
+ active: state.isActive,
3238
+ disabled: !state.isEnabled,
3239
+ disabledReason: state.disabledReason ?? void 0,
3240
+ onSelect: () => void state.execute(),
3241
+ className: `native-docx-table-${testId}`
3242
+ }
3243
+ );
3244
+ }
3245
+ function TableButtons() {
3246
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3247
+ /* @__PURE__ */ jsx(
3248
+ CommandAction,
3249
+ {
3250
+ command: ROW_ABOVE,
3251
+ label: "Insert row above",
3252
+ icon: /* @__PURE__ */ jsx(BetweenHorizontalStart, { size: 16 }),
3253
+ testId: "row-above"
3254
+ }
3255
+ ),
3256
+ /* @__PURE__ */ jsx(
3257
+ CommandAction,
3258
+ {
3259
+ command: ROW_BELOW,
3260
+ label: "Insert row below",
3261
+ icon: /* @__PURE__ */ jsx(BetweenHorizontalEnd, { size: 16 }),
3262
+ testId: "row-below"
3263
+ }
3264
+ ),
3265
+ /* @__PURE__ */ jsx(
3266
+ CommandAction,
3267
+ {
3268
+ command: COLUMN_LEFT,
3269
+ label: "Insert column left",
3270
+ icon: /* @__PURE__ */ jsx(BetweenVerticalStart, { size: 16 }),
3271
+ testId: "column-left"
3272
+ }
3273
+ ),
3274
+ /* @__PURE__ */ jsx(
3275
+ CommandAction,
3276
+ {
3277
+ command: COLUMN_RIGHT,
3278
+ label: "Insert column right",
3279
+ icon: /* @__PURE__ */ jsx(BetweenVerticalEnd, { size: 16 }),
3280
+ testId: "column-right"
3281
+ }
3282
+ ),
3283
+ /* @__PURE__ */ jsx(
3284
+ CommandGallery,
3285
+ {
3286
+ label: "Delete",
3287
+ icon: /* @__PURE__ */ jsx(Trash2, { size: 16 }),
3288
+ testId: "docx-table-delete",
3289
+ rows: [
3290
+ { id: "row", label: "Delete row", command: DELETE_ROW },
3291
+ { id: "column", label: "Delete column", command: DELETE_COLUMN },
3292
+ { id: "table", label: "Delete table", command: DELETE_TABLE }
3293
+ ]
3294
+ }
3295
+ ),
3296
+ /* @__PURE__ */ jsx(
3297
+ CommandAction,
3298
+ {
3299
+ command: MERGE,
3300
+ label: "Merge cells",
3301
+ icon: /* @__PURE__ */ jsx(Merge, { size: 16 }),
3302
+ testId: "merge"
3303
+ }
3304
+ ),
3305
+ /* @__PURE__ */ jsx(CommandAction, { command: SPLIT, label: "Split cell", icon: /* @__PURE__ */ jsx(Split, { size: 16 }), testId: "split" }),
3306
+ /* @__PURE__ */ jsx(
3307
+ CommandAction,
3308
+ {
3309
+ command: HEADER_ROW,
3310
+ label: "Repeat header row",
3311
+ icon: /* @__PURE__ */ jsx(Heading, { size: 16 }),
3312
+ testId: "header-row"
3313
+ }
3314
+ ),
3315
+ /* @__PURE__ */ jsx(
3316
+ CommandGallery,
3317
+ {
3318
+ label: "Cell alignment",
3319
+ icon: /* @__PURE__ */ jsx(AlignVerticalJustifyCenter, { size: 16 }),
3320
+ testId: "docx-table-align",
3321
+ rows: [
3322
+ {
3323
+ id: "top",
3324
+ label: "Top",
3325
+ icon: /* @__PURE__ */ jsx(AlignVerticalJustifyStart, { size: 16 }),
3326
+ command: ALIGN_TOP
3327
+ },
3328
+ {
3329
+ id: "center",
3330
+ label: "Center",
3331
+ icon: /* @__PURE__ */ jsx(AlignVerticalJustifyCenter, { size: 16 }),
3332
+ command: ALIGN_CENTER
3333
+ },
3334
+ {
3335
+ id: "bottom",
3336
+ label: "Bottom",
3337
+ icon: /* @__PURE__ */ jsx(AlignVerticalJustifyEnd, { size: 16 }),
3338
+ command: ALIGN_BOTTOM
3339
+ }
3340
+ ]
3341
+ }
3342
+ ),
3343
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderTarget, {}),
3344
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderStyle, {}),
3345
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderWidth, {}),
3346
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableBorderColor, {}),
3347
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, {})
3348
+ ] });
3349
+ }
3350
+ var CAPTION_LABELS = ["Figure", "Table", "Equation"];
3351
+ var CAPTION_STYLES = stylesXml(
3352
+ "",
3353
+ '<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>'
3354
+ );
3355
+ function seqIdentifier(instruction) {
3356
+ const match = /^\s*SEQ\s+("[^"]+"|\S+)/i.exec(instruction);
3357
+ return match ? match[1].replace(/"/g, "") : null;
3358
+ }
3359
+ function countSeqFields(root, label, throughId) {
3360
+ let count = 0;
3361
+ let done = false;
3362
+ const target = label.toLowerCase();
3363
+ const visit = (node) => {
3364
+ if (done) return;
3365
+ if (node.localName === "fldSimple") {
3366
+ const instr = node.attributes?.find((a) => a.localName === "instr")?.value ?? "";
3367
+ if (seqIdentifier(instr)?.toLowerCase() === target) count++;
3368
+ } else if (node.localName === "instrText") {
3369
+ const text = (node.children ?? []).map((c) => c.value ?? "").join("");
3370
+ if (seqIdentifier(text)?.toLowerCase() === target) count++;
3371
+ }
3372
+ node.children?.forEach(visit);
3373
+ if (throughId && node.id === throughId) done = true;
3374
+ };
3375
+ visit(root);
3376
+ return count;
3377
+ }
3378
+ function captionParagraphXml(label, number, text) {
3379
+ const run = (value) => `<w:r><w:t xml:space="preserve">${esc(value)}</w:t></w:r>`;
3380
+ const rest = text.trim() ? `: ${text.trim()}` : "";
3381
+ 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>`;
3382
+ }
3383
+ function base642(bytes) {
3384
+ let binary = "";
3385
+ for (let i = 0; i < bytes.length; i += 32768)
3386
+ binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
3387
+ return btoa(binary);
3388
+ }
3389
+ function captionFragmentHtml(label, number, text, revision) {
3390
+ const untracked = docxPackage({
3391
+ document: documentXml(captionParagraphXml(label, number, text)),
3392
+ styles: CAPTION_STYLES,
3393
+ title: "Caption"
3394
+ });
3395
+ const bytes = revision ? trackInsertion(untracked, revision, true) : untracked;
3396
+ return `<div data-docx-fragment="${base642(bytes)}" data-docx-fragment-end="covered"></div>`;
3397
+ }
3398
+ var TRANSFORMS = Object.fromEntries(
3399
+ ["rotateCW", "rotateCCW", "flipH", "flipV"].map((action) => [
3400
+ action,
3401
+ { type: "transformImage", action }
3402
+ ])
3403
+ );
3404
+ function TransformAction({
3405
+ action,
3406
+ label,
3407
+ icon
3408
+ }) {
3409
+ const state = useEditorCommand(TRANSFORMS[action]);
3410
+ return /* @__PURE__ */ jsx(
3411
+ DocxEditor.Toolbar.Action,
3412
+ {
3413
+ label,
3414
+ icon,
3415
+ disabled: !state.isEnabled,
3416
+ disabledReason: state.disabledReason ?? void 0,
3417
+ onSelect: () => void state.execute(),
3418
+ className: `native-docx-picture-${action}`
3419
+ }
3420
+ );
3421
+ }
3422
+ function insertCaption(editor, label, text, track) {
3423
+ const surface = editor.surface;
3424
+ const selection = surface?.state().selection;
3425
+ if (!surface || !selection) return null;
3426
+ const { paragraphId } = selection.head;
3427
+ const number = countSeqFields(
3428
+ surface.session.part().root,
3429
+ label,
3430
+ paragraphId
3431
+ ) + 1;
3432
+ const ids = surface.session.paragraphIds();
3433
+ const next = ids[ids.indexOf(paragraphId) + 1];
3434
+ const caption = `${label} ${number}${text.trim() ? `: ${text.trim()}` : ""}`;
3435
+ const outcome = track({
3436
+ label: `Inserted caption: ${caption}`,
3437
+ inserted: (inserted) => inserted !== null,
3438
+ insert: (revision) => {
3439
+ if (next) {
3440
+ surface.setSelection({
3441
+ anchor: { paragraphId: next, offset: 0 },
3442
+ head: { paragraphId: next, offset: 0 }
3443
+ });
3444
+ } else {
3445
+ editor.exec({ type: "selectAll" });
3446
+ const end = surface.state().selection?.head;
3447
+ if (!end) return null;
3448
+ surface.setSelection({ anchor: end, head: end });
3449
+ surface.splitParagraph();
3450
+ }
3451
+ const ok2 = editor.exec({
3452
+ type: "paste",
3453
+ text: caption,
3454
+ html: captionFragmentHtml(label, number, text, revision)
3455
+ }).ok;
3456
+ return ok2 ? number : null;
3457
+ }
3458
+ });
3459
+ return outcome?.result ?? null;
3460
+ }
3461
+ function CaptionButton({ defaultLabel = "Figure" }) {
3462
+ const editor = useDocxEditor();
3463
+ const track = useTrackedOrNoted();
3464
+ const [label, setLabel] = useState(defaultLabel);
3465
+ const [text, setText] = useState("");
3466
+ const [error, setError] = useState("");
3467
+ return /* @__PURE__ */ jsx(RibbonMenu, { label: "Insert caption", icon: /* @__PURE__ */ jsx(Captions, { size: 16 }), testId: "docx-insert-caption", children: (close) => /* @__PURE__ */ jsxs(
3468
+ "form",
3469
+ {
3470
+ className: "native-docx-form",
3471
+ onSubmit: (event) => {
3472
+ event.preventDefault();
3473
+ if (!editor) return;
3474
+ try {
3475
+ if (insertCaption(editor, label, text, track) === null)
3476
+ return setError("The caption could not be inserted here.");
3477
+ setText("");
3478
+ setError("");
3479
+ close();
3480
+ editor.focus();
3481
+ } catch (failure) {
3482
+ setError(failure instanceof Error ? failure.message : String(failure));
3483
+ }
3484
+ },
3485
+ children: [
3486
+ /* @__PURE__ */ jsxs("label", { children: [
3487
+ "Label",
3488
+ /* @__PURE__ */ jsx(
3489
+ "select",
3490
+ {
3491
+ value: label,
3492
+ onChange: (event) => setLabel(event.target.value),
3493
+ children: CAPTION_LABELS.map((value) => /* @__PURE__ */ jsx("option", { children: value }, value))
3494
+ }
3495
+ )
3496
+ ] }),
3497
+ /* @__PURE__ */ jsxs("label", { children: [
3498
+ "Caption",
3499
+ /* @__PURE__ */ jsx(
3500
+ "input",
3501
+ {
3502
+ autoFocus: true,
3503
+ value: text,
3504
+ placeholder: "Optional text after the number",
3505
+ "data-testid": "docx-caption-text",
3506
+ onChange: (event) => setText(event.target.value)
3507
+ }
3508
+ )
3509
+ ] }),
3510
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
3511
+ /* @__PURE__ */ jsx("button", { type: "submit", "data-testid": "docx-caption-insert", children: "Insert" })
3512
+ ]
3513
+ }
3514
+ ) });
3515
+ }
3516
+ function PictureButtons() {
3517
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3518
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageWrap, {}),
3519
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageProperties, {}),
3520
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageAltText, {}),
3521
+ /* @__PURE__ */ jsx(TransformAction, { action: "rotateCW", label: "Rotate right 90\xB0", icon: /* @__PURE__ */ jsx(RotateCw, { size: 16 }) }),
3522
+ /* @__PURE__ */ jsx(TransformAction, { action: "rotateCCW", label: "Rotate left 90\xB0", icon: /* @__PURE__ */ jsx(RotateCcw, { size: 16 }) }),
3523
+ /* @__PURE__ */ jsx(
3524
+ TransformAction,
3525
+ {
3526
+ action: "flipH",
3527
+ label: "Flip horizontal",
3528
+ icon: /* @__PURE__ */ jsx(FlipHorizontal2, { size: 16 })
3529
+ }
3530
+ ),
3531
+ /* @__PURE__ */ jsx(TransformAction, { action: "flipV", label: "Flip vertical", icon: /* @__PURE__ */ jsx(FlipVertical2, { size: 16 }) }),
3532
+ /* @__PURE__ */ jsx(CaptionButton, {})
3533
+ ] });
3534
+ }
3535
+ var LAYOUT_TAB = "Layout";
3536
+ var TABLE_TAB = "Table";
3537
+ var PICTURE_TAB = "Picture";
3538
+ function useContextualTabs(tab) {
3539
+ const inTable = useEditorState((snapshot) => snapshot.table !== null);
3540
+ const onPicture = useEditorState((snapshot) => snapshot.image !== null);
3541
+ const tabs = [...inTable ? [TABLE_TAB] : [], ...onPicture ? [PICTURE_TAB] : []];
3542
+ const contextual = tab === TABLE_TAB || tab === PICTURE_TAB;
3543
+ return { tabs, shown: contextual && !tabs.includes(tab) ? "Home" : tab };
3544
+ }
3545
+ function LayoutTabContent({ tab }) {
3546
+ if (tab === LAYOUT_TAB) return /* @__PURE__ */ jsx(LayoutButtons, {});
3547
+ if (tab === TABLE_TAB) return /* @__PURE__ */ jsx(TableButtons, {});
3548
+ if (tab === PICTURE_TAB) return /* @__PURE__ */ jsx(PictureButtons, {});
3549
+ return null;
3550
+ }
3551
+ var contains = (node, id) => node.id === id || (node.children ?? []).some((child) => contains(child, id));
3552
+ function firstParagraph(node) {
3553
+ for (const child of node.children ?? []) {
3554
+ if (child.kind === "paragraph" && child.id) return child.id;
3555
+ if (child.localName === "tbl") continue;
3556
+ const nested = firstParagraph(child);
3557
+ if (nested) return nested;
3558
+ }
3559
+ return null;
3560
+ }
3561
+ function tableCells(root, paragraphId) {
3562
+ let table = null;
3563
+ const find = (node) => {
3564
+ if (node.localName === "tbl" && contains(node, paragraphId)) table = node;
3565
+ for (const child of node.children ?? []) if (contains(child, paragraphId)) find(child);
3566
+ };
3567
+ find(root);
3568
+ if (!table) return null;
3569
+ const cells = [];
3570
+ let index = -1;
3571
+ const rows = (node) => (node.children ?? []).flatMap(
3572
+ (child) => child.localName === "tr" ? [child] : child.localName === "tbl" ? [] : rows(child)
3573
+ );
3574
+ for (const row of rows(table))
3575
+ for (const cell of (row.children ?? []).filter((child) => child.localName === "tc")) {
3576
+ const first = firstParagraph(cell);
3577
+ if (!first) continue;
3578
+ if (contains(cell, paragraphId)) index = cells.length;
3579
+ cells.push(first);
3580
+ }
3581
+ return index < 0 ? null : { cells, index };
3582
+ }
3583
+ function TableTabKeys({ scope }) {
3584
+ const editor = useDocxEditor();
3585
+ const latest = useRef(editor);
3586
+ latest.current = editor;
3587
+ useEffect(() => {
3588
+ const element = scope.current;
3589
+ if (!element) return;
3590
+ const onKeyDown = (event) => {
3591
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return;
3592
+ const editor2 = latest.current;
3593
+ const surface = editor2?.surface;
3594
+ const head = surface?.state().selection?.head;
3595
+ if (!editor2 || !surface || !head) return;
3596
+ const root = () => surface.session.part().root;
3597
+ const table = tableCells(root(), head.paragraphId);
3598
+ if (!table) return;
3599
+ event.preventDefault();
3600
+ event.stopPropagation();
3601
+ let target = table.cells[table.index + (event.shiftKey ? -1 : 1)];
3602
+ if (!target && !event.shiftKey && editor2.exec({ type: "insertRow", where: "below" }).ok) {
3603
+ const grown = tableCells(
3604
+ root(),
3605
+ surface.state().selection?.head.paragraphId ?? head.paragraphId
3606
+ );
3607
+ target = grown?.cells[table.cells.length] ?? "";
3608
+ }
3609
+ if (!target) return;
3610
+ const point = { paragraphId: target, offset: 0 };
3611
+ surface.setSelection({ anchor: point, head: point });
3612
+ };
3613
+ element.addEventListener("keydown", onKeyDown, true);
3614
+ return () => element.removeEventListener("keydown", onKeyDown, true);
3615
+ }, [scope]);
3616
+ return null;
3617
+ }
2681
3618
  function QuickParts() {
2682
3619
  const house = useHouseTemplate();
2683
3620
  if (!house || house.blocks.length === 0) return null;
@@ -2848,17 +3785,23 @@ function DefinedTermButtons() {
2848
3785
  }
2849
3786
  function DocxRibbon({
2850
3787
  onSave,
2851
- badge
3788
+ badge,
3789
+ imageGeneration
2852
3790
  }) {
2853
- const [tab, setTab] = useState("Home");
3791
+ const [selected, setTab] = useState("Home");
3792
+ const contextual = useContextualTabs(selected);
3793
+ useGenerateImageTarget();
3794
+ const tab = contextual.shown;
2854
3795
  const t = useChromeTranslate();
2855
3796
  return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon", children: [
2856
3797
  /* @__PURE__ */ jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
2857
3798
  /* @__PURE__ */ jsx("option", { children: "Home" }),
2858
3799
  /* @__PURE__ */ jsx("option", { children: "Insert" }),
2859
3800
  /* @__PURE__ */ jsx("option", { children: "Design" }),
3801
+ /* @__PURE__ */ jsx("option", { children: LAYOUT_TAB }),
2860
3802
  /* @__PURE__ */ jsx("option", { children: "References" }),
2861
- /* @__PURE__ */ jsx("option", { children: "Review" })
3803
+ /* @__PURE__ */ jsx("option", { children: "Review" }),
3804
+ contextual.tabs.map((name) => /* @__PURE__ */ jsx("option", { children: name }, name))
2862
3805
  ] }),
2863
3806
  /* @__PURE__ */ jsxs(DocxEditor.Toolbar, { t, preset: false, onSave, children: [
2864
3807
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Undo, {}),
@@ -2889,10 +3832,14 @@ function DocxRibbon({
2889
3832
  tab === "Insert" && /* @__PURE__ */ jsxs(Fragment, { children: [
2890
3833
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableInsert, {}),
2891
3834
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageInsert, {}),
3835
+ imageGeneration && /* @__PURE__ */ jsx(GenerateImageButton, { generation: imageGeneration }),
3836
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Button, { slotId: "insert.pageBreak" }),
2892
3837
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {}),
2893
3838
  /* @__PURE__ */ jsx(DefinedTermButtons, {}),
2894
3839
  /* @__PURE__ */ jsx(QuickParts, {})
2895
3840
  ] }),
3841
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableCellFill, { hidden: true }),
3842
+ /* @__PURE__ */ jsx(LayoutTabContent, { tab }),
2896
3843
  tab === "Design" && /* @__PURE__ */ jsx(HouseTemplatePanel, {}),
2897
3844
  tab === "References" && /* @__PURE__ */ jsx(NoteButtons, {}),
2898
3845
  tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3239,7 +4186,7 @@ function NativeDocxEditor({
3239
4186
  if (change.source !== "load") changed();
3240
4187
  },
3241
4188
  onFontError: (e) => setError(e.message),
3242
- children: /* @__PURE__ */ jsx(
4189
+ children: /* @__PURE__ */ jsx(DocxAuthorContext.Provider, { value: ready.author, children: /* @__PURE__ */ jsx(
3243
4190
  DefinedTermsProvider,
3244
4191
  {
3245
4192
  load: adapter.definitions,
@@ -3268,6 +4215,10 @@ function NativeDocxEditor({
3268
4215
  DocxRibbon,
3269
4216
  {
3270
4217
  onSave: () => void saveVersion(),
4218
+ imageGeneration: adapter.imageGeneration && {
4219
+ notify: adapter.notify,
4220
+ ...adapter.imageGeneration
4221
+ },
3271
4222
  badge: (
3272
4223
  // Says the document is shared live, in place of a title row above the ribbon.
3273
4224
  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
@@ -3275,6 +4226,7 @@ function NativeDocxEditor({
3275
4226
  }
3276
4227
  ),
3277
4228
  !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
4229
+ !stream && /* @__PURE__ */ jsx(TableTabKeys, { scope: stageRef }),
3278
4230
  /* @__PURE__ */ jsxs(
3279
4231
  "div",
3280
4232
  {
@@ -3312,7 +4264,7 @@ function NativeDocxEditor({
3312
4264
  }
3313
4265
  )
3314
4266
  }
3315
- )
4267
+ ) })
3316
4268
  }
3317
4269
  ) }, retry) }),
3318
4270
  /* @__PURE__ */ jsxs("div", { className: "native-docx-actions", children: [
@@ -3407,6 +4359,6 @@ async function loadFonts(baseUrl) {
3407
4359
  };
3408
4360
  }
3409
4361
 
3410
- export { BUILTIN_TEMPLATE_NAME, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, HouseTemplateProvider, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertDefinedTerm, loadDocxFonts, normalizeDocxExport, resolveHouseTemplate, reviewModule, templateFileName, updateDefinitions, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, withDirectEditing };
3411
- //# sourceMappingURL=chunk-FIEAWH4K.js.map
3412
- //# sourceMappingURL=chunk-FIEAWH4K.js.map
4362
+ 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, updateDefinitions, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
4363
+ //# sourceMappingURL=chunk-72PQ42YI.js.map
4364
+ //# sourceMappingURL=chunk-72PQ42YI.js.map