@arbidocs/blocks 0.4.95 → 0.4.96

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 (73) hide show
  1. package/dist/agents/index.cjs +281 -281
  2. package/dist/agents/index.js +5 -5
  3. package/dist/chat/index.cjs +127 -127
  4. package/dist/chat/index.js +4 -4
  5. package/dist/{chunk-DCZGYCN2.cjs → chunk-3WW32OUU.cjs} +4 -4
  6. package/dist/{chunk-DCZGYCN2.cjs.map → chunk-3WW32OUU.cjs.map} +1 -1
  7. package/dist/{chunk-E2AAIMXO.cjs → chunk-7RI3G5BO.cjs} +538 -168
  8. package/dist/chunk-7RI3G5BO.cjs.map +1 -0
  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-RZGVMV6B.cjs → chunk-CK6TU76J.cjs} +5 -5
  12. package/dist/{chunk-RZGVMV6B.cjs.map → chunk-CK6TU76J.cjs.map} +1 -1
  13. package/dist/{chunk-EJAYB7PP.js → chunk-EWSEQEPL.js} +4 -4
  14. package/dist/{chunk-EJAYB7PP.js.map → chunk-EWSEQEPL.js.map} +1 -1
  15. package/dist/{chunk-ZNDAXSDP.js → chunk-H4SVY5FH.js} +3 -3
  16. package/dist/{chunk-ZNDAXSDP.js.map → chunk-H4SVY5FH.js.map} +1 -1
  17. package/dist/{chunk-CKYA2IG5.cjs → chunk-HBF4BDRU.cjs} +3 -3
  18. package/dist/{chunk-CKYA2IG5.cjs.map → chunk-HBF4BDRU.cjs.map} +1 -1
  19. package/dist/{chunk-24CS7JWU.js → chunk-HOXKR26S.js} +3 -3
  20. package/dist/{chunk-24CS7JWU.js.map → chunk-HOXKR26S.js.map} +1 -1
  21. package/dist/{chunk-PDEHIGJ5.js → chunk-ICBAFX5B.js} +3 -3
  22. package/dist/{chunk-PDEHIGJ5.js.map → chunk-ICBAFX5B.js.map} +1 -1
  23. package/dist/{chunk-OYMYNYFV.cjs → chunk-IF6VAYIA.cjs} +5 -5
  24. package/dist/{chunk-OYMYNYFV.cjs.map → chunk-IF6VAYIA.cjs.map} +1 -1
  25. package/dist/{chunk-EOEDGJ75.js → chunk-JID3CZSY.js} +3 -3
  26. package/dist/{chunk-EOEDGJ75.js.map → chunk-JID3CZSY.js.map} +1 -1
  27. package/dist/{chunk-WXLKMEMB.js → chunk-K27SDLLN.js} +538 -168
  28. package/dist/chunk-K27SDLLN.js.map +1 -0
  29. package/dist/{chunk-VYKMBLDX.cjs → chunk-MP6FK43E.cjs} +8 -8
  30. package/dist/{chunk-VYKMBLDX.cjs.map → chunk-MP6FK43E.cjs.map} +1 -1
  31. package/dist/{chunk-VQ65UHTV.cjs → chunk-MROSCSPW.cjs} +45 -45
  32. package/dist/{chunk-VQ65UHTV.cjs.map → chunk-MROSCSPW.cjs.map} +1 -1
  33. package/dist/{chunk-TOVSTC2M.cjs → chunk-PSN4VWI7.cjs} +5 -5
  34. package/dist/{chunk-TOVSTC2M.cjs.map → chunk-PSN4VWI7.cjs.map} +1 -1
  35. package/dist/{chunk-3OXWJ3CZ.cjs → chunk-SC33PQTQ.cjs} +5 -5
  36. package/dist/{chunk-3OXWJ3CZ.cjs.map → chunk-SC33PQTQ.cjs.map} +1 -1
  37. package/dist/{chunk-HUFLBZWI.js → chunk-SF4BDUZS.js} +3 -3
  38. package/dist/{chunk-HUFLBZWI.js.map → chunk-SF4BDUZS.js.map} +1 -1
  39. package/dist/{chunk-QQH2NI5U.js → chunk-SJVUKXS6.js} +6 -6
  40. package/dist/{chunk-QQH2NI5U.js.map → chunk-SJVUKXS6.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-YPKSLTL5.js → chunk-Y52CEKVD.js} +3 -3
  48. package/dist/{chunk-YPKSLTL5.js.map → chunk-Y52CEKVD.js.map} +1 -1
  49. package/dist/connect/docx.cjs +6 -6
  50. package/dist/connect/docx.js +4 -4
  51. package/dist/connect/index.cjs +46 -46
  52. package/dist/connect/index.js +16 -16
  53. package/dist/docx/NativeDocxEditor.d.ts +3 -1
  54. package/dist/docx/index.cjs +24 -24
  55. package/dist/docx/index.js +3 -3
  56. package/dist/docx/review/DocxEditorReview.d.ts +4 -2
  57. package/dist/docx/review/commentText.d.ts +7 -0
  58. package/dist/facts/index.cjs +5 -5
  59. package/dist/facts/index.js +2 -2
  60. package/dist/index.cjs +1117 -1117
  61. package/dist/index.js +17 -17
  62. package/dist/settings/index.cjs +1 -1
  63. package/dist/settings/index.js +1 -1
  64. package/dist/shell/index.cjs +67 -67
  65. package/dist/shell/index.js +3 -3
  66. package/dist/studio/index.cjs +7 -7
  67. package/dist/studio/index.js +6 -6
  68. package/dist/workflow-canvas/index.cjs +49 -49
  69. package/dist/workflow-canvas/index.js +6 -6
  70. package/package.json +4 -4
  71. package/src/docx/styles.css +473 -91
  72. package/dist/chunk-E2AAIMXO.cjs.map +0 -1
  73. package/dist/chunk-WXLKMEMB.js.map +0 -1
@@ -7,14 +7,14 @@ var fflate = require('fflate');
7
7
  var store = require('@docx-editor.dev/core/store');
8
8
  var React3 = require('react');
9
9
  var react = require('@docx-editor.dev/react');
10
+ var ui = require('@arbidocs/react/ui');
11
+ var lucideReact = require('lucide-react');
10
12
  var jsxRuntime = require('react/jsx-runtime');
11
13
  var reactDom = require('react-dom');
12
- var ui = require('@arbidocs/react/ui');
13
14
  var server = require('react-dom/server');
14
15
  var Y = require('yjs');
15
16
  var coedit = require('@arbidocs/coedit');
16
17
  var hocuspocus = require('@arbidocs/coedit/hocuspocus');
17
- var lucideReact = require('lucide-react');
18
18
 
19
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
20
 
@@ -161,7 +161,25 @@ var ReviewItemContext = React3.createContext(null);
161
161
  function useReviewItem() {
162
162
  return React3.useContext(ReviewItemContext);
163
163
  }
164
+
165
+ // src/docx/review/commentText.ts
166
+ var SKIP = /* @__PURE__ */ new Set(["delText", "instrText", "delInstrText", "pPr", "rPr"]);
167
+ function inline(node) {
168
+ if (node.kind === "textValue") return node.value ?? "";
169
+ if (node.localName && SKIP.has(node.localName)) return "";
170
+ if (node.localName === "br" || node.localName === "cr") return "\n";
171
+ if (node.localName === "tab") return " ";
172
+ return (node.children ?? []).map(inline).join("");
173
+ }
174
+ function commentText(entry) {
175
+ if (entry.kind !== "comment") return entry.text;
176
+ const blocks = entry.item?.comment?.blocks ?? [];
177
+ const text = blocks.map(inline).join("\n").trim();
178
+ return text || entry.text;
179
+ }
164
180
  var CARD_GAP = 8;
181
+ var CLUSTER_GAP = 24;
182
+ var REVIEW_MARGIN_MIN_WIDTH = 900;
165
183
  var REVISION_LABELS = {
166
184
  insert: "Inserted",
167
185
  delete: "Deleted",
@@ -177,33 +195,102 @@ function isTopLevel(entry) {
177
195
  if (entry.kind === "custom") return entry.item.carded;
178
196
  return true;
179
197
  }
180
- function formatDate(value) {
181
- if (!value) return "";
198
+ function kindLabel(entry) {
199
+ if (entry.kind === "revision") return REVISION_LABELS[entry.revisionKind] ?? "Change";
200
+ if (entry.kind === "comment") return entry.resolved ? "Resolved" : "Comment";
201
+ return "Note";
202
+ }
203
+ function parseDate(value) {
204
+ if (!value) return null;
182
205
  const date = new Date(value);
183
- return Number.isNaN(date.getTime()) ? "" : date.toLocaleString();
206
+ return Number.isNaN(date.getTime()) ? null : date;
207
+ }
208
+ function relativeDate(date) {
209
+ const seconds = (Date.now() - date.getTime()) / 1e3;
210
+ if (seconds < 45) return "just now";
211
+ if (seconds < 45 * 60) return `${Math.round(seconds / 60)}m ago`;
212
+ if (seconds < 22 * 3600) return `${Math.round(seconds / 3600)}h ago`;
213
+ if (seconds < 7 * 86400) return `${Math.round(seconds / 86400)}d ago`;
214
+ const sameYear = date.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear();
215
+ return date.toLocaleDateString(void 0, {
216
+ day: "numeric",
217
+ month: "short",
218
+ year: sameYear ? void 0 : "numeric"
219
+ });
220
+ }
221
+ function authorHue(author) {
222
+ let hash = 0;
223
+ for (const char of author) hash = hash * 31 + char.charCodeAt(0) | 0;
224
+ return Math.abs(hash) % 360;
184
225
  }
185
226
  function Avatar({ entry }) {
186
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-review-avatar", "aria-hidden": "true", children: entry.initials });
227
+ return /* @__PURE__ */ jsxRuntime.jsx(
228
+ "span",
229
+ {
230
+ className: "native-docx-review-avatar",
231
+ "aria-hidden": "true",
232
+ style: { "--review-author-hue": authorHue(entry.author || "") },
233
+ children: entry.initials
234
+ }
235
+ );
187
236
  }
188
- function Message({ entry }) {
189
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-message", children: [
190
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-byline", children: [
191
- /* @__PURE__ */ jsxRuntime.jsx(Avatar, { entry }),
237
+ function Byline({ entry, children }) {
238
+ const date = parseDate(entry.date);
239
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-byline", children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar, { entry }),
241
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-who", children: [
192
242
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-review-author", children: entry.author || "Reviewer" }),
193
- /* @__PURE__ */ jsxRuntime.jsx("time", { className: "native-docx-review-date", children: formatDate(entry.date) })
243
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-review-meta", children: [
244
+ kindLabel(entry),
245
+ date && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
246
+ " \xB7 ",
247
+ /* @__PURE__ */ jsxRuntime.jsx(
248
+ "time",
249
+ {
250
+ className: "native-docx-review-date",
251
+ dateTime: date.toISOString(),
252
+ title: date.toLocaleString(),
253
+ children: relativeDate(date)
254
+ }
255
+ )
256
+ ] })
257
+ ] })
258
+ ] }),
259
+ children
260
+ ] });
261
+ }
262
+ function ChangeText({ entry }) {
263
+ const removed = entry.revisionKind === "replace" ? entry.replacedText : void 0;
264
+ const struck = entry.revisionKind === "delete" || entry.revisionKind === "moveFrom";
265
+ const added = entry.revisionKind === "insert" || entry.revisionKind === "moveTo" || removed !== void 0;
266
+ return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "native-docx-review-change", "data-testid": "review-text", children: [
267
+ removed && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
268
+ /* @__PURE__ */ jsxRuntime.jsx("del", { children: removed }),
269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-review-arrow", "aria-hidden": "true", children: "\u2192" })
194
270
  ] }),
195
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "native-docx-review-text", "data-testid": "review-text", children: entry.text })
271
+ struck ? /* @__PURE__ */ jsxRuntime.jsx("del", { children: entry.text }) : added ? /* @__PURE__ */ jsxRuntime.jsx("ins", { children: entry.text }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: entry.text })
272
+ ] });
273
+ }
274
+ function CommentText({ text, testId }) {
275
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-review-text", "data-testid": testId, children: /* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { className: "native-docx-review-markdown", allowHtml: false, children: text }) });
276
+ }
277
+ function Reply({ entry }) {
278
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-message", children: [
279
+ /* @__PURE__ */ jsxRuntime.jsx(Byline, { entry }),
280
+ /* @__PURE__ */ jsxRuntime.jsx(CommentText, { text: commentText(entry) })
196
281
  ] });
197
282
  }
198
283
  function ReplyForm({
199
284
  entry,
200
- review
285
+ review,
286
+ onDismiss
201
287
  }) {
202
288
  const [text, setText] = React3.useState("");
203
289
  return /* @__PURE__ */ jsxRuntime.jsxs(
204
290
  "form",
205
291
  {
206
292
  className: "native-docx-review-reply",
293
+ onClick: (event) => event.stopPropagation(),
207
294
  onSubmit: (event) => {
208
295
  event.preventDefault();
209
296
  const value = text.trim();
@@ -215,25 +302,39 @@ function ReplyForm({
215
302
  {
216
303
  "data-testid": "review-reply-input",
217
304
  "aria-label": "Reply",
218
- placeholder: "Reply",
305
+ placeholder: "Reply\u2026",
219
306
  value: text,
220
307
  onChange: (event) => setText(event.target.value),
221
308
  onKeyDown: (event) => {
222
309
  if (event.key === "Escape") {
310
+ event.preventDefault();
223
311
  event.stopPropagation();
224
- review.setActive(null);
312
+ onDismiss();
225
313
  }
226
314
  }
227
315
  }
228
316
  ),
229
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: !text.trim(), children: "Reply" }),
317
+ /* @__PURE__ */ jsxRuntime.jsx(
318
+ "button",
319
+ {
320
+ type: "submit",
321
+ className: "native-docx-review-icon native-docx-review-send",
322
+ "aria-label": "Reply",
323
+ title: "Reply",
324
+ disabled: !text.trim(),
325
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SendHorizontal, { size: 14 })
326
+ }
327
+ ),
230
328
  /* @__PURE__ */ jsxRuntime.jsx(
231
329
  "button",
232
330
  {
233
331
  type: "button",
332
+ className: "native-docx-review-icon",
234
333
  "data-testid": "review-reply-cancel",
235
- onClick: () => review.setActive(null),
236
- children: "Cancel"
334
+ "aria-label": "Cancel",
335
+ title: "Cancel",
336
+ onClick: onDismiss,
337
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14 })
237
338
  }
238
339
  )
239
340
  ]
@@ -246,7 +347,8 @@ function Card({
246
347
  review,
247
348
  extra,
248
349
  cardRef,
249
- top
350
+ top,
351
+ onDismiss
250
352
  }) {
251
353
  const resolved = entry.kind === "comment" && entry.resolved;
252
354
  return /* @__PURE__ */ jsxRuntime.jsx(ReviewItemContext.Provider, { value: entry, children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -265,60 +367,85 @@ function Card({
265
367
  },
266
368
  "data-testid": "review-card",
267
369
  "data-kind": entry.kind,
370
+ "data-change": entry.kind === "revision" ? entry.revisionKind : void 0,
268
371
  "data-active": entry.isActive || void 0,
269
372
  "data-resolved": resolved || void 0,
270
- style: { top },
373
+ "data-floating": top === void 0 || void 0,
374
+ style: top === void 0 ? void 0 : { top },
271
375
  onClick: () => {
272
376
  if (entry.activatable && !entry.isActive) review.setActive(entry.key);
273
377
  },
274
378
  children: [
275
- entry.kind === "revision" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-review-kind", children: REVISION_LABELS[entry.revisionKind] ?? "Change" }),
276
- /* @__PURE__ */ jsxRuntime.jsx(Message, { entry }),
277
- replies.map((reply) => /* @__PURE__ */ jsxRuntime.jsx(Message, { entry: reply }, reply.key)),
379
+ /* @__PURE__ */ jsxRuntime.jsx(Byline, { entry, children: entry.kind === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-tools", children: [
380
+ /* @__PURE__ */ jsxRuntime.jsx(
381
+ "button",
382
+ {
383
+ type: "button",
384
+ className: "native-docx-review-icon",
385
+ "data-testid": "review-resolve",
386
+ "aria-label": entry.resolved ? "Reopen" : "Resolve",
387
+ title: entry.resolved ? "Reopen" : "Resolve",
388
+ onClick: (event) => {
389
+ event.stopPropagation();
390
+ review.resolve(entry, !entry.resolved);
391
+ },
392
+ children: entry.resolved ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleCheck, { size: 14 })
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsxRuntime.jsx(
396
+ "button",
397
+ {
398
+ type: "button",
399
+ className: "native-docx-review-icon",
400
+ "data-testid": "review-delete",
401
+ "aria-label": "Delete",
402
+ title: "Delete",
403
+ onClick: (event) => {
404
+ event.stopPropagation();
405
+ review.remove(entry);
406
+ },
407
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 14 })
408
+ }
409
+ )
410
+ ] }) }),
411
+ entry.kind === "revision" ? /* @__PURE__ */ jsxRuntime.jsx(ChangeText, { entry }) : /* @__PURE__ */ jsxRuntime.jsx(CommentText, { text: commentText(entry), testId: "review-text" }),
412
+ replies.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-review-thread", children: replies.map((reply) => /* @__PURE__ */ jsxRuntime.jsx(Reply, { entry: reply }, reply.key)) }),
278
413
  extra,
279
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-actions", children: [
280
- entry.kind === "revision" && !entry.readOnly && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
281
- /* @__PURE__ */ jsxRuntime.jsx(
282
- "button",
283
- {
284
- type: "button",
285
- "data-testid": "review-accept",
286
- onClick: () => review.accept(entry),
287
- children: "Accept"
288
- }
289
- ),
290
- /* @__PURE__ */ jsxRuntime.jsx(
291
- "button",
292
- {
293
- type: "button",
294
- "data-testid": "review-reject",
295
- onClick: () => review.reject(entry),
296
- children: "Reject"
297
- }
298
- )
299
- ] }),
300
- entry.kind === "comment" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
301
- /* @__PURE__ */ jsxRuntime.jsx(
302
- "button",
303
- {
304
- type: "button",
305
- "data-testid": "review-resolve",
306
- onClick: () => review.resolve(entry, !entry.resolved),
307
- children: entry.resolved ? "Reopen" : "Resolve"
308
- }
309
- ),
310
- /* @__PURE__ */ jsxRuntime.jsx(
311
- "button",
312
- {
313
- type: "button",
314
- "data-testid": "review-delete",
315
- onClick: () => review.remove(entry),
316
- children: "Delete"
317
- }
318
- )
319
- ] })
414
+ entry.kind === "revision" && !entry.readOnly && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-decide", children: [
415
+ /* @__PURE__ */ jsxRuntime.jsxs(
416
+ "button",
417
+ {
418
+ type: "button",
419
+ className: "native-docx-review-accept",
420
+ "data-testid": "review-accept",
421
+ onClick: (event) => {
422
+ event.stopPropagation();
423
+ review.accept(entry);
424
+ },
425
+ children: [
426
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14 }),
427
+ "Accept"
428
+ ]
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsxRuntime.jsxs(
432
+ "button",
433
+ {
434
+ type: "button",
435
+ className: "native-docx-review-reject",
436
+ "data-testid": "review-reject",
437
+ onClick: (event) => {
438
+ event.stopPropagation();
439
+ review.reject(entry);
440
+ },
441
+ children: [
442
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14 }),
443
+ "Reject"
444
+ ]
445
+ }
446
+ )
320
447
  ] }),
321
- entry.isActive && entry.kind !== "custom" && /* @__PURE__ */ jsxRuntime.jsx(ReplyForm, { entry, review })
448
+ entry.isActive && entry.kind !== "custom" && /* @__PURE__ */ jsxRuntime.jsx(ReplyForm, { entry, review, onDismiss })
322
449
  ]
323
450
  }
324
451
  ) });
@@ -338,7 +465,8 @@ function Draft({
338
465
  ref: formRef,
339
466
  className: "native-docx-review-card native-docx-review-draft",
340
467
  "data-testid": "review-draft",
341
- style: { top },
468
+ "data-floating": top === void 0 || void 0,
469
+ style: top === void 0 ? void 0 : { top },
342
470
  onSubmit: (event) => {
343
471
  event.preventDefault();
344
472
  if (text.trim()) onSubmit(text.trim());
@@ -350,22 +478,53 @@ function Draft({
350
478
  ref: input,
351
479
  "data-testid": "review-draft-input",
352
480
  "aria-label": "Comment",
353
- placeholder: "Add a comment",
481
+ placeholder: "Add a comment\u2026",
354
482
  value: text,
355
483
  onChange: (event) => setText(event.target.value),
356
484
  onKeyDown: (event) => {
357
- if (event.key === "Escape") onCancel();
485
+ if (event.key === "Escape") {
486
+ event.preventDefault();
487
+ event.stopPropagation();
488
+ onCancel();
489
+ }
358
490
  }
359
491
  }
360
492
  ),
361
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-actions", children: [
362
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: !text.trim(), children: "Comment" }),
363
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-testid": "review-draft-cancel", onClick: onCancel, children: "Cancel" })
493
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-decide", children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "native-docx-review-accept", disabled: !text.trim(), children: "Comment" }),
495
+ /* @__PURE__ */ jsxRuntime.jsx(
496
+ "button",
497
+ {
498
+ type: "button",
499
+ className: "native-docx-review-plain",
500
+ "data-testid": "review-draft-cancel",
501
+ onClick: onCancel,
502
+ children: "Cancel"
503
+ }
504
+ )
364
505
  ] })
365
506
  ]
366
507
  }
367
508
  );
368
509
  }
510
+ function useScrollArea(anchor, ready) {
511
+ const [area, setArea] = React3.useState({
512
+ element: null,
513
+ width: 0
514
+ });
515
+ React3.useLayoutEffect(() => {
516
+ const element = anchor.current?.closest(".docx-editor__scroll-container") ?? null;
517
+ const measure = () => setArea(
518
+ (previous) => previous.element === element && previous.width === (element?.clientWidth ?? 0) ? previous : { element, width: element?.clientWidth ?? 0 }
519
+ );
520
+ measure();
521
+ if (!element || typeof ResizeObserver === "undefined") return;
522
+ const observer = new ResizeObserver(measure);
523
+ observer.observe(element);
524
+ return () => observer.disconnect();
525
+ }, [anchor, ready]);
526
+ return area;
527
+ }
369
528
  function DocxEditorReviewRail({
370
529
  children
371
530
  }) {
@@ -376,6 +535,10 @@ function DocxEditorReviewRail({
376
535
  const [tops, setTops] = React3.useState({});
377
536
  const [showResolved, setShowResolved] = React3.useState(true);
378
537
  const elements = React3.useRef(/* @__PURE__ */ new Map());
538
+ const anchor = React3.useRef(null);
539
+ const toolbar = React3.useRef(null);
540
+ const area = useScrollArea(anchor, review.ready);
541
+ const layout = area.width === 0 || area.width >= REVIEW_MARGIN_MIN_WIDTH ? "margin" : "compact";
379
542
  const openDraft = React3.useCallback(() => {
380
543
  const placement = editor?.getSelectionPlacement();
381
544
  if (!placement) return;
@@ -384,9 +547,22 @@ function DocxEditorReviewRail({
384
547
  }, [editor, review.setPaneOpen]);
385
548
  React3.useEffect(() => registry?.register(), [registry]);
386
549
  React3.useEffect(() => registry?.registerCommentDraft(openDraft), [registry, openDraft]);
550
+ const shownLayout = React3.useRef(null);
551
+ React3.useEffect(() => {
552
+ if (!review.ready || area.width === 0 || shownLayout.current === layout) return;
553
+ editor?.setReviewPaneOptions({ opening: layout === "margin" ? "auto" : "manual" });
554
+ const deepLinked = shownLayout.current === null && review.activeKey !== null;
555
+ shownLayout.current = layout;
556
+ if (layout === "margin") review.setPaneOpen(true);
557
+ else if (!deepLinked) {
558
+ setDraft(null);
559
+ review.setPaneOpen(false);
560
+ }
561
+ }, [editor, layout, area.width, review.ready, review.setPaneOpen]);
387
562
  const scale = editor?.getRenderScale() ?? 1;
388
563
  const top = review.items.filter(isTopLevel).filter((entry) => entry.anchorY !== null).filter((entry) => showResolved || entry.kind !== "comment" || !entry.resolved);
389
- const hasChanges = review.items.some((entry) => entry.kind === "revision");
564
+ const changes = review.items.filter((entry) => entry.kind === "revision").length;
565
+ const comments = top.filter((entry) => entry.kind === "comment").length;
390
566
  const hasResolved = review.items.some((entry) => entry.kind === "comment" && entry.resolved);
391
567
  const repliesOf = React3.useCallback(
392
568
  (entry) => review.items.filter(
@@ -394,10 +570,49 @@ function DocxEditorReviewRail({
394
570
  ),
395
571
  [review.items]
396
572
  );
573
+ const column = layout === "margin" && review.paneOpen && (top.length > 0 || draft !== null);
574
+ const bubbleOpen = layout === "compact" && (review.paneOpen || draft !== null);
575
+ React3.useEffect(() => {
576
+ const element = area.element;
577
+ if (!element) return;
578
+ if (column) element.setAttribute("data-review-margin", "");
579
+ else element.removeAttribute("data-review-margin");
580
+ return () => element.removeAttribute("data-review-margin");
581
+ }, [area.element, column]);
582
+ const dismiss = React3.useCallback(() => {
583
+ setDraft(null);
584
+ if (layout === "compact") review.setPaneOpen(false);
585
+ else review.setActive(null);
586
+ }, [layout, review.setPaneOpen, review.setActive]);
587
+ React3.useEffect(() => {
588
+ const element = area.element;
589
+ if (!element || !bubbleOpen) return;
590
+ const close = (event) => {
591
+ const target = event.target;
592
+ if (target instanceof Element && target.closest(
593
+ ".native-docx-review-bubble, .native-docx-review-marker, .native-docx-review-add"
594
+ ))
595
+ return;
596
+ dismiss();
597
+ };
598
+ const escape = (event) => {
599
+ if (event.key !== "Escape" || event.defaultPrevented) return;
600
+ if (!(event.target instanceof Node) || !element.contains(event.target)) return;
601
+ event.preventDefault();
602
+ dismiss();
603
+ };
604
+ const document2 = element.ownerDocument;
605
+ element.addEventListener("pointerdown", close);
606
+ document2.addEventListener("keydown", escape);
607
+ return () => {
608
+ element.removeEventListener("pointerdown", close);
609
+ document2.removeEventListener("keydown", escape);
610
+ };
611
+ }, [area.element, bubbleOpen, dismiss]);
397
612
  React3.useLayoutEffect(() => {
398
- if (!review.paneOpen) return;
613
+ if (!column) return;
399
614
  const next = {};
400
- let floor = 0;
615
+ let floor = (toolbar.current?.offsetHeight ?? 0) + CARD_GAP;
401
616
  const rows = top.map((entry) => ({
402
617
  key: entry.key,
403
618
  y: (entry.anchorY ?? 0) * scale
@@ -415,7 +630,7 @@ function DocxEditorReviewRail({
415
630
  return same ? previous : next;
416
631
  });
417
632
  });
418
- if (!review.ready) return null;
633
+ if (!review.ready) return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: anchor, className: "native-docx-review" });
419
634
  const selection = editor?.getSelectionPlacement();
420
635
  const addComment = selection && /* @__PURE__ */ jsxRuntime.jsx(
421
636
  "button",
@@ -424,108 +639,254 @@ function DocxEditorReviewRail({
424
639
  className: "native-docx-review-add",
425
640
  "data-testid": "review-add-comment",
426
641
  "aria-label": "Add comment",
642
+ title: "Add comment",
427
643
  style: { top: selection.anchorY * scale },
428
644
  onMouseDown: (event) => event.preventDefault(),
429
645
  onClick: openDraft,
430
- children: "+"
646
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquarePlus, { size: 14 })
431
647
  }
432
648
  );
433
- if (!review.paneOpen) {
434
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-markers", "data-testid": "review-markers", children: [
649
+ if (column) {
650
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: anchor, className: "native-docx-review", children: /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "native-docx-review-pane", "data-testid": "review-pane", "aria-label": "Review", children: [
435
651
  addComment,
652
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: toolbar, className: "native-docx-review-toolbar", "data-testid": "review-toolbar", children: [
653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-review-summary", children: [
654
+ changes && `${changes} ${changes === 1 ? "change" : "changes"}`,
655
+ comments && `${comments} ${comments === 1 ? "comment" : "comments"}`
656
+ ].filter(Boolean).join(" \xB7 ") || "Review" }),
657
+ hasResolved && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "native-docx-review-switch", children: [
658
+ /* @__PURE__ */ jsxRuntime.jsx(
659
+ "input",
660
+ {
661
+ type: "checkbox",
662
+ "data-testid": "review-show-resolved",
663
+ checked: showResolved,
664
+ onChange: (event) => setShowResolved(event.target.checked)
665
+ }
666
+ ),
667
+ "Resolved"
668
+ ] }),
669
+ changes > 0 && /* @__PURE__ */ jsxRuntime.jsx(BulkActions, { review, navigate: true })
670
+ ] }),
671
+ draft && /* @__PURE__ */ jsxRuntime.jsx(
672
+ Draft,
673
+ {
674
+ top: tops.__draft ?? draft.anchorY * scale,
675
+ formRef: (element) => {
676
+ if (element) elements.current.set("__draft", element);
677
+ },
678
+ onCancel: () => setDraft(null),
679
+ onSubmit: (text) => {
680
+ if (review.addComment(text)) setDraft(null);
681
+ }
682
+ }
683
+ ),
436
684
  top.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
685
+ Card,
686
+ {
687
+ entry,
688
+ replies: repliesOf(entry),
689
+ review,
690
+ extra: children?.(entry),
691
+ top: tops[entry.key] ?? (entry.anchorY ?? 0) * scale,
692
+ onDismiss: dismiss,
693
+ cardRef: (element) => {
694
+ if (element) elements.current.set(entry.key, element);
695
+ else elements.current.delete(entry.key);
696
+ }
697
+ },
698
+ entry.key
699
+ ))
700
+ ] }) });
701
+ }
702
+ const clusters = [];
703
+ for (const entry of [...top].sort((a, b) => (a.anchorY ?? 0) - (b.anchorY ?? 0))) {
704
+ const y = (entry.anchorY ?? 0) * scale;
705
+ const last = clusters[clusters.length - 1];
706
+ if (last && y - last.y < CLUSTER_GAP) last.entries.push(entry);
707
+ else clusters.push({ y, entries: [entry] });
708
+ }
709
+ const shown = bubbleOpen ? draft ? null : clusters.find((cluster) => cluster.entries.some((entry) => entry.isActive)) ?? clusters[0] : void 0;
710
+ const focus = shown?.entries.find((entry) => entry.isActive) ?? shown?.entries[0];
711
+ const position = focus ? top.indexOf(focus) : -1;
712
+ const step = (offset) => {
713
+ const next = top[position + offset];
714
+ if (next) review.setActive(next.key);
715
+ };
716
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: anchor, className: "native-docx-review", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-markers", "data-testid": "review-markers", children: [
717
+ addComment,
718
+ clusters.map((cluster) => {
719
+ const [first] = cluster.entries;
720
+ const comment = cluster.entries.some((entry) => entry.kind === "comment");
721
+ return /* @__PURE__ */ jsxRuntime.jsxs(
437
722
  "button",
438
723
  {
439
724
  type: "button",
440
725
  className: "native-docx-review-marker",
441
726
  "data-testid": "review-marker",
442
- "data-kind": entry.kind,
443
- "aria-label": `${entry.author}: ${entry.text.slice(0, 60)}`,
444
- style: { top: (entry.anchorY ?? 0) * scale },
727
+ "data-kind": comment ? "comment" : first.kind,
728
+ "data-active": cluster === shown || void 0,
729
+ "aria-label": cluster.entries.length === 1 ? `${first.author}: ${first.text.slice(0, 60)}` : `${cluster.entries.length} comments and changes`,
730
+ "aria-expanded": layout === "compact" ? cluster === shown : void 0,
731
+ style: { top: cluster.y },
445
732
  onClick: () => {
733
+ if (layout === "compact" && cluster === shown) return dismiss();
734
+ setDraft(null);
446
735
  review.setPaneOpen(true);
447
- review.setActive(entry.key);
448
- }
736
+ review.setActive(first.key);
737
+ },
738
+ children: [
739
+ comment ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { size: 13 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PencilLine, { size: 13 }),
740
+ cluster.entries.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-review-count", children: cluster.entries.length })
741
+ ]
449
742
  },
450
- entry.key
451
- ))
452
- ] });
453
- }
454
- return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "native-docx-review-pane", "data-testid": "review-pane", "aria-label": "Review", children: [
455
- addComment,
456
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-toolbar", "data-testid": "review-toolbar", children: [
457
- hasChanges && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
458
- /* @__PURE__ */ jsxRuntime.jsx(
459
- "button",
460
- {
461
- type: "button",
462
- "data-testid": "review-previous",
463
- onClick: () => review.navigate("previous"),
464
- children: "Previous change"
465
- }
466
- ),
467
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-testid": "review-next", onClick: () => review.navigate("next"), children: "Next change" }),
468
- /* @__PURE__ */ jsxRuntime.jsx(
469
- "button",
470
- {
471
- type: "button",
472
- "data-testid": "review-accept-all",
473
- onClick: () => review.resolveAll("accept"),
474
- children: "Accept all"
475
- }
476
- ),
477
- /* @__PURE__ */ jsxRuntime.jsx(
478
- "button",
479
- {
480
- type: "button",
481
- "data-testid": "review-reject-all",
482
- onClick: () => review.resolveAll("reject"),
483
- children: "Reject all"
743
+ first.key
744
+ );
745
+ }),
746
+ bubbleOpen && (draft || shown || top.length === 0) && /* @__PURE__ */ jsxRuntime.jsx(
747
+ "div",
748
+ {
749
+ className: "native-docx-review-bubble",
750
+ "data-testid": "review-bubble",
751
+ role: "dialog",
752
+ "data-state": "open",
753
+ "aria-label": "Review",
754
+ style: {
755
+ top: draft ? draft.anchorY * scale : shown?.y ?? 0,
756
+ width: Math.max(220, Math.min(340, area.width - 56))
757
+ },
758
+ onKeyDown: (event) => {
759
+ if (event.key === "Escape") {
760
+ event.preventDefault();
761
+ event.stopPropagation();
762
+ dismiss();
484
763
  }
485
- )
486
- ] }),
487
- hasResolved && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
488
- /* @__PURE__ */ jsxRuntime.jsx(
489
- "input",
764
+ },
765
+ children: draft ? /* @__PURE__ */ jsxRuntime.jsx(
766
+ Draft,
490
767
  {
491
- type: "checkbox",
492
- "data-testid": "review-show-resolved",
493
- checked: showResolved,
494
- onChange: (event) => setShowResolved(event.target.checked)
768
+ onCancel: dismiss,
769
+ onSubmit: (text) => {
770
+ if (review.addComment(text)) setDraft(null);
771
+ }
495
772
  }
496
- ),
497
- "Show resolved"
498
- ] })
773
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
774
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-bubble-head", children: [
775
+ top.length > 1 && position >= 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
776
+ /* @__PURE__ */ jsxRuntime.jsx(
777
+ "button",
778
+ {
779
+ type: "button",
780
+ className: "native-docx-review-icon",
781
+ "data-testid": "review-bubble-previous",
782
+ "aria-label": "Previous",
783
+ disabled: position === 0,
784
+ onClick: () => step(-1),
785
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { size: 14 })
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-review-summary", children: [
789
+ position + 1,
790
+ " of ",
791
+ top.length
792
+ ] }),
793
+ /* @__PURE__ */ jsxRuntime.jsx(
794
+ "button",
795
+ {
796
+ type: "button",
797
+ className: "native-docx-review-icon",
798
+ "data-testid": "review-bubble-next",
799
+ "aria-label": "Next",
800
+ disabled: position === top.length - 1,
801
+ onClick: () => step(1),
802
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { size: 14 })
803
+ }
804
+ )
805
+ ] }),
806
+ /* @__PURE__ */ jsxRuntime.jsx(
807
+ "button",
808
+ {
809
+ type: "button",
810
+ className: "native-docx-review-icon native-docx-review-close",
811
+ "data-testid": "review-bubble-close",
812
+ "aria-label": "Close",
813
+ onClick: dismiss,
814
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14 })
815
+ }
816
+ )
817
+ ] }),
818
+ shown?.entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
819
+ Card,
820
+ {
821
+ entry,
822
+ replies: repliesOf(entry),
823
+ review,
824
+ extra: children?.(entry),
825
+ onDismiss: dismiss
826
+ },
827
+ entry.key
828
+ )),
829
+ !shown && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "native-docx-review-empty", "data-testid": "review-empty", children: "No comments or changes." }),
830
+ changes > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "native-docx-review-bubble-foot", children: /* @__PURE__ */ jsxRuntime.jsx(BulkActions, { review }) })
831
+ ] })
832
+ }
833
+ )
834
+ ] }) });
835
+ }
836
+ function BulkActions({ review, navigate }) {
837
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-review-bulk", children: [
838
+ navigate && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "native-docx-review-segment", children: [
839
+ /* @__PURE__ */ jsxRuntime.jsx(
840
+ "button",
841
+ {
842
+ type: "button",
843
+ className: "native-docx-review-icon",
844
+ "data-testid": "review-previous",
845
+ "aria-label": "Previous change",
846
+ title: "Previous change",
847
+ onClick: () => review.navigate("previous"),
848
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { size: 14 })
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsxRuntime.jsx(
852
+ "button",
853
+ {
854
+ type: "button",
855
+ className: "native-docx-review-icon",
856
+ "data-testid": "review-next",
857
+ "aria-label": "Next change",
858
+ title: "Next change",
859
+ onClick: () => review.navigate("next"),
860
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14 })
861
+ }
862
+ )
499
863
  ] }),
500
- draft && /* @__PURE__ */ jsxRuntime.jsx(
501
- Draft,
864
+ /* @__PURE__ */ jsxRuntime.jsxs(
865
+ "button",
502
866
  {
503
- top: tops.__draft ?? draft.anchorY * scale,
504
- formRef: (element) => {
505
- if (element) elements.current.set("__draft", element);
506
- },
507
- onCancel: () => setDraft(null),
508
- onSubmit: (text) => {
509
- if (review.addComment(text)) setDraft(null);
510
- }
867
+ type: "button",
868
+ className: "native-docx-review-accept",
869
+ "data-testid": "review-accept-all",
870
+ onClick: () => review.resolveAll("accept"),
871
+ children: [
872
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCheck, { size: 14 }),
873
+ "Accept all"
874
+ ]
511
875
  }
512
876
  ),
513
- top.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
514
- Card,
877
+ /* @__PURE__ */ jsxRuntime.jsxs(
878
+ "button",
515
879
  {
516
- entry,
517
- replies: repliesOf(entry),
518
- review,
519
- extra: children?.(entry),
520
- top: tops[entry.key] ?? (entry.anchorY ?? 0) * scale,
521
- cardRef: (element) => {
522
- if (element) elements.current.set(entry.key, element);
523
- else elements.current.delete(entry.key);
524
- }
525
- },
526
- entry.key
527
- )),
528
- top.length === 0 && !draft && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "native-docx-review-empty", "data-testid": "review-empty", children: "No comments or changes." })
880
+ type: "button",
881
+ className: "native-docx-review-reject",
882
+ "data-testid": "review-reject-all",
883
+ onClick: () => review.resolveAll("reject"),
884
+ children: [
885
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14 }),
886
+ "Reject all"
887
+ ]
888
+ }
889
+ )
529
890
  ] });
530
891
  }
531
892
  function ReviewCardSlot({ children }) {
@@ -607,14 +968,7 @@ function DocxCommentActivity() {
607
968
  /* @__PURE__ */ jsxRuntime.jsx(ui.DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.DialogTitle, { children: "Document comment" }) }),
608
969
  thread.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 border-b pb-4 last:border-0", children: [
609
970
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: entry.author || "Reviewer" }),
610
- /* @__PURE__ */ jsxRuntime.jsx(
611
- "p",
612
- {
613
- className: "whitespace-pre-wrap break-words text-sm",
614
- "data-testid": "docx-full-comment-text",
615
- children: entry.text
616
- }
617
- )
971
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "docx-full-comment-text", children: /* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { allowHtml: false, children: commentText(entry) }) })
618
972
  ] }, entry.key)),
619
973
  statuses.map(([id, run]) => /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", className: "text-sm", children: [
620
974
  run.label,
@@ -2010,7 +2364,10 @@ function DefinedTermButtons() {
2010
2364
  )
2011
2365
  ] });
2012
2366
  }
2013
- function DocxRibbon({ onSave }) {
2367
+ function DocxRibbon({
2368
+ onSave,
2369
+ badge
2370
+ }) {
2014
2371
  const [tab, setTab] = React3.useState("Home");
2015
2372
  const t = react.useChromeTranslate();
2016
2373
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-ribbon", children: [
@@ -2056,7 +2413,8 @@ function DocxRibbon({ onSave }) {
2056
2413
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Comments, {}),
2057
2414
  /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Review options", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Ellipsis, { size: 16 }), children: /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Reviewers, {}) })
2058
2415
  ] })
2059
- ] })
2416
+ ] }),
2417
+ badge && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-ribbon-badge", "data-testid": "docx-ribbon-badge", children: badge })
2060
2418
  ] });
2061
2419
  }
2062
2420
  var fixedZoomMode = { type: "fixed" };
@@ -2321,7 +2679,10 @@ function NativeDocxEditor({
2321
2679
  const resize = new ResizeObserver(fit);
2322
2680
  const chrome = new MutationObserver(fit);
2323
2681
  resize.observe(viewport);
2324
- chrome.observe(viewport, { attributes: true, attributeFilter: ["style", "data-review-pane"] });
2682
+ chrome.observe(viewport, {
2683
+ attributes: true,
2684
+ attributeFilter: ["style", "data-review-pane", "data-review-margin"]
2685
+ });
2325
2686
  fit();
2326
2687
  fitCleanup.current = () => {
2327
2688
  resize.disconnect();
@@ -2403,7 +2764,16 @@ function NativeDocxEditor({
2403
2764
  trackMoves: true
2404
2765
  }
2405
2766
  ),
2406
- !stream && /* @__PURE__ */ jsxRuntime.jsx(DocxRibbon, { onSave: () => void saveVersion() }),
2767
+ !stream && /* @__PURE__ */ jsxRuntime.jsx(
2768
+ DocxRibbon,
2769
+ {
2770
+ onSave: () => void saveVersion(),
2771
+ badge: (
2772
+ // Says the document is shared live, in place of a title row above the ribbon.
2773
+ ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
2774
+ )
2775
+ }
2776
+ ),
2407
2777
  /* @__PURE__ */ jsxRuntime.jsxs(
2408
2778
  "div",
2409
2779
  {
@@ -2554,5 +2924,5 @@ exports.useDefinedTerms = useDefinedTerms;
2554
2924
  exports.useReview = useReview;
2555
2925
  exports.useReviewItem = useReviewItem;
2556
2926
  exports.withDirectEditing = withDirectEditing;
2557
- //# sourceMappingURL=chunk-E2AAIMXO.cjs.map
2558
- //# sourceMappingURL=chunk-E2AAIMXO.cjs.map
2927
+ //# sourceMappingURL=chunk-7RI3G5BO.cjs.map
2928
+ //# sourceMappingURL=chunk-7RI3G5BO.cjs.map