@beyondwork/docx-react-component 1.0.29 → 1.0.31

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 (383) hide show
  1. package/package.json +65 -96
  2. package/src/README.md +85 -0
  3. package/src/api/README.md +26 -0
  4. package/src/api/public-types.ts +1952 -0
  5. package/src/api/session-state.ts +62 -0
  6. package/src/compare/diff-engine.ts +623 -0
  7. package/src/compare/export-redlines.ts +280 -0
  8. package/src/compare/index.ts +25 -0
  9. package/src/compare/snapshot.ts +97 -0
  10. package/src/component-inventory.md +99 -0
  11. package/src/core/README.md +10 -0
  12. package/src/core/commands/README.md +3 -0
  13. package/{dist/chunk-TJBP2K4T.js → src/core/commands/formatting-commands.ts} +536 -196
  14. package/src/core/commands/image-commands.ts +373 -0
  15. package/src/core/commands/index.ts +1879 -0
  16. package/src/core/commands/list-commands.ts +565 -0
  17. package/src/core/commands/paragraph-layout-commands.ts +339 -0
  18. package/src/core/commands/review-commands.ts +108 -0
  19. package/{dist/core/commands/section-layout-commands.cjs → src/core/commands/section-layout-commands.ts} +340 -137
  20. package/src/core/commands/structural-helpers.ts +309 -0
  21. package/{dist/core/commands/style-commands.cjs → src/core/commands/style-commands.ts} +113 -65
  22. package/src/core/commands/table-structure-commands.ts +854 -0
  23. package/{dist/chunk-UZXBISGO.js → src/core/commands/text-commands.ts} +142 -86
  24. package/src/core/schema/README.md +3 -0
  25. package/src/core/schema/text-schema.ts +516 -0
  26. package/src/core/search/search-text.ts +357 -0
  27. package/src/core/selection/README.md +3 -0
  28. package/src/core/selection/mapping.ts +289 -0
  29. package/src/core/selection/review-anchors.ts +183 -0
  30. package/src/core/state/README.md +3 -0
  31. package/src/core/state/editor-state.ts +892 -0
  32. package/src/core/state/text-transaction.ts +869 -0
  33. package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
  34. package/src/formats/xlsx/io/parse-sheet.ts +459 -0
  35. package/src/formats/xlsx/io/parse-styles.ts +59 -0
  36. package/src/formats/xlsx/io/parse-workbook.ts +75 -0
  37. package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
  38. package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
  39. package/src/formats/xlsx/io/serialize-styles.ts +98 -0
  40. package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
  41. package/src/formats/xlsx/io/xlsx-session.ts +314 -0
  42. package/src/formats/xlsx/model/cell.ts +189 -0
  43. package/src/formats/xlsx/model/sheet.ts +326 -0
  44. package/src/formats/xlsx/model/styles.ts +118 -0
  45. package/src/formats/xlsx/model/workbook.ts +453 -0
  46. package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
  47. package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
  48. package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
  49. package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
  50. package/src/index.ts +142 -0
  51. package/src/io/README.md +10 -0
  52. package/src/io/docx-session.ts +3175 -0
  53. package/src/io/export/README.md +3 -0
  54. package/src/io/export/export-session.ts +220 -0
  55. package/src/io/export/minimal-docx.ts +115 -0
  56. package/src/io/export/reattach-preserved-parts.ts +54 -0
  57. package/src/io/export/serialize-comments.ts +947 -0
  58. package/src/io/export/serialize-footnotes.ts +394 -0
  59. package/src/io/export/serialize-headers-footers.ts +368 -0
  60. package/src/io/export/serialize-main-document.ts +1342 -0
  61. package/src/io/export/serialize-numbering.ts +218 -0
  62. package/src/io/export/serialize-revisions.ts +389 -0
  63. package/src/io/export/serialize-runtime-revisions.ts +463 -0
  64. package/src/io/export/serialize-tables.ts +174 -0
  65. package/src/io/export/split-review-boundaries.ts +356 -0
  66. package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
  67. package/src/io/export/table-properties-xml.ts +318 -0
  68. package/src/io/normalize/README.md +3 -0
  69. package/src/io/normalize/normalize-text.ts +670 -0
  70. package/src/io/ooxml/README.md +3 -0
  71. package/src/io/ooxml/highlight-colors.ts +39 -0
  72. package/src/io/ooxml/numbering-sentinels.ts +44 -0
  73. package/src/io/ooxml/parse-comments.ts +852 -0
  74. package/src/io/ooxml/parse-complex-content.ts +287 -0
  75. package/src/io/ooxml/parse-fields.ts +834 -0
  76. package/src/io/ooxml/parse-footnotes.ts +952 -0
  77. package/src/io/ooxml/parse-headers-footers.ts +1212 -0
  78. package/src/io/ooxml/parse-inline-media.ts +461 -0
  79. package/src/io/ooxml/parse-main-document.ts +2947 -0
  80. package/src/io/ooxml/parse-numbering.ts +747 -0
  81. package/src/io/ooxml/parse-revisions.ts +1045 -0
  82. package/src/io/ooxml/parse-settings.ts +184 -0
  83. package/src/io/ooxml/parse-shapes.ts +296 -0
  84. package/src/io/ooxml/parse-styles.ts +639 -0
  85. package/src/io/ooxml/parse-tables.ts +627 -0
  86. package/src/io/ooxml/parse-theme.ts +346 -0
  87. package/src/io/ooxml/part-manifest.ts +136 -0
  88. package/src/io/ooxml/revision-boundaries.ts +475 -0
  89. package/src/io/ooxml/workflow-payload.ts +544 -0
  90. package/src/io/opc/README.md +3 -0
  91. package/src/io/opc/corrupt-package.ts +166 -0
  92. package/src/io/opc/docx-package.ts +74 -0
  93. package/src/io/opc/package-reader.ts +325 -0
  94. package/src/io/opc/package-writer.ts +273 -0
  95. package/src/io/source-package-provenance.ts +241 -0
  96. package/{dist/chunk-RMH72RZI.js → src/legal/bookmarks.ts} +130 -44
  97. package/src/legal/cross-references.ts +414 -0
  98. package/src/legal/defined-terms.ts +203 -0
  99. package/src/legal/index.ts +32 -0
  100. package/src/legal/signature-blocks.ts +259 -0
  101. package/src/model/README.md +3 -0
  102. package/src/model/canonical-document.ts +2722 -0
  103. package/src/model/cds-1.0.0.ts +212 -0
  104. package/src/model/snapshot.ts +760 -0
  105. package/src/preservation/README.md +3 -0
  106. package/src/preservation/markup-compatibility.ts +48 -0
  107. package/src/preservation/opaque-fragment-store.ts +89 -0
  108. package/src/preservation/opaque-region.ts +233 -0
  109. package/src/preservation/package-preservation.ts +113 -0
  110. package/src/preservation/preserved-part-manifest.ts +56 -0
  111. package/src/preservation/relationship-retention.ts +57 -0
  112. package/src/preservation/store.ts +255 -0
  113. package/src/review/README.md +16 -0
  114. package/src/review/store/README.md +3 -0
  115. package/src/review/store/comment-anchors.ts +70 -0
  116. package/src/review/store/comment-remapping.ts +154 -0
  117. package/src/review/store/comment-store.ts +349 -0
  118. package/src/review/store/comment-thread.ts +109 -0
  119. package/src/review/store/revision-actions.ts +423 -0
  120. package/src/review/store/revision-store.ts +323 -0
  121. package/src/review/store/revision-types.ts +182 -0
  122. package/src/review/store/runtime-comment-store.ts +43 -0
  123. package/src/runtime/README.md +3 -0
  124. package/src/runtime/ai-action-policy.ts +764 -0
  125. package/src/runtime/context-analytics.ts +824 -0
  126. package/src/runtime/document-layout.ts +332 -0
  127. package/src/runtime/document-locations.ts +521 -0
  128. package/src/runtime/document-navigation.ts +616 -0
  129. package/src/runtime/document-outline.ts +440 -0
  130. package/src/runtime/document-runtime.ts +4055 -0
  131. package/src/runtime/document-search.ts +145 -0
  132. package/src/runtime/event-refresh-hints.ts +137 -0
  133. package/src/runtime/numbering-prefix.ts +244 -0
  134. package/src/runtime/page-layout-estimation.ts +305 -0
  135. package/src/runtime/read-only-diagnostics-runtime.ts +241 -0
  136. package/src/runtime/resolved-numbering-geometry.ts +293 -0
  137. package/src/runtime/review-runtime.ts +44 -0
  138. package/src/runtime/revision-runtime.ts +107 -0
  139. package/src/runtime/session-capabilities.ts +192 -0
  140. package/src/runtime/story-context.ts +164 -0
  141. package/src/runtime/story-targeting.ts +162 -0
  142. package/src/runtime/suggestions-snapshot.ts +137 -0
  143. package/src/runtime/surface-projection.ts +1553 -0
  144. package/src/runtime/table-commands.ts +173 -0
  145. package/src/runtime/table-schema.ts +309 -0
  146. package/src/runtime/table-style-resolver.ts +409 -0
  147. package/src/runtime/view-state.ts +493 -0
  148. package/src/runtime/virtualized-rendering.ts +258 -0
  149. package/src/runtime/workflow-markup.ts +393 -0
  150. package/src/ui/README.md +30 -0
  151. package/src/ui/WordReviewEditor.tsx +5268 -0
  152. package/src/ui/browser-export.ts +52 -0
  153. package/src/ui/comments/README.md +3 -0
  154. package/src/ui/compatibility/README.md +3 -0
  155. package/src/ui/editor-command-bag.ts +127 -0
  156. package/src/ui/editor-runtime-boundary.ts +1558 -0
  157. package/src/ui/editor-shell-view.tsx +144 -0
  158. package/src/ui/editor-surface/README.md +3 -0
  159. package/src/ui/editor-surface-controller.tsx +66 -0
  160. package/src/ui/headless/comment-decoration-model.ts +124 -0
  161. package/src/ui/headless/preserve-editor-selection.ts +5 -0
  162. package/src/ui/headless/revision-decoration-model.ts +128 -0
  163. package/src/ui/headless/selection-helpers.ts +54 -0
  164. package/src/ui/headless/selection-tool-context.ts +19 -0
  165. package/src/ui/headless/selection-tool-resolver.ts +752 -0
  166. package/src/ui/headless/selection-tool-types.ts +129 -0
  167. package/src/ui/headless/selection-toolbar-model.ts +11 -0
  168. package/src/ui/headless/use-editor-keyboard.ts +103 -0
  169. package/src/ui/review/README.md +3 -0
  170. package/src/ui/runtime-shortcut-dispatch.ts +365 -0
  171. package/src/ui/runtime-snapshot-selectors.ts +197 -0
  172. package/src/ui/shared/revision-filters.ts +31 -0
  173. package/src/ui/status/README.md +3 -0
  174. package/src/ui/theme/README.md +3 -0
  175. package/src/ui/toolbar/README.md +3 -0
  176. package/src/ui/workflow-surface-blocked-rails.ts +94 -0
  177. package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
  178. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
  179. package/src/ui-tailwind/chrome/responsive-chrome.ts +46 -0
  180. package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
  181. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +64 -0
  182. package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
  183. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +121 -0
  184. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
  185. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +30 -0
  186. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +365 -0
  187. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
  188. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
  189. package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
  190. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +303 -0
  191. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
  192. package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
  193. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
  194. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +186 -0
  195. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
  196. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +250 -0
  197. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  198. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  199. package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
  200. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +189 -0
  201. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
  202. package/src/ui-tailwind/editor-surface/pm-decorations.ts +411 -0
  203. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  204. package/src/ui-tailwind/editor-surface/pm-schema.ts +927 -0
  205. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +567 -0
  206. package/src/ui-tailwind/editor-surface/search-plugin.ts +168 -0
  207. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +63 -0
  208. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  209. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  210. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +129 -0
  211. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +58 -0
  212. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  213. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +1047 -0
  214. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  215. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +503 -0
  216. package/src/ui-tailwind/index.ts +62 -0
  217. package/src/ui-tailwind/page-chrome-model.ts +27 -0
  218. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +406 -0
  219. package/src/ui-tailwind/review/tw-health-panel.tsx +149 -0
  220. package/src/ui-tailwind/review/tw-review-rail.tsx +130 -0
  221. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  222. package/src/ui-tailwind/status/tw-status-bar.tsx +65 -0
  223. package/{dist → src}/ui-tailwind/theme/editor-theme.css +58 -40
  224. package/src/ui-tailwind/toolbar/toolbar-layout.ts +47 -0
  225. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +52 -0
  226. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +1478 -0
  227. package/src/ui-tailwind/tw-review-workspace.tsx +1587 -0
  228. package/src/validation/README.md +3 -0
  229. package/src/validation/compatibility-engine.ts +878 -0
  230. package/src/validation/compatibility-report.ts +161 -0
  231. package/src/validation/diagnostics.ts +204 -0
  232. package/src/validation/docx-comment-proof.ts +720 -0
  233. package/src/validation/import-diagnostics.ts +128 -0
  234. package/src/validation/low-priority-word-surfaces.ts +373 -0
  235. package/dist/canonical-document-BLEbzL2J.d.cts +0 -844
  236. package/dist/canonical-document-BLEbzL2J.d.ts +0 -844
  237. package/dist/chunk-2FJS5GZM.js +0 -763
  238. package/dist/chunk-2FJS5GZM.js.map +0 -1
  239. package/dist/chunk-2OQBZS3F.js +0 -446
  240. package/dist/chunk-2OQBZS3F.js.map +0 -1
  241. package/dist/chunk-2S7W4KFO.js +0 -127
  242. package/dist/chunk-2S7W4KFO.js.map +0 -1
  243. package/dist/chunk-2TG72QSW.js +0 -3874
  244. package/dist/chunk-2TG72QSW.js.map +0 -1
  245. package/dist/chunk-36QNIZBO.js +0 -532
  246. package/dist/chunk-36QNIZBO.js.map +0 -1
  247. package/dist/chunk-4AQOYAW4.js +0 -3069
  248. package/dist/chunk-4AQOYAW4.js.map +0 -1
  249. package/dist/chunk-4D5EWJ3P.js +0 -77
  250. package/dist/chunk-4D5EWJ3P.js.map +0 -1
  251. package/dist/chunk-5FN54NDH.js +0 -2257
  252. package/dist/chunk-5FN54NDH.js.map +0 -1
  253. package/dist/chunk-BOYGQYRQ.js +0 -7306
  254. package/dist/chunk-BOYGQYRQ.js.map +0 -1
  255. package/dist/chunk-CN3XMECL.js +0 -212
  256. package/dist/chunk-CN3XMECL.js.map +0 -1
  257. package/dist/chunk-EBI3BX6U.js +0 -164
  258. package/dist/chunk-EBI3BX6U.js.map +0 -1
  259. package/dist/chunk-EILUG3VB.js +0 -1275
  260. package/dist/chunk-EILUG3VB.js.map +0 -1
  261. package/dist/chunk-FUDY333O.js +0 -70
  262. package/dist/chunk-FUDY333O.js.map +0 -1
  263. package/dist/chunk-GBVOWFIK.js +0 -1237
  264. package/dist/chunk-GBVOWFIK.js.map +0 -1
  265. package/dist/chunk-H4TQ3H3Y.js +0 -262
  266. package/dist/chunk-H4TQ3H3Y.js.map +0 -1
  267. package/dist/chunk-JGB3IXZO.js +0 -189
  268. package/dist/chunk-JGB3IXZO.js.map +0 -1
  269. package/dist/chunk-KD2QRQPY.js +0 -4342
  270. package/dist/chunk-KD2QRQPY.js.map +0 -1
  271. package/dist/chunk-KLMXQVYK.js +0 -369
  272. package/dist/chunk-KLMXQVYK.js.map +0 -1
  273. package/dist/chunk-KZUG5KFQ.js +0 -214
  274. package/dist/chunk-KZUG5KFQ.js.map +0 -1
  275. package/dist/chunk-QDAQ4CJU.js +0 -345
  276. package/dist/chunk-QDAQ4CJU.js.map +0 -1
  277. package/dist/chunk-RMH72RZI.js.map +0 -1
  278. package/dist/chunk-SWKWQZXM.js +0 -117
  279. package/dist/chunk-SWKWQZXM.js.map +0 -1
  280. package/dist/chunk-TJBP2K4T.js.map +0 -1
  281. package/dist/chunk-TLCEAQDQ.js +0 -542
  282. package/dist/chunk-TLCEAQDQ.js.map +0 -1
  283. package/dist/chunk-UZXBISGO.js.map +0 -1
  284. package/dist/chunk-WGBAKP3Q.js +0 -3220
  285. package/dist/chunk-WGBAKP3Q.js.map +0 -1
  286. package/dist/compare/index.cjs +0 -5475
  287. package/dist/compare/index.cjs.map +0 -1
  288. package/dist/compare/index.d.cts +0 -114
  289. package/dist/compare/index.d.ts +0 -114
  290. package/dist/compare/index.js +0 -731
  291. package/dist/compare/index.js.map +0 -1
  292. package/dist/core/commands/formatting-commands.cjs +0 -828
  293. package/dist/core/commands/formatting-commands.cjs.map +0 -1
  294. package/dist/core/commands/formatting-commands.d.cts +0 -63
  295. package/dist/core/commands/formatting-commands.d.ts +0 -63
  296. package/dist/core/commands/formatting-commands.js +0 -37
  297. package/dist/core/commands/formatting-commands.js.map +0 -1
  298. package/dist/core/commands/image-commands.cjs +0 -2023
  299. package/dist/core/commands/image-commands.cjs.map +0 -1
  300. package/dist/core/commands/image-commands.d.cts +0 -58
  301. package/dist/core/commands/image-commands.d.ts +0 -58
  302. package/dist/core/commands/image-commands.js +0 -18
  303. package/dist/core/commands/image-commands.js.map +0 -1
  304. package/dist/core/commands/section-layout-commands.cjs.map +0 -1
  305. package/dist/core/commands/section-layout-commands.d.cts +0 -62
  306. package/dist/core/commands/section-layout-commands.d.ts +0 -62
  307. package/dist/core/commands/section-layout-commands.js +0 -21
  308. package/dist/core/commands/section-layout-commands.js.map +0 -1
  309. package/dist/core/commands/style-commands.cjs.map +0 -1
  310. package/dist/core/commands/style-commands.d.cts +0 -13
  311. package/dist/core/commands/style-commands.d.ts +0 -13
  312. package/dist/core/commands/style-commands.js +0 -9
  313. package/dist/core/commands/style-commands.js.map +0 -1
  314. package/dist/core/commands/table-structure-commands.cjs +0 -1883
  315. package/dist/core/commands/table-structure-commands.cjs.map +0 -1
  316. package/dist/core/commands/table-structure-commands.d.cts +0 -59
  317. package/dist/core/commands/table-structure-commands.d.ts +0 -59
  318. package/dist/core/commands/table-structure-commands.js +0 -12
  319. package/dist/core/commands/table-structure-commands.js.map +0 -1
  320. package/dist/core/commands/text-commands.cjs +0 -2391
  321. package/dist/core/commands/text-commands.cjs.map +0 -1
  322. package/dist/core/commands/text-commands.d.cts +0 -24
  323. package/dist/core/commands/text-commands.d.ts +0 -24
  324. package/dist/core/commands/text-commands.js +0 -28
  325. package/dist/core/commands/text-commands.js.map +0 -1
  326. package/dist/core/selection/mapping.cjs +0 -200
  327. package/dist/core/selection/mapping.cjs.map +0 -1
  328. package/dist/core/selection/mapping.d.cts +0 -2
  329. package/dist/core/selection/mapping.d.ts +0 -2
  330. package/dist/core/selection/mapping.js +0 -31
  331. package/dist/core/selection/mapping.js.map +0 -1
  332. package/dist/core/state/editor-state.cjs +0 -2278
  333. package/dist/core/state/editor-state.cjs.map +0 -1
  334. package/dist/core/state/editor-state.d.cts +0 -2
  335. package/dist/core/state/editor-state.d.ts +0 -2
  336. package/dist/core/state/editor-state.js +0 -26
  337. package/dist/core/state/editor-state.js.map +0 -1
  338. package/dist/index.cjs +0 -38553
  339. package/dist/index.cjs.map +0 -1
  340. package/dist/index.d.cts +0 -15
  341. package/dist/index.d.ts +0 -15
  342. package/dist/index.js +0 -7856
  343. package/dist/index.js.map +0 -1
  344. package/dist/io/docx-session.cjs +0 -16236
  345. package/dist/io/docx-session.cjs.map +0 -1
  346. package/dist/io/docx-session.d.cts +0 -21
  347. package/dist/io/docx-session.d.ts +0 -21
  348. package/dist/io/docx-session.js +0 -18
  349. package/dist/io/docx-session.js.map +0 -1
  350. package/dist/legal/index.cjs +0 -3900
  351. package/dist/legal/index.cjs.map +0 -1
  352. package/dist/legal/index.d.cts +0 -86
  353. package/dist/legal/index.d.ts +0 -86
  354. package/dist/legal/index.js +0 -616
  355. package/dist/legal/index.js.map +0 -1
  356. package/dist/public-types-7ZL_94cz.d.ts +0 -1573
  357. package/dist/public-types-CeMaDueh.d.cts +0 -1573
  358. package/dist/public-types.cjs +0 -19
  359. package/dist/public-types.cjs.map +0 -1
  360. package/dist/public-types.d.cts +0 -2
  361. package/dist/public-types.d.ts +0 -2
  362. package/dist/public-types.js +0 -1
  363. package/dist/public-types.js.map +0 -1
  364. package/dist/runtime/document-runtime.cjs +0 -11140
  365. package/dist/runtime/document-runtime.cjs.map +0 -1
  366. package/dist/runtime/document-runtime.d.cts +0 -231
  367. package/dist/runtime/document-runtime.d.ts +0 -231
  368. package/dist/runtime/document-runtime.js +0 -21
  369. package/dist/runtime/document-runtime.js.map +0 -1
  370. package/dist/structural-helpers-CilgOVhh.d.cts +0 -10
  371. package/dist/structural-helpers-q0Gd-eBN.d.ts +0 -10
  372. package/dist/ui-tailwind/editor-surface/search-plugin.cjs +0 -313
  373. package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +0 -1
  374. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +0 -67
  375. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +0 -67
  376. package/dist/ui-tailwind/editor-surface/search-plugin.js +0 -23
  377. package/dist/ui-tailwind/editor-surface/search-plugin.js.map +0 -1
  378. package/dist/ui-tailwind/index.cjs +0 -4833
  379. package/dist/ui-tailwind/index.cjs.map +0 -1
  380. package/dist/ui-tailwind/index.d.cts +0 -617
  381. package/dist/ui-tailwind/index.d.ts +0 -617
  382. package/dist/ui-tailwind/index.js +0 -575
  383. package/dist/ui-tailwind/index.js.map +0 -1
@@ -0,0 +1,1478 @@
1
+ import React from "react";
2
+
3
+ import * as Popover from "@radix-ui/react-popover";
4
+ import * as Select from "@radix-ui/react-select";
5
+ import * as Toggle from "@radix-ui/react-toggle";
6
+ import * as ToggleGroup from "@radix-ui/react-toggle-group";
7
+ import * as Tooltip from "@radix-ui/react-tooltip";
8
+ import {
9
+ AlertCircle,
10
+ AlignCenter,
11
+ AlignJustify,
12
+ AlignLeft,
13
+ AlignRight,
14
+ Baseline,
15
+ Bold,
16
+ ChevronDown,
17
+ Download,
18
+ Eye,
19
+ EyeOff,
20
+ FileText,
21
+ Highlighter,
22
+ ImagePlus,
23
+ Indent,
24
+ Italic,
25
+ List,
26
+ MessageSquare,
27
+ Minus,
28
+ Monitor,
29
+ MoreHorizontal,
30
+ Outdent,
31
+ PanelRight,
32
+ Plus,
33
+ Redo2,
34
+ RotateCcw,
35
+ Rows3,
36
+ Strikethrough,
37
+ Subscript,
38
+ Superscript,
39
+ Underline,
40
+ Undo2,
41
+ } from "lucide-react";
42
+
43
+ import type {
44
+ ActiveListContext,
45
+ CompatibilityPanelSnapshot,
46
+ EditorStoryTarget,
47
+ EditorWarning,
48
+ FormattingStateSnapshot,
49
+ FormattingAlignment,
50
+ InsertImageOptions,
51
+ SectionBreakType,
52
+ StyleCatalogSnapshot,
53
+ WorkflowBlockedCommandReason,
54
+ WordReviewEditorChromePreset,
55
+ WorkspaceMode,
56
+ ZoomLevel,
57
+ } from "../../api/public-types";
58
+ import type { SessionCapabilities } from "../../runtime/session-capabilities";
59
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
60
+ import { TwHealthPanel } from "../review/tw-health-panel";
61
+ import { resolveToolbarLayoutModel } from "./toolbar-layout";
62
+ import { TwToolbarIconButton } from "./tw-toolbar-icon-button";
63
+
64
+ export interface TwToolbarProps {
65
+ capabilities?: SessionCapabilities;
66
+ compatibility?: CompatibilityPanelSnapshot;
67
+ warnings?: EditorWarning[];
68
+ blockedReasons?: WorkflowBlockedCommandReason[];
69
+ showDiagnosticsChrome?: boolean;
70
+ interactionPolicy?: ToolbarInteractionPolicy;
71
+ preset?: WordReviewEditorChromePreset;
72
+ compactMode?: boolean;
73
+ workspaceMode: WorkspaceMode;
74
+ zoomLevel?: ZoomLevel;
75
+ formattingState?: FormattingStateSnapshot;
76
+ activeListContext?: ActiveListContext | null;
77
+ styleCatalog?: StyleCatalogSnapshot;
78
+ /** Display toggle for tracked change decorations (not a runtime mutation toggle). */
79
+ showTrackedChanges: boolean;
80
+ /** Active story target — shows a breadcrumb when editing a secondary story. */
81
+ activeStory?: EditorStoryTarget;
82
+ /** Called when the user clicks the story breadcrumb to return to main body. */
83
+ onCloseStory?: () => void;
84
+ onUndo: () => void;
85
+ onRedo: () => void;
86
+ onSetParagraphStyle?: (styleId: string) => void;
87
+ onToggleBold?: () => void;
88
+ onToggleItalic?: () => void;
89
+ onToggleUnderline?: () => void;
90
+ onToggleStrikethrough?: () => void;
91
+ onToggleSuperscript?: () => void;
92
+ onToggleSubscript?: () => void;
93
+ onSetFontFamily?: (fontFamily: string) => void;
94
+ onSetFontSize?: (fontSize: number) => void;
95
+ onSetTextColor?: (color: string) => void;
96
+ onSetHighlightColor?: (color: string | null) => void;
97
+ onSetAlignment?: (alignment: FormattingAlignment) => void;
98
+ onToggleBulletedList?: () => void;
99
+ onToggleNumberedList?: () => void;
100
+ onOutdent?: () => void;
101
+ onIndent?: () => void;
102
+ onAddComment: () => void;
103
+ onInsertPageBreak?: () => void;
104
+ onInsertTable?: () => void;
105
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
106
+ onInsertImage?: (options: InsertImageOptions) => void;
107
+ onExport: () => void;
108
+ onWorkspaceModeChange: (value: WorkspaceMode) => void;
109
+ showSidebarToggle?: boolean;
110
+ isSidebarOpen?: boolean;
111
+ onToggleSidebar?: () => void;
112
+ onZoomChange?: (level: ZoomLevel) => void;
113
+ onShowTrackedChangesChange: (show: boolean) => void;
114
+ onRestartNumbering?: () => void;
115
+ onContinueNumbering?: () => void;
116
+ onUpdateFields?: () => void;
117
+ onUpdateTableOfContents?: () => void;
118
+ }
119
+
120
+ export interface ToolbarInteractionPolicy {
121
+ mode: "edit" | "suggest" | "comment" | "view" | "blocked";
122
+ canFormatText: boolean;
123
+ canInsertStructural: boolean;
124
+ canAddComment: boolean;
125
+ }
126
+
127
+ export function getSupportedZoomPresets(): ReadonlyArray<number> {
128
+ return [75, 100, 125, 150];
129
+ }
130
+
131
+ const focusRingClass =
132
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
133
+
134
+ const FONT_FAMILIES = ["Arial", "Times New Roman", "Calibri", "Cambria", "Georgia", "Verdana"];
135
+ const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36];
136
+ const TEXT_COLORS = ["#1f1f1f", "#5c5852", "#1660a8", "#50684d", "#9b4f49", "#7b5f32"];
137
+ const HIGHLIGHT_COLORS = [
138
+ { value: "#ffff00", label: "Yellow" },
139
+ { value: "#d9e7f7", label: "Blue" },
140
+ { value: "#f1dbd6", label: "Rose" },
141
+ { value: "#e6e0d4", label: "Stone" },
142
+ { value: null, label: "None" },
143
+ ] as const;
144
+
145
+ export function TwToolbar(props: TwToolbarProps) {
146
+ const caps = props.capabilities;
147
+ const preset = props.preset ?? "advanced";
148
+ const isCompact = props.compactMode ?? false;
149
+ const workspaceMode = props.workspaceMode;
150
+ const paragraphStyles = props.styleCatalog?.paragraphs ?? [];
151
+ const zoomLevel = props.zoomLevel ?? 100;
152
+ const canEdit = props.interactionPolicy?.canFormatText ?? (caps ? caps.canEdit : false);
153
+ const canInsertStructural = props.interactionPolicy?.canInsertStructural ?? canEdit;
154
+ const canAddComment = props.interactionPolicy?.canAddComment ?? (caps ? caps.canAddComment : false);
155
+ const showStyleSelectors = preset === "advanced";
156
+ const showAdvancedFormatting = preset === "advanced";
157
+ const showFormattingColors = preset !== "review";
158
+ const showInsertMenu = preset === "simple" || preset === "advanced";
159
+ const showTrackedChangesToggle = preset !== "simple";
160
+ const showDiagnosticsChrome = props.showDiagnosticsChrome ?? true;
161
+ const showHealth = showDiagnosticsChrome && Boolean(props.compatibility && props.warnings);
162
+ const showListActions = preset === "simple" || preset === "advanced";
163
+ const showUpdateActions = preset === "advanced";
164
+ const showSidebarToggle = props.showSidebarToggle ?? false;
165
+ const layoutModel = resolveToolbarLayoutModel({
166
+ compactMode: isCompact,
167
+ preset,
168
+ hasActiveListContext: Boolean(props.activeListContext),
169
+ });
170
+ const showPostFormattingDivider =
171
+ layoutModel.showListActionsInRow ||
172
+ layoutModel.showSpacingActionsInRow ||
173
+ (showInsertMenu && layoutModel.showInsertActionsInRow) ||
174
+ (showUpdateActions && layoutModel.showUpdateActionsInRow) ||
175
+ layoutModel.showCompactOverflow;
176
+ const zoomLabel =
177
+ typeof zoomLevel === "number"
178
+ ? `${zoomLevel}%`
179
+ : zoomLevel === "pageWidth"
180
+ ? "Fit width"
181
+ : "Fit page";
182
+
183
+ return (
184
+ <header
185
+ className={[
186
+ "shrink-0 rounded-xl border border-border/70 bg-canvas/92 px-2.5 shadow-[0_8px_20px_-18px_var(--color-shadow-strong)] backdrop-blur-sm",
187
+ isCompact
188
+ ? "flex min-h-11 flex-wrap items-center gap-1.5 py-2"
189
+ : "flex h-11 items-center gap-1",
190
+ ].join(" ")}
191
+ >
192
+ {/* Left cluster: undo/redo + formatting */}
193
+ <div className={`flex min-w-0 flex-1 items-center gap-0.5 ${isCompact ? "flex-wrap" : ""}`}>
194
+ <TwToolbarIconButton
195
+ icon={Undo2}
196
+ label="Undo"
197
+ disabled={caps ? !caps.canUndo : true}
198
+ onClick={props.onUndo}
199
+ />
200
+ <TwToolbarIconButton
201
+ icon={Redo2}
202
+ label="Redo"
203
+ disabled={caps ? !caps.canRedo : true}
204
+ onClick={props.onRedo}
205
+ />
206
+ <div className="mx-1 h-4 w-px bg-border" />
207
+
208
+ {showStyleSelectors && layoutModel.showStyleSelectorsInRow ? (
209
+ <>
210
+ <ToolbarParagraphStyleSelect
211
+ disabled={!canEdit || paragraphStyles.length === 0 || !props.onSetParagraphStyle}
212
+ styles={paragraphStyles}
213
+ value={props.formattingState?.paragraphStyleId}
214
+ onValueChange={props.onSetParagraphStyle}
215
+ />
216
+
217
+ <ToolbarFontFamilySelect
218
+ disabled={!canEdit || !props.onSetFontFamily}
219
+ value={props.formattingState?.fontFamily}
220
+ onValueChange={props.onSetFontFamily}
221
+ />
222
+ <ToolbarFontSizeSelect
223
+ disabled={!canEdit || !props.onSetFontSize}
224
+ value={props.formattingState?.fontSize}
225
+ onValueChange={props.onSetFontSize}
226
+ />
227
+
228
+ <div className="mx-1 h-4 w-px bg-border" />
229
+ </>
230
+ ) : null}
231
+
232
+ <TwToolbarIconButton
233
+ icon={Bold}
234
+ label="Bold"
235
+ active={props.formattingState?.bold ?? false}
236
+ disabled={!canEdit}
237
+ onClick={props.onToggleBold}
238
+ />
239
+ <TwToolbarIconButton
240
+ icon={Italic}
241
+ label="Italic"
242
+ active={props.formattingState?.italic ?? false}
243
+ disabled={!canEdit}
244
+ onClick={props.onToggleItalic}
245
+ />
246
+ <TwToolbarIconButton
247
+ icon={Underline}
248
+ label="Underline"
249
+ active={props.formattingState?.underline ?? false}
250
+ disabled={!canEdit}
251
+ onClick={props.onToggleUnderline}
252
+ />
253
+ {showAdvancedFormatting ? (
254
+ <ToolbarFormattingOverflow
255
+ disabled={!canEdit}
256
+ formattingState={props.formattingState}
257
+ onToggleStrikethrough={props.onToggleStrikethrough}
258
+ onToggleSuperscript={props.onToggleSuperscript}
259
+ onToggleSubscript={props.onToggleSubscript}
260
+ />
261
+ ) : null}
262
+ {showFormattingColors ? (
263
+ <>
264
+ <ToolbarColorPopover
265
+ ariaLabel="Text color"
266
+ colors={TEXT_COLORS.map((value) => ({ value, label: value }))}
267
+ disabled={!canEdit || !props.onSetTextColor}
268
+ icon={<Baseline className="h-3.5 w-3.5" />}
269
+ onSelect={(value) => {
270
+ if (value) {
271
+ props.onSetTextColor?.(value);
272
+ }
273
+ }}
274
+ title="Text color"
275
+ />
276
+ <ToolbarColorPopover
277
+ ariaLabel="Highlight color"
278
+ colors={HIGHLIGHT_COLORS.map((entry) => ({ value: entry.value, label: entry.label }))}
279
+ disabled={!canEdit || !props.onSetHighlightColor}
280
+ icon={<Highlighter className="h-3.5 w-3.5" />}
281
+ onSelect={(value) => props.onSetHighlightColor?.(value)}
282
+ title="Highlight color"
283
+ />
284
+ <ToolbarAlignmentPopover
285
+ activeAlignment={props.formattingState?.alignment}
286
+ disabled={!canEdit || !props.onSetAlignment}
287
+ onSelect={(alignment) => props.onSetAlignment?.(alignment)}
288
+ />
289
+ </>
290
+ ) : null}
291
+
292
+ {showPostFormattingDivider ? <div className="mx-1 h-4 w-px bg-border" /> : null}
293
+
294
+ {showListActions && layoutModel.showListActionsInRow ? (
295
+ <>
296
+ <TwToolbarIconButton
297
+ icon={List}
298
+ label="Bulleted list"
299
+ active={Boolean(props.activeListContext && !props.activeListContext.isOrdered)}
300
+ disabled={!canEdit}
301
+ onClick={props.onToggleBulletedList}
302
+ />
303
+ <TwToolbarIconButton
304
+ icon={Rows3}
305
+ label="Numbered list"
306
+ active={Boolean(props.activeListContext?.isOrdered)}
307
+ disabled={!canEdit}
308
+ onClick={props.onToggleNumberedList}
309
+ />
310
+ </>
311
+ ) : null}
312
+ {layoutModel.showSpacingActionsInRow ? (
313
+ <>
314
+ <TwToolbarIconButton
315
+ icon={Outdent}
316
+ label="Outdent"
317
+ disabled={!canEdit}
318
+ onClick={props.onOutdent}
319
+ />
320
+ <TwToolbarIconButton
321
+ icon={Indent}
322
+ label="Indent"
323
+ disabled={!canEdit}
324
+ onClick={props.onIndent}
325
+ />
326
+ </>
327
+ ) : null}
328
+ {layoutModel.showListContinuationInRow ? (
329
+ <>
330
+ <button
331
+ type="button"
332
+ aria-label="Restart numbering"
333
+ disabled={!canEdit || !props.onRestartNumbering}
334
+ onMouseDown={preserveEditorSelectionMouseDown}
335
+ onClick={props.onRestartNumbering}
336
+ className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
337
+ >
338
+ Restart
339
+ </button>
340
+ <button
341
+ type="button"
342
+ aria-label="Continue numbering"
343
+ disabled={!canEdit || !props.onContinueNumbering}
344
+ onMouseDown={preserveEditorSelectionMouseDown}
345
+ onClick={props.onContinueNumbering}
346
+ className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
347
+ >
348
+ Continue
349
+ </button>
350
+ </>
351
+ ) : null}
352
+ {showInsertMenu && layoutModel.showInsertActionsInRow ? (
353
+ <ToolbarInsertMenu
354
+ disabled={!canInsertStructural}
355
+ onInsertImage={props.onInsertImage}
356
+ onInsertPageBreak={props.onInsertPageBreak}
357
+ onInsertSectionBreak={props.onInsertSectionBreak}
358
+ onInsertTable={props.onInsertTable}
359
+ />
360
+ ) : null}
361
+ {showUpdateActions && layoutModel.showUpdateActionsInRow ? (
362
+ <>
363
+ <button
364
+ type="button"
365
+ aria-label="Refresh fields"
366
+ disabled={!canEdit || !props.onUpdateFields}
367
+ onMouseDown={preserveEditorSelectionMouseDown}
368
+ onClick={props.onUpdateFields}
369
+ className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
370
+ >
371
+ Fields
372
+ </button>
373
+ <button
374
+ type="button"
375
+ aria-label="Refresh table of contents"
376
+ disabled={!canEdit || !props.onUpdateTableOfContents}
377
+ onMouseDown={preserveEditorSelectionMouseDown}
378
+ onClick={props.onUpdateTableOfContents}
379
+ className={`inline-flex h-7 items-center rounded-md px-2 text-xs font-medium text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
380
+ >
381
+ TOC
382
+ </button>
383
+ </>
384
+ ) : null}
385
+ {layoutModel.showCompactOverflow ? (
386
+ <ToolbarCompactOverflow
387
+ activeListContext={props.activeListContext}
388
+ canEdit={canEdit}
389
+ canInsertStructural={canInsertStructural}
390
+ formattingState={props.formattingState}
391
+ paragraphStyles={paragraphStyles}
392
+ showInsertMenu={showInsertMenu}
393
+ showListActions={showListActions}
394
+ showStyleSelectors={showStyleSelectors}
395
+ showUpdateActions={showUpdateActions}
396
+ onSetParagraphStyle={props.onSetParagraphStyle}
397
+ onSetFontFamily={props.onSetFontFamily}
398
+ onSetFontSize={props.onSetFontSize}
399
+ onToggleBulletedList={props.onToggleBulletedList}
400
+ onToggleNumberedList={props.onToggleNumberedList}
401
+ onOutdent={props.onOutdent}
402
+ onIndent={props.onIndent}
403
+ onRestartNumbering={props.onRestartNumbering}
404
+ onContinueNumbering={props.onContinueNumbering}
405
+ onInsertPageBreak={props.onInsertPageBreak}
406
+ onInsertTable={props.onInsertTable}
407
+ onInsertSectionBreak={props.onInsertSectionBreak}
408
+ onInsertImage={props.onInsertImage}
409
+ onUpdateFields={props.onUpdateFields}
410
+ onUpdateTableOfContents={props.onUpdateTableOfContents}
411
+ />
412
+ ) : null}
413
+
414
+ {/* Story focus breadcrumb — visible when editing a secondary story */}
415
+ {props.activeStory && props.activeStory.kind !== "main" ? (
416
+ <>
417
+ <div className="mx-1 h-4 w-px bg-border" />
418
+ <button
419
+ type="button"
420
+ onClick={props.onCloseStory}
421
+ onMouseDown={preserveEditorSelectionMouseDown}
422
+ className={`inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent hover:bg-surface transition-colors outline-none ${focusRingClass}`}
423
+ aria-label={`Editing ${storyLabel(props.activeStory)} — click to return to main body`}
424
+ >
425
+ <span className="text-secondary">&larr;</span>
426
+ {storyLabel(props.activeStory)}
427
+ </button>
428
+ </>
429
+ ) : null}
430
+ </div>
431
+
432
+ {/* Right cluster: comment, track changes, markup, view, export */}
433
+ <div className={`flex items-center gap-0.5 ${isCompact ? "ml-auto flex-wrap justify-end" : ""}`}>
434
+ {showSidebarToggle ? (
435
+ <>
436
+ <TwToolbarIconButton
437
+ icon={PanelRight}
438
+ label="Toggle sidebar"
439
+ active={props.isSidebarOpen ?? false}
440
+ onClick={props.onToggleSidebar}
441
+ />
442
+ <div className="mx-1 h-4 w-px bg-border" />
443
+ </>
444
+ ) : null}
445
+
446
+ <TwToolbarIconButton
447
+ icon={MessageSquare}
448
+ label="Add comment"
449
+ disabled={!canAddComment}
450
+ emphasis
451
+ onClick={props.onAddComment}
452
+ />
453
+
454
+ {showTrackedChangesToggle ? (
455
+ <>
456
+ <Tooltip.Root>
457
+ <Tooltip.Trigger asChild>
458
+ <Toggle.Root
459
+ pressed={props.showTrackedChanges}
460
+ onPressedChange={props.onShowTrackedChangesChange}
461
+ disabled={caps ? !caps.trackChangesSupported : false}
462
+ onMouseDown={preserveEditorSelectionMouseDown}
463
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none disabled:opacity-40 ${focusRingClass}`}
464
+ >
465
+ {props.showTrackedChanges ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
466
+ </Toggle.Root>
467
+ </Tooltip.Trigger>
468
+ <Tooltip.Portal>
469
+ <Tooltip.Content
470
+ className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50"
471
+ sideOffset={6}
472
+ >
473
+ {props.showTrackedChanges ? "Hide tracked changes" : "Show tracked changes"}
474
+ </Tooltip.Content>
475
+ </Tooltip.Portal>
476
+ </Tooltip.Root>
477
+
478
+ <div className="mx-1 h-4 w-px bg-border" />
479
+ </>
480
+ ) : null}
481
+
482
+ {/* View mode toggle group: Canvas (clean, flowing) / Page (layout-sensitive) */}
483
+ <ToggleGroup.Root
484
+ type="single"
485
+ value={workspaceMode}
486
+ onValueChange={(v: string) => {
487
+ if (v) props.onWorkspaceModeChange(v as WorkspaceMode);
488
+ }}
489
+ className="flex items-center gap-0.5"
490
+ >
491
+ <Tooltip.Root>
492
+ <Tooltip.Trigger asChild>
493
+ <ToggleGroup.Item
494
+ value="canvas"
495
+ aria-label="Canvas workspace"
496
+ onMouseDown={preserveEditorSelectionMouseDown}
497
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none ${focusRingClass}`}
498
+ >
499
+ <Monitor className="h-3.5 w-3.5" />
500
+ </ToggleGroup.Item>
501
+ </Tooltip.Trigger>
502
+ <Tooltip.Portal>
503
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
504
+ Canvas — clean flowing text
505
+ </Tooltip.Content>
506
+ </Tooltip.Portal>
507
+ </Tooltip.Root>
508
+ <Tooltip.Root>
509
+ <Tooltip.Trigger asChild>
510
+ <ToggleGroup.Item
511
+ value="page"
512
+ aria-label="Page workspace"
513
+ onMouseDown={preserveEditorSelectionMouseDown}
514
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface data-[state=on]:bg-canvas data-[state=on]:text-accent data-[state=on]:ring-1 data-[state=on]:ring-accent/30 data-[state=on]:shadow-sm outline-none ${focusRingClass}`}
515
+ >
516
+ <FileText className="h-3.5 w-3.5" />
517
+ </ToggleGroup.Item>
518
+ </Tooltip.Trigger>
519
+ <Tooltip.Portal>
520
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
521
+ Page — layout-sensitive view
522
+ </Tooltip.Content>
523
+ </Tooltip.Portal>
524
+ </Tooltip.Root>
525
+ </ToggleGroup.Root>
526
+
527
+ {/* Zoom controls — available in all workspace modes */}
528
+ {props.onZoomChange ? (
529
+ <>
530
+ <div className="mx-1 h-4 w-px bg-border" />
531
+ <div className="flex items-center gap-0.5">
532
+ <Tooltip.Root>
533
+ <Tooltip.Trigger asChild>
534
+ <button
535
+ type="button"
536
+ aria-label="Zoom out"
537
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
538
+ disabled={typeof zoomLevel === "number" && zoomLevel <= 50}
539
+ onMouseDown={preserveEditorSelectionMouseDown}
540
+ onClick={() => {
541
+ const current = typeof zoomLevel === "number" ? zoomLevel : 100;
542
+ props.onZoomChange!(Math.max(50, current - 10));
543
+ }}
544
+ >
545
+ <Minus className="h-3 w-3" />
546
+ </button>
547
+ </Tooltip.Trigger>
548
+ <Tooltip.Portal>
549
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
550
+ Zoom out
551
+ </Tooltip.Content>
552
+ </Tooltip.Portal>
553
+ </Tooltip.Root>
554
+
555
+ <Popover.Root>
556
+ <Tooltip.Root>
557
+ <Tooltip.Trigger asChild>
558
+ <Popover.Trigger asChild>
559
+ <button
560
+ type="button"
561
+ aria-label={`Zoom: ${zoomLabel}`}
562
+ onMouseDown={preserveEditorSelectionMouseDown}
563
+ className={`inline-flex h-7 items-center justify-center rounded-md px-1.5 text-[11px] font-medium text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
564
+ >
565
+ {zoomLabel}
566
+ </button>
567
+ </Popover.Trigger>
568
+ </Tooltip.Trigger>
569
+ <Tooltip.Portal>
570
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
571
+ Zoom level
572
+ </Tooltip.Content>
573
+ </Tooltip.Portal>
574
+ </Tooltip.Root>
575
+ <Popover.Portal>
576
+ <Popover.Content
577
+ className="w-[140px] rounded-lg bg-canvas shadow-lg ring-1 ring-border p-1 z-50"
578
+ sideOffset={8}
579
+ align="center"
580
+ >
581
+ {getSupportedZoomPresets().map((preset) => {
582
+ const label = `${preset}%`;
583
+ return (
584
+ <Popover.Close key={preset} asChild>
585
+ <button
586
+ type="button"
587
+ onMouseDown={preserveEditorSelectionMouseDown}
588
+ className={`w-full rounded-md px-3 py-1.5 text-left text-xs transition-colors hover:bg-surface ${
589
+ zoomLevel === preset ? "font-semibold text-accent" : "text-primary"
590
+ }`}
591
+ onClick={() => props.onZoomChange!(preset)}
592
+ >
593
+ {label}
594
+ </button>
595
+ </Popover.Close>
596
+ );
597
+ })}
598
+ </Popover.Content>
599
+ </Popover.Portal>
600
+ </Popover.Root>
601
+
602
+ <Tooltip.Root>
603
+ <Tooltip.Trigger asChild>
604
+ <button
605
+ type="button"
606
+ aria-label="Zoom in"
607
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
608
+ disabled={typeof zoomLevel === "number" && zoomLevel >= 200}
609
+ onMouseDown={preserveEditorSelectionMouseDown}
610
+ onClick={() => {
611
+ const current = typeof zoomLevel === "number" ? zoomLevel : 100;
612
+ props.onZoomChange!(Math.min(200, current + 10));
613
+ }}
614
+ >
615
+ <Plus className="h-3 w-3" />
616
+ </button>
617
+ </Tooltip.Trigger>
618
+ <Tooltip.Portal>
619
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
620
+ Zoom in
621
+ </Tooltip.Content>
622
+ </Tooltip.Portal>
623
+ </Tooltip.Root>
624
+ </div>
625
+ </>
626
+ ) : null}
627
+
628
+ {showHealth ? (
629
+ <>
630
+ <Popover.Root>
631
+ <Tooltip.Root>
632
+ <Tooltip.Trigger asChild>
633
+ <Popover.Trigger asChild>
634
+ <button
635
+ type="button"
636
+ aria-label="Document health"
637
+ onMouseDown={preserveEditorSelectionMouseDown}
638
+ className={`relative inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface hover:text-primary outline-none ${focusRingClass}`}
639
+ >
640
+ <AlertCircle className="h-4 w-4" />
641
+ {(caps?.healthIssueCount ?? 0) > 0 ? (
642
+ <span className="absolute -top-0.5 -right-0.5 flex h-3 min-w-[12px] items-center justify-center rounded-full bg-tertiary text-[8px] font-medium text-white">
643
+ {caps?.healthIssueCount}
644
+ </span>
645
+ ) : null}
646
+ </button>
647
+ </Popover.Trigger>
648
+ </Tooltip.Trigger>
649
+ <Tooltip.Portal>
650
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
651
+ {(caps?.healthIssueCount ?? 0) > 0
652
+ ? `Document health — ${caps?.healthIssueCount} issue${(caps?.healthIssueCount ?? 0) !== 1 ? "s" : ""}`
653
+ : "Document health — no issues"}
654
+ </Tooltip.Content>
655
+ </Tooltip.Portal>
656
+ </Tooltip.Root>
657
+ <Popover.Portal>
658
+ <Popover.Content
659
+ className="w-[360px] max-h-[480px] overflow-y-auto rounded-lg bg-canvas shadow-lg ring-1 ring-border p-3 z-50"
660
+ sideOffset={8}
661
+ align="end"
662
+ >
663
+ <TwHealthPanel
664
+ blockedReasons={props.blockedReasons}
665
+ compatibility={props.compatibility!}
666
+ warnings={props.warnings!}
667
+ />
668
+ </Popover.Content>
669
+ </Popover.Portal>
670
+ </Popover.Root>
671
+ <div className="mx-1 h-4 w-px bg-border" />
672
+ </>
673
+ ) : null}
674
+
675
+ <TwToolbarIconButton
676
+ icon={Download}
677
+ label={caps?.exportBlocked ? "Export blocked" : "Download document"}
678
+ disabled={caps ? !caps.canExport : true}
679
+ emphasis
680
+ onClick={props.onExport}
681
+ />
682
+ </div>
683
+ </header>
684
+ );
685
+ }
686
+
687
+ function ToolbarParagraphStyleSelect(props: {
688
+ styles: StyleCatalogSnapshot["paragraphs"];
689
+ value?: string;
690
+ disabled: boolean;
691
+ onValueChange?: (styleId: string) => void;
692
+ }) {
693
+ const resolvedValue =
694
+ props.value && props.styles.some((style) => style.styleId === props.value)
695
+ ? props.value
696
+ : "";
697
+
698
+ return (
699
+ <Select.Root
700
+ disabled={props.disabled}
701
+ onValueChange={(value) => props.onValueChange?.(value)}
702
+ value={resolvedValue}
703
+ >
704
+ <Select.Trigger
705
+ aria-label="Paragraph style"
706
+ aria-disabled={props.disabled || undefined}
707
+ data-disabled={props.disabled ? "" : undefined}
708
+ onMouseDown={preserveEditorSelectionMouseDown}
709
+ className={`inline-flex h-7 min-w-[8.5rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2.5 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
710
+ >
711
+ <Select.Value placeholder="Style" />
712
+ <Select.Icon>
713
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
714
+ </Select.Icon>
715
+ </Select.Trigger>
716
+ <Select.Portal>
717
+ <Select.Content
718
+ align="start"
719
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
720
+ position="popper"
721
+ sideOffset={8}
722
+ >
723
+ <Select.Viewport className="p-1">
724
+ {props.styles.map((style) => (
725
+ <Select.Item
726
+ className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
727
+ key={style.styleId}
728
+ value={style.styleId}
729
+ >
730
+ <Select.ItemText>{style.displayName}</Select.ItemText>
731
+ </Select.Item>
732
+ ))}
733
+ </Select.Viewport>
734
+ </Select.Content>
735
+ </Select.Portal>
736
+ </Select.Root>
737
+ );
738
+ }
739
+
740
+ function ToolbarFontFamilySelect(props: {
741
+ value?: string;
742
+ disabled: boolean;
743
+ onValueChange?: (fontFamily: string) => void;
744
+ }) {
745
+ const resolvedValue = props.value && FONT_FAMILIES.includes(props.value) ? props.value : "";
746
+
747
+ return (
748
+ <Select.Root
749
+ disabled={props.disabled}
750
+ onValueChange={(value) => props.onValueChange?.(value)}
751
+ value={resolvedValue}
752
+ >
753
+ <Select.Trigger
754
+ aria-label="Font family"
755
+ aria-disabled={props.disabled || undefined}
756
+ data-disabled={props.disabled ? "" : undefined}
757
+ onMouseDown={preserveEditorSelectionMouseDown}
758
+ className={`inline-flex h-7 min-w-[7rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
759
+ >
760
+ <Select.Value placeholder="Font" />
761
+ <Select.Icon>
762
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
763
+ </Select.Icon>
764
+ </Select.Trigger>
765
+ <Select.Portal>
766
+ <Select.Content
767
+ align="start"
768
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
769
+ position="popper"
770
+ sideOffset={8}
771
+ >
772
+ <Select.Viewport className="p-1">
773
+ {FONT_FAMILIES.map((font) => (
774
+ <Select.Item
775
+ className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
776
+ key={font}
777
+ value={font}
778
+ >
779
+ <Select.ItemText>{font}</Select.ItemText>
780
+ </Select.Item>
781
+ ))}
782
+ </Select.Viewport>
783
+ </Select.Content>
784
+ </Select.Portal>
785
+ </Select.Root>
786
+ );
787
+ }
788
+
789
+ function ToolbarFontSizeSelect(props: {
790
+ value?: number;
791
+ disabled: boolean;
792
+ onValueChange?: (fontSize: number) => void;
793
+ }) {
794
+ const resolvedValue =
795
+ typeof props.value === "number" && FONT_SIZES.includes(props.value) ? String(props.value) : "";
796
+
797
+ return (
798
+ <Select.Root
799
+ disabled={props.disabled}
800
+ onValueChange={(value) => props.onValueChange?.(Number(value))}
801
+ value={resolvedValue}
802
+ >
803
+ <Select.Trigger
804
+ aria-label="Font size"
805
+ aria-disabled={props.disabled || undefined}
806
+ data-disabled={props.disabled ? "" : undefined}
807
+ onMouseDown={preserveEditorSelectionMouseDown}
808
+ className={`inline-flex h-7 min-w-[4rem] items-center justify-between gap-2 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
809
+ >
810
+ <Select.Value placeholder="Size" />
811
+ <Select.Icon>
812
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
813
+ </Select.Icon>
814
+ </Select.Trigger>
815
+ <Select.Portal>
816
+ <Select.Content
817
+ align="start"
818
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
819
+ position="popper"
820
+ sideOffset={8}
821
+ >
822
+ <Select.Viewport className="p-1">
823
+ {FONT_SIZES.map((size) => (
824
+ <Select.Item
825
+ className={`flex cursor-pointer items-center rounded-md px-2.5 py-1.5 text-xs text-primary outline-none data-[highlighted]:bg-surface data-[state=checked]:bg-canvas data-[state=checked]:text-accent data-[state=checked]:ring-1 data-[state=checked]:ring-accent/20 ${focusRingClass}`}
826
+ key={size}
827
+ value={String(size)}
828
+ >
829
+ <Select.ItemText>{size}</Select.ItemText>
830
+ </Select.Item>
831
+ ))}
832
+ </Select.Viewport>
833
+ </Select.Content>
834
+ </Select.Portal>
835
+ </Select.Root>
836
+ );
837
+ }
838
+
839
+ function ToolbarCompactOverflow(props: {
840
+ activeListContext?: ActiveListContext | null;
841
+ canEdit: boolean;
842
+ canInsertStructural: boolean;
843
+ formattingState?: FormattingStateSnapshot;
844
+ paragraphStyles: StyleCatalogSnapshot["paragraphs"];
845
+ showInsertMenu: boolean;
846
+ showListActions: boolean;
847
+ showStyleSelectors: boolean;
848
+ showUpdateActions: boolean;
849
+ onSetParagraphStyle?: (styleId: string) => void;
850
+ onSetFontFamily?: (fontFamily: string) => void;
851
+ onSetFontSize?: (fontSize: number) => void;
852
+ onToggleBulletedList?: () => void;
853
+ onToggleNumberedList?: () => void;
854
+ onOutdent?: () => void;
855
+ onIndent?: () => void;
856
+ onRestartNumbering?: () => void;
857
+ onContinueNumbering?: () => void;
858
+ onInsertPageBreak?: () => void;
859
+ onInsertTable?: () => void;
860
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
861
+ onInsertImage?: (options: InsertImageOptions) => void;
862
+ onUpdateFields?: () => void;
863
+ onUpdateTableOfContents?: () => void;
864
+ }) {
865
+ const [open, setOpen] = React.useState(false);
866
+
867
+ async function handleImageChange(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
868
+ const file = event.target.files?.[0];
869
+ if (!file || !props.canInsertStructural || !props.onInsertImage) {
870
+ event.target.value = "";
871
+ return;
872
+ }
873
+
874
+ const data = new Uint8Array(await file.arrayBuffer());
875
+ props.onInsertImage({
876
+ data,
877
+ mimeType: file.type || "image/png",
878
+ altText: file.name,
879
+ });
880
+ setOpen(false);
881
+ event.target.value = "";
882
+ }
883
+
884
+ return (
885
+ <div className="relative">
886
+ <Tooltip.Root>
887
+ <Tooltip.Trigger asChild>
888
+ <button
889
+ type="button"
890
+ aria-label="More document tools"
891
+ aria-expanded={open}
892
+ onMouseDown={preserveEditorSelectionMouseDown}
893
+ onClick={() => setOpen((value) => !value)}
894
+ className={`inline-flex h-7 items-center gap-1 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
895
+ >
896
+ More
897
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
898
+ </button>
899
+ </Tooltip.Trigger>
900
+ <Tooltip.Portal>
901
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
902
+ More document tools
903
+ </Tooltip.Content>
904
+ </Tooltip.Portal>
905
+ </Tooltip.Root>
906
+ {open ? (
907
+ <div className="absolute left-0 top-9 z-50 w-[min(20rem,calc(100vw-2rem))] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
908
+ <div className="space-y-3">
909
+ {props.showStyleSelectors ? (
910
+ <div className="space-y-2">
911
+ <div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
912
+ Text
913
+ </div>
914
+ <div className="grid gap-2">
915
+ <ToolbarParagraphStyleSelect
916
+ disabled={!props.canEdit || props.paragraphStyles.length === 0 || !props.onSetParagraphStyle}
917
+ styles={props.paragraphStyles}
918
+ value={props.formattingState?.paragraphStyleId}
919
+ onValueChange={(styleId) => {
920
+ props.onSetParagraphStyle?.(styleId);
921
+ setOpen(false);
922
+ }}
923
+ />
924
+ <div className="grid grid-cols-2 gap-2">
925
+ <ToolbarFontFamilySelect
926
+ disabled={!props.canEdit || !props.onSetFontFamily}
927
+ value={props.formattingState?.fontFamily}
928
+ onValueChange={(fontFamily) => {
929
+ props.onSetFontFamily?.(fontFamily);
930
+ setOpen(false);
931
+ }}
932
+ />
933
+ <ToolbarFontSizeSelect
934
+ disabled={!props.canEdit || !props.onSetFontSize}
935
+ value={props.formattingState?.fontSize}
936
+ onValueChange={(fontSize) => {
937
+ props.onSetFontSize?.(fontSize);
938
+ setOpen(false);
939
+ }}
940
+ />
941
+ </div>
942
+ </div>
943
+ </div>
944
+ ) : null}
945
+
946
+ {props.showListActions ? (
947
+ <div className="space-y-1">
948
+ <div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
949
+ Structure
950
+ </div>
951
+ <ToolbarMenuButton
952
+ ariaLabel="Bulleted list"
953
+ disabled={!props.canEdit || !props.onToggleBulletedList}
954
+ icon={<List className="h-3.5 w-3.5" />}
955
+ label="Bulleted list"
956
+ onClick={() => {
957
+ props.onToggleBulletedList?.();
958
+ setOpen(false);
959
+ }}
960
+ />
961
+ <ToolbarMenuButton
962
+ ariaLabel="Numbered list"
963
+ disabled={!props.canEdit || !props.onToggleNumberedList}
964
+ icon={<Rows3 className="h-3.5 w-3.5" />}
965
+ label="Numbered list"
966
+ onClick={() => {
967
+ props.onToggleNumberedList?.();
968
+ setOpen(false);
969
+ }}
970
+ />
971
+ </div>
972
+ ) : null}
973
+
974
+ <div className="space-y-1">
975
+ <div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
976
+ Paragraph
977
+ </div>
978
+ <ToolbarMenuButton
979
+ ariaLabel="Outdent"
980
+ disabled={!props.canEdit || !props.onOutdent}
981
+ icon={<Outdent className="h-3.5 w-3.5" />}
982
+ label="Outdent"
983
+ onClick={() => {
984
+ props.onOutdent?.();
985
+ setOpen(false);
986
+ }}
987
+ />
988
+ <ToolbarMenuButton
989
+ ariaLabel="Indent"
990
+ disabled={!props.canEdit || !props.onIndent}
991
+ icon={<Indent className="h-3.5 w-3.5" />}
992
+ label="Indent"
993
+ onClick={() => {
994
+ props.onIndent?.();
995
+ setOpen(false);
996
+ }}
997
+ />
998
+ {props.activeListContext ? (
999
+ <>
1000
+ <ToolbarMenuButton
1001
+ ariaLabel="Restart numbering"
1002
+ disabled={!props.canEdit || !props.onRestartNumbering}
1003
+ icon={<Rows3 className="h-3.5 w-3.5" />}
1004
+ label="Restart numbering"
1005
+ onClick={() => {
1006
+ props.onRestartNumbering?.();
1007
+ setOpen(false);
1008
+ }}
1009
+ />
1010
+ <ToolbarMenuButton
1011
+ ariaLabel="Continue numbering"
1012
+ disabled={!props.canEdit || !props.onContinueNumbering}
1013
+ icon={<Rows3 className="h-3.5 w-3.5" />}
1014
+ label="Continue numbering"
1015
+ onClick={() => {
1016
+ props.onContinueNumbering?.();
1017
+ setOpen(false);
1018
+ }}
1019
+ />
1020
+ </>
1021
+ ) : null}
1022
+ </div>
1023
+
1024
+ {props.showInsertMenu ? (
1025
+ <div className="space-y-1">
1026
+ <div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
1027
+ Insert
1028
+ </div>
1029
+ <ToolbarMenuButton
1030
+ ariaLabel="Insert page break"
1031
+ disabled={!props.canInsertStructural || !props.onInsertPageBreak}
1032
+ icon={<Minus className="h-3.5 w-3.5" />}
1033
+ label="Page break"
1034
+ onClick={() => {
1035
+ props.onInsertPageBreak?.();
1036
+ setOpen(false);
1037
+ }}
1038
+ />
1039
+ <ToolbarMenuButton
1040
+ ariaLabel="Insert table"
1041
+ disabled={!props.canInsertStructural || !props.onInsertTable}
1042
+ icon={<Rows3 className="h-3.5 w-3.5" />}
1043
+ label="Table"
1044
+ onClick={() => {
1045
+ props.onInsertTable?.();
1046
+ setOpen(false);
1047
+ }}
1048
+ />
1049
+ <label
1050
+ className={`flex h-8 cursor-pointer items-center gap-2 rounded-md px-2 text-left text-xs font-medium text-primary transition-colors hover:bg-surface ${
1051
+ !props.canInsertStructural || !props.onInsertImage ? "pointer-events-none opacity-40" : ""
1052
+ }`}
1053
+ >
1054
+ <ImagePlus className="h-3.5 w-3.5 text-secondary" />
1055
+ <span>Image</span>
1056
+ <input
1057
+ accept="image/png,image/jpeg,image/gif"
1058
+ aria-label="Insert image"
1059
+ className="sr-only"
1060
+ disabled={!props.canInsertStructural || !props.onInsertImage}
1061
+ type="file"
1062
+ onChange={(event) => {
1063
+ void handleImageChange(event);
1064
+ }}
1065
+ />
1066
+ </label>
1067
+ <ToolbarMenuButton
1068
+ ariaLabel="Insert next-page section break"
1069
+ disabled={!props.canInsertStructural || !props.onInsertSectionBreak}
1070
+ icon={<FileText className="h-3.5 w-3.5" />}
1071
+ label="Next-page section break"
1072
+ onClick={() => {
1073
+ props.onInsertSectionBreak?.("nextPage");
1074
+ setOpen(false);
1075
+ }}
1076
+ />
1077
+ </div>
1078
+ ) : null}
1079
+
1080
+ {props.showUpdateActions ? (
1081
+ <div className="space-y-1">
1082
+ <div className="px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
1083
+ Refresh
1084
+ </div>
1085
+ <ToolbarMenuButton
1086
+ ariaLabel="Refresh fields"
1087
+ disabled={!props.canEdit || !props.onUpdateFields}
1088
+ icon={<RotateCcw className="h-3.5 w-3.5" />}
1089
+ label="Fields"
1090
+ onClick={() => {
1091
+ props.onUpdateFields?.();
1092
+ setOpen(false);
1093
+ }}
1094
+ />
1095
+ <ToolbarMenuButton
1096
+ ariaLabel="Refresh table of contents"
1097
+ disabled={!props.canEdit || !props.onUpdateTableOfContents}
1098
+ icon={<RotateCcw className="h-3.5 w-3.5" />}
1099
+ label="Table of contents"
1100
+ onClick={() => {
1101
+ props.onUpdateTableOfContents?.();
1102
+ setOpen(false);
1103
+ }}
1104
+ />
1105
+ </div>
1106
+ ) : null}
1107
+ </div>
1108
+ </div>
1109
+ ) : null}
1110
+ </div>
1111
+ );
1112
+ }
1113
+
1114
+ function ToolbarFormattingOverflow(props: {
1115
+ disabled: boolean;
1116
+ formattingState?: FormattingStateSnapshot;
1117
+ onToggleStrikethrough?: () => void;
1118
+ onToggleSuperscript?: () => void;
1119
+ onToggleSubscript?: () => void;
1120
+ }) {
1121
+ const [open, setOpen] = React.useState(false);
1122
+
1123
+ return (
1124
+ <div className="relative">
1125
+ <Tooltip.Root>
1126
+ <Tooltip.Trigger asChild>
1127
+ <button
1128
+ type="button"
1129
+ aria-label="More text formatting"
1130
+ aria-expanded={open}
1131
+ disabled={props.disabled}
1132
+ onMouseDown={preserveEditorSelectionMouseDown}
1133
+ onClick={() => setOpen((value) => !value)}
1134
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
1135
+ >
1136
+ <MoreHorizontal className="h-3.5 w-3.5" />
1137
+ </button>
1138
+ </Tooltip.Trigger>
1139
+ <Tooltip.Portal>
1140
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
1141
+ More text formatting
1142
+ </Tooltip.Content>
1143
+ </Tooltip.Portal>
1144
+ </Tooltip.Root>
1145
+ {open ? (
1146
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
1147
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
1148
+ Text styling
1149
+ </div>
1150
+ <div className="grid grid-cols-3 gap-1">
1151
+ <ToolbarPopoverActionButton
1152
+ active={props.formattingState?.strikethrough ?? false}
1153
+ ariaLabel="Strikethrough"
1154
+ disabled={props.disabled}
1155
+ icon={<Strikethrough className="h-3.5 w-3.5" />}
1156
+ onClick={() => {
1157
+ props.onToggleStrikethrough?.();
1158
+ setOpen(false);
1159
+ }}
1160
+ />
1161
+ <ToolbarPopoverActionButton
1162
+ active={props.formattingState?.superscript ?? false}
1163
+ ariaLabel="Superscript"
1164
+ disabled={props.disabled}
1165
+ icon={<Superscript className="h-3.5 w-3.5" />}
1166
+ onClick={() => {
1167
+ props.onToggleSuperscript?.();
1168
+ setOpen(false);
1169
+ }}
1170
+ />
1171
+ <ToolbarPopoverActionButton
1172
+ active={props.formattingState?.subscript ?? false}
1173
+ ariaLabel="Subscript"
1174
+ disabled={props.disabled}
1175
+ icon={<Subscript className="h-3.5 w-3.5" />}
1176
+ onClick={() => {
1177
+ props.onToggleSubscript?.();
1178
+ setOpen(false);
1179
+ }}
1180
+ />
1181
+ </div>
1182
+ </div>
1183
+ ) : null}
1184
+ </div>
1185
+ );
1186
+ }
1187
+
1188
+ function ToolbarColorPopover(props: {
1189
+ ariaLabel: string;
1190
+ colors: ReadonlyArray<{ value: string | null; label: string }>;
1191
+ disabled: boolean;
1192
+ icon: React.ReactNode;
1193
+ title: string;
1194
+ onSelect: (value: string | null) => void;
1195
+ }) {
1196
+ const [open, setOpen] = React.useState(false);
1197
+
1198
+ return (
1199
+ <div className="relative">
1200
+ <Tooltip.Root>
1201
+ <Tooltip.Trigger asChild>
1202
+ <button
1203
+ type="button"
1204
+ aria-label={props.ariaLabel}
1205
+ aria-expanded={open}
1206
+ disabled={props.disabled}
1207
+ onMouseDown={preserveEditorSelectionMouseDown}
1208
+ onClick={() => setOpen((value) => !value)}
1209
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
1210
+ >
1211
+ {props.icon}
1212
+ </button>
1213
+ </Tooltip.Trigger>
1214
+ <Tooltip.Portal>
1215
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
1216
+ {props.title}
1217
+ </Tooltip.Content>
1218
+ </Tooltip.Portal>
1219
+ </Tooltip.Root>
1220
+ {open ? (
1221
+ <div className="absolute left-0 top-9 z-50 w-[180px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
1222
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
1223
+ {props.title}
1224
+ </div>
1225
+ <div className="grid grid-cols-3 gap-1">
1226
+ {props.colors.map((color) => (
1227
+ <button
1228
+ key={`${props.ariaLabel}-${color.label}`}
1229
+ type="button"
1230
+ aria-label={`${props.title} ${color.label}`}
1231
+ disabled={props.disabled}
1232
+ onMouseDown={preserveEditorSelectionMouseDown}
1233
+ onClick={() => {
1234
+ props.onSelect(color.value);
1235
+ setOpen(false);
1236
+ }}
1237
+ className={`inline-flex h-8 items-center justify-center rounded-md border border-border text-[10px] font-medium text-primary transition-transform hover:scale-[1.04] disabled:cursor-not-allowed disabled:opacity-40 ${
1238
+ color.value ? "" : "bg-surface"
1239
+ } ${focusRingClass}`}
1240
+ style={color.value ? { backgroundColor: color.value } : undefined}
1241
+ >
1242
+ {color.value ? <span className="sr-only">{color.label}</span> : "None"}
1243
+ </button>
1244
+ ))}
1245
+ </div>
1246
+ </div>
1247
+ ) : null}
1248
+ </div>
1249
+ );
1250
+ }
1251
+
1252
+ function ToolbarAlignmentPopover(props: {
1253
+ activeAlignment?: FormattingAlignment;
1254
+ disabled: boolean;
1255
+ onSelect: (alignment: FormattingAlignment) => void;
1256
+ }) {
1257
+ const [open, setOpen] = React.useState(false);
1258
+ const alignments = [
1259
+ { value: "left" as const, label: "Align left", icon: <AlignLeft className="h-3.5 w-3.5" /> },
1260
+ { value: "center" as const, label: "Align center", icon: <AlignCenter className="h-3.5 w-3.5" /> },
1261
+ { value: "right" as const, label: "Align right", icon: <AlignRight className="h-3.5 w-3.5" /> },
1262
+ { value: "justify" as const, label: "Align justify", icon: <AlignJustify className="h-3.5 w-3.5" /> },
1263
+ ];
1264
+
1265
+ return (
1266
+ <div className="relative">
1267
+ <Tooltip.Root>
1268
+ <Tooltip.Trigger asChild>
1269
+ <button
1270
+ type="button"
1271
+ aria-label="Paragraph alignment"
1272
+ aria-expanded={open}
1273
+ disabled={props.disabled}
1274
+ onMouseDown={preserveEditorSelectionMouseDown}
1275
+ onClick={() => setOpen((value) => !value)}
1276
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
1277
+ >
1278
+ {(alignments.find((entry) => entry.value === props.activeAlignment) ?? alignments[0])?.icon}
1279
+ </button>
1280
+ </Tooltip.Trigger>
1281
+ <Tooltip.Portal>
1282
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
1283
+ Paragraph alignment
1284
+ </Tooltip.Content>
1285
+ </Tooltip.Portal>
1286
+ </Tooltip.Root>
1287
+ {open ? (
1288
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
1289
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
1290
+ Paragraph alignment
1291
+ </div>
1292
+ <div className="grid grid-cols-2 gap-1">
1293
+ {alignments.map((entry) => (
1294
+ <ToolbarPopoverActionButton
1295
+ key={entry.value}
1296
+ active={props.activeAlignment === entry.value}
1297
+ ariaLabel={entry.label}
1298
+ disabled={props.disabled}
1299
+ icon={entry.icon}
1300
+ onClick={() => {
1301
+ props.onSelect(entry.value);
1302
+ setOpen(false);
1303
+ }}
1304
+ />
1305
+ ))}
1306
+ </div>
1307
+ </div>
1308
+ ) : null}
1309
+ </div>
1310
+ );
1311
+ }
1312
+
1313
+ function ToolbarInsertMenu(props: {
1314
+ disabled: boolean;
1315
+ onInsertPageBreak?: () => void;
1316
+ onInsertTable?: () => void;
1317
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
1318
+ onInsertImage?: (options: InsertImageOptions) => void;
1319
+ }) {
1320
+ const [open, setOpen] = React.useState(false);
1321
+
1322
+ async function handleImageChange(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
1323
+ const file = event.target.files?.[0];
1324
+ if (!file || props.disabled || !props.onInsertImage) {
1325
+ event.target.value = "";
1326
+ return;
1327
+ }
1328
+ const data = new Uint8Array(await file.arrayBuffer());
1329
+ props.onInsertImage({
1330
+ data,
1331
+ mimeType: file.type || "image/png",
1332
+ altText: file.name,
1333
+ });
1334
+ setOpen(false);
1335
+ event.target.value = "";
1336
+ }
1337
+
1338
+ return (
1339
+ <div className="relative">
1340
+ <Tooltip.Root>
1341
+ <Tooltip.Trigger asChild>
1342
+ <button
1343
+ type="button"
1344
+ aria-label="Insert"
1345
+ aria-expanded={open}
1346
+ disabled={props.disabled}
1347
+ onMouseDown={preserveEditorSelectionMouseDown}
1348
+ onClick={() => setOpen((value) => !value)}
1349
+ className={`inline-flex h-7 items-center gap-1 rounded-md border border-border bg-canvas px-2 text-xs font-medium text-primary transition-colors hover:bg-surface outline-none disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
1350
+ >
1351
+ Insert
1352
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
1353
+ </button>
1354
+ </Tooltip.Trigger>
1355
+ <Tooltip.Portal>
1356
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
1357
+ Insert
1358
+ </Tooltip.Content>
1359
+ </Tooltip.Portal>
1360
+ </Tooltip.Root>
1361
+ {open ? (
1362
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
1363
+ <div className="space-y-1">
1364
+ <ToolbarMenuButton
1365
+ ariaLabel="Insert page break"
1366
+ disabled={props.disabled || !props.onInsertPageBreak}
1367
+ icon={<Minus className="h-3.5 w-3.5" />}
1368
+ label="Page break"
1369
+ onClick={() => {
1370
+ props.onInsertPageBreak?.();
1371
+ setOpen(false);
1372
+ }}
1373
+ />
1374
+ <ToolbarMenuButton
1375
+ ariaLabel="Insert table"
1376
+ disabled={props.disabled || !props.onInsertTable}
1377
+ icon={<Rows3 className="h-3.5 w-3.5" />}
1378
+ label="Table"
1379
+ onClick={() => {
1380
+ props.onInsertTable?.();
1381
+ setOpen(false);
1382
+ }}
1383
+ />
1384
+ <label
1385
+ className={`flex h-8 cursor-pointer items-center gap-2 rounded-md px-2 text-xs font-medium text-primary transition-colors hover:bg-surface ${
1386
+ props.disabled || !props.onInsertImage ? "pointer-events-none opacity-40" : ""
1387
+ }`}
1388
+ >
1389
+ <ImagePlus className="h-3.5 w-3.5 text-secondary" />
1390
+ <span>Image</span>
1391
+ <input
1392
+ accept="image/png,image/jpeg,image/gif"
1393
+ aria-label="Insert image"
1394
+ className="sr-only"
1395
+ disabled={props.disabled || !props.onInsertImage}
1396
+ type="file"
1397
+ onChange={(event) => {
1398
+ void handleImageChange(event);
1399
+ }}
1400
+ />
1401
+ </label>
1402
+ <ToolbarMenuButton
1403
+ ariaLabel="Insert next-page section break"
1404
+ disabled={props.disabled || !props.onInsertSectionBreak}
1405
+ icon={<FileText className="h-3.5 w-3.5" />}
1406
+ label="Next-page section break"
1407
+ onClick={() => {
1408
+ props.onInsertSectionBreak?.("nextPage");
1409
+ setOpen(false);
1410
+ }}
1411
+ />
1412
+ </div>
1413
+ </div>
1414
+ ) : null}
1415
+ </div>
1416
+ );
1417
+ }
1418
+
1419
+ function ToolbarPopoverActionButton(props: {
1420
+ active: boolean;
1421
+ ariaLabel: string;
1422
+ disabled: boolean;
1423
+ icon: React.ReactNode;
1424
+ onClick?: () => void;
1425
+ }) {
1426
+ return (
1427
+ <button
1428
+ type="button"
1429
+ aria-label={props.ariaLabel}
1430
+ aria-pressed={props.active}
1431
+ disabled={props.disabled}
1432
+ onMouseDown={preserveEditorSelectionMouseDown}
1433
+ onClick={props.onClick}
1434
+ className={`inline-flex h-8 items-center justify-center rounded-md border border-border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${
1435
+ props.active ? "bg-canvas text-accent ring-1 ring-accent/30 shadow-sm" : "bg-canvas text-secondary hover:bg-surface"
1436
+ } ${focusRingClass}`}
1437
+ >
1438
+ {props.icon}
1439
+ </button>
1440
+ );
1441
+ }
1442
+
1443
+ function ToolbarMenuButton(props: {
1444
+ ariaLabel: string;
1445
+ disabled: boolean;
1446
+ icon: React.ReactNode;
1447
+ label: string;
1448
+ onClick?: () => void;
1449
+ }) {
1450
+ return (
1451
+ <button
1452
+ type="button"
1453
+ aria-label={props.ariaLabel}
1454
+ disabled={props.disabled}
1455
+ onMouseDown={preserveEditorSelectionMouseDown}
1456
+ onClick={props.onClick}
1457
+ className={`flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
1458
+ >
1459
+ <span className="text-secondary">{props.icon}</span>
1460
+ <span>{props.label}</span>
1461
+ </button>
1462
+ );
1463
+ }
1464
+
1465
+ function storyLabel(target: EditorStoryTarget): string {
1466
+ switch (target.kind) {
1467
+ case "header":
1468
+ return `Header (${target.variant})`;
1469
+ case "footer":
1470
+ return `Footer (${target.variant})`;
1471
+ case "footnote":
1472
+ return "Footnote";
1473
+ case "endnote":
1474
+ return "Endnote";
1475
+ default:
1476
+ return "Document";
1477
+ }
1478
+ }