docxodus 11.0.0 → 12.0.0

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 (68) hide show
  1. package/README.md +7 -5
  2. package/dist/editor-comments.d.ts +129 -0
  3. package/dist/editor-comments.d.ts.map +1 -0
  4. package/dist/editor-comments.js +805 -0
  5. package/dist/editor-comments.js.map +1 -0
  6. package/dist/editor-headerfooter.d.ts +125 -85
  7. package/dist/editor-headerfooter.d.ts.map +1 -1
  8. package/dist/editor-headerfooter.js +572 -306
  9. package/dist/editor-headerfooter.js.map +1 -1
  10. package/dist/editor-image-patch.d.ts +38 -0
  11. package/dist/editor-image-patch.d.ts.map +1 -0
  12. package/dist/editor-image-patch.js +101 -0
  13. package/dist/editor-image-patch.js.map +1 -0
  14. package/dist/editor.bundle.js +3760 -785
  15. package/dist/editor.d.ts +237 -5
  16. package/dist/editor.d.ts.map +1 -1
  17. package/dist/editor.js +1063 -112
  18. package/dist/editor.js.map +1 -1
  19. package/dist/embed.bundle.js +3784 -759
  20. package/dist/embed.d.ts +1 -1
  21. package/dist/embed.d.ts.map +1 -1
  22. package/dist/embed.iife.js +3772 -747
  23. package/dist/embed.js +10 -1
  24. package/dist/embed.js.map +1 -1
  25. package/dist/export-assets.json +14 -14
  26. package/dist/export-browser.bundle.js +25 -0
  27. package/dist/index.d.ts +5 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/pagination.bundle.js +25 -0
  32. package/dist/pagination.d.ts +6 -0
  33. package/dist/pagination.d.ts.map +1 -1
  34. package/dist/pagination.js +19 -0
  35. package/dist/pagination.js.map +1 -1
  36. package/dist/ribbon-chrome.d.ts +20 -4
  37. package/dist/ribbon-chrome.d.ts.map +1 -1
  38. package/dist/ribbon-chrome.js +550 -182
  39. package/dist/ribbon-chrome.js.map +1 -1
  40. package/dist/ribbon.d.ts +5 -4
  41. package/dist/ribbon.d.ts.map +1 -1
  42. package/dist/ribbon.js +941 -141
  43. package/dist/ribbon.js.map +1 -1
  44. package/dist/session.bundle.js +48 -3
  45. package/dist/session.d.ts +35 -4
  46. package/dist/session.d.ts.map +1 -1
  47. package/dist/session.js +44 -3
  48. package/dist/session.js.map +1 -1
  49. package/dist/types.d.ts +114 -5
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js.map +1 -1
  52. package/dist/viewport.d.ts +7 -0
  53. package/dist/viewport.d.ts.map +1 -1
  54. package/dist/viewport.js +12 -0
  55. package/dist/viewport.js.map +1 -1
  56. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  57. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
  58. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  59. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  60. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  61. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  62. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  63. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  64. package/dist/wasm/_framework/dotnet.boot.js +6 -6
  65. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  66. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  67. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  68. package/package.json +2 -2
@@ -14,6 +14,12 @@
14
14
  * `data-dxr-list` does the same job for the `<input list>` → `<datalist id>`
15
15
  * link, which is by-id and cannot use a data attribute directly.
16
16
  *
17
+ * ── Shape ───────────────────────────────────────────────────────────────────
18
+ * Word's: a title bar, a tab strip (Home · Insert · Layout · References ·
19
+ * Review · View, plus the contextual Table and Header & Footer tabs), the panel
20
+ * for the active tab, the document with its comment gutter, and a status bar
21
+ * that carries the anchor rail, the page/word counts and the zoom control.
22
+ *
17
23
  * ── Responsiveness ──────────────────────────────────────────────────────────
18
24
  * Layout keys off `data-chrome` on the root ("full" | "compact"), which
19
25
  * `ribbon.ts` sets from a ResizeObserver on the ROOT rather than from viewport
@@ -22,8 +28,10 @@
22
28
  * `[data-chrome="full"]` adds back the desktop affordances.
23
29
  */
24
30
  /** Bumped whenever RIBBON_CSS changes, so a stale injected stylesheet is replaced. */
25
- export const RIBBON_STYLE_VERSION = "9";
31
+ export const RIBBON_STYLE_VERSION = "10";
26
32
  export const RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
33
+ /** Width of the comment gutter the surface reserves beside the sheet, in px. */
34
+ export const COMMENT_GUTTER_WIDTH = 264;
27
35
  /**
28
36
  * The ribbon stylesheet. Every rule is scoped under `.dxr` so injecting it is
29
37
  * safe on a host page we do not control — the same guarantee `embed.ts` gives
@@ -51,7 +59,7 @@ export const RIBBON_CSS = `
51
59
  --dxr-data: #0f766e;
52
60
  --dxr-danger: #dc2626;
53
61
  --dxr-danger-wash: #fef2f2;
54
- --dxr-desk: #f1f5f9;
62
+ --dxr-desk: #eef2f6;
55
63
  --dxr-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
56
64
  --dxr-serif: Georgia, "Times New Roman", serif;
57
65
  --dxr-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
@@ -60,7 +68,8 @@ export const RIBBON_CSS = `
60
68
  --dxr-shadow-lg: 0 4px 6px rgba(15, 23, 42, .05), 0 10px 15px rgba(15, 23, 42, .04);
61
69
  --dxr-shadow-xl: 0 8px 16px rgba(15, 23, 42, .06), 0 20px 25px rgba(15, 23, 42, .05);
62
70
  --dxr-ease: cubic-bezier(.4, 0, .2, 1);
63
- --dxr-tap: 30px;
71
+ --dxr-tap: 28px;
72
+ --dxr-gutter: ${COMMENT_GUTTER_WIDTH}px;
64
73
 
65
74
  position: relative;
66
75
  display: flex;
@@ -100,7 +109,7 @@ export const RIBBON_CSS = `
100
109
  mask dissolves content at exactly those edges — the horizontal twin of
101
110
  .dxr-scroll's gradient veils — and lifts entirely once the strip fits, so
102
111
  nothing is ever faded unless there is more behind it. */
103
- .dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail { --dxr-fade-l: 0px; --dxr-fade-r: 0px; }
112
+ .dxr-titlebar, .dxr-tabs, .dxr-panel, .dxr-rail, .dxr-findbar { --dxr-fade-l: 0px; --dxr-fade-r: 0px; }
104
113
  .dxr [data-fade] {
105
114
  -webkit-mask-image: linear-gradient(to right,
106
115
  transparent, #000 var(--dxr-fade-l),
@@ -175,7 +184,7 @@ export const RIBBON_CSS = `
175
184
  .dxr-tabs {
176
185
  display: flex;
177
186
  gap: 1px;
178
- padding: 6px 10px 0;
187
+ padding: 4px 10px 0;
179
188
  overflow-x: auto;
180
189
  scrollbar-width: none;
181
190
  }
@@ -184,7 +193,7 @@ export const RIBBON_CSS = `
184
193
  tab is a 2px accent underline sitting on the panel's own hairline. */
185
194
  .dxr-tab {
186
195
  flex: 0 0 auto;
187
- padding: 7px 14px 8px;
196
+ padding: 6px 13px 7px;
188
197
  border: none;
189
198
  background: none;
190
199
  color: var(--dxr-muted);
@@ -200,8 +209,9 @@ export const RIBBON_CSS = `
200
209
  font-weight: 600;
201
210
  box-shadow: inset 0 -2px 0 var(--dxr-accent);
202
211
  }
203
- /* Contextual tab — present only while the caret is inside a table. */
212
+ /* Contextual tabs — present only while the caret is in a table / a header or footer story. */
204
213
  .dxr-tab[data-contextual] { color: var(--dxr-accent); }
214
+ .dxr-tab[data-contextual]::before { content: "\\2022"; margin-right: 6px; opacity: .6; }
205
215
  .dxr-tab[hidden] { display: none; }
206
216
 
207
217
  /* ── Ribbon panels ────────────────────────────────────────────────────────── */
@@ -210,7 +220,7 @@ export const RIBBON_CSS = `
210
220
  .dxr-panel {
211
221
  display: none;
212
222
  align-items: stretch;
213
- padding: 7px 10px 8px;
223
+ padding: 6px 10px 7px;
214
224
  overflow-x: auto;
215
225
  overscroll-behavior-x: contain;
216
226
  }
@@ -221,8 +231,8 @@ export const RIBBON_CSS = `
221
231
  flex: 0 0 auto;
222
232
  display: flex;
223
233
  flex-direction: column;
224
- gap: 5px;
225
- padding: 0 13px;
234
+ gap: 4px;
235
+ padding: 0 12px;
226
236
  border-right: 1px solid var(--dxr-rule);
227
237
  }
228
238
  .dxr-group:last-child { border-right: none; }
@@ -235,6 +245,7 @@ export const RIBBON_CSS = `
235
245
  text-transform: uppercase;
236
246
  }
237
247
  .dxr-row { display: flex; gap: 3px; align-items: center; }
248
+ .dxr-row + .dxr-row { margin-top: 1px; }
238
249
 
239
250
  /* ── Controls ─────────────────────────────────────────────────────────────── */
240
251
  /* One button family, house-style: every control is a ghost — no borders, no
@@ -243,13 +254,13 @@ export const RIBBON_CSS = `
243
254
  surface has exactly one accent-colored action. */
244
255
  .dxr button, .dxr label.dxr-btn {
245
256
  min-height: var(--dxr-tap);
246
- padding: 5px 9px;
257
+ padding: 4px 8px;
247
258
  border: none;
248
259
  border-radius: 6px;
249
260
  background: none;
250
261
  color: var(--dxr-ink);
251
262
  font: inherit;
252
- font-size: 13px;
263
+ font-size: 12.5px;
253
264
  font-weight: 500;
254
265
  line-height: 1.15;
255
266
  cursor: pointer;
@@ -260,7 +271,7 @@ export const RIBBON_CSS = `
260
271
  .dxr button:hover, .dxr label.dxr-btn:hover { background: #f1f5f9; }
261
272
  .dxr button:active { background: #e2e8f0; }
262
273
  .dxr button:disabled { opacity: .38; cursor: default; background: none; }
263
- .dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash-on); }
274
+ .dxr button.dxr-on, .dxr button[aria-pressed="true"] { color: var(--dxr-accent); background: var(--dxr-wash-on); }
264
275
  .dxr-quick button, .dxr-quick label.dxr-btn { padding: 4px 10px; font-size: 12.5px; }
265
276
  /* Save — the house primary: flat accent, sm shadow, the 1px lift on hover. */
266
277
  .dxr-quick button[data-dxr="save"]:not(:disabled) {
@@ -280,7 +291,7 @@ export const RIBBON_CSS = `
280
291
  transform: translateY(0);
281
292
  }
282
293
  /* Icon controls sit quieter than text ones until touched (house IconButton). */
283
- .dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; color: var(--dxr-muted); }
294
+ .dxr button.dxr-icon { min-width: var(--dxr-tap); padding: 4px 6px; text-align: center; color: var(--dxr-muted); }
284
295
  .dxr button.dxr-icon:hover:not(:disabled) { color: var(--dxr-ink); }
285
296
  .dxr button.dxr-icon.dxr-on { color: var(--dxr-accent); }
286
297
  /* Icons are inline SVG drawn from the same shapes the control acts on (text lines,
@@ -289,9 +300,9 @@ export const RIBBON_CSS = `
289
300
  .dxr button svg { display: block; margin: 0 auto; fill: currentColor; }
290
301
  .dxr button.dxr-wide { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
291
302
  .dxr button.dxr-danger:hover { color: var(--dxr-danger); background: var(--dxr-danger-wash); }
292
- .dxr select, .dxr input[type="number"], .dxr input[type="text"] {
303
+ .dxr select, .dxr input[type="number"], .dxr input[type="text"], .dxr input[type="search"], .dxr input[type="url"] {
293
304
  min-height: var(--dxr-tap);
294
- padding: 4px 6px;
305
+ padding: 3px 6px;
295
306
  border: 1px solid var(--dxr-rule);
296
307
  border-radius: 6px;
297
308
  background: var(--dxr-sheet);
@@ -305,29 +316,72 @@ export const RIBBON_CSS = `
305
316
  outline: 2px solid var(--dxr-accent);
306
317
  outline-offset: 1px;
307
318
  }
308
- .dxr [data-dxr="fontsize"] { width: 62px; }
309
- .dxr [data-dxr="fontfamily"] { max-width: 132px; }
310
- .dxr [data-dxr="pgstart"] { width: 64px; }
319
+ .dxr [data-dxr="fontsize"] { width: 58px; }
320
+ .dxr [data-dxr="fontfamily"] { width: 138px; }
321
+ .dxr [data-dxr="style"] { width: 148px; }
322
+ .dxr [data-dxr="pgstart"], .dxr [data-dxr="spacebefore"], .dxr [data-dxr="spaceafter"],
323
+ .dxr [data-dxr="specialindentval"] { width: 62px; }
324
+ .dxr [data-dxr="linespacing"], .dxr [data-dxr="listmenu"], .dxr [data-dxr="highlight"],
325
+ .dxr [data-dxr="markup"], .dxr [data-dxr="zoom"], .dxr [data-dxr="margins"],
326
+ .dxr [data-dxr="orientation"], .dxr [data-dxr="pagesize"], .dxr [data-dxr="specialindent"] { max-width: 150px; }
311
327
  .dxr-toggle {
312
328
  display: inline-flex;
313
329
  gap: 5px;
314
330
  align-items: center;
331
+ min-height: var(--dxr-tap);
315
332
  color: var(--dxr-ink);
316
333
  font-size: 12.5px;
317
334
  white-space: nowrap;
318
335
  cursor: pointer;
319
336
  }
337
+ .dxr-toggle input[type="checkbox"] { accent-color: var(--dxr-accent); margin: 0; }
320
338
  .dxr-note { color: var(--dxr-muted); font-size: 11.5px; }
339
+ .dxr-kbd { color: var(--dxr-faint); font-family: var(--dxr-mono); font-size: 10px; }
321
340
 
322
- /* ── Anchor rail ──────────────────────────────────────────────────────────────
341
+ /* Colour swatch buttons: the button shows the current colour as a bar under its glyph and
342
+ opens the native picker that sits (visually hidden) inside it. */
343
+ .dxr-swatch { position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 2px; }
344
+ .dxr-swatch > button { padding-bottom: 6px; }
345
+ .dxr-swatch .dxr-swatch-bar {
346
+ position: absolute; left: 7px; right: 7px; bottom: 4px; height: 3px; border-radius: 2px;
347
+ background: var(--dxr-swatch, #1e293b); pointer-events: none;
348
+ }
349
+ .dxr-swatch input[type="color"] {
350
+ position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: 0; padding: 0;
351
+ }
352
+ .dxr-swatch [data-dxr="highlight"] {
353
+ min-height: var(--dxr-tap);
354
+ }
355
+ .dxr-hl-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 2px; vertical-align: -2px; margin-right: 4px; border: 1px solid rgba(15,23,42,.15); }
356
+
357
+ /* ── Find & replace bar ───────────────────────────────────────────────────── */
358
+ .dxr-findbar {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 6px;
362
+ padding: 5px 10px;
363
+ overflow-x: auto;
364
+ border-top: 1px solid var(--dxr-rule);
365
+ background: var(--dxr-chrome);
366
+ scrollbar-width: none;
367
+ }
368
+ .dxr-findbar[hidden] { display: none; }
369
+ .dxr-findbar > *, .dxr-findbar [data-dxr="replacegroup"] { flex: 0 0 auto; white-space: nowrap; }
370
+ .dxr-findbar [data-dxr="replacegroup"] { display: inline-flex; align-items: center; gap: 6px; }
371
+ .dxr-findbar input[type="search"], .dxr-findbar input[type="text"] { width: 180px; }
372
+ .dxr-findbar .dxr-findcount { min-width: 5ch; color: var(--dxr-muted); font-family: var(--dxr-mono); font-size: 11.5px; }
373
+
374
+ /* ── Status bar (carries the anchor rail) ─────────────────────────────────────
323
375
  The addressing spine made permanent chrome. Every block in this editor is
324
376
  addressable as kind:scope:unid, and the model of record is a live WASM session —
325
377
  so the surface states both, live, instead of hiding them behind devtools. It also
326
- reports each command's real cost, which is what a smoke test needs to see. */
378
+ reports each command's real cost, which is what a smoke test needs to see. Word's
379
+ own status-bar cells (page, words, zoom) sit on the same strip. */
327
380
  .dxr-rail {
328
381
  display: flex;
329
382
  align-items: center;
330
- height: 25px;
383
+ flex: 0 0 auto;
384
+ height: 26px;
331
385
  padding: 0 10px;
332
386
  overflow-x: auto;
333
387
  background: #fafafa;
@@ -342,12 +396,12 @@ export const RIBBON_CSS = `
342
396
  display: flex;
343
397
  align-items: baseline;
344
398
  gap: 6px;
345
- padding: 0 13px;
399
+ padding: 0 12px;
346
400
  border-right: 1px solid var(--dxr-rule);
347
401
  white-space: nowrap;
348
402
  }
349
403
  .dxr-rail .dxr-cell:first-child { padding-left: 0; }
350
- .dxr-rail .dxr-cell:last-child { border-right: none; }
404
+ .dxr-rail .dxr-cell.dxr-cell-end { margin-left: auto; border-right: none; padding-right: 0; }
351
405
  .dxr-rail .dxr-k {
352
406
  color: var(--dxr-faint);
353
407
  font-family: var(--dxr-ui);
@@ -357,7 +411,11 @@ export const RIBBON_CSS = `
357
411
  }
358
412
  .dxr-rail .dxr-v { color: var(--dxr-data); }
359
413
  .dxr-rail .dxr-v.dxr-flash { animation: dxr-railflash .45s ease-out; }
414
+ .dxr-rail .dxr-v.dxr-plain { color: var(--dxr-ink); font-family: var(--dxr-ui); font-size: 11.5px; }
360
415
  @keyframes dxr-railflash { from { background: #ccfbf1; } to { background: transparent; } }
416
+ .dxr-zoom { display: inline-flex; align-items: center; gap: 2px; }
417
+ .dxr-zoom button { min-height: 22px; min-width: 22px; padding: 0 5px; font-size: 13px; color: var(--dxr-muted); }
418
+ .dxr-zoom select { min-height: 22px; padding: 0 4px; font-size: 11.5px; }
361
419
 
362
420
  /* ── Hint ─────────────────────────────────────────────────────────────────── */
363
421
  .dxr-hint {
@@ -409,7 +467,10 @@ export const RIBBON_CSS = `
409
467
  margin-top: -26px;
410
468
  background: linear-gradient(to top, var(--dxr-desk), transparent);
411
469
  }
412
- .dxr[data-chrome] .dxr-surface { margin: 26px auto; padding: 0 16px 96px; }
470
+ .dxr[data-chrome] .dxr-surface { position: relative; margin: 26px auto; padding: 0 16px 96px; }
471
+ /* The comment gutter sits to the right of the sheet; the surface reserves its width so the
472
+ page shifts left rather than the bubbles covering it — Word's markup area. */
473
+ .dxr[data-chrome="full"] .dxr-surface[data-comments="on"] { padding-right: calc(var(--dxr-gutter) + 8px); }
413
474
  .dxr-surface [contenteditable="true"]:focus {
414
475
  outline: 2px solid var(--dxr-accent);
415
476
  outline-offset: 2px;
@@ -417,77 +478,65 @@ export const RIBBON_CSS = `
417
478
  }
418
479
  @media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: var(--dxr-wash); } }
419
480
 
420
- /* Header/footer bands: stories live in their own OOXML parts outside the body,
421
- so they dock as their own regions. Styled from the same tokens as the ribbon
422
- so the surface reads as one instrument rather than two apps. */
481
+ /* Header/footer bands (continuous view): the top and bottom margin of the sheet, drawn the way
482
+ Word draws a header that is being edited — a dashed rule with a small tag in the margin. */
423
483
  .dxr-surface .docx-hf-band {
424
484
  /* Docked outside the zoomed sheet, so it takes the page's on-screen width from the
425
485
  custom property the viewport publishes rather than stretching to the whole surface. */
486
+ position: relative;
426
487
  width: min(100%, var(--docx-sheet-width, 100%));
427
- margin: 0 auto 14px;
428
- padding: 9px 72px 13px;
429
- border: 1px solid var(--dxr-rule);
430
- border-left: 2px solid #5eead4;
431
- border-radius: 6px;
432
- background: var(--dxr-sheet);
433
- box-shadow: var(--dxr-shadow-sm);
434
- }
435
- .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
436
- .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px auto 0; }
437
- .dxr-surface .docx-hf-chrome {
438
- display: flex;
439
- gap: 8px;
440
- align-items: center;
441
- flex-wrap: wrap;
442
- margin: 0 0 7px -60px;
443
- color: var(--dxr-muted);
444
- font-size: 9.5px;
445
- font-weight: 600;
446
- letter-spacing: .11em;
447
- text-transform: uppercase;
448
- }
449
- .dxr-surface .docx-hf-chrome select, .dxr-surface .docx-hf-chrome input {
450
- padding: 3px 5px;
451
- border: 1px solid var(--dxr-rule);
452
- border-radius: 6px;
488
+ margin: 0 auto;
489
+ padding: 34px 72px 12px;
453
490
  background: var(--dxr-sheet);
454
- color: var(--dxr-ink);
455
- font: inherit;
456
- font-family: var(--dxr-ui);
457
- font-size: 12.5px;
458
- font-weight: 400;
459
- letter-spacing: normal;
460
- text-transform: none;
461
- }
462
- .dxr-surface .docx-hf-chrome input[data-hf-pagestart] { width: 64px; }
463
- .dxr-surface .docx-hf-label { font-weight: 600; }
464
- .dxr-surface .docx-hf-warning {
465
- margin: 0 0 8px -60px;
466
- padding: 7px 9px;
467
- border: 1px solid #fde68a;
468
- border-left: 2px solid #d97706;
469
- border-radius: 6px;
470
- background: #fffbeb;
471
- color: #92400e;
472
- font-size: 12px;
473
- line-height: 1.45;
474
- }
475
- .dxr-surface .docx-hf-warning button {
476
- margin-left: 6px;
477
- padding: 2px 8px;
478
- border-color: #fde68a;
479
- background: #fff;
480
- font-size: 12px;
481
- }
482
- .dxr-surface .docx-hf-placeholder { color: var(--dxr-faint); font-style: italic; }
483
- .dxr-surface .docx-hf-inherited {
484
- color: var(--dxr-muted);
485
- font-style: italic;
486
- font-weight: 400;
487
- letter-spacing: normal;
488
- text-transform: none;
491
+ border-radius: 3px 3px 0 0;
492
+ box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
489
493
  }
490
- .dxr-surface .docx-hf-band[data-hf-inherited] { border-style: dashed; }
494
+ .dxr-surface .docx-hf-band[data-hf-band="footer"] { padding: 12px 72px 34px; border-radius: 0 0 3px 3px; }
495
+ /* The band and the sheet are one piece of paper: kill the sheet's own top/bottom corners and
496
+ let the dashed rule separate the stories from the body. */
497
+ .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; border-radius: 0; box-shadow: none; }
498
+ .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin-top: 0; }
499
+ .dxr[data-chrome] .dxr-surface[data-view="continuous"] .docx-hf-band ~ .docx-body-flow { box-shadow: none; }
500
+ .dxr[data-chrome] .dxr-surface[data-view="continuous"]:has(.docx-hf-band) .docx-body-flow { padding: 18px 0; }
501
+ .dxr-surface .docx-hf-band::after {
502
+ content: ""; position: absolute; left: 0; right: 0; bottom: 0; border-bottom: 1px dashed var(--dxr-rule-strong);
503
+ }
504
+ .dxr-surface .docx-hf-band[data-hf-band="footer"]::after { top: 0; bottom: auto; border-bottom: 0; border-top: 1px dashed var(--dxr-rule-strong); }
505
+ .dxr-surface .docx-hf-band:hover::after, .dxr-surface .docx-hf-band[data-hf-active]::after { border-color: var(--dxr-accent); }
506
+ .dxr-surface .docx-hf-tag {
507
+ position: absolute; left: 72px; bottom: -9px; z-index: 2;
508
+ display: flex; align-items: center; gap: 8px;
509
+ padding: 1px 7px; border: 1px solid var(--dxr-rule-strong); border-radius: 4px;
510
+ background: #fff; color: var(--dxr-muted);
511
+ font-family: var(--dxr-ui); font-size: 9.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
512
+ }
513
+ .dxr-surface .docx-hf-band[data-hf-band="footer"] .docx-hf-tag { bottom: auto; top: -9px; }
514
+ .dxr-surface .docx-hf-band[data-hf-active] .docx-hf-tag { border-color: var(--dxr-accent); color: var(--dxr-accent); }
515
+ .dxr-surface .docx-hf-kinds { display: inline-flex; gap: 2px; }
516
+ .dxr-surface .docx-hf-kinds button {
517
+ min-height: 0; padding: 1px 6px; border-radius: 3px; font-family: var(--dxr-ui); font-size: 9.5px; font-weight: 600;
518
+ letter-spacing: .06em; text-transform: uppercase; color: var(--dxr-muted); background: none;
519
+ }
520
+ .dxr-surface .docx-hf-kinds button[data-on] { background: var(--dxr-wash-on); color: var(--dxr-accent); }
521
+ .dxr-surface .docx-hf-inherited { font-weight: 400; letter-spacing: normal; text-transform: none; font-style: italic; }
522
+ .dxr-surface .docx-hf-placeholder { color: var(--dxr-faint); font-style: italic; cursor: text; }
523
+ /* Bands only: a page host's height is the band the paginator reserved, and a floor here would
524
+ read as "the story grew" and trigger a needless re-paginate. */
525
+ .dxr-surface .docx-hf-band .docx-hf-body { min-height: 1.4em; }
526
+ /* Page view: the page's own header/footer area is click-to-edit. */
527
+ .dxr-surface .page-header[data-hf-page], .dxr-surface .page-footer[data-hf-page] { cursor: text; }
528
+ .dxr-surface .page-header[data-hf-page]:hover, .dxr-surface .page-footer[data-hf-page]:hover {
529
+ outline: 1px dashed var(--dxr-rule-strong); outline-offset: 2px;
530
+ }
531
+ .dxr-surface .page-header[data-hf-active], .dxr-surface .page-footer[data-hf-active] {
532
+ outline: 1px dashed var(--dxr-accent); outline-offset: 2px; z-index: 5; background: #fff;
533
+ }
534
+ .dxr-surface [data-hf-active][data-hf-label]::before {
535
+ content: attr(data-hf-label); position: absolute; left: -2px; top: -18px;
536
+ padding: 1px 6px; border: 1px solid var(--dxr-accent); border-radius: 4px; background: #fff; color: var(--dxr-accent);
537
+ font-family: var(--dxr-ui); font-size: 9px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; line-height: 1.4;
538
+ }
539
+ .dxr-surface .page-footer[data-hf-active][data-hf-label]::before { top: auto; bottom: -18px; }
491
540
  /* The sheet IS the page. The editor's viewport sizes .docx-body-flow to the section's page
492
541
  width and its section wrappers to the authored text column, so the horizontal gutters here
493
542
  are the document's own w:sectPr margins, not a padding this chrome invents; only the
@@ -502,25 +551,6 @@ export const RIBBON_CSS = `
502
551
  box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
503
552
  }
504
553
 
505
- /* ── Review: comment threads ──────────────────────────────────────────────── */
506
- .dxr-threads {
507
- display: flex; flex-direction: column; gap: 4px;
508
- max-height: 92px; overflow-y: auto; min-width: 260px; padding-right: 2px;
509
- }
510
- .dxr-thread {
511
- display: flex; align-items: center; gap: 6px;
512
- font-size: 11px; line-height: 1.3; padding: 3px 6px;
513
- border: 1px solid var(--dxr-rule); border-radius: 4px; background: var(--dxr-chrome-sunk);
514
- }
515
- .dxr-thread[data-resolved] { opacity: 0.55; }
516
- .dxr-thread[data-reply] { margin-left: 14px; }
517
- .dxr-thread .dxr-tauthor { font-weight: 600; white-space: nowrap; }
518
- .dxr-thread .dxr-ttext {
519
- flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px;
520
- }
521
- .dxr-thread button { flex: none; }
522
- .dxr-group.dxr-grow { flex: 1; min-width: 0; }
523
-
524
554
  /* ── Table size picker ────────────────────────────────────────────────────── */
525
555
  .dxr-pop {
526
556
  display: none;
@@ -552,6 +582,12 @@ export const RIBBON_CSS = `
552
582
  margin-top: 8px;
553
583
  font-size: 12px;
554
584
  }
585
+ /* Small prompt popovers (link URL, page setup), anchored under their button. */
586
+ .dxr-pop form { display: flex; flex-direction: column; gap: 8px; min-width: 260px; font-size: 12.5px; }
587
+ .dxr-pop form label { display: flex; flex-direction: column; gap: 3px; color: var(--dxr-muted); font-size: 11.5px; }
588
+ .dxr-pop form .dxr-row { justify-content: flex-end; }
589
+ .dxr-pop form button[type="submit"] { background: var(--dxr-accent); color: #fff; }
590
+ .dxr-pop form button[type="submit"]:hover { background: var(--dxr-accent-hover); }
555
591
 
556
592
  /* ── Compact chrome (the mobile-first base) ───────────────────────────────────
557
593
  One scrolling strip per tab instead of a multi-row ribbon: group labels turn
@@ -559,9 +595,18 @@ export const RIBBON_CSS = `
559
595
  bottom sheet because there is no room to hang a popover off a button. */
560
596
  /* A 320px phone cannot hold the product name AND the file actions, and the file
561
597
  actions are what a user came for. The strip also scrolls, so nothing is stranded. */
598
+ /* Touch targets grow with the LAYOUT, not the pointer alone: a phone-width chrome is driven
599
+ by a thumb whether or not the browser reports a coarse pointer (an emulated narrow viewport
600
+ does not), so the compact strip adopts the same 40px control floor the coarse-pointer media
601
+ query sets, keeping every icon button at a real tap size. */
602
+ .dxr[data-chrome="compact"] { --dxr-tap: 40px; }
603
+ /* The floor buys tap SIZE; the rows must not spend it again on padding. A phone stacks three
604
+ chrome rows — title, tabs, commands — above the page, so every pixel of row padding is paid
605
+ three times before the document starts, and the arcade demo's card (which sizes itself to the
606
+ game screen plus its controls) runs out of viewport when it is. */
562
607
  .dxr[data-chrome="compact"] .dxr-titlebar {
563
608
  gap: 6px;
564
- padding: 5px 8px 0;
609
+ padding: 0 8px;
565
610
  }
566
611
  .dxr[data-chrome="compact"] .dxr-brandname { display: none; }
567
612
  .dxr[data-chrome="compact"] .dxr-brand { flex: 0 1 auto; }
@@ -572,12 +617,16 @@ export const RIBBON_CSS = `
572
617
  .dxr[data-chrome="compact"] .dxr-quick button,
573
618
  .dxr[data-chrome="compact"] .dxr-quick label.dxr-btn { padding: 4px 8px; }
574
619
  .dxr[data-chrome="compact"] .dxr-status { display: none; }
575
- .dxr[data-chrome="compact"] .dxr-tabs { padding: 5px 8px 0; }
576
- .dxr[data-chrome="compact"] .dxr-tab { padding: 6px 12px 7px; font-size: 12px; }
620
+ .dxr[data-chrome="compact"] .dxr-tabs { padding: 0 8px; }
621
+ /* A tab is a WIDE target — its label plus 11px of side padding already clears the tap floor
622
+ horizontally — and it is navigation rather than a command, so it does not need the command
623
+ row's full height. Word's own phone ribbon keeps its tab strip slim for the same reason: the
624
+ page starts higher. The commands below keep the full 40px floor. */
625
+ .dxr[data-chrome="compact"] .dxr-tab { min-height: 34px; padding: 6px 11px 7px; font-size: 12px; }
577
626
  .dxr[data-chrome="compact"] .dxr-panel {
578
627
  align-items: center;
579
628
  gap: 4px;
580
- padding: 5px 8px;
629
+ padding: 2px 8px;
581
630
  scroll-snap-type: x proximity;
582
631
  }
583
632
  .dxr[data-chrome="compact"] .dxr-group {
@@ -591,14 +640,17 @@ export const RIBBON_CSS = `
591
640
  .dxr[data-chrome="compact"] .dxr-note { display: none; }
592
641
  .dxr[data-chrome="compact"] .dxr-rail { display: none; }
593
642
  .dxr[data-chrome="compact"] .dxr-hint { display: none; }
643
+ .dxr[data-chrome="compact"] .dxr-row + .dxr-row { margin-top: 0; }
594
644
  /* Compact trims the chrome around the page, never the page: the document's own column width
595
645
  is what the viewport's fit-to-width zoom scales, so a phone shows a whole smaller page
596
646
  instead of a narrower one that breaks its lines somewhere Word never would. */
597
647
  .dxr[data-chrome="compact"] .dxr-surface { margin: 12px auto; padding: 0 10px 64px; }
598
648
  .dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 0; }
599
- .dxr[data-chrome="compact"] .dxr-surface .docx-hf-band { padding: 9px 18px 13px; }
600
- .dxr[data-chrome="compact"] .dxr-surface .docx-hf-chrome,
601
- .dxr[data-chrome="compact"] .dxr-surface .docx-hf-warning { margin-left: 0; }
649
+ .dxr[data-chrome="compact"] .dxr-surface .docx-hf-band { padding: 26px 18px 10px; }
650
+ .dxr[data-chrome="compact"] .dxr-surface .docx-hf-band[data-hf-band="footer"] { padding: 10px 18px 26px; }
651
+ .dxr[data-chrome="compact"] .dxr-surface .docx-hf-tag { left: 18px; }
652
+ /* No room for a markup column on a phone: comments stay highlighted, bubbles step aside. */
653
+ .dxr[data-chrome="compact"] .docx-comment-gutter, .dxr[data-chrome="compact"] .docx-comment-leaders { display: none; }
602
654
  /* A popover anchored to a button has nowhere to go on a narrow surface, so the
603
655
  picker docks to the bottom edge where a thumb already is. */
604
656
  .dxr[data-chrome="compact"] .dxr-pop[data-open] {
@@ -814,13 +866,37 @@ export const RIBBON_CSS = `
814
866
  }
815
867
  `;
816
868
  const ICON_ALIGN = (bars) => `<svg width="15" height="13" viewBox="0 0 15 13" aria-hidden="true">${bars}</svg>`;
869
+ /** Word's highlighter palette (ST_HighlightColor) with its screen colours, in Word's order. */
870
+ export const HIGHLIGHT_COLORS = [
871
+ { value: "yellow", label: "Yellow", css: "#ffff00" },
872
+ { value: "green", label: "Bright green", css: "#00ff00" },
873
+ { value: "cyan", label: "Turquoise", css: "#00ffff" },
874
+ { value: "magenta", label: "Pink", css: "#ff00ff" },
875
+ { value: "blue", label: "Blue", css: "#0000ff" },
876
+ { value: "red", label: "Red", css: "#ff0000" },
877
+ { value: "darkBlue", label: "Dark blue", css: "#000080" },
878
+ { value: "darkCyan", label: "Teal", css: "#008080" },
879
+ { value: "darkGreen", label: "Green", css: "#008000" },
880
+ { value: "darkMagenta", label: "Violet", css: "#800080" },
881
+ { value: "darkRed", label: "Dark red", css: "#800000" },
882
+ { value: "darkYellow", label: "Dark yellow", css: "#808000" },
883
+ { value: "darkGray", label: "Gray 50%", css: "#808080" },
884
+ { value: "lightGray", label: "Gray 25%", css: "#c0c0c0" },
885
+ { value: "black", label: "Black", css: "#000000" },
886
+ ];
887
+ /** The fonts every Word install has; the document's own fonts are added on top at open. */
888
+ export const COMMON_FONTS = [
889
+ "Calibri", "Cambria", "Arial", "Times New Roman", "Georgia", "Garamond", "Verdana",
890
+ "Tahoma", "Trebuchet MS", "Book Antiqua", "Century Gothic", "Courier New", "Consolas",
891
+ "Segoe UI", "Helvetica",
892
+ ];
817
893
  /**
818
894
  * The ribbon's DOM, as one template.
819
895
  *
820
896
  * `data-dxr` names are the addressing contract (see the module header); the
821
897
  * behavioural attributes (`data-cmd`, `data-align`, `data-indent`, `data-list`,
822
- * `data-tt`) are what `ribbon.ts` delegates on, so adding a control here that
823
- * follows an existing convention needs no wiring change.
898
+ * `data-tt`, `data-hf`, `data-rev`, `data-cmt`) are what `ribbon.ts` delegates on,
899
+ * so adding a control here that follows an existing convention needs no wiring change.
824
900
  */
825
901
  export const RIBBON_HTML = `
826
902
  <div class="dxr-chrome">
@@ -844,25 +920,22 @@ export const RIBBON_HTML = `
844
920
  <button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
845
921
  <button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
846
922
  <button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
923
+ <button type="button" class="dxr-tab" role="tab" data-tab="references" aria-selected="false">References</button>
847
924
  <button type="button" class="dxr-tab" role="tab" data-tab="review" aria-selected="false">Review</button>
925
+ <button type="button" class="dxr-tab" role="tab" data-tab="view" aria-selected="false">View</button>
848
926
  <button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
927
+ <button type="button" class="dxr-tab" role="tab" data-tab="headerfooter" data-contextual aria-selected="false" hidden>Header &amp; Footer</button>
849
928
  </div>
850
929
 
851
930
  <div class="dxr-ribbon" data-dxr="ribbon" aria-disabled="true">
852
931
  <!-- HOME ──────────────────────────────────────────────────────────────── -->
853
932
  <div class="dxr-panel" data-panel="home" data-active>
854
933
  <div class="dxr-group">
855
- <span class="dxr-glabel">Text</span>
856
- <div class="dxr-row">
857
- <button type="button" class="dxr-icon" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
858
- <button type="button" class="dxr-icon" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
859
- <button type="button" class="dxr-icon" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
860
- <button type="button" class="dxr-icon" data-cmd="strike" title="Strikethrough"><s>S</s></button>
861
- <button type="button" class="dxr-icon" data-cmd="code" title="Inline code">&lt;/&gt;</button>
862
- <button type="button" class="dxr-icon" data-cmd="superscript" title="Superscript">x&#178;</button>
863
- <button type="button" class="dxr-icon" data-cmd="subscript" title="Subscript">x&#8322;</button>
864
- </div>
934
+ <span class="dxr-glabel">Font</span>
865
935
  <div class="dxr-row">
936
+ <select data-dxr="fontfamily" title="Font family — applies to the selection">
937
+ <option value="">Font&#8230;</option>
938
+ </select>
866
939
  <input data-dxr="fontsize" data-dxr-list="fontsizes" type="number" min="1" max="1638" step="0.5"
867
940
  placeholder="pt" title="Font size in points — type any value or pick a preset" />
868
941
  <datalist data-dxr="fontsizes">
@@ -871,34 +944,74 @@ export const RIBBON_HTML = `
871
944
  <option>20</option><option>24</option><option>28</option><option>36</option>
872
945
  <option>48</option><option>72</option><option>96</option>
873
946
  </datalist>
874
- <select data-dxr="fontfamily" title="Font family — applies to the selection">
875
- <option value="">Font&#8230;</option>
876
- <option>Calibri</option><option>Times New Roman</option><option>Arial</option>
877
- <option>Georgia</option><option>Cambria</option><option>Courier New</option>
878
- <option>Verdana</option><option>Garamond</option>
879
- </select>
947
+ <button type="button" class="dxr-icon" data-dxr="fontgrow" title="Increase font size (Ctrl+])">A<sup>&#9650;</sup></button>
948
+ <button type="button" class="dxr-icon" data-dxr="fontshrink" title="Decrease font size (Ctrl+[)">A<sub>&#9660;</sub></button>
949
+ <button type="button" class="dxr-icon" data-dxr="clearformat" title="Clear all formatting">A&#10008;</button>
950
+ </div>
951
+ <div class="dxr-row">
952
+ <button type="button" class="dxr-icon" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
953
+ <button type="button" class="dxr-icon" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
954
+ <button type="button" class="dxr-icon" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
955
+ <button type="button" class="dxr-icon" data-cmd="strike" title="Strikethrough"><s>S</s></button>
956
+ <button type="button" class="dxr-icon" data-cmd="superscript" title="Superscript">x&#178;</button>
957
+ <button type="button" class="dxr-icon" data-cmd="subscript" title="Subscript">x&#8322;</button>
958
+ <button type="button" class="dxr-icon" data-dxr="smallcaps" title="Small caps" aria-pressed="false"
959
+ style="font-variant: small-caps;">Aa</button>
960
+ <button type="button" class="dxr-icon" data-cmd="code" title="Inline code">&lt;/&gt;</button>
961
+ <span class="dxr-swatch" title="Font color">
962
+ <button type="button" class="dxr-icon" data-dxr="fontcolorbtn" aria-label="Font color"><b>A</b></button>
963
+ <span class="dxr-swatch-bar" data-dxr="fontcolorbar"></span>
964
+ <input type="color" data-dxr="fontcolor" value="#1e293b" aria-label="Pick a font color" />
965
+ </span>
966
+ <span class="dxr-swatch" title="Text highlight color">
967
+ <select data-dxr="highlight" aria-label="Text highlight color">
968
+ <option value="">&#9998; Highlight</option>
969
+ <option value="none">No color</option>
970
+ </select>
971
+ </span>
880
972
  </div>
881
973
  </div>
882
974
 
883
975
  <div class="dxr-group">
884
976
  <span class="dxr-glabel">Paragraph</span>
885
977
  <div class="dxr-row">
886
- <button type="button" class="dxr-icon" data-align="left" title="Align left">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="9" height="1.6"/>')}</button>
887
- <button type="button" class="dxr-icon" data-align="center" title="Align center">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="3" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="3" y="11.4" width="9" height="1.6"/>')}</button>
888
- <button type="button" class="dxr-icon" data-align="right" title="Align right">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="6" y="11.4" width="9" height="1.6"/>')}</button>
889
- <button type="button" class="dxr-icon" data-align="justify" title="Justify">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="15" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/>')}</button>
978
+ <button type="button" class="dxr-icon" data-list="bullet" title="Bullets">&#8226;&#8801;</button>
979
+ <button type="button" class="dxr-icon" data-list="decimal" title="Numbering">1.&#8801;</button>
980
+ <select data-dxr="listmenu" title="Numbering format">
981
+ <option value="">List style&#8230;</option>
982
+ <option value="bullet">&#8226; Bullet</option>
983
+ <option value="decimal">1. 2. 3.</option>
984
+ <option value="decimalParenthesis">(1) (2) (3)</option>
985
+ <option value="lowerLetter">a. b. c.</option>
986
+ <option value="lowerLetterParenthesis">(a) (b) (c)</option>
987
+ <option value="upperLetter">A. B. C.</option>
988
+ <option value="lowerRoman">i. ii. iii.</option>
989
+ <option value="lowerRomanParenthesis">(i) (ii) (iii)</option>
990
+ <option value="upperRoman">I. II. III.</option>
991
+ <option value="none">No list</option>
992
+ </select>
890
993
  <button type="button" class="dxr-icon" data-indent="-720" title="Decrease indent">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="6" y="7.6" width="9" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/><path d="M4.6 4.2v4.6L0.6 6.5z"/>')}</button>
891
994
  <button type="button" class="dxr-icon" data-indent="720" title="Increase indent">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="6" y="7.6" width="9" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/><path d="M0.6 4.2v4.6l4-2.3z"/>')}</button>
892
995
  </div>
893
996
  <div class="dxr-row">
894
- <button type="button" data-list="bullet" title="Bullet list">Bullets</button>
895
- <button type="button" data-list="decimal" title="Numbered list">Numbered</button>
896
- <button type="button" data-pagebreak title="Start this block on a new page">Page break</button>
997
+ <button type="button" class="dxr-icon" data-align="left" title="Align left (Ctrl+L)">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="9" height="1.6"/>')}</button>
998
+ <button type="button" class="dxr-icon" data-align="center" title="Center (Ctrl+E)">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="3" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="3" y="11.4" width="9" height="1.6"/>')}</button>
999
+ <button type="button" class="dxr-icon" data-align="right" title="Align right (Ctrl+R)">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="6" y="11.4" width="9" height="1.6"/>')}</button>
1000
+ <button type="button" class="dxr-icon" data-align="justify" title="Justify (Ctrl+J)">${ICON_ALIGN('<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="15" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/>')}</button>
1001
+ <select data-dxr="linespacing" title="Line spacing">
1002
+ <option value="">&#8597; Spacing&#8230;</option>
1003
+ <option value="1">1.0</option>
1004
+ <option value="1.15">1.15</option>
1005
+ <option value="1.5">1.5</option>
1006
+ <option value="2">2.0</option>
1007
+ <option value="2.5">2.5</option>
1008
+ <option value="3">3.0</option>
1009
+ </select>
897
1010
  </div>
898
1011
  </div>
899
1012
 
900
1013
  <div class="dxr-group">
901
- <span class="dxr-glabel">Block</span>
1014
+ <span class="dxr-glabel">Styles</span>
902
1015
  <div class="dxr-row">
903
1016
  <select data-dxr="style" title="Paragraph style">
904
1017
  <option value="">Style&#8230;</option>
@@ -913,17 +1026,64 @@ export const RIBBON_HTML = `
913
1026
  <button type="button" data-dxr="delblock" class="dxr-danger" title="Delete the block the caret is in">Delete block</button>
914
1027
  </div>
915
1028
  </div>
1029
+
1030
+ <div class="dxr-group">
1031
+ <span class="dxr-glabel">Editing</span>
1032
+ <div class="dxr-row">
1033
+ <button type="button" data-dxr="findtoggle" title="Find (Ctrl+F)">&#128269; Find</button>
1034
+ </div>
1035
+ <div class="dxr-row">
1036
+ <button type="button" data-dxr="replacetoggle" title="Find and replace (Ctrl+H)">Replace</button>
1037
+ </div>
1038
+ </div>
916
1039
  </div>
917
1040
 
918
1041
  <!-- INSERT ────────────────────────────────────────────────────────────── -->
919
1042
  <div class="dxr-panel" data-panel="insert">
920
1043
  <div class="dxr-group">
921
- <span class="dxr-glabel">Table</span>
1044
+ <span class="dxr-glabel">Pages</span>
1045
+ <div class="dxr-row">
1046
+ <button type="button" data-pagebreak title="Start this block on a new page (Ctrl+Enter)">Page break</button>
1047
+ </div>
1048
+ </div>
1049
+ <div class="dxr-group">
1050
+ <span class="dxr-glabel">Tables</span>
922
1051
  <div class="dxr-row">
923
1052
  <button type="button" data-dxr="table" title="Insert a table — pick its size on the grid">&#9638; Table</button>
924
1053
  </div>
925
1054
  </div>
926
-
1055
+ <div class="dxr-group">
1056
+ <span class="dxr-glabel">Illustrations</span>
1057
+ <div class="dxr-row">
1058
+ <label class="dxr-btn" tabindex="0" title="Insert a picture from a file">&#128444; Picture<input data-dxr="picturefile" type="file" accept="image/png,image/jpeg,image/gif,image/bmp,image/webp,image/tiff" hidden /></label>
1059
+ </div>
1060
+ </div>
1061
+ <div class="dxr-group">
1062
+ <span class="dxr-glabel">Links</span>
1063
+ <div class="dxr-row">
1064
+ <button type="button" data-dxr="link" title="Link the selected text to a URL (Ctrl+K)">&#128279; Link</button>
1065
+ <button type="button" data-dxr="unlink" title="Remove the link at the caret">Unlink</button>
1066
+ </div>
1067
+ </div>
1068
+ <div class="dxr-group">
1069
+ <span class="dxr-glabel">Comments</span>
1070
+ <div class="dxr-row">
1071
+ <button type="button" data-dxr="insertcomment" title="Comment on the selection">&#128172; Comment</button>
1072
+ </div>
1073
+ </div>
1074
+ <div class="dxr-group">
1075
+ <span class="dxr-glabel">Header &amp; Footer</span>
1076
+ <div class="dxr-row">
1077
+ <button type="button" data-dxr="gotoheader" title="Edit the header">Header</button>
1078
+ <button type="button" data-dxr="gotofooter" title="Edit the footer">Footer</button>
1079
+ <select data-dxr="pagenummenu" title="Add a page number to the footer">
1080
+ <option value=""># Page number&#8230;</option>
1081
+ <option value="currentPage">Page number</option>
1082
+ <option value="totalPages">Total pages</option>
1083
+ <option value="pageOf">Page X of Y</option>
1084
+ </select>
1085
+ </div>
1086
+ </div>
927
1087
  <div class="dxr-group">
928
1088
  <span class="dxr-glabel">Rules</span>
929
1089
  <div class="dxr-row">
@@ -939,25 +1099,52 @@ export const RIBBON_HTML = `
939
1099
  <button type="button" data-dxr="hrClear" title="Remove the rule or paragraph border">Clear</button>
940
1100
  </div>
941
1101
  </div>
1102
+ </div>
942
1103
 
1104
+ <!-- LAYOUT ────────────────────────────────────────────────────────────── -->
1105
+ <div class="dxr-panel" data-panel="layout">
943
1106
  <div class="dxr-group">
944
- <span class="dxr-glabel">References</span>
1107
+ <span class="dxr-glabel">Page setup</span>
945
1108
  <div class="dxr-row">
946
- <button type="button" data-dxr="footnote" title="Cite a new footnote at the caret">Footnote</button>
947
- <button type="button" data-dxr="endnote" title="Cite a new endnote at the caret">Endnote</button>
1109
+ <select data-dxr="margins" title="Page margins for this section">
1110
+ <option value="">Margins&#8230;</option>
1111
+ <option value="1440,1440,1440,1440">Normal — 1&quot; all round</option>
1112
+ <option value="720,720,720,720">Narrow — 0.5&quot; all round</option>
1113
+ <option value="1440,1440,1080,1080">Moderate — 1&quot; top/bottom, 0.75&quot; sides</option>
1114
+ <option value="1440,1440,2880,2880">Wide — 1&quot; top/bottom, 2&quot; sides</option>
1115
+ </select>
1116
+ <select data-dxr="orientation" title="Page orientation for this section">
1117
+ <option value="">Orientation&#8230;</option>
1118
+ <option value="portrait">Portrait</option>
1119
+ <option value="landscape">Landscape</option>
1120
+ </select>
1121
+ </div>
1122
+ <div class="dxr-row">
1123
+ <select data-dxr="pagesize" title="Paper size for this section">
1124
+ <option value="">Size&#8230;</option>
1125
+ <option value="12240,15840">Letter — 8.5 &times; 11&quot;</option>
1126
+ <option value="12240,20160">Legal — 8.5 &times; 14&quot;</option>
1127
+ <option value="11906,16838">A4 — 210 &times; 297 mm</option>
1128
+ <option value="15840,24480">Tabloid — 11 &times; 17&quot;</option>
1129
+ </select>
1130
+ <span class="dxr-note" data-dxr="pagesetupnote"></span>
948
1131
  </div>
949
1132
  </div>
950
- </div>
951
1133
 
952
- <!-- LAYOUT ────────────────────────────────────────────────────────────── -->
953
- <div class="dxr-panel" data-panel="layout">
954
1134
  <div class="dxr-group">
955
- <span class="dxr-glabel">View</span>
1135
+ <span class="dxr-glabel">Paragraph</span>
956
1136
  <div class="dxr-row">
957
- <label class="dxr-toggle"><input data-dxr="paginated" type="checkbox" /> Page view</label>
1137
+ <label class="dxr-toggle">Before <input data-dxr="spacebefore" type="number" min="0" step="1" placeholder="pt" title="Space before the paragraph, in points" /></label>
1138
+ <label class="dxr-toggle">After <input data-dxr="spaceafter" type="number" min="0" step="1" placeholder="pt" title="Space after the paragraph, in points" /></label>
958
1139
  </div>
959
1140
  <div class="dxr-row">
960
- <label class="dxr-toggle"><input data-dxr="headerfooter" type="checkbox" /> Header &amp; footer bands</label>
1141
+ <select data-dxr="specialindent" title="First-line or hanging indent">
1142
+ <option value="">Special&#8230;</option>
1143
+ <option value="none">(none)</option>
1144
+ <option value="firstLine">First line</option>
1145
+ <option value="hanging">Hanging</option>
1146
+ </select>
1147
+ <label class="dxr-toggle">by <input data-dxr="specialindentval" type="number" min="0" step="0.1" value="0.5" title="Indent amount, in inches" /> in</label>
961
1148
  </div>
962
1149
  </div>
963
1150
 
@@ -983,42 +1170,115 @@ export const RIBBON_HTML = `
983
1170
  </div>
984
1171
  </div>
985
1172
 
986
- <!-- The field lands in the running footer (Word's convention, and where the band
987
- puts it), so it belongs with the section's numbering rather than under Insert,
988
- whose controls all act at the caret. -->
1173
+ <div class="dxr-group">
1174
+ <span class="dxr-glabel">View</span>
1175
+ <div class="dxr-row">
1176
+ <label class="dxr-toggle"><input data-dxr="paginated" type="checkbox" /> Page view</label>
1177
+ </div>
1178
+ <div class="dxr-row">
1179
+ <label class="dxr-toggle"><input data-dxr="headerfooter" type="checkbox" checked /> Headers &amp; footers</label>
1180
+ </div>
1181
+ </div>
1182
+ </div>
1183
+
1184
+ <!-- REFERENCES ───────────────────────────────────────────────────────── -->
1185
+ <div class="dxr-panel" data-panel="references">
1186
+ <div class="dxr-group">
1187
+ <span class="dxr-glabel">Table of contents</span>
1188
+ <div class="dxr-row">
1189
+ <button type="button" data-dxr="toc" title="Insert a table of contents built from the document's headings">&#9776; Table of Contents</button>
1190
+ </div>
1191
+ </div>
1192
+ <div class="dxr-group">
1193
+ <span class="dxr-glabel">Footnotes</span>
1194
+ <div class="dxr-row">
1195
+ <button type="button" data-dxr="footnote" title="Cite a new footnote at the caret">Insert Footnote</button>
1196
+ <button type="button" data-dxr="endnote" title="Cite a new endnote at the caret">Insert Endnote</button>
1197
+ </div>
1198
+ </div>
989
1199
  <div class="dxr-group">
990
1200
  <span class="dxr-glabel">Footer fields</span>
991
1201
  <div class="dxr-row">
992
1202
  <button type="button" data-dxr="pagenum" title="Add a page-number field to the footer">Page number</button>
993
1203
  <button type="button" data-dxr="totalpages" title="Add a total-pages field to the footer">Total pages</button>
994
1204
  </div>
995
- <div class="dxr-row">
996
- <span class="dxr-note">Added to the footer story.</span>
997
- </div>
998
1205
  </div>
999
1206
  </div>
1000
1207
 
1001
- <!-- REVIEW ────────────────────────────────────────────────────────────
1002
- Comment authoring over the session's native comment family (issue #580):
1003
- the button comments the selection (the whole block when collapsed), and
1004
- the thread list is live session truth with resolve/reopen per thread. -->
1208
+ <!-- REVIEW ────────────────────────────────────────────────────────────── -->
1005
1209
  <div class="dxr-panel" data-panel="review">
1006
1210
  <div class="dxr-group">
1007
1211
  <span class="dxr-glabel">Comments</span>
1008
1212
  <div class="dxr-row">
1009
- <input data-dxr="commenttext" type="text" placeholder="Comment text&#8230;"
1010
- title="The comment body; markdown is honored" />
1011
- <button type="button" data-dxr="comment"
1012
- title="Comment on the selection, or on the whole block when nothing is selected">&#128172; Comment</button>
1213
+ <button type="button" data-dxr="comment" title="Comment on the selection (Ctrl+Alt+M)">&#128172; New Comment</button>
1214
+ <button type="button" data-dxr="commentdelete" class="dxr-danger" title="Delete the active comment thread">Delete</button>
1215
+ <button type="button" data-dxr="commentprev" class="dxr-icon" title="Previous comment">&#9650;</button>
1216
+ <button type="button" data-dxr="commentnext" class="dxr-icon" title="Next comment">&#9660;</button>
1217
+ </div>
1218
+ <div class="dxr-row">
1219
+ <button type="button" data-dxr="commentresolve" title="Resolve or reopen the active thread">Resolve</button>
1220
+ <label class="dxr-toggle"><input data-dxr="showcomments" type="checkbox" checked /> Show comments</label>
1221
+ <span class="dxr-note" data-dxr="commentcount"></span>
1222
+ </div>
1223
+ </div>
1224
+ <div class="dxr-group">
1225
+ <span class="dxr-glabel">Tracking</span>
1226
+ <div class="dxr-row">
1227
+ <label class="dxr-toggle"><input data-dxr="trackchanges" type="checkbox" /> Track changes</label>
1228
+ </div>
1229
+ <div class="dxr-row">
1230
+ <select data-dxr="markup" title="How revisions are shown">
1231
+ <option value="all">All markup</option>
1232
+ <option value="none">No markup</option>
1233
+ </select>
1234
+ <input data-dxr="author" type="text" placeholder="Author" title="Name stamped on your comments and revisions" style="width: 110px;" />
1235
+ </div>
1236
+ </div>
1237
+ <div class="dxr-group">
1238
+ <span class="dxr-glabel">Changes</span>
1239
+ <div class="dxr-row">
1240
+ <button type="button" data-rev="accept" title="Accept the change at the caret">&#10003; Accept</button>
1241
+ <button type="button" data-rev="reject" title="Reject the change at the caret">&#10007; Reject</button>
1242
+ <button type="button" data-rev="prev" class="dxr-icon" title="Previous change">&#9650;</button>
1243
+ <button type="button" data-rev="next" class="dxr-icon" title="Next change">&#9660;</button>
1244
+ </div>
1245
+ <div class="dxr-row">
1246
+ <button type="button" data-rev="acceptall" title="Accept every tracked change">Accept all</button>
1247
+ <button type="button" data-rev="rejectall" title="Reject every tracked change">Reject all</button>
1248
+ <span class="dxr-note" data-dxr="revcount"></span>
1249
+ </div>
1250
+ </div>
1251
+ </div>
1252
+
1253
+ <!-- VIEW ──────────────────────────────────────────────────────────────── -->
1254
+ <div class="dxr-panel" data-panel="view">
1255
+ <div class="dxr-group">
1256
+ <span class="dxr-glabel">Views</span>
1257
+ <div class="dxr-row">
1258
+ <button type="button" data-dxr="viewpage" title="Page view — real page boxes, headers and footers in the margins">&#128441; Page view</button>
1259
+ <button type="button" data-dxr="viewweb" title="Continuous view — one sheet, no page breaks">&#9776; Continuous</button>
1013
1260
  </div>
1261
+ </div>
1262
+ <div class="dxr-group">
1263
+ <span class="dxr-glabel">Zoom</span>
1014
1264
  <div class="dxr-row">
1015
- <span class="dxr-note">Comments the selected text; the whole block when the caret is collapsed.</span>
1265
+ <select data-dxr="zoom" title="Zoom">
1266
+ <option value="0.5">50%</option>
1267
+ <option value="0.75">75%</option>
1268
+ <option value="0.9">90%</option>
1269
+ <option value="1" selected>100%</option>
1270
+ <option value="1.25">125%</option>
1271
+ <option value="1.5">150%</option>
1272
+ <option value="2">200%</option>
1273
+ </select>
1274
+ <button type="button" data-dxr="zoomfit" title="Fit the page to the window">Fit width</button>
1016
1275
  </div>
1017
1276
  </div>
1018
- <div class="dxr-group dxr-grow">
1019
- <span class="dxr-glabel">Threads</span>
1020
- <div class="dxr-threads" data-dxr="threads" aria-live="polite">
1021
- <span class="dxr-note">No comments yet.</span>
1277
+ <div class="dxr-group">
1278
+ <span class="dxr-glabel">Show</span>
1279
+ <div class="dxr-row">
1280
+ <label class="dxr-toggle"><input data-dxr="showrail" type="checkbox" checked /> Status bar</label>
1281
+ <label class="dxr-toggle"><input data-dxr="showhint" type="checkbox" checked /> Editing hint</label>
1022
1282
  </div>
1023
1283
  </div>
1024
1284
  </div>
@@ -1043,21 +1303,118 @@ export const RIBBON_HTML = `
1043
1303
  <button type="button" data-tt="delCol" class="dxr-danger" title="Delete this column">Delete column</button>
1044
1304
  </div>
1045
1305
  </div>
1306
+ <div class="dxr-group">
1307
+ <span class="dxr-glabel">Merge</span>
1308
+ <div class="dxr-row">
1309
+ <button type="button" data-tt="mergeRight" title="Merge this cell with the one to its right">Merge right</button>
1310
+ <button type="button" data-tt="mergeDown" title="Merge this cell with the one below">Merge down</button>
1311
+ <button type="button" data-tt="unmerge" title="Split a merged cell back into its cells">Split</button>
1312
+ </div>
1313
+ </div>
1314
+ <div class="dxr-group">
1315
+ <span class="dxr-glabel">Design</span>
1316
+ <div class="dxr-row">
1317
+ <button type="button" data-tt="borders" title="Draw all borders">Borders</button>
1318
+ <button type="button" data-tt="noborders" title="Remove all borders">No borders</button>
1319
+ <span class="dxr-swatch" title="Cell shading">
1320
+ <button type="button" class="dxr-icon" data-dxr="shadebtn" aria-label="Cell shading">&#9638;</button>
1321
+ <span class="dxr-swatch-bar" data-dxr="shadebar" style="--dxr-swatch: #fef08a;"></span>
1322
+ <input type="color" data-dxr="shade" value="#fef08a" aria-label="Pick a cell shading" />
1323
+ </span>
1324
+ <button type="button" data-tt="noshade" title="Remove cell shading">No shading</button>
1325
+ </div>
1326
+ <div class="dxr-row">
1327
+ <label class="dxr-toggle"><input data-dxr="repeatheader" type="checkbox" /> Repeat header row</label>
1328
+ <button type="button" data-tt="delTable" class="dxr-danger" title="Delete the whole table">Delete table</button>
1329
+ </div>
1330
+ </div>
1331
+ </div>
1332
+
1333
+ <!-- HEADER & FOOTER (contextual) ────────────────────────────────────────
1334
+ Word's Header & Footer Tools: the two option checkboxes, page-number fields,
1335
+ navigation between the stories, and Close. -->
1336
+ <div class="dxr-panel" data-panel="headerfooter">
1337
+ <div class="dxr-group">
1338
+ <span class="dxr-glabel">Options</span>
1339
+ <div class="dxr-row">
1340
+ <label class="dxr-toggle"><input data-dxr="hfFirst" type="checkbox" /> Different first page</label>
1341
+ </div>
1342
+ <div class="dxr-row">
1343
+ <label class="dxr-toggle"><input data-dxr="hfEven" type="checkbox" /> Different odd &amp; even pages</label>
1344
+ </div>
1345
+ </div>
1346
+ <div class="dxr-group">
1347
+ <span class="dxr-glabel">Insert</span>
1348
+ <div class="dxr-row">
1349
+ <button type="button" data-hf="pagenum" title="Insert the page number at the end of this line">Page number</button>
1350
+ <button type="button" data-hf="totalpages" title="Insert the total page count">Total pages</button>
1351
+ <button type="button" data-hf="pageof" title="Insert &quot;Page X of Y&quot;">Page X of Y</button>
1352
+ </div>
1353
+ </div>
1354
+ <div class="dxr-group">
1355
+ <span class="dxr-glabel">Navigation</span>
1356
+ <div class="dxr-row">
1357
+ <button type="button" data-hf="header" title="Go to the header">Go to Header</button>
1358
+ <button type="button" data-hf="footer" title="Go to the footer">Go to Footer</button>
1359
+ </div>
1360
+ <div class="dxr-row">
1361
+ <span class="dxr-note" data-dxr="hfstory">Header</span>
1362
+ </div>
1363
+ </div>
1364
+ <div class="dxr-group">
1365
+ <span class="dxr-glabel">Close</span>
1366
+ <div class="dxr-row">
1367
+ <button type="button" data-hf="close" title="Return to the document body (Esc)">&#10005; Close Header and Footer</button>
1368
+ </div>
1369
+ </div>
1046
1370
  </div>
1047
1371
  </div>
1048
1372
 
1049
- <!-- Anchor rail — live engine state, not decoration. -->
1050
- <div class="dxr-rail" data-dxr-rail>
1051
- <span class="dxr-cell"><span class="dxr-k">anchor</span><span class="dxr-v" data-dxr="railAnchor">&#8212;</span></span>
1052
- <span class="dxr-cell"><span class="dxr-k">blocks</span><span class="dxr-v" data-dxr="railBlocks">&#8212;</span></span>
1053
- <span class="dxr-cell"><span class="dxr-k">session</span><span class="dxr-v" data-dxr="railSession">&#8212;</span></span>
1054
- <span class="dxr-cell"><span class="dxr-k">last op</span><span class="dxr-v" data-dxr="railOp">&#8212;</span></span>
1373
+ <!-- Find & replace strip, under the ribbon. -->
1374
+ <div class="dxr-findbar" data-dxr="findbar" hidden>
1375
+ <input data-dxr="findtext" type="search" placeholder="Find" aria-label="Find" />
1376
+ <button type="button" class="dxr-icon" data-dxr="findprev" title="Previous match (Shift+Enter)">&#9650;</button>
1377
+ <button type="button" class="dxr-icon" data-dxr="findnext" title="Next match (Enter)">&#9660;</button>
1378
+ <span class="dxr-findcount" data-dxr="findcount"></span>
1379
+ <label class="dxr-toggle"><input data-dxr="findcase" type="checkbox" /> Match case</label>
1380
+ <span data-dxr="replacegroup">
1381
+ <input data-dxr="replacetext" type="text" placeholder="Replace with" aria-label="Replace with" />
1382
+ <button type="button" data-dxr="replaceone" title="Replace this match">Replace</button>
1383
+ <button type="button" data-dxr="replaceall" title="Replace every match">Replace all</button>
1384
+ </span>
1385
+ <span class="dxr-spacer" style="flex:1"></span>
1386
+ <button type="button" class="dxr-icon" data-dxr="findclose" title="Close (Esc)">&#10005;</button>
1055
1387
  </div>
1056
1388
  </div>
1057
1389
 
1058
1390
  <div class="dxr-scroll" data-dxr-scroll>
1059
1391
  <p class="dxr-hint" data-dxr-hint></p>
1060
- <div class="dxr-surface" data-dxr="editor" data-dxr-surface data-view="continuous"></div>
1392
+ <div class="dxr-surface" data-dxr="editor" data-dxr-surface data-view="continuous" data-comments="on"></div>
1393
+ </div>
1394
+
1395
+ <!-- Status bar — live engine state (the anchor rail), Word's page/word cells, and zoom. -->
1396
+ <div class="dxr-rail" data-dxr-rail>
1397
+ <span class="dxr-cell"><span class="dxr-k">anchor</span><span class="dxr-v" data-dxr="railAnchor">&#8212;</span></span>
1398
+ <span class="dxr-cell"><span class="dxr-k">blocks</span><span class="dxr-v" data-dxr="railBlocks">&#8212;</span></span>
1399
+ <span class="dxr-cell"><span class="dxr-k">session</span><span class="dxr-v" data-dxr="railSession">&#8212;</span></span>
1400
+ <span class="dxr-cell"><span class="dxr-k">last op</span><span class="dxr-v" data-dxr="railOp">&#8212;</span></span>
1401
+ <span class="dxr-cell"><span class="dxr-v dxr-plain" data-dxr="pageinfo">&#8212;</span></span>
1402
+ <span class="dxr-cell"><span class="dxr-v dxr-plain" data-dxr="wordcount">&#8212;</span></span>
1403
+ <span class="dxr-cell dxr-cell-end">
1404
+ <span class="dxr-zoom">
1405
+ <button type="button" data-dxr="zoomout" title="Zoom out" aria-label="Zoom out">&#8722;</button>
1406
+ <select data-dxr="zoomlevel" title="Zoom" aria-label="Zoom level">
1407
+ <option value="0.5">50%</option>
1408
+ <option value="0.75">75%</option>
1409
+ <option value="0.9">90%</option>
1410
+ <option value="1" selected>100%</option>
1411
+ <option value="1.25">125%</option>
1412
+ <option value="1.5">150%</option>
1413
+ <option value="2">200%</option>
1414
+ </select>
1415
+ <button type="button" data-dxr="zoomin" title="Zoom in" aria-label="Zoom in">+</button>
1416
+ </span>
1417
+ </span>
1061
1418
  </div>
1062
1419
 
1063
1420
  <!-- Table size picker, anchored to the Insert tab's Table button. -->
@@ -1078,6 +1435,17 @@ export const RIBBON_HTML = `
1078
1435
  </div>
1079
1436
  </div>
1080
1437
 
1438
+ <!-- Link popover, anchored to the Insert tab's Link button. -->
1439
+ <div class="dxr-pop" data-dxr="linkpop">
1440
+ <form data-dxr="linkform">
1441
+ <label>Address <input data-dxr="linkurl" type="url" placeholder="https://" required /></label>
1442
+ <div class="dxr-row">
1443
+ <button type="button" data-dxr="linkcancel">Cancel</button>
1444
+ <button type="submit">Insert link</button>
1445
+ </div>
1446
+ </form>
1447
+ </div>
1448
+
1081
1449
  <div class="dxr-loader" data-dxr="loader" aria-live="polite" hidden>
1082
1450
  <div class="dxr-loader-grid">
1083
1451
  <div class="dxr-visual" aria-hidden="true">
@@ -1101,8 +1469,8 @@ export const RIBBON_HTML = `
1101
1469
  </div>
1102
1470
  `;
1103
1471
  /** The default hint copy, shown above the document in full chrome. */
1104
- export const RIBBON_HINT_HTML = "Click any paragraph, heading, table cell, footnote or header/footer line to edit it. " +
1472
+ export const RIBBON_HINT_HTML = "Click any paragraph, heading, table cell, footnote, header or footer to edit it. " +
1105
1473
  "<kbd>Enter</kbd> splits a block, <kbd>Backspace</kbd> at the start merges it into the previous one, " +
1106
- "<kbd>Ctrl</kbd>+<kbd>Z</kbd> undoes. Only the block you changed re-renders — everything else keeps " +
1107
- "full fidelity, and <b>Save</b> writes a lossless .docx.";
1474
+ "<kbd>Ctrl</kbd>+<kbd>Z</kbd> undoes. Select text and press <b>New Comment</b> to annotate it. Only the block you " +
1475
+ "changed re-renders — everything else keeps full fidelity, and <b>Save</b> writes a lossless .docx.";
1108
1476
  //# sourceMappingURL=ribbon-chrome.js.map