@arbidocs/blocks 0.4.95 → 0.4.97

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 (106) hide show
  1. package/dist/agents/index.cjs +283 -283
  2. package/dist/agents/index.js +7 -7
  3. package/dist/chat/index.cjs +127 -127
  4. package/dist/chat/index.js +4 -4
  5. package/dist/{chunk-DCZGYCN2.cjs → chunk-3US5OIDQ.cjs} +5 -5
  6. package/dist/{chunk-DCZGYCN2.cjs.map → chunk-3US5OIDQ.cjs.map} +1 -1
  7. package/dist/{chunk-QQH2NI5U.js → chunk-5NQIBJPP.js} +8 -8
  8. package/dist/{chunk-QQH2NI5U.js.map → chunk-5NQIBJPP.js.map} +1 -1
  9. package/dist/{chunk-KBPHLW2R.cjs → chunk-B4SRYJLM.cjs} +3 -3
  10. package/dist/{chunk-KBPHLW2R.cjs.map → chunk-B4SRYJLM.cjs.map} +1 -1
  11. package/dist/chunk-D54JXTPG.cjs +164 -0
  12. package/dist/chunk-D54JXTPG.cjs.map +1 -0
  13. package/dist/{chunk-VQ65UHTV.cjs → chunk-DDYXFALG.cjs} +47 -47
  14. package/dist/{chunk-VQ65UHTV.cjs.map → chunk-DDYXFALG.cjs.map} +1 -1
  15. package/dist/{chunk-RZGVMV6B.cjs → chunk-EHB7J6SR.cjs} +5 -5
  16. package/dist/{chunk-RZGVMV6B.cjs.map → chunk-EHB7J6SR.cjs.map} +1 -1
  17. package/dist/{chunk-EJAYB7PP.js → chunk-EWSEQEPL.js} +4 -4
  18. package/dist/{chunk-EJAYB7PP.js.map → chunk-EWSEQEPL.js.map} +1 -1
  19. package/dist/{chunk-3OXWJ3CZ.cjs → chunk-GVOTXXZG.cjs} +5 -5
  20. package/dist/{chunk-3OXWJ3CZ.cjs.map → chunk-GVOTXXZG.cjs.map} +1 -1
  21. package/dist/{chunk-CKYA2IG5.cjs → chunk-HBF4BDRU.cjs} +3 -3
  22. package/dist/{chunk-CKYA2IG5.cjs.map → chunk-HBF4BDRU.cjs.map} +1 -1
  23. package/dist/{chunk-E2AAIMXO.cjs → chunk-HGJGALLN.cjs} +832 -219
  24. package/dist/chunk-HGJGALLN.cjs.map +1 -0
  25. package/dist/{chunk-24CS7JWU.js → chunk-HOXKR26S.js} +3 -3
  26. package/dist/{chunk-24CS7JWU.js.map → chunk-HOXKR26S.js.map} +1 -1
  27. package/dist/{chunk-PDEHIGJ5.js → chunk-ICBAFX5B.js} +3 -3
  28. package/dist/{chunk-PDEHIGJ5.js.map → chunk-ICBAFX5B.js.map} +1 -1
  29. package/dist/{chunk-OYMYNYFV.cjs → chunk-IF6VAYIA.cjs} +5 -5
  30. package/dist/{chunk-OYMYNYFV.cjs.map → chunk-IF6VAYIA.cjs.map} +1 -1
  31. package/dist/{chunk-TOVSTC2M.cjs → chunk-LENXDIMY.cjs} +5 -5
  32. package/dist/{chunk-TOVSTC2M.cjs.map → chunk-LENXDIMY.cjs.map} +1 -1
  33. package/dist/{chunk-VYKMBLDX.cjs → chunk-MP6FK43E.cjs} +8 -8
  34. package/dist/{chunk-VYKMBLDX.cjs.map → chunk-MP6FK43E.cjs.map} +1 -1
  35. package/dist/{chunk-HUFLBZWI.js → chunk-PGDL26TQ.js} +4 -4
  36. package/dist/{chunk-HUFLBZWI.js.map → chunk-PGDL26TQ.js.map} +1 -1
  37. package/dist/{chunk-WXLKMEMB.js → chunk-QIEEFBZ5.js} +834 -221
  38. package/dist/chunk-QIEEFBZ5.js.map +1 -0
  39. package/dist/{chunk-YPKSLTL5.js → chunk-RSUOOMPT.js} +3 -3
  40. package/dist/{chunk-YPKSLTL5.js.map → chunk-RSUOOMPT.js.map} +1 -1
  41. package/dist/{chunk-UMY6Y6BM.js → chunk-VLOPZHFH.js} +3 -3
  42. package/dist/{chunk-UMY6Y6BM.js.map → chunk-VLOPZHFH.js.map} +1 -1
  43. package/dist/{chunk-GNHOB2ZB.cjs → chunk-XMBYDLZQ.cjs} +4 -4
  44. package/dist/{chunk-GNHOB2ZB.cjs.map → chunk-XMBYDLZQ.cjs.map} +1 -1
  45. package/dist/{chunk-YG7DGGCA.js → chunk-XQDM6T6C.js} +3 -3
  46. package/dist/{chunk-YG7DGGCA.js.map → chunk-XQDM6T6C.js.map} +1 -1
  47. package/dist/{chunk-ZNDAXSDP.js → chunk-YH6SSJDW.js} +3 -3
  48. package/dist/{chunk-ZNDAXSDP.js.map → chunk-YH6SSJDW.js.map} +1 -1
  49. package/dist/{chunk-EOEDGJ75.js → chunk-Z3G65XHZ.js} +3 -3
  50. package/dist/{chunk-EOEDGJ75.js.map → chunk-Z3G65XHZ.js.map} +1 -1
  51. package/dist/chunk-ZCOVDK2E.js +153 -0
  52. package/dist/chunk-ZCOVDK2E.js.map +1 -0
  53. package/dist/components/DetailPanel.d.ts +106 -0
  54. package/dist/connect/docx.cjs +6 -6
  55. package/dist/connect/docx.js +4 -4
  56. package/dist/connect/index.cjs +47 -47
  57. package/dist/connect/index.js +17 -17
  58. package/dist/databases/DatabaseRowDetail.d.ts +32 -0
  59. package/dist/databases/DatabaseTable.d.ts +3 -1
  60. package/dist/databases/DatabaseToolbar.d.ts +9 -1
  61. package/dist/databases/PickCellEditor.d.ts +8 -1
  62. package/dist/databases/SelectCellEditor.d.ts +6 -1
  63. package/dist/databases/column-types.d.ts +5 -0
  64. package/dist/databases/databaseColumns.d.ts +7 -0
  65. package/dist/databases/index.cjs +381 -11
  66. package/dist/databases/index.cjs.map +1 -1
  67. package/dist/databases/index.d.ts +7 -5
  68. package/dist/databases/index.js +379 -14
  69. package/dist/databases/index.js.map +1 -1
  70. package/dist/databases/linkCells.d.ts +4 -2
  71. package/dist/docx/NativeDocxEditor.d.ts +3 -1
  72. package/dist/docx/RibbonMenu.d.ts +15 -0
  73. package/dist/docx/StyleGallery.d.ts +9 -0
  74. package/dist/docx/WordReferences.d.ts +21 -0
  75. package/dist/docx/index.cjs +24 -24
  76. package/dist/docx/index.js +3 -3
  77. package/dist/docx/review/DocxEditorReview.d.ts +4 -2
  78. package/dist/docx/review/commentText.d.ts +7 -0
  79. package/dist/docx/wordStyles.d.ts +21 -0
  80. package/dist/facts/index.cjs +5 -5
  81. package/dist/facts/index.js +2 -2
  82. package/dist/files/index.cjs +308 -0
  83. package/dist/files/index.cjs.map +1 -1
  84. package/dist/files/index.js +298 -2
  85. package/dist/files/index.js.map +1 -1
  86. package/dist/files/views/gridViews.d.ts +80 -0
  87. package/dist/files/views/index.d.ts +2 -0
  88. package/dist/files/views/useGridViews.d.ts +38 -0
  89. package/dist/index.cjs +1237 -1237
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.js +100 -139
  93. package/dist/index.js.map +1 -1
  94. package/dist/settings/index.cjs +1 -1
  95. package/dist/settings/index.js +1 -1
  96. package/dist/shell/index.cjs +67 -67
  97. package/dist/shell/index.js +3 -3
  98. package/dist/studio/index.cjs +7 -7
  99. package/dist/studio/index.js +6 -6
  100. package/dist/tags/TagDetail.d.ts +1 -0
  101. package/dist/workflow-canvas/index.cjs +51 -51
  102. package/dist/workflow-canvas/index.js +8 -8
  103. package/package.json +4 -4
  104. package/src/docx/styles.css +564 -91
  105. package/dist/chunk-E2AAIMXO.cjs.map +0 -1
  106. package/dist/chunk-WXLKMEMB.js.map +0 -1
@@ -1,18 +1,18 @@
1
1
  "use client";
2
2
  import { stylesXml, definitionBookmark, DEFINITION_BOOKMARK_PREFIX, compareTerms, groupDefinitions, definitionLabels, filterTermGroups, toDefinition, definitionIdOfBookmark, esc, parseDefinition, docxPackage, documentXml } from './chunk-D3QPYKZG.js';
3
- import { filterMentionCandidates, MentionMenu, findMentionQuery, listOptionId, extractMentions } from './chunk-AA3PT6KR.js';
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
6
  import React3, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
7
- import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useChromeTranslate, DocxEditor, createFontSource } from '@docx-editor.dev/react';
7
+ import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, useEditorCommand } from '@docx-editor.dev/react';
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, Superscript, NotebookText } from 'lucide-react';
8
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
11
  import { createPortal } from 'react-dom';
10
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Button, DialogFooter, cn, splitMarkdownBlocks, AiMarkdown } from '@arbidocs/react/ui';
11
12
  import { renderToStaticMarkup } from 'react-dom/server';
12
13
  import * as Y from 'yjs';
13
14
  import { DOCUMENT_COLLABORATION_VERSIONS, collaborationModule } from '@arbidocs/coedit';
14
15
  import { createHocuspocusCollaboration } from '@arbidocs/coedit/hocuspocus';
15
- import { Type, Ellipsis, RefreshCw, WifiOff, Check, Download, Save, BookA, ListRestart } from 'lucide-react';
16
16
 
17
17
  function normalizeDocxExport(data) {
18
18
  const files = unzipSync(new Uint8Array(data));
@@ -136,7 +136,25 @@ var ReviewItemContext = createContext(null);
136
136
  function useReviewItem() {
137
137
  return useContext(ReviewItemContext);
138
138
  }
139
+
140
+ // src/docx/review/commentText.ts
141
+ var SKIP = /* @__PURE__ */ new Set(["delText", "instrText", "delInstrText", "pPr", "rPr"]);
142
+ function inline(node) {
143
+ if (node.kind === "textValue") return node.value ?? "";
144
+ if (node.localName && SKIP.has(node.localName)) return "";
145
+ if (node.localName === "br" || node.localName === "cr") return "\n";
146
+ if (node.localName === "tab") return " ";
147
+ return (node.children ?? []).map(inline).join("");
148
+ }
149
+ function commentText(entry) {
150
+ if (entry.kind !== "comment") return entry.text;
151
+ const blocks = entry.item?.comment?.blocks ?? [];
152
+ const text = blocks.map(inline).join("\n").trim();
153
+ return text || entry.text;
154
+ }
139
155
  var CARD_GAP = 8;
156
+ var CLUSTER_GAP = 24;
157
+ var REVIEW_MARGIN_MIN_WIDTH = 900;
140
158
  var REVISION_LABELS = {
141
159
  insert: "Inserted",
142
160
  delete: "Deleted",
@@ -152,33 +170,102 @@ function isTopLevel(entry) {
152
170
  if (entry.kind === "custom") return entry.item.carded;
153
171
  return true;
154
172
  }
155
- function formatDate(value) {
156
- if (!value) return "";
173
+ function kindLabel(entry) {
174
+ if (entry.kind === "revision") return REVISION_LABELS[entry.revisionKind] ?? "Change";
175
+ if (entry.kind === "comment") return entry.resolved ? "Resolved" : "Comment";
176
+ return "Note";
177
+ }
178
+ function parseDate(value) {
179
+ if (!value) return null;
157
180
  const date = new Date(value);
158
- return Number.isNaN(date.getTime()) ? "" : date.toLocaleString();
181
+ return Number.isNaN(date.getTime()) ? null : date;
182
+ }
183
+ function relativeDate(date) {
184
+ const seconds = (Date.now() - date.getTime()) / 1e3;
185
+ if (seconds < 45) return "just now";
186
+ if (seconds < 45 * 60) return `${Math.round(seconds / 60)}m ago`;
187
+ if (seconds < 22 * 3600) return `${Math.round(seconds / 3600)}h ago`;
188
+ if (seconds < 7 * 86400) return `${Math.round(seconds / 86400)}d ago`;
189
+ const sameYear = date.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear();
190
+ return date.toLocaleDateString(void 0, {
191
+ day: "numeric",
192
+ month: "short",
193
+ year: sameYear ? void 0 : "numeric"
194
+ });
195
+ }
196
+ function authorHue(author) {
197
+ let hash = 0;
198
+ for (const char of author) hash = hash * 31 + char.charCodeAt(0) | 0;
199
+ return Math.abs(hash) % 360;
159
200
  }
160
201
  function Avatar({ entry }) {
161
- return /* @__PURE__ */ jsx("span", { className: "native-docx-review-avatar", "aria-hidden": "true", children: entry.initials });
202
+ return /* @__PURE__ */ jsx(
203
+ "span",
204
+ {
205
+ className: "native-docx-review-avatar",
206
+ "aria-hidden": "true",
207
+ style: { "--review-author-hue": authorHue(entry.author || "") },
208
+ children: entry.initials
209
+ }
210
+ );
162
211
  }
163
- function Message({ entry }) {
164
- return /* @__PURE__ */ jsxs("div", { className: "native-docx-review-message", children: [
165
- /* @__PURE__ */ jsxs("div", { className: "native-docx-review-byline", children: [
166
- /* @__PURE__ */ jsx(Avatar, { entry }),
212
+ function Byline({ entry, children }) {
213
+ const date = parseDate(entry.date);
214
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-review-byline", children: [
215
+ /* @__PURE__ */ jsx(Avatar, { entry }),
216
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-review-who", children: [
167
217
  /* @__PURE__ */ jsx("span", { className: "native-docx-review-author", children: entry.author || "Reviewer" }),
168
- /* @__PURE__ */ jsx("time", { className: "native-docx-review-date", children: formatDate(entry.date) })
218
+ /* @__PURE__ */ jsxs("span", { className: "native-docx-review-meta", children: [
219
+ kindLabel(entry),
220
+ date && /* @__PURE__ */ jsxs(Fragment, { children: [
221
+ " \xB7 ",
222
+ /* @__PURE__ */ jsx(
223
+ "time",
224
+ {
225
+ className: "native-docx-review-date",
226
+ dateTime: date.toISOString(),
227
+ title: date.toLocaleString(),
228
+ children: relativeDate(date)
229
+ }
230
+ )
231
+ ] })
232
+ ] })
233
+ ] }),
234
+ children
235
+ ] });
236
+ }
237
+ function ChangeText({ entry }) {
238
+ const removed = entry.revisionKind === "replace" ? entry.replacedText : void 0;
239
+ const struck = entry.revisionKind === "delete" || entry.revisionKind === "moveFrom";
240
+ const added = entry.revisionKind === "insert" || entry.revisionKind === "moveTo" || removed !== void 0;
241
+ return /* @__PURE__ */ jsxs("p", { className: "native-docx-review-change", "data-testid": "review-text", children: [
242
+ removed && /* @__PURE__ */ jsxs(Fragment, { children: [
243
+ /* @__PURE__ */ jsx("del", { children: removed }),
244
+ /* @__PURE__ */ jsx("span", { className: "native-docx-review-arrow", "aria-hidden": "true", children: "\u2192" })
169
245
  ] }),
170
- /* @__PURE__ */ jsx("p", { className: "native-docx-review-text", "data-testid": "review-text", children: entry.text })
246
+ struck ? /* @__PURE__ */ jsx("del", { children: entry.text }) : added ? /* @__PURE__ */ jsx("ins", { children: entry.text }) : /* @__PURE__ */ jsx("span", { children: entry.text })
247
+ ] });
248
+ }
249
+ function CommentText({ text, testId }) {
250
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-review-text", "data-testid": testId, children: /* @__PURE__ */ jsx(AiMarkdown, { className: "native-docx-review-markdown", allowHtml: false, children: text }) });
251
+ }
252
+ function Reply({ entry }) {
253
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-review-message", children: [
254
+ /* @__PURE__ */ jsx(Byline, { entry }),
255
+ /* @__PURE__ */ jsx(CommentText, { text: commentText(entry) })
171
256
  ] });
172
257
  }
173
258
  function ReplyForm({
174
259
  entry,
175
- review
260
+ review,
261
+ onDismiss
176
262
  }) {
177
263
  const [text, setText] = useState("");
178
264
  return /* @__PURE__ */ jsxs(
179
265
  "form",
180
266
  {
181
267
  className: "native-docx-review-reply",
268
+ onClick: (event) => event.stopPropagation(),
182
269
  onSubmit: (event) => {
183
270
  event.preventDefault();
184
271
  const value = text.trim();
@@ -190,25 +277,39 @@ function ReplyForm({
190
277
  {
191
278
  "data-testid": "review-reply-input",
192
279
  "aria-label": "Reply",
193
- placeholder: "Reply",
280
+ placeholder: "Reply\u2026",
194
281
  value: text,
195
282
  onChange: (event) => setText(event.target.value),
196
283
  onKeyDown: (event) => {
197
284
  if (event.key === "Escape") {
285
+ event.preventDefault();
198
286
  event.stopPropagation();
199
- review.setActive(null);
287
+ onDismiss();
200
288
  }
201
289
  }
202
290
  }
203
291
  ),
204
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: !text.trim(), children: "Reply" }),
292
+ /* @__PURE__ */ jsx(
293
+ "button",
294
+ {
295
+ type: "submit",
296
+ className: "native-docx-review-icon native-docx-review-send",
297
+ "aria-label": "Reply",
298
+ title: "Reply",
299
+ disabled: !text.trim(),
300
+ children: /* @__PURE__ */ jsx(SendHorizontal, { size: 14 })
301
+ }
302
+ ),
205
303
  /* @__PURE__ */ jsx(
206
304
  "button",
207
305
  {
208
306
  type: "button",
307
+ className: "native-docx-review-icon",
209
308
  "data-testid": "review-reply-cancel",
210
- onClick: () => review.setActive(null),
211
- children: "Cancel"
309
+ "aria-label": "Cancel",
310
+ title: "Cancel",
311
+ onClick: onDismiss,
312
+ children: /* @__PURE__ */ jsx(X, { size: 14 })
212
313
  }
213
314
  )
214
315
  ]
@@ -221,7 +322,8 @@ function Card({
221
322
  review,
222
323
  extra,
223
324
  cardRef,
224
- top
325
+ top,
326
+ onDismiss
225
327
  }) {
226
328
  const resolved = entry.kind === "comment" && entry.resolved;
227
329
  return /* @__PURE__ */ jsx(ReviewItemContext.Provider, { value: entry, children: /* @__PURE__ */ jsxs(
@@ -240,60 +342,85 @@ function Card({
240
342
  },
241
343
  "data-testid": "review-card",
242
344
  "data-kind": entry.kind,
345
+ "data-change": entry.kind === "revision" ? entry.revisionKind : void 0,
243
346
  "data-active": entry.isActive || void 0,
244
347
  "data-resolved": resolved || void 0,
245
- style: { top },
348
+ "data-floating": top === void 0 || void 0,
349
+ style: top === void 0 ? void 0 : { top },
246
350
  onClick: () => {
247
351
  if (entry.activatable && !entry.isActive) review.setActive(entry.key);
248
352
  },
249
353
  children: [
250
- entry.kind === "revision" && /* @__PURE__ */ jsx("div", { className: "native-docx-review-kind", children: REVISION_LABELS[entry.revisionKind] ?? "Change" }),
251
- /* @__PURE__ */ jsx(Message, { entry }),
252
- replies.map((reply) => /* @__PURE__ */ jsx(Message, { entry: reply }, reply.key)),
354
+ /* @__PURE__ */ jsx(Byline, { entry, children: entry.kind === "comment" && /* @__PURE__ */ jsxs("div", { className: "native-docx-review-tools", children: [
355
+ /* @__PURE__ */ jsx(
356
+ "button",
357
+ {
358
+ type: "button",
359
+ className: "native-docx-review-icon",
360
+ "data-testid": "review-resolve",
361
+ "aria-label": entry.resolved ? "Reopen" : "Resolve",
362
+ title: entry.resolved ? "Reopen" : "Resolve",
363
+ onClick: (event) => {
364
+ event.stopPropagation();
365
+ review.resolve(entry, !entry.resolved);
366
+ },
367
+ children: entry.resolved ? /* @__PURE__ */ jsx(RotateCcw, { size: 14 }) : /* @__PURE__ */ jsx(CircleCheck, { size: 14 })
368
+ }
369
+ ),
370
+ /* @__PURE__ */ jsx(
371
+ "button",
372
+ {
373
+ type: "button",
374
+ className: "native-docx-review-icon",
375
+ "data-testid": "review-delete",
376
+ "aria-label": "Delete",
377
+ title: "Delete",
378
+ onClick: (event) => {
379
+ event.stopPropagation();
380
+ review.remove(entry);
381
+ },
382
+ children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
383
+ }
384
+ )
385
+ ] }) }),
386
+ entry.kind === "revision" ? /* @__PURE__ */ jsx(ChangeText, { entry }) : /* @__PURE__ */ jsx(CommentText, { text: commentText(entry), testId: "review-text" }),
387
+ replies.length > 0 && /* @__PURE__ */ jsx("div", { className: "native-docx-review-thread", children: replies.map((reply) => /* @__PURE__ */ jsx(Reply, { entry: reply }, reply.key)) }),
253
388
  extra,
254
- /* @__PURE__ */ jsxs("div", { className: "native-docx-review-actions", children: [
255
- entry.kind === "revision" && !entry.readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
256
- /* @__PURE__ */ jsx(
257
- "button",
258
- {
259
- type: "button",
260
- "data-testid": "review-accept",
261
- onClick: () => review.accept(entry),
262
- children: "Accept"
263
- }
264
- ),
265
- /* @__PURE__ */ jsx(
266
- "button",
267
- {
268
- type: "button",
269
- "data-testid": "review-reject",
270
- onClick: () => review.reject(entry),
271
- children: "Reject"
272
- }
273
- )
274
- ] }),
275
- entry.kind === "comment" && /* @__PURE__ */ jsxs(Fragment, { children: [
276
- /* @__PURE__ */ jsx(
277
- "button",
278
- {
279
- type: "button",
280
- "data-testid": "review-resolve",
281
- onClick: () => review.resolve(entry, !entry.resolved),
282
- children: entry.resolved ? "Reopen" : "Resolve"
283
- }
284
- ),
285
- /* @__PURE__ */ jsx(
286
- "button",
287
- {
288
- type: "button",
289
- "data-testid": "review-delete",
290
- onClick: () => review.remove(entry),
291
- children: "Delete"
292
- }
293
- )
294
- ] })
389
+ entry.kind === "revision" && !entry.readOnly && /* @__PURE__ */ jsxs("div", { className: "native-docx-review-decide", children: [
390
+ /* @__PURE__ */ jsxs(
391
+ "button",
392
+ {
393
+ type: "button",
394
+ className: "native-docx-review-accept",
395
+ "data-testid": "review-accept",
396
+ onClick: (event) => {
397
+ event.stopPropagation();
398
+ review.accept(entry);
399
+ },
400
+ children: [
401
+ /* @__PURE__ */ jsx(Check, { size: 14 }),
402
+ "Accept"
403
+ ]
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsxs(
407
+ "button",
408
+ {
409
+ type: "button",
410
+ className: "native-docx-review-reject",
411
+ "data-testid": "review-reject",
412
+ onClick: (event) => {
413
+ event.stopPropagation();
414
+ review.reject(entry);
415
+ },
416
+ children: [
417
+ /* @__PURE__ */ jsx(X, { size: 14 }),
418
+ "Reject"
419
+ ]
420
+ }
421
+ )
295
422
  ] }),
296
- entry.isActive && entry.kind !== "custom" && /* @__PURE__ */ jsx(ReplyForm, { entry, review })
423
+ entry.isActive && entry.kind !== "custom" && /* @__PURE__ */ jsx(ReplyForm, { entry, review, onDismiss })
297
424
  ]
298
425
  }
299
426
  ) });
@@ -313,7 +440,8 @@ function Draft({
313
440
  ref: formRef,
314
441
  className: "native-docx-review-card native-docx-review-draft",
315
442
  "data-testid": "review-draft",
316
- style: { top },
443
+ "data-floating": top === void 0 || void 0,
444
+ style: top === void 0 ? void 0 : { top },
317
445
  onSubmit: (event) => {
318
446
  event.preventDefault();
319
447
  if (text.trim()) onSubmit(text.trim());
@@ -325,22 +453,53 @@ function Draft({
325
453
  ref: input,
326
454
  "data-testid": "review-draft-input",
327
455
  "aria-label": "Comment",
328
- placeholder: "Add a comment",
456
+ placeholder: "Add a comment\u2026",
329
457
  value: text,
330
458
  onChange: (event) => setText(event.target.value),
331
459
  onKeyDown: (event) => {
332
- if (event.key === "Escape") onCancel();
460
+ if (event.key === "Escape") {
461
+ event.preventDefault();
462
+ event.stopPropagation();
463
+ onCancel();
464
+ }
333
465
  }
334
466
  }
335
467
  ),
336
- /* @__PURE__ */ jsxs("div", { className: "native-docx-review-actions", children: [
337
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: !text.trim(), children: "Comment" }),
338
- /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "review-draft-cancel", onClick: onCancel, children: "Cancel" })
468
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-review-decide", children: [
469
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "native-docx-review-accept", disabled: !text.trim(), children: "Comment" }),
470
+ /* @__PURE__ */ jsx(
471
+ "button",
472
+ {
473
+ type: "button",
474
+ className: "native-docx-review-plain",
475
+ "data-testid": "review-draft-cancel",
476
+ onClick: onCancel,
477
+ children: "Cancel"
478
+ }
479
+ )
339
480
  ] })
340
481
  ]
341
482
  }
342
483
  );
343
484
  }
485
+ function useScrollArea(anchor, ready) {
486
+ const [area, setArea] = useState({
487
+ element: null,
488
+ width: 0
489
+ });
490
+ useLayoutEffect(() => {
491
+ const element = anchor.current?.closest(".docx-editor__scroll-container") ?? null;
492
+ const measure = () => setArea(
493
+ (previous) => previous.element === element && previous.width === (element?.clientWidth ?? 0) ? previous : { element, width: element?.clientWidth ?? 0 }
494
+ );
495
+ measure();
496
+ if (!element || typeof ResizeObserver === "undefined") return;
497
+ const observer = new ResizeObserver(measure);
498
+ observer.observe(element);
499
+ return () => observer.disconnect();
500
+ }, [anchor, ready]);
501
+ return area;
502
+ }
344
503
  function DocxEditorReviewRail({
345
504
  children
346
505
  }) {
@@ -351,6 +510,10 @@ function DocxEditorReviewRail({
351
510
  const [tops, setTops] = useState({});
352
511
  const [showResolved, setShowResolved] = useState(true);
353
512
  const elements = useRef(/* @__PURE__ */ new Map());
513
+ const anchor = useRef(null);
514
+ const toolbar = useRef(null);
515
+ const area = useScrollArea(anchor, review.ready);
516
+ const layout = area.width === 0 || area.width >= REVIEW_MARGIN_MIN_WIDTH ? "margin" : "compact";
354
517
  const openDraft = useCallback(() => {
355
518
  const placement = editor?.getSelectionPlacement();
356
519
  if (!placement) return;
@@ -359,9 +522,22 @@ function DocxEditorReviewRail({
359
522
  }, [editor, review.setPaneOpen]);
360
523
  useEffect(() => registry?.register(), [registry]);
361
524
  useEffect(() => registry?.registerCommentDraft(openDraft), [registry, openDraft]);
525
+ const shownLayout = useRef(null);
526
+ useEffect(() => {
527
+ if (!review.ready || area.width === 0 || shownLayout.current === layout) return;
528
+ editor?.setReviewPaneOptions({ opening: layout === "margin" ? "auto" : "manual" });
529
+ const deepLinked = shownLayout.current === null && review.activeKey !== null;
530
+ shownLayout.current = layout;
531
+ if (layout === "margin") review.setPaneOpen(true);
532
+ else if (!deepLinked) {
533
+ setDraft(null);
534
+ review.setPaneOpen(false);
535
+ }
536
+ }, [editor, layout, area.width, review.ready, review.setPaneOpen]);
362
537
  const scale = editor?.getRenderScale() ?? 1;
363
538
  const top = review.items.filter(isTopLevel).filter((entry) => entry.anchorY !== null).filter((entry) => showResolved || entry.kind !== "comment" || !entry.resolved);
364
- const hasChanges = review.items.some((entry) => entry.kind === "revision");
539
+ const changes = review.items.filter((entry) => entry.kind === "revision").length;
540
+ const comments = top.filter((entry) => entry.kind === "comment").length;
365
541
  const hasResolved = review.items.some((entry) => entry.kind === "comment" && entry.resolved);
366
542
  const repliesOf = useCallback(
367
543
  (entry) => review.items.filter(
@@ -369,10 +545,49 @@ function DocxEditorReviewRail({
369
545
  ),
370
546
  [review.items]
371
547
  );
548
+ const column = layout === "margin" && review.paneOpen && (top.length > 0 || draft !== null);
549
+ const bubbleOpen = layout === "compact" && (review.paneOpen || draft !== null);
550
+ useEffect(() => {
551
+ const element = area.element;
552
+ if (!element) return;
553
+ if (column) element.setAttribute("data-review-margin", "");
554
+ else element.removeAttribute("data-review-margin");
555
+ return () => element.removeAttribute("data-review-margin");
556
+ }, [area.element, column]);
557
+ const dismiss = useCallback(() => {
558
+ setDraft(null);
559
+ if (layout === "compact") review.setPaneOpen(false);
560
+ else review.setActive(null);
561
+ }, [layout, review.setPaneOpen, review.setActive]);
562
+ useEffect(() => {
563
+ const element = area.element;
564
+ if (!element || !bubbleOpen) return;
565
+ const close = (event) => {
566
+ const target = event.target;
567
+ if (target instanceof Element && target.closest(
568
+ ".native-docx-review-bubble, .native-docx-review-marker, .native-docx-review-add"
569
+ ))
570
+ return;
571
+ dismiss();
572
+ };
573
+ const escape = (event) => {
574
+ if (event.key !== "Escape" || event.defaultPrevented) return;
575
+ if (!(event.target instanceof Node) || !element.contains(event.target)) return;
576
+ event.preventDefault();
577
+ dismiss();
578
+ };
579
+ const document2 = element.ownerDocument;
580
+ element.addEventListener("pointerdown", close);
581
+ document2.addEventListener("keydown", escape);
582
+ return () => {
583
+ element.removeEventListener("pointerdown", close);
584
+ document2.removeEventListener("keydown", escape);
585
+ };
586
+ }, [area.element, bubbleOpen, dismiss]);
372
587
  useLayoutEffect(() => {
373
- if (!review.paneOpen) return;
588
+ if (!column) return;
374
589
  const next = {};
375
- let floor = 0;
590
+ let floor = (toolbar.current?.offsetHeight ?? 0) + CARD_GAP;
376
591
  const rows = top.map((entry) => ({
377
592
  key: entry.key,
378
593
  y: (entry.anchorY ?? 0) * scale
@@ -390,7 +605,7 @@ function DocxEditorReviewRail({
390
605
  return same ? previous : next;
391
606
  });
392
607
  });
393
- if (!review.ready) return null;
608
+ if (!review.ready) return /* @__PURE__ */ jsx("div", { ref: anchor, className: "native-docx-review" });
394
609
  const selection = editor?.getSelectionPlacement();
395
610
  const addComment = selection && /* @__PURE__ */ jsx(
396
611
  "button",
@@ -399,108 +614,254 @@ function DocxEditorReviewRail({
399
614
  className: "native-docx-review-add",
400
615
  "data-testid": "review-add-comment",
401
616
  "aria-label": "Add comment",
617
+ title: "Add comment",
402
618
  style: { top: selection.anchorY * scale },
403
619
  onMouseDown: (event) => event.preventDefault(),
404
620
  onClick: openDraft,
405
- children: "+"
621
+ children: /* @__PURE__ */ jsx(MessageSquarePlus, { size: 14 })
406
622
  }
407
623
  );
408
- if (!review.paneOpen) {
409
- return /* @__PURE__ */ jsxs("div", { className: "native-docx-review-markers", "data-testid": "review-markers", children: [
624
+ if (column) {
625
+ return /* @__PURE__ */ jsx("div", { ref: anchor, className: "native-docx-review", children: /* @__PURE__ */ jsxs("aside", { className: "native-docx-review-pane", "data-testid": "review-pane", "aria-label": "Review", children: [
410
626
  addComment,
627
+ /* @__PURE__ */ jsxs("div", { ref: toolbar, className: "native-docx-review-toolbar", "data-testid": "review-toolbar", children: [
628
+ /* @__PURE__ */ jsx("span", { className: "native-docx-review-summary", children: [
629
+ changes && `${changes} ${changes === 1 ? "change" : "changes"}`,
630
+ comments && `${comments} ${comments === 1 ? "comment" : "comments"}`
631
+ ].filter(Boolean).join(" \xB7 ") || "Review" }),
632
+ hasResolved && /* @__PURE__ */ jsxs("label", { className: "native-docx-review-switch", children: [
633
+ /* @__PURE__ */ jsx(
634
+ "input",
635
+ {
636
+ type: "checkbox",
637
+ "data-testid": "review-show-resolved",
638
+ checked: showResolved,
639
+ onChange: (event) => setShowResolved(event.target.checked)
640
+ }
641
+ ),
642
+ "Resolved"
643
+ ] }),
644
+ changes > 0 && /* @__PURE__ */ jsx(BulkActions, { review, navigate: true })
645
+ ] }),
646
+ draft && /* @__PURE__ */ jsx(
647
+ Draft,
648
+ {
649
+ top: tops.__draft ?? draft.anchorY * scale,
650
+ formRef: (element) => {
651
+ if (element) elements.current.set("__draft", element);
652
+ },
653
+ onCancel: () => setDraft(null),
654
+ onSubmit: (text) => {
655
+ if (review.addComment(text)) setDraft(null);
656
+ }
657
+ }
658
+ ),
411
659
  top.map((entry) => /* @__PURE__ */ jsx(
660
+ Card,
661
+ {
662
+ entry,
663
+ replies: repliesOf(entry),
664
+ review,
665
+ extra: children?.(entry),
666
+ top: tops[entry.key] ?? (entry.anchorY ?? 0) * scale,
667
+ onDismiss: dismiss,
668
+ cardRef: (element) => {
669
+ if (element) elements.current.set(entry.key, element);
670
+ else elements.current.delete(entry.key);
671
+ }
672
+ },
673
+ entry.key
674
+ ))
675
+ ] }) });
676
+ }
677
+ const clusters = [];
678
+ for (const entry of [...top].sort((a, b) => (a.anchorY ?? 0) - (b.anchorY ?? 0))) {
679
+ const y = (entry.anchorY ?? 0) * scale;
680
+ const last = clusters[clusters.length - 1];
681
+ if (last && y - last.y < CLUSTER_GAP) last.entries.push(entry);
682
+ else clusters.push({ y, entries: [entry] });
683
+ }
684
+ const shown = bubbleOpen ? draft ? null : clusters.find((cluster) => cluster.entries.some((entry) => entry.isActive)) ?? clusters[0] : void 0;
685
+ const focus = shown?.entries.find((entry) => entry.isActive) ?? shown?.entries[0];
686
+ const position = focus ? top.indexOf(focus) : -1;
687
+ const step = (offset) => {
688
+ const next = top[position + offset];
689
+ if (next) review.setActive(next.key);
690
+ };
691
+ return /* @__PURE__ */ jsx("div", { ref: anchor, className: "native-docx-review", children: /* @__PURE__ */ jsxs("div", { className: "native-docx-review-markers", "data-testid": "review-markers", children: [
692
+ addComment,
693
+ clusters.map((cluster) => {
694
+ const [first] = cluster.entries;
695
+ const comment = cluster.entries.some((entry) => entry.kind === "comment");
696
+ return /* @__PURE__ */ jsxs(
412
697
  "button",
413
698
  {
414
699
  type: "button",
415
700
  className: "native-docx-review-marker",
416
701
  "data-testid": "review-marker",
417
- "data-kind": entry.kind,
418
- "aria-label": `${entry.author}: ${entry.text.slice(0, 60)}`,
419
- style: { top: (entry.anchorY ?? 0) * scale },
702
+ "data-kind": comment ? "comment" : first.kind,
703
+ "data-active": cluster === shown || void 0,
704
+ "aria-label": cluster.entries.length === 1 ? `${first.author}: ${first.text.slice(0, 60)}` : `${cluster.entries.length} comments and changes`,
705
+ "aria-expanded": layout === "compact" ? cluster === shown : void 0,
706
+ style: { top: cluster.y },
420
707
  onClick: () => {
708
+ if (layout === "compact" && cluster === shown) return dismiss();
709
+ setDraft(null);
421
710
  review.setPaneOpen(true);
422
- review.setActive(entry.key);
423
- }
711
+ review.setActive(first.key);
712
+ },
713
+ children: [
714
+ comment ? /* @__PURE__ */ jsx(MessageSquare, { size: 13 }) : /* @__PURE__ */ jsx(PencilLine, { size: 13 }),
715
+ cluster.entries.length > 1 && /* @__PURE__ */ jsx("span", { className: "native-docx-review-count", children: cluster.entries.length })
716
+ ]
424
717
  },
425
- entry.key
426
- ))
427
- ] });
428
- }
429
- return /* @__PURE__ */ jsxs("aside", { className: "native-docx-review-pane", "data-testid": "review-pane", "aria-label": "Review", children: [
430
- addComment,
431
- /* @__PURE__ */ jsxs("div", { className: "native-docx-review-toolbar", "data-testid": "review-toolbar", children: [
432
- hasChanges && /* @__PURE__ */ jsxs(Fragment, { children: [
433
- /* @__PURE__ */ jsx(
434
- "button",
435
- {
436
- type: "button",
437
- "data-testid": "review-previous",
438
- onClick: () => review.navigate("previous"),
439
- children: "Previous change"
440
- }
441
- ),
442
- /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "review-next", onClick: () => review.navigate("next"), children: "Next change" }),
443
- /* @__PURE__ */ jsx(
444
- "button",
445
- {
446
- type: "button",
447
- "data-testid": "review-accept-all",
448
- onClick: () => review.resolveAll("accept"),
449
- children: "Accept all"
450
- }
451
- ),
452
- /* @__PURE__ */ jsx(
453
- "button",
454
- {
455
- type: "button",
456
- "data-testid": "review-reject-all",
457
- onClick: () => review.resolveAll("reject"),
458
- children: "Reject all"
718
+ first.key
719
+ );
720
+ }),
721
+ bubbleOpen && (draft || shown || top.length === 0) && /* @__PURE__ */ jsx(
722
+ "div",
723
+ {
724
+ className: "native-docx-review-bubble",
725
+ "data-testid": "review-bubble",
726
+ role: "dialog",
727
+ "data-state": "open",
728
+ "aria-label": "Review",
729
+ style: {
730
+ top: draft ? draft.anchorY * scale : shown?.y ?? 0,
731
+ width: Math.max(220, Math.min(340, area.width - 56))
732
+ },
733
+ onKeyDown: (event) => {
734
+ if (event.key === "Escape") {
735
+ event.preventDefault();
736
+ event.stopPropagation();
737
+ dismiss();
459
738
  }
460
- )
461
- ] }),
462
- hasResolved && /* @__PURE__ */ jsxs("label", { children: [
463
- /* @__PURE__ */ jsx(
464
- "input",
739
+ },
740
+ children: draft ? /* @__PURE__ */ jsx(
741
+ Draft,
465
742
  {
466
- type: "checkbox",
467
- "data-testid": "review-show-resolved",
468
- checked: showResolved,
469
- onChange: (event) => setShowResolved(event.target.checked)
743
+ onCancel: dismiss,
744
+ onSubmit: (text) => {
745
+ if (review.addComment(text)) setDraft(null);
746
+ }
470
747
  }
471
- ),
472
- "Show resolved"
473
- ] })
748
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
749
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-review-bubble-head", children: [
750
+ top.length > 1 && position >= 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
751
+ /* @__PURE__ */ jsx(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ className: "native-docx-review-icon",
756
+ "data-testid": "review-bubble-previous",
757
+ "aria-label": "Previous",
758
+ disabled: position === 0,
759
+ onClick: () => step(-1),
760
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsxs("span", { className: "native-docx-review-summary", children: [
764
+ position + 1,
765
+ " of ",
766
+ top.length
767
+ ] }),
768
+ /* @__PURE__ */ jsx(
769
+ "button",
770
+ {
771
+ type: "button",
772
+ className: "native-docx-review-icon",
773
+ "data-testid": "review-bubble-next",
774
+ "aria-label": "Next",
775
+ disabled: position === top.length - 1,
776
+ onClick: () => step(1),
777
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
778
+ }
779
+ )
780
+ ] }),
781
+ /* @__PURE__ */ jsx(
782
+ "button",
783
+ {
784
+ type: "button",
785
+ className: "native-docx-review-icon native-docx-review-close",
786
+ "data-testid": "review-bubble-close",
787
+ "aria-label": "Close",
788
+ onClick: dismiss,
789
+ children: /* @__PURE__ */ jsx(X, { size: 14 })
790
+ }
791
+ )
792
+ ] }),
793
+ shown?.entries.map((entry) => /* @__PURE__ */ jsx(
794
+ Card,
795
+ {
796
+ entry,
797
+ replies: repliesOf(entry),
798
+ review,
799
+ extra: children?.(entry),
800
+ onDismiss: dismiss
801
+ },
802
+ entry.key
803
+ )),
804
+ !shown && /* @__PURE__ */ jsx("p", { className: "native-docx-review-empty", "data-testid": "review-empty", children: "No comments or changes." }),
805
+ changes > 1 && /* @__PURE__ */ jsx("div", { className: "native-docx-review-bubble-foot", children: /* @__PURE__ */ jsx(BulkActions, { review }) })
806
+ ] })
807
+ }
808
+ )
809
+ ] }) });
810
+ }
811
+ function BulkActions({ review, navigate }) {
812
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-review-bulk", children: [
813
+ navigate && /* @__PURE__ */ jsxs("span", { className: "native-docx-review-segment", children: [
814
+ /* @__PURE__ */ jsx(
815
+ "button",
816
+ {
817
+ type: "button",
818
+ className: "native-docx-review-icon",
819
+ "data-testid": "review-previous",
820
+ "aria-label": "Previous change",
821
+ title: "Previous change",
822
+ onClick: () => review.navigate("previous"),
823
+ children: /* @__PURE__ */ jsx(ChevronUp, { size: 14 })
824
+ }
825
+ ),
826
+ /* @__PURE__ */ jsx(
827
+ "button",
828
+ {
829
+ type: "button",
830
+ className: "native-docx-review-icon",
831
+ "data-testid": "review-next",
832
+ "aria-label": "Next change",
833
+ title: "Next change",
834
+ onClick: () => review.navigate("next"),
835
+ children: /* @__PURE__ */ jsx(ChevronDown, { size: 14 })
836
+ }
837
+ )
474
838
  ] }),
475
- draft && /* @__PURE__ */ jsx(
476
- Draft,
839
+ /* @__PURE__ */ jsxs(
840
+ "button",
477
841
  {
478
- top: tops.__draft ?? draft.anchorY * scale,
479
- formRef: (element) => {
480
- if (element) elements.current.set("__draft", element);
481
- },
482
- onCancel: () => setDraft(null),
483
- onSubmit: (text) => {
484
- if (review.addComment(text)) setDraft(null);
485
- }
842
+ type: "button",
843
+ className: "native-docx-review-accept",
844
+ "data-testid": "review-accept-all",
845
+ onClick: () => review.resolveAll("accept"),
846
+ children: [
847
+ /* @__PURE__ */ jsx(CheckCheck, { size: 14 }),
848
+ "Accept all"
849
+ ]
486
850
  }
487
851
  ),
488
- top.map((entry) => /* @__PURE__ */ jsx(
489
- Card,
852
+ /* @__PURE__ */ jsxs(
853
+ "button",
490
854
  {
491
- entry,
492
- replies: repliesOf(entry),
493
- review,
494
- extra: children?.(entry),
495
- top: tops[entry.key] ?? (entry.anchorY ?? 0) * scale,
496
- cardRef: (element) => {
497
- if (element) elements.current.set(entry.key, element);
498
- else elements.current.delete(entry.key);
499
- }
500
- },
501
- entry.key
502
- )),
503
- top.length === 0 && !draft && /* @__PURE__ */ jsx("p", { className: "native-docx-review-empty", "data-testid": "review-empty", children: "No comments or changes." })
855
+ type: "button",
856
+ className: "native-docx-review-reject",
857
+ "data-testid": "review-reject-all",
858
+ onClick: () => review.resolveAll("reject"),
859
+ children: [
860
+ /* @__PURE__ */ jsx(X, { size: 14 }),
861
+ "Reject all"
862
+ ]
863
+ }
864
+ )
504
865
  ] });
505
866
  }
506
867
  function ReviewCardSlot({ children }) {
@@ -582,14 +943,7 @@ function DocxCommentActivity() {
582
943
  /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Document comment" }) }),
583
944
  thread.map((entry) => /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b pb-4 last:border-0", children: [
584
945
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: entry.author || "Reviewer" }),
585
- /* @__PURE__ */ jsx(
586
- "p",
587
- {
588
- className: "whitespace-pre-wrap break-words text-sm",
589
- "data-testid": "docx-full-comment-text",
590
- children: entry.text
591
- }
592
- )
946
+ /* @__PURE__ */ jsx("div", { "data-testid": "docx-full-comment-text", children: /* @__PURE__ */ jsx(AiMarkdown, { allowHtml: false, children: commentText(entry) }) })
593
947
  ] }, entry.key)),
594
948
  statuses.map(([id, run]) => /* @__PURE__ */ jsxs("p", { role: "status", className: "text-sm", children: [
595
949
  run.label,
@@ -1855,57 +2209,12 @@ var WordDraftStream = class {
1855
2209
  }
1856
2210
  }
1857
2211
  };
1858
- function contentPath(documentId) {
1859
- const match = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/.exec(documentId);
1860
- return match ? `/${encodeURIComponent(match[1])}/content?version=${match[2]}` : `/${encodeURIComponent(documentId)}/content`;
1861
- }
1862
- var drafts = /* @__PURE__ */ new Map();
1863
- var stateBytes = (room) => Y.encodeStateAsUpdate(room.ydoc);
1864
- var encodeState = (room) => {
1865
- let binary = "";
1866
- for (const byte of stateBytes(room)) binary += String.fromCharCode(byte);
1867
- return btoa(binary);
1868
- };
1869
- function covered(room, vector, receipt) {
1870
- const decode = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
1871
- const server = Y.decodeStateVector(decode(vector)), local = Y.decodeStateVector(Y.encodeStateVector(room.ydoc));
1872
- for (const [client, clock] of local) if ((server.get(client) || 0) < clock) return false;
1873
- const deleted = Y.decodeUpdate(stateBytes(room)).ds.clients;
1874
- const saved = Y.decodeUpdate(decode(receipt)).ds.clients;
1875
- for (const [client, ranges] of deleted)
1876
- for (const range of ranges) {
1877
- if (!(saved.get(client) || []).some(
1878
- (s) => s.clock <= range.clock && s.clock + s.len >= range.clock + range.len
1879
- ))
1880
- return false;
1881
- }
1882
- return true;
1883
- }
1884
- var EditorBoundary = class extends React3.Component {
1885
- state = {};
1886
- static getDerivedStateFromError(error) {
1887
- return { error };
1888
- }
1889
- render() {
1890
- return this.state.error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "native-docx-error", children: [
1891
- "Could not render this Word document: ",
1892
- this.state.error.message
1893
- ] }) : this.props.children;
1894
- }
1895
- };
1896
- function DocxParticipants({ session }) {
1897
- const [people, setPeople] = useState([]);
1898
- useEffect(() => {
1899
- if (!session) return;
1900
- setPeople(session.participants());
1901
- return session.subscribeParticipants(setPeople);
1902
- }, [session]);
1903
- return /* @__PURE__ */ jsx("div", { className: "native-docx-presence", "aria-label": "People viewing this document", children: people.map((p) => /* @__PURE__ */ jsx("span", { className: "native-docx-avatar", title: p.name, children: p.name.split(" ").map((s) => s[0]).slice(0, 2).join("") }, p.actorId)) });
1904
- }
1905
2212
  function RibbonMenu({
1906
2213
  label,
1907
2214
  icon,
1908
- children
2215
+ children,
2216
+ className,
2217
+ testId
1909
2218
  }) {
1910
2219
  const [open, setOpen] = useState(false);
1911
2220
  const element = useRef(null);
@@ -1929,10 +2238,11 @@ function RibbonMenu({
1929
2238
  "button",
1930
2239
  {
1931
2240
  type: "button",
1932
- className: "native-docx-ribbon-trigger",
2241
+ className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
1933
2242
  "aria-label": label,
1934
2243
  title: label,
1935
2244
  "aria-expanded": open,
2245
+ "data-testid": testId,
1936
2246
  onMouseDown: (event) => event.preventDefault(),
1937
2247
  onClick: () => setOpen((value) => !value),
1938
2248
  children: icon
@@ -1948,11 +2258,295 @@ function RibbonMenu({
1948
2258
  if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
1949
2259
  event.preventDefault();
1950
2260
  },
1951
- children
2261
+ children: typeof children === "function" ? children(() => setOpen(false)) : children
2262
+ }
2263
+ )
2264
+ ] });
2265
+ }
2266
+
2267
+ // src/docx/wordStyles.ts
2268
+ var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
2269
+ function findStyle(options, name) {
2270
+ const wanted = name.toLowerCase();
2271
+ const id = name.replace(/(^|\s)(\w)/g, (_, _s, c) => c.toUpperCase());
2272
+ return options.find((o) => o.name.toLowerCase() === wanted) ?? options.find((o) => o.styleId.toLowerCase() === id.toLowerCase());
2273
+ }
2274
+ function styleLabel(name) {
2275
+ return name === name.toLowerCase() ? name.replace(/(^|\s)(\w)/g, (m) => m.toUpperCase()) : name;
2276
+ }
2277
+ function wordShortcut(event) {
2278
+ const mod = event.ctrlKey || event.metaKey;
2279
+ if (!mod) return null;
2280
+ if (event.altKey && !event.shiftKey) {
2281
+ switch (event.code) {
2282
+ case "KeyF":
2283
+ return "footnote";
2284
+ case "KeyD":
2285
+ return "endnote";
2286
+ case "Digit1":
2287
+ case "Numpad1":
2288
+ return "heading 1";
2289
+ case "Digit2":
2290
+ case "Numpad2":
2291
+ return "heading 2";
2292
+ case "Digit3":
2293
+ case "Numpad3":
2294
+ return "heading 3";
2295
+ }
2296
+ }
2297
+ if (event.shiftKey && !event.altKey && event.code === "KeyN") return "Normal";
2298
+ return null;
2299
+ }
2300
+ var FILTER_FROM = 8;
2301
+ function previewStyle(option) {
2302
+ const { fontFamily, fontSizePt, bold, italic, color } = option.preview;
2303
+ return {
2304
+ fontFamily: fontFamily ? `"${fontFamily}", serif` : void 0,
2305
+ // In the face of the style, but within a row: 9-18pt.
2306
+ fontSize: fontSizePt ? `${Math.min(18, Math.max(9, fontSizePt))}pt` : void 0,
2307
+ fontWeight: bold ? 700 : void 0,
2308
+ fontStyle: italic ? "italic" : void 0,
2309
+ color: color ? `#${color}` : void 0
2310
+ };
2311
+ }
2312
+ function Gallery({ close }) {
2313
+ const { value, setValue, options, isEnabled } = useParagraphStyle();
2314
+ const editor = useDocxEditor();
2315
+ const [query, setQuery] = useState("");
2316
+ const quick = useMemo(
2317
+ () => QUICK_STYLES.map((name) => findStyle(options, name)).filter(
2318
+ (o) => !!o
2319
+ ),
2320
+ [options]
2321
+ );
2322
+ const shown = useMemo(() => {
2323
+ const q = query.trim().toLowerCase();
2324
+ return q ? options.filter(
2325
+ (o) => o.name.toLowerCase().includes(q) || o.styleId.toLowerCase().includes(q)
2326
+ ) : options;
2327
+ }, [options, query]);
2328
+ const apply = (option) => {
2329
+ setValue(option.styleId);
2330
+ close();
2331
+ editor?.surface?.focus();
2332
+ };
2333
+ const nav = useListKeyNav({
2334
+ open: true,
2335
+ items: shown,
2336
+ onPick: apply,
2337
+ onDismiss: close,
2338
+ resetKey: query
2339
+ });
2340
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-style-gallery", "data-testid": "docx-style-gallery", children: [
2341
+ quick.length > 0 && /* @__PURE__ */ jsx("div", { className: "native-docx-style-quick", role: "group", "aria-label": "Quick styles", children: quick.map((option) => /* @__PURE__ */ jsx(
2342
+ "button",
2343
+ {
2344
+ type: "button",
2345
+ "aria-pressed": value === option.styleId,
2346
+ disabled: !isEnabled,
2347
+ "data-style-id": option.styleId,
2348
+ title: styleLabel(option.name),
2349
+ onClick: () => apply(option),
2350
+ children: /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
2351
+ },
2352
+ option.styleId
2353
+ )) }),
2354
+ options.length > FILTER_FROM && /* @__PURE__ */ jsx(
2355
+ "input",
2356
+ {
2357
+ className: "native-docx-style-filter",
2358
+ placeholder: "Find a style",
2359
+ "aria-label": "Find a style",
2360
+ value: query,
2361
+ autoFocus: true,
2362
+ onChange: (event) => setQuery(event.target.value),
2363
+ onKeyDown: (event) => {
2364
+ nav.handleKeyDown(event);
2365
+ },
2366
+ ...nav.comboboxProps
2367
+ }
2368
+ ),
2369
+ /* @__PURE__ */ jsxs(
2370
+ "ul",
2371
+ {
2372
+ className: "native-docx-style-list",
2373
+ role: "listbox",
2374
+ id: nav.listboxId,
2375
+ "aria-label": "Document styles",
2376
+ children: [
2377
+ shown.map((option, index) => /* @__PURE__ */ jsx(
2378
+ "li",
2379
+ {
2380
+ id: listOptionId(nav.listboxId, index),
2381
+ role: "option",
2382
+ "aria-selected": value === option.styleId,
2383
+ "aria-disabled": !isEnabled,
2384
+ "data-active": index === nav.selectedIndex || void 0,
2385
+ "data-style-id": option.styleId,
2386
+ onMouseEnter: () => nav.setSelectedIndex(index),
2387
+ onClick: () => isEnabled && apply(option),
2388
+ children: /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
2389
+ },
2390
+ option.styleId
2391
+ )),
2392
+ shown.length === 0 && /* @__PURE__ */ jsx("li", { className: "native-docx-style-empty", children: "No matching style" })
2393
+ ]
2394
+ }
2395
+ )
2396
+ ] });
2397
+ }
2398
+ function StyleGallery() {
2399
+ const { value, options } = useParagraphStyle();
2400
+ const current = options.find((o) => o.styleId === value);
2401
+ const name = current ? styleLabel(current.name) : "Styles";
2402
+ return /* @__PURE__ */ jsx(
2403
+ RibbonMenu,
2404
+ {
2405
+ label: "Styles",
2406
+ className: "native-docx-style-trigger",
2407
+ testId: "docx-style-trigger",
2408
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [
2409
+ /* @__PURE__ */ jsx("span", { className: "native-docx-style-current", "data-testid": "docx-current-style", children: name }),
2410
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, "aria-hidden": true })
2411
+ ] }),
2412
+ children: (close) => /* @__PURE__ */ jsx(Gallery, { close })
2413
+ }
2414
+ );
2415
+ }
2416
+ function useInsertNote(kind) {
2417
+ const command = useEditorCommand(kind === "footnote" ? "insert.footnote" : "insert.endnote");
2418
+ const { options } = useParagraphStyle();
2419
+ const editor = useDocxEditor();
2420
+ const insert = () => {
2421
+ if (!command.execute()) return false;
2422
+ const text = findStyle(options, `${kind} text`);
2423
+ if (text && editor)
2424
+ withDirectEditing(
2425
+ editor,
2426
+ () => editor.exec({ type: "setParagraphStyle", styleId: text.styleId })
2427
+ );
2428
+ return true;
2429
+ };
2430
+ return { insert, isEnabled: command.isEnabled, disabledReason: command.disabledReason };
2431
+ }
2432
+ function NoteAction({
2433
+ kind,
2434
+ label,
2435
+ shortcut,
2436
+ icon
2437
+ }) {
2438
+ const note = useInsertNote(kind);
2439
+ return /* @__PURE__ */ jsx(
2440
+ DocxEditor.Toolbar.Action,
2441
+ {
2442
+ label: `${label} (${shortcut})`,
2443
+ icon,
2444
+ disabled: !note.isEnabled,
2445
+ disabledReason: note.disabledReason ?? void 0,
2446
+ onSelect: () => void note.insert(),
2447
+ className: `native-docx-insert-${kind}`
2448
+ }
2449
+ );
2450
+ }
2451
+ function NoteButtons() {
2452
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2453
+ /* @__PURE__ */ jsx(
2454
+ NoteAction,
2455
+ {
2456
+ kind: "footnote",
2457
+ label: "Insert footnote",
2458
+ shortcut: "Ctrl+Alt+F",
2459
+ icon: /* @__PURE__ */ jsx(Superscript, { size: 16 })
2460
+ }
2461
+ ),
2462
+ /* @__PURE__ */ jsx(
2463
+ NoteAction,
2464
+ {
2465
+ kind: "endnote",
2466
+ label: "Insert endnote",
2467
+ shortcut: "Ctrl+Alt+D",
2468
+ icon: /* @__PURE__ */ jsx(NotebookText, { size: 16 })
1952
2469
  }
1953
2470
  )
1954
2471
  ] });
1955
2472
  }
2473
+ function WordShortcuts({ scope }) {
2474
+ const footnote = useInsertNote("footnote");
2475
+ const endnote = useInsertNote("endnote");
2476
+ const styles = useParagraphStyle();
2477
+ const editor = useDocxEditor();
2478
+ const latest = useRef({ footnote, endnote, styles, editor });
2479
+ latest.current = { footnote, endnote, styles, editor };
2480
+ useEffect(() => {
2481
+ const element = scope.current;
2482
+ if (!element) return;
2483
+ const onKeyDown = (event) => {
2484
+ const action = wordShortcut(event);
2485
+ if (!action) return;
2486
+ const { footnote: footnote2, endnote: endnote2, styles: styles2, editor: editor2 } = latest.current;
2487
+ if (action === "footnote" || action === "endnote") {
2488
+ if (!(action === "footnote" ? footnote2 : endnote2).insert()) return;
2489
+ } else {
2490
+ const style = findStyle(styles2.options, action);
2491
+ if (!style) return;
2492
+ styles2.setValue(style.styleId);
2493
+ }
2494
+ event.preventDefault();
2495
+ event.stopPropagation();
2496
+ editor2?.surface?.focus();
2497
+ };
2498
+ element.addEventListener("keydown", onKeyDown, true);
2499
+ return () => element.removeEventListener("keydown", onKeyDown, true);
2500
+ }, [scope]);
2501
+ return null;
2502
+ }
2503
+ function contentPath(documentId) {
2504
+ const match = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/.exec(documentId);
2505
+ return match ? `/${encodeURIComponent(match[1])}/content?version=${match[2]}` : `/${encodeURIComponent(documentId)}/content`;
2506
+ }
2507
+ var drafts = /* @__PURE__ */ new Map();
2508
+ var stateBytes = (room) => Y.encodeStateAsUpdate(room.ydoc);
2509
+ var encodeState = (room) => {
2510
+ let binary = "";
2511
+ for (const byte of stateBytes(room)) binary += String.fromCharCode(byte);
2512
+ return btoa(binary);
2513
+ };
2514
+ function covered(room, vector, receipt) {
2515
+ const decode = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
2516
+ const server = Y.decodeStateVector(decode(vector)), local = Y.decodeStateVector(Y.encodeStateVector(room.ydoc));
2517
+ for (const [client, clock] of local) if ((server.get(client) || 0) < clock) return false;
2518
+ const deleted = Y.decodeUpdate(stateBytes(room)).ds.clients;
2519
+ const saved = Y.decodeUpdate(decode(receipt)).ds.clients;
2520
+ for (const [client, ranges] of deleted)
2521
+ for (const range of ranges) {
2522
+ if (!(saved.get(client) || []).some(
2523
+ (s) => s.clock <= range.clock && s.clock + s.len >= range.clock + range.len
2524
+ ))
2525
+ return false;
2526
+ }
2527
+ return true;
2528
+ }
2529
+ var EditorBoundary = class extends React3.Component {
2530
+ state = {};
2531
+ static getDerivedStateFromError(error) {
2532
+ return { error };
2533
+ }
2534
+ render() {
2535
+ return this.state.error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "native-docx-error", children: [
2536
+ "Could not render this Word document: ",
2537
+ this.state.error.message
2538
+ ] }) : this.props.children;
2539
+ }
2540
+ };
2541
+ function DocxParticipants({ session }) {
2542
+ const [people, setPeople] = useState([]);
2543
+ useEffect(() => {
2544
+ if (!session) return;
2545
+ setPeople(session.participants());
2546
+ return session.subscribeParticipants(setPeople);
2547
+ }, [session]);
2548
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-presence", "aria-label": "People viewing this document", children: people.map((p) => /* @__PURE__ */ jsx("span", { className: "native-docx-avatar", title: p.name, children: p.name.split(" ").map((s) => s[0]).slice(0, 2).join("") }, p.actorId)) });
2549
+ }
1956
2550
  function DefinedTermButtons() {
1957
2551
  const terms = useDefinedTerms();
1958
2552
  if (!terms) return null;
@@ -1985,13 +2579,17 @@ function DefinedTermButtons() {
1985
2579
  )
1986
2580
  ] });
1987
2581
  }
1988
- function DocxRibbon({ onSave }) {
2582
+ function DocxRibbon({
2583
+ onSave,
2584
+ badge
2585
+ }) {
1989
2586
  const [tab, setTab] = useState("Home");
1990
2587
  const t = useChromeTranslate();
1991
2588
  return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon", children: [
1992
2589
  /* @__PURE__ */ jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
1993
2590
  /* @__PURE__ */ jsx("option", { children: "Home" }),
1994
2591
  /* @__PURE__ */ jsx("option", { children: "Insert" }),
2592
+ /* @__PURE__ */ jsx("option", { children: "References" }),
1995
2593
  /* @__PURE__ */ jsx("option", { children: "Review" })
1996
2594
  ] }),
1997
2595
  /* @__PURE__ */ jsxs(DocxEditor.Toolbar, { t, preset: false, onSave, children: [
@@ -1999,6 +2597,7 @@ function DocxRibbon({ onSave }) {
1999
2597
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Redo, {}),
2000
2598
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Save, {}),
2001
2599
  tab === "Home" && /* @__PURE__ */ jsxs(Fragment, { children: [
2600
+ /* @__PURE__ */ jsx(StyleGallery, {}),
2002
2601
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Bold, {}),
2003
2602
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Italic, {}),
2004
2603
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Underline, {}),
@@ -2009,7 +2608,6 @@ function DocxRibbon({ onSave }) {
2009
2608
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Highlight, {})
2010
2609
  ] }),
2011
2610
  /* @__PURE__ */ jsxs(RibbonMenu, { label: "More formatting", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: [
2012
- /* @__PURE__ */ jsx(DocxEditor.Toolbar.StylePicker, {}),
2013
2611
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Alignment, {}),
2014
2612
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.BulletList, {}),
2015
2613
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.NumberedList, {}),
@@ -2026,12 +2624,14 @@ function DocxRibbon({ onSave }) {
2026
2624
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {}),
2027
2625
  /* @__PURE__ */ jsx(DefinedTermButtons, {})
2028
2626
  ] }),
2627
+ tab === "References" && /* @__PURE__ */ jsx(NoteButtons, {}),
2029
2628
  tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
2030
2629
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.EditingMode, {}),
2031
2630
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Comments, {}),
2032
2631
  /* @__PURE__ */ jsx(RibbonMenu, { label: "Review options", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Reviewers, {}) })
2033
2632
  ] })
2034
- ] })
2633
+ ] }),
2634
+ badge && /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-badge", "data-testid": "docx-ribbon-badge", children: badge })
2035
2635
  ] });
2036
2636
  }
2037
2637
  var fixedZoomMode = { type: "fixed" };
@@ -2296,7 +2896,10 @@ function NativeDocxEditor({
2296
2896
  const resize = new ResizeObserver(fit);
2297
2897
  const chrome = new MutationObserver(fit);
2298
2898
  resize.observe(viewport);
2299
- chrome.observe(viewport, { attributes: true, attributeFilter: ["style", "data-review-pane"] });
2899
+ chrome.observe(viewport, {
2900
+ attributes: true,
2901
+ attributeFilter: ["style", "data-review-pane", "data-review-margin"]
2902
+ });
2300
2903
  fit();
2301
2904
  fitCleanup.current = () => {
2302
2905
  resize.disconnect();
@@ -2378,7 +2981,17 @@ function NativeDocxEditor({
2378
2981
  trackMoves: true
2379
2982
  }
2380
2983
  ),
2381
- !stream && /* @__PURE__ */ jsx(DocxRibbon, { onSave: () => void saveVersion() }),
2984
+ !stream && /* @__PURE__ */ jsx(
2985
+ DocxRibbon,
2986
+ {
2987
+ onSave: () => void saveVersion(),
2988
+ badge: (
2989
+ // Says the document is shared live, in place of a title row above the ribbon.
2990
+ 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
2991
+ )
2992
+ }
2993
+ ),
2994
+ !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
2382
2995
  /* @__PURE__ */ jsxs(
2383
2996
  "div",
2384
2997
  {
@@ -2510,5 +3123,5 @@ async function loadFonts(baseUrl) {
2510
3123
  }
2511
3124
 
2512
3125
  export { DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, NativeDocxEditor, editorPort, findDefinitionsSection, insertDefinedTerm, loadDocxFonts, normalizeDocxExport, reviewModule, updateDefinitions, useDefinedTerms, useReview, useReviewItem, withDirectEditing };
2513
- //# sourceMappingURL=chunk-WXLKMEMB.js.map
2514
- //# sourceMappingURL=chunk-WXLKMEMB.js.map
3126
+ //# sourceMappingURL=chunk-QIEEFBZ5.js.map
3127
+ //# sourceMappingURL=chunk-QIEEFBZ5.js.map