@beyondwork/docx-react-component 1.0.29 → 1.0.30

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 (381) 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/review-queue-bar.tsx +97 -0
  180. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +64 -0
  181. package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
  182. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +121 -0
  183. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
  184. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +30 -0
  185. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +365 -0
  186. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
  187. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
  188. package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
  189. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
  190. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
  191. package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
  192. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
  193. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +186 -0
  194. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
  195. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +200 -0
  196. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  197. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  198. package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
  199. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +189 -0
  200. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
  201. package/src/ui-tailwind/editor-surface/pm-decorations.ts +411 -0
  202. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  203. package/src/ui-tailwind/editor-surface/pm-schema.ts +927 -0
  204. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +567 -0
  205. package/src/ui-tailwind/editor-surface/search-plugin.ts +168 -0
  206. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +65 -0
  207. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  208. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  209. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +129 -0
  210. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +58 -0
  211. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  212. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +1047 -0
  213. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  214. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +503 -0
  215. package/src/ui-tailwind/index.ts +62 -0
  216. package/src/ui-tailwind/page-chrome-model.ts +27 -0
  217. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +406 -0
  218. package/src/ui-tailwind/review/tw-health-panel.tsx +149 -0
  219. package/src/ui-tailwind/review/tw-review-rail.tsx +122 -0
  220. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  221. package/src/ui-tailwind/status/tw-status-bar.tsx +65 -0
  222. package/{dist → src}/ui-tailwind/theme/editor-theme.css +58 -40
  223. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +52 -0
  224. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +1133 -0
  225. package/src/ui-tailwind/tw-review-workspace.tsx +1460 -0
  226. package/src/validation/README.md +3 -0
  227. package/src/validation/compatibility-engine.ts +878 -0
  228. package/src/validation/compatibility-report.ts +161 -0
  229. package/src/validation/diagnostics.ts +204 -0
  230. package/src/validation/docx-comment-proof.ts +720 -0
  231. package/src/validation/import-diagnostics.ts +128 -0
  232. package/src/validation/low-priority-word-surfaces.ts +373 -0
  233. package/dist/canonical-document-BLEbzL2J.d.cts +0 -844
  234. package/dist/canonical-document-BLEbzL2J.d.ts +0 -844
  235. package/dist/chunk-2FJS5GZM.js +0 -763
  236. package/dist/chunk-2FJS5GZM.js.map +0 -1
  237. package/dist/chunk-2OQBZS3F.js +0 -446
  238. package/dist/chunk-2OQBZS3F.js.map +0 -1
  239. package/dist/chunk-2S7W4KFO.js +0 -127
  240. package/dist/chunk-2S7W4KFO.js.map +0 -1
  241. package/dist/chunk-2TG72QSW.js +0 -3874
  242. package/dist/chunk-2TG72QSW.js.map +0 -1
  243. package/dist/chunk-36QNIZBO.js +0 -532
  244. package/dist/chunk-36QNIZBO.js.map +0 -1
  245. package/dist/chunk-4AQOYAW4.js +0 -3069
  246. package/dist/chunk-4AQOYAW4.js.map +0 -1
  247. package/dist/chunk-4D5EWJ3P.js +0 -77
  248. package/dist/chunk-4D5EWJ3P.js.map +0 -1
  249. package/dist/chunk-5FN54NDH.js +0 -2257
  250. package/dist/chunk-5FN54NDH.js.map +0 -1
  251. package/dist/chunk-BOYGQYRQ.js +0 -7306
  252. package/dist/chunk-BOYGQYRQ.js.map +0 -1
  253. package/dist/chunk-CN3XMECL.js +0 -212
  254. package/dist/chunk-CN3XMECL.js.map +0 -1
  255. package/dist/chunk-EBI3BX6U.js +0 -164
  256. package/dist/chunk-EBI3BX6U.js.map +0 -1
  257. package/dist/chunk-EILUG3VB.js +0 -1275
  258. package/dist/chunk-EILUG3VB.js.map +0 -1
  259. package/dist/chunk-FUDY333O.js +0 -70
  260. package/dist/chunk-FUDY333O.js.map +0 -1
  261. package/dist/chunk-GBVOWFIK.js +0 -1237
  262. package/dist/chunk-GBVOWFIK.js.map +0 -1
  263. package/dist/chunk-H4TQ3H3Y.js +0 -262
  264. package/dist/chunk-H4TQ3H3Y.js.map +0 -1
  265. package/dist/chunk-JGB3IXZO.js +0 -189
  266. package/dist/chunk-JGB3IXZO.js.map +0 -1
  267. package/dist/chunk-KD2QRQPY.js +0 -4342
  268. package/dist/chunk-KD2QRQPY.js.map +0 -1
  269. package/dist/chunk-KLMXQVYK.js +0 -369
  270. package/dist/chunk-KLMXQVYK.js.map +0 -1
  271. package/dist/chunk-KZUG5KFQ.js +0 -214
  272. package/dist/chunk-KZUG5KFQ.js.map +0 -1
  273. package/dist/chunk-QDAQ4CJU.js +0 -345
  274. package/dist/chunk-QDAQ4CJU.js.map +0 -1
  275. package/dist/chunk-RMH72RZI.js.map +0 -1
  276. package/dist/chunk-SWKWQZXM.js +0 -117
  277. package/dist/chunk-SWKWQZXM.js.map +0 -1
  278. package/dist/chunk-TJBP2K4T.js.map +0 -1
  279. package/dist/chunk-TLCEAQDQ.js +0 -542
  280. package/dist/chunk-TLCEAQDQ.js.map +0 -1
  281. package/dist/chunk-UZXBISGO.js.map +0 -1
  282. package/dist/chunk-WGBAKP3Q.js +0 -3220
  283. package/dist/chunk-WGBAKP3Q.js.map +0 -1
  284. package/dist/compare/index.cjs +0 -5475
  285. package/dist/compare/index.cjs.map +0 -1
  286. package/dist/compare/index.d.cts +0 -114
  287. package/dist/compare/index.d.ts +0 -114
  288. package/dist/compare/index.js +0 -731
  289. package/dist/compare/index.js.map +0 -1
  290. package/dist/core/commands/formatting-commands.cjs +0 -828
  291. package/dist/core/commands/formatting-commands.cjs.map +0 -1
  292. package/dist/core/commands/formatting-commands.d.cts +0 -63
  293. package/dist/core/commands/formatting-commands.d.ts +0 -63
  294. package/dist/core/commands/formatting-commands.js +0 -37
  295. package/dist/core/commands/formatting-commands.js.map +0 -1
  296. package/dist/core/commands/image-commands.cjs +0 -2023
  297. package/dist/core/commands/image-commands.cjs.map +0 -1
  298. package/dist/core/commands/image-commands.d.cts +0 -58
  299. package/dist/core/commands/image-commands.d.ts +0 -58
  300. package/dist/core/commands/image-commands.js +0 -18
  301. package/dist/core/commands/image-commands.js.map +0 -1
  302. package/dist/core/commands/section-layout-commands.cjs.map +0 -1
  303. package/dist/core/commands/section-layout-commands.d.cts +0 -62
  304. package/dist/core/commands/section-layout-commands.d.ts +0 -62
  305. package/dist/core/commands/section-layout-commands.js +0 -21
  306. package/dist/core/commands/section-layout-commands.js.map +0 -1
  307. package/dist/core/commands/style-commands.cjs.map +0 -1
  308. package/dist/core/commands/style-commands.d.cts +0 -13
  309. package/dist/core/commands/style-commands.d.ts +0 -13
  310. package/dist/core/commands/style-commands.js +0 -9
  311. package/dist/core/commands/style-commands.js.map +0 -1
  312. package/dist/core/commands/table-structure-commands.cjs +0 -1883
  313. package/dist/core/commands/table-structure-commands.cjs.map +0 -1
  314. package/dist/core/commands/table-structure-commands.d.cts +0 -59
  315. package/dist/core/commands/table-structure-commands.d.ts +0 -59
  316. package/dist/core/commands/table-structure-commands.js +0 -12
  317. package/dist/core/commands/table-structure-commands.js.map +0 -1
  318. package/dist/core/commands/text-commands.cjs +0 -2391
  319. package/dist/core/commands/text-commands.cjs.map +0 -1
  320. package/dist/core/commands/text-commands.d.cts +0 -24
  321. package/dist/core/commands/text-commands.d.ts +0 -24
  322. package/dist/core/commands/text-commands.js +0 -28
  323. package/dist/core/commands/text-commands.js.map +0 -1
  324. package/dist/core/selection/mapping.cjs +0 -200
  325. package/dist/core/selection/mapping.cjs.map +0 -1
  326. package/dist/core/selection/mapping.d.cts +0 -2
  327. package/dist/core/selection/mapping.d.ts +0 -2
  328. package/dist/core/selection/mapping.js +0 -31
  329. package/dist/core/selection/mapping.js.map +0 -1
  330. package/dist/core/state/editor-state.cjs +0 -2278
  331. package/dist/core/state/editor-state.cjs.map +0 -1
  332. package/dist/core/state/editor-state.d.cts +0 -2
  333. package/dist/core/state/editor-state.d.ts +0 -2
  334. package/dist/core/state/editor-state.js +0 -26
  335. package/dist/core/state/editor-state.js.map +0 -1
  336. package/dist/index.cjs +0 -38553
  337. package/dist/index.cjs.map +0 -1
  338. package/dist/index.d.cts +0 -15
  339. package/dist/index.d.ts +0 -15
  340. package/dist/index.js +0 -7856
  341. package/dist/index.js.map +0 -1
  342. package/dist/io/docx-session.cjs +0 -16236
  343. package/dist/io/docx-session.cjs.map +0 -1
  344. package/dist/io/docx-session.d.cts +0 -21
  345. package/dist/io/docx-session.d.ts +0 -21
  346. package/dist/io/docx-session.js +0 -18
  347. package/dist/io/docx-session.js.map +0 -1
  348. package/dist/legal/index.cjs +0 -3900
  349. package/dist/legal/index.cjs.map +0 -1
  350. package/dist/legal/index.d.cts +0 -86
  351. package/dist/legal/index.d.ts +0 -86
  352. package/dist/legal/index.js +0 -616
  353. package/dist/legal/index.js.map +0 -1
  354. package/dist/public-types-7ZL_94cz.d.ts +0 -1573
  355. package/dist/public-types-CeMaDueh.d.cts +0 -1573
  356. package/dist/public-types.cjs +0 -19
  357. package/dist/public-types.cjs.map +0 -1
  358. package/dist/public-types.d.cts +0 -2
  359. package/dist/public-types.d.ts +0 -2
  360. package/dist/public-types.js +0 -1
  361. package/dist/public-types.js.map +0 -1
  362. package/dist/runtime/document-runtime.cjs +0 -11140
  363. package/dist/runtime/document-runtime.cjs.map +0 -1
  364. package/dist/runtime/document-runtime.d.cts +0 -231
  365. package/dist/runtime/document-runtime.d.ts +0 -231
  366. package/dist/runtime/document-runtime.js +0 -21
  367. package/dist/runtime/document-runtime.js.map +0 -1
  368. package/dist/structural-helpers-CilgOVhh.d.cts +0 -10
  369. package/dist/structural-helpers-q0Gd-eBN.d.ts +0 -10
  370. package/dist/ui-tailwind/editor-surface/search-plugin.cjs +0 -313
  371. package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +0 -1
  372. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +0 -67
  373. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +0 -67
  374. package/dist/ui-tailwind/editor-surface/search-plugin.js +0 -23
  375. package/dist/ui-tailwind/editor-surface/search-plugin.js.map +0 -1
  376. package/dist/ui-tailwind/index.cjs +0 -4833
  377. package/dist/ui-tailwind/index.cjs.map +0 -1
  378. package/dist/ui-tailwind/index.d.cts +0 -617
  379. package/dist/ui-tailwind/index.d.ts +0 -617
  380. package/dist/ui-tailwind/index.js +0 -575
  381. package/dist/ui-tailwind/index.js.map +0 -1
@@ -0,0 +1,1133 @@
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
+ Plus,
32
+ Redo2,
33
+ Rows3,
34
+ Strikethrough,
35
+ Subscript,
36
+ Superscript,
37
+ Underline,
38
+ Undo2,
39
+ } from "lucide-react";
40
+
41
+ import type {
42
+ ActiveListContext,
43
+ CompatibilityPanelSnapshot,
44
+ EditorStoryTarget,
45
+ EditorWarning,
46
+ FormattingStateSnapshot,
47
+ FormattingAlignment,
48
+ InsertImageOptions,
49
+ SectionBreakType,
50
+ StyleCatalogSnapshot,
51
+ WorkflowBlockedCommandReason,
52
+ WordReviewEditorChromePreset,
53
+ WorkspaceMode,
54
+ ZoomLevel,
55
+ } from "../../api/public-types";
56
+ import type { SessionCapabilities } from "../../runtime/session-capabilities";
57
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
58
+ import { TwHealthPanel } from "../review/tw-health-panel";
59
+ import { TwToolbarIconButton } from "./tw-toolbar-icon-button";
60
+
61
+ export interface TwToolbarProps {
62
+ capabilities?: SessionCapabilities;
63
+ compatibility?: CompatibilityPanelSnapshot;
64
+ warnings?: EditorWarning[];
65
+ blockedReasons?: WorkflowBlockedCommandReason[];
66
+ showDiagnosticsChrome?: boolean;
67
+ interactionPolicy?: ToolbarInteractionPolicy;
68
+ preset?: WordReviewEditorChromePreset;
69
+ workspaceMode: WorkspaceMode;
70
+ zoomLevel?: ZoomLevel;
71
+ formattingState?: FormattingStateSnapshot;
72
+ activeListContext?: ActiveListContext | null;
73
+ styleCatalog?: StyleCatalogSnapshot;
74
+ /** Display toggle for tracked change decorations (not a runtime mutation toggle). */
75
+ showTrackedChanges: boolean;
76
+ /** Active story target — shows a breadcrumb when editing a secondary story. */
77
+ activeStory?: EditorStoryTarget;
78
+ /** Called when the user clicks the story breadcrumb to return to main body. */
79
+ onCloseStory?: () => void;
80
+ onUndo: () => void;
81
+ onRedo: () => void;
82
+ onSetParagraphStyle?: (styleId: string) => void;
83
+ onToggleBold?: () => void;
84
+ onToggleItalic?: () => void;
85
+ onToggleUnderline?: () => void;
86
+ onToggleStrikethrough?: () => void;
87
+ onToggleSuperscript?: () => void;
88
+ onToggleSubscript?: () => void;
89
+ onSetFontFamily?: (fontFamily: string) => void;
90
+ onSetFontSize?: (fontSize: number) => void;
91
+ onSetTextColor?: (color: string) => void;
92
+ onSetHighlightColor?: (color: string | null) => void;
93
+ onSetAlignment?: (alignment: FormattingAlignment) => void;
94
+ onToggleBulletedList?: () => void;
95
+ onToggleNumberedList?: () => void;
96
+ onOutdent?: () => void;
97
+ onIndent?: () => void;
98
+ onAddComment: () => void;
99
+ onInsertPageBreak?: () => void;
100
+ onInsertTable?: () => void;
101
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
102
+ onInsertImage?: (options: InsertImageOptions) => void;
103
+ onExport: () => void;
104
+ onWorkspaceModeChange: (value: WorkspaceMode) => void;
105
+ onZoomChange?: (level: ZoomLevel) => void;
106
+ onShowTrackedChangesChange: (show: boolean) => void;
107
+ onRestartNumbering?: () => void;
108
+ onContinueNumbering?: () => void;
109
+ onUpdateFields?: () => void;
110
+ onUpdateTableOfContents?: () => void;
111
+ }
112
+
113
+ export interface ToolbarInteractionPolicy {
114
+ mode: "edit" | "suggest" | "comment" | "view" | "blocked";
115
+ canFormatText: boolean;
116
+ canInsertStructural: boolean;
117
+ canAddComment: boolean;
118
+ }
119
+
120
+ export function getSupportedZoomPresets(): ReadonlyArray<number> {
121
+ return [75, 100, 125, 150];
122
+ }
123
+
124
+ const focusRingClass =
125
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas";
126
+
127
+ const FONT_FAMILIES = ["Arial", "Times New Roman", "Calibri", "Cambria", "Georgia", "Verdana"];
128
+ const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36];
129
+ const TEXT_COLORS = ["#1f1f1f", "#5c5852", "#1660a8", "#50684d", "#9b4f49", "#7b5f32"];
130
+ const HIGHLIGHT_COLORS = [
131
+ { value: "#ffff00", label: "Yellow" },
132
+ { value: "#d9e7f7", label: "Blue" },
133
+ { value: "#f1dbd6", label: "Rose" },
134
+ { value: "#e6e0d4", label: "Stone" },
135
+ { value: null, label: "None" },
136
+ ] as const;
137
+
138
+ export function TwToolbar(props: TwToolbarProps) {
139
+ const caps = props.capabilities;
140
+ const preset = props.preset ?? "advanced";
141
+ const workspaceMode = props.workspaceMode;
142
+ const paragraphStyles = props.styleCatalog?.paragraphs ?? [];
143
+ const zoomLevel = props.zoomLevel ?? 100;
144
+ const canEdit = props.interactionPolicy?.canFormatText ?? (caps ? caps.canEdit : false);
145
+ const canInsertStructural = props.interactionPolicy?.canInsertStructural ?? canEdit;
146
+ const canAddComment = props.interactionPolicy?.canAddComment ?? (caps ? caps.canAddComment : false);
147
+ const showStyleSelectors = preset === "advanced";
148
+ const showAdvancedFormatting = preset === "advanced";
149
+ const showFormattingColors = preset !== "review";
150
+ const showInsertMenu = preset === "simple" || preset === "advanced";
151
+ const showTrackedChangesToggle = preset !== "simple";
152
+ const showDiagnosticsChrome = props.showDiagnosticsChrome ?? true;
153
+ const showHealth = showDiagnosticsChrome && Boolean(props.compatibility && props.warnings);
154
+ const showListActions = preset === "simple" || preset === "advanced";
155
+ const showUpdateActions = preset === "advanced";
156
+ const zoomLabel =
157
+ typeof zoomLevel === "number"
158
+ ? `${zoomLevel}%`
159
+ : zoomLevel === "pageWidth"
160
+ ? "Fit width"
161
+ : "Fit page";
162
+
163
+ return (
164
+ <header className="flex h-11 shrink-0 items-center gap-1 rounded-xl border border-border/70 bg-canvas/92 px-2.5 shadow-[0_8px_20px_-18px_var(--color-shadow-strong)] backdrop-blur-sm">
165
+ {/* Left cluster: undo/redo + formatting */}
166
+ <div className="flex min-w-0 flex-1 items-center gap-0.5">
167
+ <TwToolbarIconButton
168
+ icon={Undo2}
169
+ label="Undo"
170
+ disabled={caps ? !caps.canUndo : true}
171
+ onClick={props.onUndo}
172
+ />
173
+ <TwToolbarIconButton
174
+ icon={Redo2}
175
+ label="Redo"
176
+ disabled={caps ? !caps.canRedo : true}
177
+ onClick={props.onRedo}
178
+ />
179
+ <div className="mx-1 h-4 w-px bg-border" />
180
+
181
+ {showStyleSelectors ? (
182
+ <>
183
+ <ToolbarParagraphStyleSelect
184
+ disabled={!canEdit || paragraphStyles.length === 0 || !props.onSetParagraphStyle}
185
+ styles={paragraphStyles}
186
+ value={props.formattingState?.paragraphStyleId}
187
+ onValueChange={props.onSetParagraphStyle}
188
+ />
189
+
190
+ <ToolbarFontFamilySelect
191
+ disabled={!canEdit || !props.onSetFontFamily}
192
+ value={props.formattingState?.fontFamily}
193
+ onValueChange={props.onSetFontFamily}
194
+ />
195
+ <ToolbarFontSizeSelect
196
+ disabled={!canEdit || !props.onSetFontSize}
197
+ value={props.formattingState?.fontSize}
198
+ onValueChange={props.onSetFontSize}
199
+ />
200
+
201
+ <div className="mx-1 h-4 w-px bg-border" />
202
+ </>
203
+ ) : null}
204
+
205
+ <TwToolbarIconButton
206
+ icon={Bold}
207
+ label="Bold"
208
+ active={props.formattingState?.bold ?? false}
209
+ disabled={!canEdit}
210
+ onClick={props.onToggleBold}
211
+ />
212
+ <TwToolbarIconButton
213
+ icon={Italic}
214
+ label="Italic"
215
+ active={props.formattingState?.italic ?? false}
216
+ disabled={!canEdit}
217
+ onClick={props.onToggleItalic}
218
+ />
219
+ <TwToolbarIconButton
220
+ icon={Underline}
221
+ label="Underline"
222
+ active={props.formattingState?.underline ?? false}
223
+ disabled={!canEdit}
224
+ onClick={props.onToggleUnderline}
225
+ />
226
+ {showAdvancedFormatting ? (
227
+ <ToolbarFormattingOverflow
228
+ disabled={!canEdit}
229
+ formattingState={props.formattingState}
230
+ onToggleStrikethrough={props.onToggleStrikethrough}
231
+ onToggleSuperscript={props.onToggleSuperscript}
232
+ onToggleSubscript={props.onToggleSubscript}
233
+ />
234
+ ) : null}
235
+ {showFormattingColors ? (
236
+ <>
237
+ <ToolbarColorPopover
238
+ ariaLabel="Text color"
239
+ colors={TEXT_COLORS.map((value) => ({ value, label: value }))}
240
+ disabled={!canEdit || !props.onSetTextColor}
241
+ icon={<Baseline className="h-3.5 w-3.5" />}
242
+ onSelect={(value) => {
243
+ if (value) {
244
+ props.onSetTextColor?.(value);
245
+ }
246
+ }}
247
+ title="Text color"
248
+ />
249
+ <ToolbarColorPopover
250
+ ariaLabel="Highlight color"
251
+ colors={HIGHLIGHT_COLORS.map((entry) => ({ value: entry.value, label: entry.label }))}
252
+ disabled={!canEdit || !props.onSetHighlightColor}
253
+ icon={<Highlighter className="h-3.5 w-3.5" />}
254
+ onSelect={(value) => props.onSetHighlightColor?.(value)}
255
+ title="Highlight color"
256
+ />
257
+ <ToolbarAlignmentPopover
258
+ activeAlignment={props.formattingState?.alignment}
259
+ disabled={!canEdit || !props.onSetAlignment}
260
+ onSelect={(alignment) => props.onSetAlignment?.(alignment)}
261
+ />
262
+ </>
263
+ ) : null}
264
+
265
+ <div className="mx-1 h-4 w-px bg-border" />
266
+
267
+ {showListActions ? (
268
+ <>
269
+ <TwToolbarIconButton
270
+ icon={List}
271
+ label="Bulleted list"
272
+ active={Boolean(props.activeListContext && !props.activeListContext.isOrdered)}
273
+ disabled={!canEdit}
274
+ onClick={props.onToggleBulletedList}
275
+ />
276
+ <TwToolbarIconButton
277
+ icon={Rows3}
278
+ label="Numbered list"
279
+ active={Boolean(props.activeListContext?.isOrdered)}
280
+ disabled={!canEdit}
281
+ onClick={props.onToggleNumberedList}
282
+ />
283
+ </>
284
+ ) : null}
285
+
286
+ <TwToolbarIconButton
287
+ icon={Outdent}
288
+ label="Outdent"
289
+ disabled={!canEdit}
290
+ onClick={props.onOutdent}
291
+ />
292
+ <TwToolbarIconButton
293
+ icon={Indent}
294
+ label="Indent"
295
+ disabled={!canEdit}
296
+ onClick={props.onIndent}
297
+ />
298
+ {showListActions && props.activeListContext ? (
299
+ <>
300
+ <button
301
+ type="button"
302
+ aria-label="Restart numbering"
303
+ disabled={!canEdit || !props.onRestartNumbering}
304
+ onMouseDown={preserveEditorSelectionMouseDown}
305
+ onClick={props.onRestartNumbering}
306
+ 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}`}
307
+ >
308
+ Restart
309
+ </button>
310
+ <button
311
+ type="button"
312
+ aria-label="Continue numbering"
313
+ disabled={!canEdit || !props.onContinueNumbering}
314
+ onMouseDown={preserveEditorSelectionMouseDown}
315
+ onClick={props.onContinueNumbering}
316
+ 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}`}
317
+ >
318
+ Continue
319
+ </button>
320
+ </>
321
+ ) : null}
322
+ {showInsertMenu ? (
323
+ <ToolbarInsertMenu
324
+ disabled={!canInsertStructural}
325
+ onInsertImage={props.onInsertImage}
326
+ onInsertPageBreak={props.onInsertPageBreak}
327
+ onInsertSectionBreak={props.onInsertSectionBreak}
328
+ onInsertTable={props.onInsertTable}
329
+ />
330
+ ) : null}
331
+ {showUpdateActions ? (
332
+ <>
333
+ <button
334
+ type="button"
335
+ aria-label="Refresh fields"
336
+ disabled={!canEdit || !props.onUpdateFields}
337
+ onMouseDown={preserveEditorSelectionMouseDown}
338
+ onClick={props.onUpdateFields}
339
+ 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}`}
340
+ >
341
+ Fields
342
+ </button>
343
+ <button
344
+ type="button"
345
+ aria-label="Refresh table of contents"
346
+ disabled={!canEdit || !props.onUpdateTableOfContents}
347
+ onMouseDown={preserveEditorSelectionMouseDown}
348
+ onClick={props.onUpdateTableOfContents}
349
+ 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}`}
350
+ >
351
+ TOC
352
+ </button>
353
+ </>
354
+ ) : null}
355
+
356
+ {/* Story focus breadcrumb — visible when editing a secondary story */}
357
+ {props.activeStory && props.activeStory.kind !== "main" ? (
358
+ <>
359
+ <div className="mx-1 h-4 w-px bg-border" />
360
+ <button
361
+ type="button"
362
+ onClick={props.onCloseStory}
363
+ onMouseDown={preserveEditorSelectionMouseDown}
364
+ 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}`}
365
+ aria-label={`Editing ${storyLabel(props.activeStory)} — click to return to main body`}
366
+ >
367
+ <span className="text-secondary">&larr;</span>
368
+ {storyLabel(props.activeStory)}
369
+ </button>
370
+ </>
371
+ ) : null}
372
+ </div>
373
+
374
+ {/* Right cluster: comment, track changes, markup, view, export */}
375
+ <div className="flex items-center gap-0.5">
376
+ <TwToolbarIconButton
377
+ icon={MessageSquare}
378
+ label="Add comment"
379
+ disabled={!canAddComment}
380
+ emphasis
381
+ onClick={props.onAddComment}
382
+ />
383
+
384
+ {showTrackedChangesToggle ? (
385
+ <>
386
+ <Tooltip.Root>
387
+ <Tooltip.Trigger asChild>
388
+ <Toggle.Root
389
+ pressed={props.showTrackedChanges}
390
+ onPressedChange={props.onShowTrackedChangesChange}
391
+ disabled={caps ? !caps.trackChangesSupported : false}
392
+ onMouseDown={preserveEditorSelectionMouseDown}
393
+ 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}`}
394
+ >
395
+ {props.showTrackedChanges ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
396
+ </Toggle.Root>
397
+ </Tooltip.Trigger>
398
+ <Tooltip.Portal>
399
+ <Tooltip.Content
400
+ className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50"
401
+ sideOffset={6}
402
+ >
403
+ {props.showTrackedChanges ? "Hide tracked changes" : "Show tracked changes"}
404
+ </Tooltip.Content>
405
+ </Tooltip.Portal>
406
+ </Tooltip.Root>
407
+
408
+ <div className="mx-1 h-4 w-px bg-border" />
409
+ </>
410
+ ) : null}
411
+
412
+ {/* View mode toggle group: Canvas (clean, flowing) / Page (layout-sensitive) */}
413
+ <ToggleGroup.Root
414
+ type="single"
415
+ value={workspaceMode}
416
+ onValueChange={(v: string) => {
417
+ if (v) props.onWorkspaceModeChange(v as WorkspaceMode);
418
+ }}
419
+ className="flex items-center gap-0.5"
420
+ >
421
+ <Tooltip.Root>
422
+ <Tooltip.Trigger asChild>
423
+ <ToggleGroup.Item
424
+ value="canvas"
425
+ aria-label="Canvas workspace"
426
+ onMouseDown={preserveEditorSelectionMouseDown}
427
+ 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}`}
428
+ >
429
+ <Monitor className="h-3.5 w-3.5" />
430
+ </ToggleGroup.Item>
431
+ </Tooltip.Trigger>
432
+ <Tooltip.Portal>
433
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
434
+ Canvas — clean flowing text
435
+ </Tooltip.Content>
436
+ </Tooltip.Portal>
437
+ </Tooltip.Root>
438
+ <Tooltip.Root>
439
+ <Tooltip.Trigger asChild>
440
+ <ToggleGroup.Item
441
+ value="page"
442
+ aria-label="Page workspace"
443
+ onMouseDown={preserveEditorSelectionMouseDown}
444
+ 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}`}
445
+ >
446
+ <FileText className="h-3.5 w-3.5" />
447
+ </ToggleGroup.Item>
448
+ </Tooltip.Trigger>
449
+ <Tooltip.Portal>
450
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
451
+ Page — layout-sensitive view
452
+ </Tooltip.Content>
453
+ </Tooltip.Portal>
454
+ </Tooltip.Root>
455
+ </ToggleGroup.Root>
456
+
457
+ {/* Zoom controls — available in all workspace modes */}
458
+ {props.onZoomChange ? (
459
+ <>
460
+ <div className="mx-1 h-4 w-px bg-border" />
461
+ <div className="flex items-center gap-0.5">
462
+ <Tooltip.Root>
463
+ <Tooltip.Trigger asChild>
464
+ <button
465
+ type="button"
466
+ aria-label="Zoom out"
467
+ className={`inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary transition-colors hover:bg-surface outline-none ${focusRingClass}`}
468
+ disabled={typeof zoomLevel === "number" && zoomLevel <= 50}
469
+ onMouseDown={preserveEditorSelectionMouseDown}
470
+ onClick={() => {
471
+ const current = typeof zoomLevel === "number" ? zoomLevel : 100;
472
+ props.onZoomChange!(Math.max(50, current - 10));
473
+ }}
474
+ >
475
+ <Minus className="h-3 w-3" />
476
+ </button>
477
+ </Tooltip.Trigger>
478
+ <Tooltip.Portal>
479
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
480
+ Zoom out
481
+ </Tooltip.Content>
482
+ </Tooltip.Portal>
483
+ </Tooltip.Root>
484
+
485
+ <Popover.Root>
486
+ <Tooltip.Root>
487
+ <Tooltip.Trigger asChild>
488
+ <Popover.Trigger asChild>
489
+ <button
490
+ type="button"
491
+ aria-label={`Zoom: ${zoomLabel}`}
492
+ onMouseDown={preserveEditorSelectionMouseDown}
493
+ 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}`}
494
+ >
495
+ {zoomLabel}
496
+ </button>
497
+ </Popover.Trigger>
498
+ </Tooltip.Trigger>
499
+ <Tooltip.Portal>
500
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
501
+ Zoom level
502
+ </Tooltip.Content>
503
+ </Tooltip.Portal>
504
+ </Tooltip.Root>
505
+ <Popover.Portal>
506
+ <Popover.Content
507
+ className="w-[140px] rounded-lg bg-canvas shadow-lg ring-1 ring-border p-1 z-50"
508
+ sideOffset={8}
509
+ align="center"
510
+ >
511
+ {getSupportedZoomPresets().map((preset) => {
512
+ const label = `${preset}%`;
513
+ return (
514
+ <Popover.Close key={preset} asChild>
515
+ <button
516
+ type="button"
517
+ onMouseDown={preserveEditorSelectionMouseDown}
518
+ className={`w-full rounded-md px-3 py-1.5 text-left text-xs transition-colors hover:bg-surface ${
519
+ zoomLevel === preset ? "font-semibold text-accent" : "text-primary"
520
+ }`}
521
+ onClick={() => props.onZoomChange!(preset)}
522
+ >
523
+ {label}
524
+ </button>
525
+ </Popover.Close>
526
+ );
527
+ })}
528
+ </Popover.Content>
529
+ </Popover.Portal>
530
+ </Popover.Root>
531
+
532
+ <Tooltip.Root>
533
+ <Tooltip.Trigger asChild>
534
+ <button
535
+ type="button"
536
+ aria-label="Zoom in"
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 >= 200}
539
+ onMouseDown={preserveEditorSelectionMouseDown}
540
+ onClick={() => {
541
+ const current = typeof zoomLevel === "number" ? zoomLevel : 100;
542
+ props.onZoomChange!(Math.min(200, current + 10));
543
+ }}
544
+ >
545
+ <Plus 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 in
551
+ </Tooltip.Content>
552
+ </Tooltip.Portal>
553
+ </Tooltip.Root>
554
+ </div>
555
+ </>
556
+ ) : null}
557
+
558
+ {showHealth ? (
559
+ <>
560
+ <Popover.Root>
561
+ <Tooltip.Root>
562
+ <Tooltip.Trigger asChild>
563
+ <Popover.Trigger asChild>
564
+ <button
565
+ type="button"
566
+ aria-label="Document health"
567
+ onMouseDown={preserveEditorSelectionMouseDown}
568
+ 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}`}
569
+ >
570
+ <AlertCircle className="h-4 w-4" />
571
+ {(caps?.healthIssueCount ?? 0) > 0 ? (
572
+ <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">
573
+ {caps?.healthIssueCount}
574
+ </span>
575
+ ) : null}
576
+ </button>
577
+ </Popover.Trigger>
578
+ </Tooltip.Trigger>
579
+ <Tooltip.Portal>
580
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
581
+ {(caps?.healthIssueCount ?? 0) > 0
582
+ ? `Document health — ${caps?.healthIssueCount} issue${(caps?.healthIssueCount ?? 0) !== 1 ? "s" : ""}`
583
+ : "Document health — no issues"}
584
+ </Tooltip.Content>
585
+ </Tooltip.Portal>
586
+ </Tooltip.Root>
587
+ <Popover.Portal>
588
+ <Popover.Content
589
+ className="w-[360px] max-h-[480px] overflow-y-auto rounded-lg bg-canvas shadow-lg ring-1 ring-border p-3 z-50"
590
+ sideOffset={8}
591
+ align="end"
592
+ >
593
+ <TwHealthPanel
594
+ blockedReasons={props.blockedReasons}
595
+ compatibility={props.compatibility!}
596
+ warnings={props.warnings!}
597
+ />
598
+ </Popover.Content>
599
+ </Popover.Portal>
600
+ </Popover.Root>
601
+ <div className="mx-1 h-4 w-px bg-border" />
602
+ </>
603
+ ) : null}
604
+
605
+ <TwToolbarIconButton
606
+ icon={Download}
607
+ label={caps?.exportBlocked ? "Export blocked" : "Download document"}
608
+ disabled={caps ? !caps.canExport : true}
609
+ emphasis
610
+ onClick={props.onExport}
611
+ />
612
+ </div>
613
+ </header>
614
+ );
615
+ }
616
+
617
+ function ToolbarParagraphStyleSelect(props: {
618
+ styles: StyleCatalogSnapshot["paragraphs"];
619
+ value?: string;
620
+ disabled: boolean;
621
+ onValueChange?: (styleId: string) => void;
622
+ }) {
623
+ const resolvedValue =
624
+ props.value && props.styles.some((style) => style.styleId === props.value)
625
+ ? props.value
626
+ : "";
627
+
628
+ return (
629
+ <Select.Root
630
+ disabled={props.disabled}
631
+ onValueChange={(value) => props.onValueChange?.(value)}
632
+ value={resolvedValue}
633
+ >
634
+ <Select.Trigger
635
+ aria-label="Paragraph style"
636
+ aria-disabled={props.disabled || undefined}
637
+ data-disabled={props.disabled ? "" : undefined}
638
+ onMouseDown={preserveEditorSelectionMouseDown}
639
+ 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}`}
640
+ >
641
+ <Select.Value placeholder="Style" />
642
+ <Select.Icon>
643
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
644
+ </Select.Icon>
645
+ </Select.Trigger>
646
+ <Select.Portal>
647
+ <Select.Content
648
+ align="start"
649
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
650
+ position="popper"
651
+ sideOffset={8}
652
+ >
653
+ <Select.Viewport className="p-1">
654
+ {props.styles.map((style) => (
655
+ <Select.Item
656
+ 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}`}
657
+ key={style.styleId}
658
+ value={style.styleId}
659
+ >
660
+ <Select.ItemText>{style.displayName}</Select.ItemText>
661
+ </Select.Item>
662
+ ))}
663
+ </Select.Viewport>
664
+ </Select.Content>
665
+ </Select.Portal>
666
+ </Select.Root>
667
+ );
668
+ }
669
+
670
+ function ToolbarFontFamilySelect(props: {
671
+ value?: string;
672
+ disabled: boolean;
673
+ onValueChange?: (fontFamily: string) => void;
674
+ }) {
675
+ const resolvedValue = props.value && FONT_FAMILIES.includes(props.value) ? props.value : "";
676
+
677
+ return (
678
+ <Select.Root
679
+ disabled={props.disabled}
680
+ onValueChange={(value) => props.onValueChange?.(value)}
681
+ value={resolvedValue}
682
+ >
683
+ <Select.Trigger
684
+ aria-label="Font family"
685
+ aria-disabled={props.disabled || undefined}
686
+ data-disabled={props.disabled ? "" : undefined}
687
+ onMouseDown={preserveEditorSelectionMouseDown}
688
+ 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}`}
689
+ >
690
+ <Select.Value placeholder="Font" />
691
+ <Select.Icon>
692
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
693
+ </Select.Icon>
694
+ </Select.Trigger>
695
+ <Select.Portal>
696
+ <Select.Content
697
+ align="start"
698
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
699
+ position="popper"
700
+ sideOffset={8}
701
+ >
702
+ <Select.Viewport className="p-1">
703
+ {FONT_FAMILIES.map((font) => (
704
+ <Select.Item
705
+ 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}`}
706
+ key={font}
707
+ value={font}
708
+ >
709
+ <Select.ItemText>{font}</Select.ItemText>
710
+ </Select.Item>
711
+ ))}
712
+ </Select.Viewport>
713
+ </Select.Content>
714
+ </Select.Portal>
715
+ </Select.Root>
716
+ );
717
+ }
718
+
719
+ function ToolbarFontSizeSelect(props: {
720
+ value?: number;
721
+ disabled: boolean;
722
+ onValueChange?: (fontSize: number) => void;
723
+ }) {
724
+ const resolvedValue =
725
+ typeof props.value === "number" && FONT_SIZES.includes(props.value) ? String(props.value) : "";
726
+
727
+ return (
728
+ <Select.Root
729
+ disabled={props.disabled}
730
+ onValueChange={(value) => props.onValueChange?.(Number(value))}
731
+ value={resolvedValue}
732
+ >
733
+ <Select.Trigger
734
+ aria-label="Font size"
735
+ aria-disabled={props.disabled || undefined}
736
+ data-disabled={props.disabled ? "" : undefined}
737
+ onMouseDown={preserveEditorSelectionMouseDown}
738
+ 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}`}
739
+ >
740
+ <Select.Value placeholder="Size" />
741
+ <Select.Icon>
742
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
743
+ </Select.Icon>
744
+ </Select.Trigger>
745
+ <Select.Portal>
746
+ <Select.Content
747
+ align="start"
748
+ className="z-50 overflow-hidden rounded-lg bg-canvas shadow-lg ring-1 ring-border"
749
+ position="popper"
750
+ sideOffset={8}
751
+ >
752
+ <Select.Viewport className="p-1">
753
+ {FONT_SIZES.map((size) => (
754
+ <Select.Item
755
+ 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}`}
756
+ key={size}
757
+ value={String(size)}
758
+ >
759
+ <Select.ItemText>{size}</Select.ItemText>
760
+ </Select.Item>
761
+ ))}
762
+ </Select.Viewport>
763
+ </Select.Content>
764
+ </Select.Portal>
765
+ </Select.Root>
766
+ );
767
+ }
768
+
769
+ function ToolbarFormattingOverflow(props: {
770
+ disabled: boolean;
771
+ formattingState?: FormattingStateSnapshot;
772
+ onToggleStrikethrough?: () => void;
773
+ onToggleSuperscript?: () => void;
774
+ onToggleSubscript?: () => void;
775
+ }) {
776
+ const [open, setOpen] = React.useState(false);
777
+
778
+ return (
779
+ <div className="relative">
780
+ <Tooltip.Root>
781
+ <Tooltip.Trigger asChild>
782
+ <button
783
+ type="button"
784
+ aria-label="More text formatting"
785
+ aria-expanded={open}
786
+ disabled={props.disabled}
787
+ onMouseDown={preserveEditorSelectionMouseDown}
788
+ onClick={() => setOpen((value) => !value)}
789
+ 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}`}
790
+ >
791
+ <MoreHorizontal className="h-3.5 w-3.5" />
792
+ </button>
793
+ </Tooltip.Trigger>
794
+ <Tooltip.Portal>
795
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
796
+ More text formatting
797
+ </Tooltip.Content>
798
+ </Tooltip.Portal>
799
+ </Tooltip.Root>
800
+ {open ? (
801
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
802
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
803
+ Text styling
804
+ </div>
805
+ <div className="grid grid-cols-3 gap-1">
806
+ <ToolbarPopoverActionButton
807
+ active={props.formattingState?.strikethrough ?? false}
808
+ ariaLabel="Strikethrough"
809
+ disabled={props.disabled}
810
+ icon={<Strikethrough className="h-3.5 w-3.5" />}
811
+ onClick={() => {
812
+ props.onToggleStrikethrough?.();
813
+ setOpen(false);
814
+ }}
815
+ />
816
+ <ToolbarPopoverActionButton
817
+ active={props.formattingState?.superscript ?? false}
818
+ ariaLabel="Superscript"
819
+ disabled={props.disabled}
820
+ icon={<Superscript className="h-3.5 w-3.5" />}
821
+ onClick={() => {
822
+ props.onToggleSuperscript?.();
823
+ setOpen(false);
824
+ }}
825
+ />
826
+ <ToolbarPopoverActionButton
827
+ active={props.formattingState?.subscript ?? false}
828
+ ariaLabel="Subscript"
829
+ disabled={props.disabled}
830
+ icon={<Subscript className="h-3.5 w-3.5" />}
831
+ onClick={() => {
832
+ props.onToggleSubscript?.();
833
+ setOpen(false);
834
+ }}
835
+ />
836
+ </div>
837
+ </div>
838
+ ) : null}
839
+ </div>
840
+ );
841
+ }
842
+
843
+ function ToolbarColorPopover(props: {
844
+ ariaLabel: string;
845
+ colors: ReadonlyArray<{ value: string | null; label: string }>;
846
+ disabled: boolean;
847
+ icon: React.ReactNode;
848
+ title: string;
849
+ onSelect: (value: string | null) => void;
850
+ }) {
851
+ const [open, setOpen] = React.useState(false);
852
+
853
+ return (
854
+ <div className="relative">
855
+ <Tooltip.Root>
856
+ <Tooltip.Trigger asChild>
857
+ <button
858
+ type="button"
859
+ aria-label={props.ariaLabel}
860
+ aria-expanded={open}
861
+ disabled={props.disabled}
862
+ onMouseDown={preserveEditorSelectionMouseDown}
863
+ onClick={() => setOpen((value) => !value)}
864
+ 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}`}
865
+ >
866
+ {props.icon}
867
+ </button>
868
+ </Tooltip.Trigger>
869
+ <Tooltip.Portal>
870
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
871
+ {props.title}
872
+ </Tooltip.Content>
873
+ </Tooltip.Portal>
874
+ </Tooltip.Root>
875
+ {open ? (
876
+ <div className="absolute left-0 top-9 z-50 w-[180px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
877
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
878
+ {props.title}
879
+ </div>
880
+ <div className="grid grid-cols-3 gap-1">
881
+ {props.colors.map((color) => (
882
+ <button
883
+ key={`${props.ariaLabel}-${color.label}`}
884
+ type="button"
885
+ aria-label={`${props.title} ${color.label}`}
886
+ disabled={props.disabled}
887
+ onMouseDown={preserveEditorSelectionMouseDown}
888
+ onClick={() => {
889
+ props.onSelect(color.value);
890
+ setOpen(false);
891
+ }}
892
+ 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 ${
893
+ color.value ? "" : "bg-surface"
894
+ } ${focusRingClass}`}
895
+ style={color.value ? { backgroundColor: color.value } : undefined}
896
+ >
897
+ {color.value ? <span className="sr-only">{color.label}</span> : "None"}
898
+ </button>
899
+ ))}
900
+ </div>
901
+ </div>
902
+ ) : null}
903
+ </div>
904
+ );
905
+ }
906
+
907
+ function ToolbarAlignmentPopover(props: {
908
+ activeAlignment?: FormattingAlignment;
909
+ disabled: boolean;
910
+ onSelect: (alignment: FormattingAlignment) => void;
911
+ }) {
912
+ const [open, setOpen] = React.useState(false);
913
+ const alignments = [
914
+ { value: "left" as const, label: "Align left", icon: <AlignLeft className="h-3.5 w-3.5" /> },
915
+ { value: "center" as const, label: "Align center", icon: <AlignCenter className="h-3.5 w-3.5" /> },
916
+ { value: "right" as const, label: "Align right", icon: <AlignRight className="h-3.5 w-3.5" /> },
917
+ { value: "justify" as const, label: "Align justify", icon: <AlignJustify className="h-3.5 w-3.5" /> },
918
+ ];
919
+
920
+ return (
921
+ <div className="relative">
922
+ <Tooltip.Root>
923
+ <Tooltip.Trigger asChild>
924
+ <button
925
+ type="button"
926
+ aria-label="Paragraph alignment"
927
+ aria-expanded={open}
928
+ disabled={props.disabled}
929
+ onMouseDown={preserveEditorSelectionMouseDown}
930
+ onClick={() => setOpen((value) => !value)}
931
+ 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}`}
932
+ >
933
+ {(alignments.find((entry) => entry.value === props.activeAlignment) ?? alignments[0])?.icon}
934
+ </button>
935
+ </Tooltip.Trigger>
936
+ <Tooltip.Portal>
937
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
938
+ Paragraph alignment
939
+ </Tooltip.Content>
940
+ </Tooltip.Portal>
941
+ </Tooltip.Root>
942
+ {open ? (
943
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
944
+ <div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
945
+ Paragraph alignment
946
+ </div>
947
+ <div className="grid grid-cols-2 gap-1">
948
+ {alignments.map((entry) => (
949
+ <ToolbarPopoverActionButton
950
+ key={entry.value}
951
+ active={props.activeAlignment === entry.value}
952
+ ariaLabel={entry.label}
953
+ disabled={props.disabled}
954
+ icon={entry.icon}
955
+ onClick={() => {
956
+ props.onSelect(entry.value);
957
+ setOpen(false);
958
+ }}
959
+ />
960
+ ))}
961
+ </div>
962
+ </div>
963
+ ) : null}
964
+ </div>
965
+ );
966
+ }
967
+
968
+ function ToolbarInsertMenu(props: {
969
+ disabled: boolean;
970
+ onInsertPageBreak?: () => void;
971
+ onInsertTable?: () => void;
972
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
973
+ onInsertImage?: (options: InsertImageOptions) => void;
974
+ }) {
975
+ const [open, setOpen] = React.useState(false);
976
+
977
+ async function handleImageChange(event: React.ChangeEvent<HTMLInputElement>): Promise<void> {
978
+ const file = event.target.files?.[0];
979
+ if (!file || props.disabled || !props.onInsertImage) {
980
+ event.target.value = "";
981
+ return;
982
+ }
983
+ const data = new Uint8Array(await file.arrayBuffer());
984
+ props.onInsertImage({
985
+ data,
986
+ mimeType: file.type || "image/png",
987
+ altText: file.name,
988
+ });
989
+ setOpen(false);
990
+ event.target.value = "";
991
+ }
992
+
993
+ return (
994
+ <div className="relative">
995
+ <Tooltip.Root>
996
+ <Tooltip.Trigger asChild>
997
+ <button
998
+ type="button"
999
+ aria-label="Insert"
1000
+ aria-expanded={open}
1001
+ disabled={props.disabled}
1002
+ onMouseDown={preserveEditorSelectionMouseDown}
1003
+ onClick={() => setOpen((value) => !value)}
1004
+ 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}`}
1005
+ >
1006
+ Insert
1007
+ <ChevronDown className="h-3.5 w-3.5 text-tertiary" />
1008
+ </button>
1009
+ </Tooltip.Trigger>
1010
+ <Tooltip.Portal>
1011
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
1012
+ Insert
1013
+ </Tooltip.Content>
1014
+ </Tooltip.Portal>
1015
+ </Tooltip.Root>
1016
+ {open ? (
1017
+ <div className="absolute left-0 top-9 z-50 w-[220px] rounded-lg bg-canvas p-2 shadow-lg ring-1 ring-border">
1018
+ <div className="space-y-1">
1019
+ <ToolbarMenuButton
1020
+ ariaLabel="Insert page break"
1021
+ disabled={props.disabled || !props.onInsertPageBreak}
1022
+ icon={<Minus className="h-3.5 w-3.5" />}
1023
+ label="Page break"
1024
+ onClick={() => {
1025
+ props.onInsertPageBreak?.();
1026
+ setOpen(false);
1027
+ }}
1028
+ />
1029
+ <ToolbarMenuButton
1030
+ ariaLabel="Insert table"
1031
+ disabled={props.disabled || !props.onInsertTable}
1032
+ icon={<Rows3 className="h-3.5 w-3.5" />}
1033
+ label="Table"
1034
+ onClick={() => {
1035
+ props.onInsertTable?.();
1036
+ setOpen(false);
1037
+ }}
1038
+ />
1039
+ <label
1040
+ 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 ${
1041
+ props.disabled || !props.onInsertImage ? "pointer-events-none opacity-40" : ""
1042
+ }`}
1043
+ >
1044
+ <ImagePlus className="h-3.5 w-3.5 text-secondary" />
1045
+ <span>Image</span>
1046
+ <input
1047
+ accept="image/png,image/jpeg,image/gif"
1048
+ aria-label="Insert image"
1049
+ className="sr-only"
1050
+ disabled={props.disabled || !props.onInsertImage}
1051
+ type="file"
1052
+ onChange={(event) => {
1053
+ void handleImageChange(event);
1054
+ }}
1055
+ />
1056
+ </label>
1057
+ <ToolbarMenuButton
1058
+ ariaLabel="Insert next-page section break"
1059
+ disabled={props.disabled || !props.onInsertSectionBreak}
1060
+ icon={<FileText className="h-3.5 w-3.5" />}
1061
+ label="Next-page section break"
1062
+ onClick={() => {
1063
+ props.onInsertSectionBreak?.("nextPage");
1064
+ setOpen(false);
1065
+ }}
1066
+ />
1067
+ </div>
1068
+ </div>
1069
+ ) : null}
1070
+ </div>
1071
+ );
1072
+ }
1073
+
1074
+ function ToolbarPopoverActionButton(props: {
1075
+ active: boolean;
1076
+ ariaLabel: string;
1077
+ disabled: boolean;
1078
+ icon: React.ReactNode;
1079
+ onClick?: () => void;
1080
+ }) {
1081
+ return (
1082
+ <button
1083
+ type="button"
1084
+ aria-label={props.ariaLabel}
1085
+ aria-pressed={props.active}
1086
+ disabled={props.disabled}
1087
+ onMouseDown={preserveEditorSelectionMouseDown}
1088
+ onClick={props.onClick}
1089
+ className={`inline-flex h-8 items-center justify-center rounded-md border border-border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${
1090
+ props.active ? "bg-canvas text-accent ring-1 ring-accent/30 shadow-sm" : "bg-canvas text-secondary hover:bg-surface"
1091
+ } ${focusRingClass}`}
1092
+ >
1093
+ {props.icon}
1094
+ </button>
1095
+ );
1096
+ }
1097
+
1098
+ function ToolbarMenuButton(props: {
1099
+ ariaLabel: string;
1100
+ disabled: boolean;
1101
+ icon: React.ReactNode;
1102
+ label: string;
1103
+ onClick?: () => void;
1104
+ }) {
1105
+ return (
1106
+ <button
1107
+ type="button"
1108
+ aria-label={props.ariaLabel}
1109
+ disabled={props.disabled}
1110
+ onMouseDown={preserveEditorSelectionMouseDown}
1111
+ onClick={props.onClick}
1112
+ 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}`}
1113
+ >
1114
+ <span className="text-secondary">{props.icon}</span>
1115
+ <span>{props.label}</span>
1116
+ </button>
1117
+ );
1118
+ }
1119
+
1120
+ function storyLabel(target: EditorStoryTarget): string {
1121
+ switch (target.kind) {
1122
+ case "header":
1123
+ return `Header (${target.variant})`;
1124
+ case "footer":
1125
+ return `Footer (${target.variant})`;
1126
+ case "footnote":
1127
+ return "Footnote";
1128
+ case "endnote":
1129
+ return "Endnote";
1130
+ default:
1131
+ return "Document";
1132
+ }
1133
+ }