@beyondwork/docx-react-component 1.0.67 → 1.0.70

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 (384) hide show
  1. package/README.md +75 -932
  2. package/package.json +26 -27
  3. package/src/api/anchor-conversion.ts +43 -0
  4. package/src/api/editor-state-types.ts +2 -1
  5. package/src/api/public-types.ts +504 -101
  6. package/src/api/session-state.ts +4 -0
  7. package/src/api/v3/README.md +91 -0
  8. package/src/api/v3/_create.ts +146 -0
  9. package/src/api/v3/_layer-metadata.ts +362 -0
  10. package/src/api/v3/_mocks.ts +84 -0
  11. package/src/api/v3/_runtime-handle.ts +162 -0
  12. package/src/api/v3/_ux-response.ts +73 -0
  13. package/src/api/v3/ai/_metadata-audit.ts +225 -0
  14. package/src/api/v3/ai/attach.ts +235 -0
  15. package/src/api/v3/ai/bundle.ts +132 -0
  16. package/src/api/v3/ai/explain.ts +144 -0
  17. package/src/api/v3/ai/export.ts +54 -0
  18. package/src/api/v3/ai/inspect.ts +118 -0
  19. package/src/api/v3/ai/policy.ts +77 -0
  20. package/src/api/v3/ai/replacement.ts +341 -0
  21. package/src/api/v3/ai/resolve.ts +133 -0
  22. package/src/api/v3/index.ts +79 -0
  23. package/src/api/v3/runtime/chart.ts +310 -0
  24. package/src/api/v3/runtime/clipboard.ts +81 -0
  25. package/src/api/v3/runtime/collab.ts +331 -0
  26. package/src/api/v3/runtime/content.ts +236 -0
  27. package/src/api/v3/runtime/document.ts +282 -0
  28. package/src/api/v3/runtime/formatting.ts +186 -0
  29. package/src/api/v3/runtime/geometry.ts +349 -0
  30. package/src/api/v3/runtime/layout.ts +108 -0
  31. package/src/api/v3/runtime/review.ts +129 -0
  32. package/src/api/v3/runtime/search.ts +74 -0
  33. package/src/api/v3/runtime/table.ts +63 -0
  34. package/src/api/v3/runtime/workflow.ts +434 -0
  35. package/src/api/v3/ui/_context.ts +86 -0
  36. package/src/api/v3/ui/_create.ts +65 -0
  37. package/src/api/v3/ui/_types.ts +520 -0
  38. package/src/api/v3/ui/chrome-composition.ts +342 -0
  39. package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
  40. package/src/api/v3/ui/chrome.ts +476 -0
  41. package/src/api/v3/ui/debug.ts +124 -0
  42. package/src/api/v3/ui/index.ts +64 -0
  43. package/src/api/v3/ui/overlays-visibility.ts +170 -0
  44. package/src/api/v3/ui/overlays.ts +427 -0
  45. package/src/api/v3/ui/scope.ts +71 -0
  46. package/src/api/v3/ui/session.ts +100 -0
  47. package/src/api/v3/ui/surface.ts +170 -0
  48. package/src/api/v3/ui/viewport.ts +303 -0
  49. package/src/core/commands/index.ts +28 -6
  50. package/src/core/commands/list-commands.ts +3 -2
  51. package/src/core/commands/section-layout-commands.ts +9 -8
  52. package/src/core/schema/text-schema.ts +16 -0
  53. package/src/core/selection/mapping.ts +33 -72
  54. package/src/core/state/editor-state.ts +96 -189
  55. package/src/index.ts +23 -4
  56. package/src/io/chart-preview-resolver.ts +1 -1
  57. package/src/io/docx-session.ts +36 -4797
  58. package/src/io/export/build-app-properties-xml.ts +1 -1
  59. package/src/io/export/serialize-comments.ts +1 -1
  60. package/src/io/export/serialize-headers-footers.ts +6 -1
  61. package/src/io/export/serialize-main-document.ts +45 -0
  62. package/src/io/export/serialize-run-formatting.ts +17 -2
  63. package/src/io/export/twip.ts +1 -1
  64. package/src/io/normalize/normalize-text.ts +27 -20
  65. package/src/io/ooxml/chart/parse-series.ts +1 -1
  66. package/src/io/ooxml/chart/resolve-color.ts +2 -2
  67. package/src/io/ooxml/chart/types.ts +1 -1
  68. package/src/io/ooxml/classify-embedding.ts +83 -33
  69. package/src/io/ooxml/parse-fill.ts +1 -1
  70. package/src/io/ooxml/parse-main-document.ts +71 -1
  71. package/src/io/ooxml/parse-object.ts +14 -10
  72. package/src/io/ooxml/parse-run-formatting.ts +47 -1
  73. package/src/io/ooxml/property-grab-bag.ts +2 -2
  74. package/src/io/ooxml/units.ts +11 -0
  75. package/src/io/ooxml/workflow-payload.ts +282 -7
  76. package/src/model/anchor.ts +85 -0
  77. package/src/model/canonical-document.ts +351 -15
  78. package/src/model/chart-types.ts +1 -1
  79. package/src/model/layout/index.ts +83 -0
  80. package/src/model/layout/page-graph-types.ts +181 -0
  81. package/src/model/layout/page-layout-snapshot.ts +105 -0
  82. package/src/model/layout/resolved-layout-types.ts +47 -0
  83. package/src/model/layout/runtime-page-graph-types.ts +102 -0
  84. package/src/model/paragraph-scope-ids.ts +72 -0
  85. package/src/model/review/comment-types.ts +112 -0
  86. package/src/model/review/index.ts +2 -0
  87. package/src/model/review/revision-types.ts +215 -0
  88. package/src/model/snapshot.ts +32 -0
  89. package/src/review/store/comment-store.ts +21 -47
  90. package/src/review/store/revision-types.ts +40 -198
  91. package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
  92. package/src/runtime/collab/runtime-collab-sync.ts +13 -3
  93. package/src/runtime/collab-session.ts +1 -1
  94. package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
  95. package/src/runtime/debug/event-ring-buffer.ts +64 -0
  96. package/src/runtime/debug/probability-sampler.ts +18 -0
  97. package/src/runtime/debug/runtime-debug-facet.ts +67 -0
  98. package/src/runtime/debug/stage-tokens.ts +31 -0
  99. package/src/runtime/debug/telemetry-bus.ts +271 -0
  100. package/src/runtime/debug/types.ts +275 -0
  101. package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
  102. package/src/runtime/document-layout.ts +8 -6
  103. package/src/runtime/document-runtime.ts +843 -1141
  104. package/src/runtime/document-search.ts +1 -1
  105. package/src/runtime/edit-ops/index.ts +1 -1
  106. package/src/runtime/external-send-runtime.ts +1 -1
  107. package/src/runtime/formatting/document-lookup.ts +235 -0
  108. package/src/runtime/formatting/field/registry.ts +41 -0
  109. package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
  110. package/src/runtime/formatting/font-resolution.ts +83 -0
  111. package/src/runtime/formatting/formatting-context.ts +903 -0
  112. package/src/runtime/formatting/formatting-types.ts +157 -0
  113. package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
  114. package/src/runtime/formatting/index.ts +125 -0
  115. package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
  116. package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
  117. package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
  118. package/src/runtime/formatting/projector.ts +75 -0
  119. package/src/runtime/formatting/resolve-effective.ts +407 -0
  120. package/src/runtime/formatting/revision-display.ts +105 -0
  121. package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
  122. package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
  123. package/src/runtime/formatting/telemetry-bridge.ts +106 -0
  124. package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
  125. package/src/runtime/geometry/caret-geometry.ts +164 -0
  126. package/src/runtime/geometry/geometry-facet.ts +364 -0
  127. package/src/runtime/geometry/geometry-types.ts +256 -0
  128. package/src/runtime/geometry/hit-test.ts +125 -0
  129. package/src/runtime/geometry/index.ts +71 -0
  130. package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
  131. package/src/runtime/geometry/invalidation.ts +35 -0
  132. package/src/runtime/geometry/object-handles.ts +77 -0
  133. package/src/runtime/geometry/overlay-rects.ts +85 -0
  134. package/src/runtime/geometry/project-anchors.ts +100 -0
  135. package/src/runtime/geometry/project-fragments.ts +216 -0
  136. package/src/runtime/geometry/projector.ts +129 -0
  137. package/src/runtime/geometry/replacement-envelope.ts +130 -0
  138. package/src/runtime/geometry/viewport.ts +218 -0
  139. package/src/runtime/layout/compat-input-ledger.ts +211 -0
  140. package/src/runtime/layout/index.ts +6 -1
  141. package/src/runtime/layout/inert-layout-facet.ts +12 -7
  142. package/src/runtime/layout/layout-engine-instance.ts +189 -11
  143. package/src/runtime/layout/layout-engine-version.ts +450 -1
  144. package/src/runtime/layout/layout-facet-types.ts +60 -0
  145. package/src/runtime/layout/layout-measurement-provider.ts +13 -0
  146. package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
  147. package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
  148. package/src/runtime/layout/page-graph.ts +62 -209
  149. package/src/runtime/layout/page-story-resolver.ts +7 -12
  150. package/src/runtime/layout/paginated-layout-engine.ts +186 -11
  151. package/src/runtime/layout/project-block-fragments.ts +11 -0
  152. package/src/runtime/layout/projector.ts +90 -0
  153. package/src/runtime/layout/public-facet.ts +187 -442
  154. package/src/runtime/layout/resolved-formatting-state.ts +158 -26
  155. package/src/runtime/layout/table-render-plan.ts +1 -1
  156. package/src/runtime/prerender/cache-envelope.ts +6 -1
  157. package/src/runtime/prerender/prerender-document.ts +18 -23
  158. package/src/runtime/render/decoration-resolver.ts +1 -1
  159. package/src/runtime/render/render-frame-types.ts +20 -0
  160. package/src/runtime/render/render-kernel.ts +94 -25
  161. package/src/runtime/scopes/_formatting-seam.ts +262 -0
  162. package/src/runtime/scopes/_scope-dependencies.ts +49 -0
  163. package/src/runtime/scopes/action-validation.ts +356 -0
  164. package/src/runtime/scopes/attach-explanation.ts +102 -0
  165. package/src/runtime/scopes/audit-bundle.ts +71 -0
  166. package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
  167. package/src/runtime/scopes/compile-scope.ts +262 -0
  168. package/src/runtime/scopes/compiler-service.ts +431 -0
  169. package/src/runtime/scopes/create-issue.ts +107 -0
  170. package/src/runtime/scopes/enumerate-scopes.ts +543 -0
  171. package/src/runtime/scopes/evidence.ts +233 -0
  172. package/src/runtime/scopes/index.ts +150 -0
  173. package/src/runtime/scopes/position-map.ts +214 -0
  174. package/src/runtime/scopes/preservation-boundary.ts +91 -0
  175. package/src/runtime/scopes/projector.ts +49 -0
  176. package/src/runtime/scopes/replaceability.ts +87 -0
  177. package/src/runtime/scopes/replacement/apply.ts +228 -0
  178. package/src/runtime/scopes/replacement/compile.ts +59 -0
  179. package/src/runtime/scopes/replacement/propose.ts +42 -0
  180. package/src/runtime/scopes/resolve-reference.ts +347 -0
  181. package/src/runtime/scopes/review-bundle.ts +141 -0
  182. package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
  183. package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
  184. package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
  185. package/src/runtime/scopes/scope-kinds/field.ts +65 -0
  186. package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
  187. package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
  188. package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
  189. package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
  190. package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
  191. package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
  192. package/src/runtime/scopes/scope-kinds/table.ts +55 -0
  193. package/src/runtime/scopes/scope-range.ts +208 -0
  194. package/src/runtime/scopes/semantic-scope-types.ts +454 -0
  195. package/src/runtime/scopes/workflow-overlap.ts +92 -0
  196. package/src/runtime/selection/index.ts +1 -1
  197. package/src/runtime/structure-ops/fragment-insert.ts +1 -1
  198. package/src/runtime/structure-ops/index.ts +1 -1
  199. package/src/runtime/surface-projection.ts +232 -262
  200. package/src/runtime/units.ts +4 -2
  201. package/src/runtime/workflow/coordinator.ts +1348 -0
  202. package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
  203. package/src/runtime/workflow/index.ts +25 -0
  204. package/src/runtime/workflow/markup-mode-policy.ts +98 -0
  205. package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
  206. package/src/runtime/workflow/metadata-persistence.ts +306 -0
  207. package/src/runtime/workflow/metadata-writer.ts +123 -0
  208. package/src/runtime/workflow/overlay-store.ts +690 -0
  209. package/src/runtime/workflow/projector.ts +127 -0
  210. package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
  211. package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
  212. package/src/runtime/workflow/rail/types.ts +198 -0
  213. package/src/runtime/workflow/scope-rail-composer.ts +39 -0
  214. package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
  215. package/src/runtime/workflow/scope-writer.ts +188 -0
  216. package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
  217. package/src/runtime/workflow/visibility-policy.ts +129 -0
  218. package/src/session/_sync-legacy.ts +66 -0
  219. package/src/session/export/embedded-reconstitute.ts +104 -0
  220. package/src/session/export/export-diagnostics.ts +85 -0
  221. package/src/session/export/export-validation.ts +110 -0
  222. package/src/session/export/index.ts +34 -0
  223. package/src/session/export/preservation-reattach.ts +30 -0
  224. package/src/session/export/serialize-dispatch.ts +165 -0
  225. package/src/session/export/stateful-export-pipeline.ts +432 -0
  226. package/src/session/export/stateful-export.ts +684 -0
  227. package/src/session/import/canonical-assembly.ts +227 -0
  228. package/src/session/import/diagnostics-session.ts +54 -0
  229. package/src/session/import/embedded-discovery.ts +225 -0
  230. package/src/session/import/embedded-offload.ts +337 -0
  231. package/src/session/import/import-diagnostics.ts +69 -0
  232. package/src/session/import/loader-types.ts +313 -0
  233. package/src/session/import/loader.ts +1834 -0
  234. package/src/session/import/normalize.ts +195 -0
  235. package/src/session/import/package-parts.ts +217 -0
  236. package/src/session/import/package-read.ts +195 -0
  237. package/src/session/import/parse-orchestration.ts +105 -0
  238. package/src/session/import/part-constants.ts +70 -0
  239. package/src/session/import/part-discovery.ts +94 -0
  240. package/src/session/import/preservation-index.ts +46 -0
  241. package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
  242. package/src/session/import/review-import.ts +508 -0
  243. package/src/session/import/styles-consolidation.ts +281 -0
  244. package/src/session/import/workflow-scope-import.ts +256 -0
  245. package/src/session/index.ts +37 -0
  246. package/src/session/session-state.ts +69 -0
  247. package/src/session/session.ts +532 -0
  248. package/src/session/shared/protection.ts +228 -0
  249. package/src/session/shared/session-utils.ts +82 -0
  250. package/src/session/types.ts +499 -0
  251. package/src/shell/chart-snapshots.ts +96 -0
  252. package/src/shell/media-previews.ts +85 -0
  253. package/src/shell/overlay-anchor-bridge.ts +53 -0
  254. package/src/shell/paste-adapter.ts +23 -0
  255. package/src/shell/ref-commands.ts +1697 -0
  256. package/src/shell/ref-utilities.ts +48 -0
  257. package/src/shell/search.ts +51 -0
  258. package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
  259. package/src/shell/ui-subscriber-channels.ts +81 -0
  260. package/src/shell/use-collab-sync.ts +116 -0
  261. package/src/ui/WordReviewEditor.tsx +496 -2051
  262. package/src/ui/editor-shell-view.tsx +30 -1
  263. package/src/ui/editor-surface-controller.tsx +49 -1
  264. package/src/ui/headless/revision-decoration-model.ts +83 -0
  265. package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
  266. package/src/ui/headless/scoped-chrome-policy.ts +2 -2
  267. package/src/ui/headless/selection-tool-context.ts +1 -1
  268. package/src/ui/headless/selection-tool-resolver.ts +1 -1
  269. package/src/ui/runtime-shortcut-dispatch.ts +46 -1
  270. package/src/ui/ui-controller-factory.ts +221 -0
  271. package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
  272. package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
  273. package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
  274. package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
  275. package/src/ui-tailwind/chart/render/area.tsx +3 -3
  276. package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
  277. package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
  278. package/src/ui-tailwind/chart/render/combo.tsx +2 -2
  279. package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
  280. package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
  281. package/src/ui-tailwind/chart/render/line.tsx +3 -3
  282. package/src/ui-tailwind/chart/render/pie.tsx +6 -6
  283. package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
  284. package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
  285. package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
  286. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
  287. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
  288. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
  289. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
  290. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
  291. package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
  292. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
  293. package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
  294. package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
  295. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
  296. package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
  297. package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
  298. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
  299. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
  300. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
  301. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
  302. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
  303. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
  304. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
  305. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
  306. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
  307. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
  308. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
  309. package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
  310. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
  311. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
  312. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
  313. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
  314. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
  315. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
  316. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
  317. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
  318. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
  319. package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
  320. package/src/ui-tailwind/debug/README.md +57 -0
  321. package/src/ui-tailwind/debug/index.ts +3 -0
  322. package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
  323. package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
  324. package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
  325. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
  326. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
  327. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
  328. package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
  329. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
  330. package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
  331. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
  332. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
  333. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
  334. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
  335. package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
  336. package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
  337. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
  338. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
  339. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
  340. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
  341. package/src/ui-tailwind/index.ts +0 -5
  342. package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
  343. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
  344. package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
  345. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
  346. package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
  347. package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
  348. package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
  349. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
  350. package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
  351. package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
  352. package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
  353. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
  354. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
  355. package/src/ui-tailwind/review-workspace/types.ts +408 -0
  356. package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
  357. package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
  358. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
  359. package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
  360. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
  361. package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
  362. package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
  363. package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
  364. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
  365. package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
  366. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
  367. package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
  368. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
  369. package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
  370. package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
  371. package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
  372. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
  373. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  374. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
  375. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
  376. package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
  377. package/src/ui-tailwind/ui-api-context.tsx +43 -0
  378. package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
  379. package/src/validation/compatibility-engine.ts +6 -6
  380. package/src/runtime/styles-cascade.ts +0 -33
  381. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
  382. /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
  383. /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
  384. /package/src/runtime/{scope-tag-registry.ts → workflow/scope-tag-registry.ts} +0 -0
@@ -0,0 +1,101 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ import type { RuntimeRenderSnapshot } from "../../api/public-types";
4
+
5
+ // P2.a — real-dimension page frame. Page frame width/height are
6
+ // `pageLayout.pageWidth/pageHeight × FRAME_PX_PER_TWIP_AT_96DPI` so
7
+ // every paper size renders at its Word-matching CSS px (Letter
8
+ // 816×1056, A4 794×1123, Legal 816×1344, …). Constants are exported
9
+ // so tests + harness panels can derive the same values.
10
+ export const FRAME_PX_PER_TWIP_AT_96DPI = 96 / 1440;
11
+
12
+ /** Floor on header/footer band heights so empty bands stay clickable. */
13
+ export const MIN_BAND_HEIGHT_PX = 20;
14
+
15
+ const FIT_WIDTH_CHROME_RESERVATION_PX = 96;
16
+ const FIT_HEIGHT_CHROME_RESERVATION_PX = 180;
17
+ const MIN_FIT_ZOOM = 0.5;
18
+ const MAX_FIT_ZOOM = 2.0;
19
+
20
+ export interface PageShellMetrics {
21
+ /** P2.a — page frame CSS px width = `pageWidth × FRAME_PX_PER_TWIP_AT_96DPI`. */
22
+ frameWidthPx?: number;
23
+ /** P2.a — page frame CSS px height = `pageHeight × FRAME_PX_PER_TWIP_AT_96DPI`. */
24
+ frameHeightPx?: number;
25
+ contentInsetStyle: CSSProperties;
26
+ pageFrameStyle: CSSProperties;
27
+ }
28
+
29
+ export function buildPageShellMetrics(
30
+ pageLayout: RuntimeRenderSnapshot["pageLayout"] | undefined,
31
+ ): PageShellMetrics {
32
+ if (!pageLayout) {
33
+ return {
34
+ contentInsetStyle: {},
35
+ pageFrameStyle: {},
36
+ frameWidthPx: 0,
37
+ frameHeightPx: 0,
38
+ };
39
+ }
40
+
41
+ const pxPerTwip = FRAME_PX_PER_TWIP_AT_96DPI;
42
+ const frameWidthPx = Math.round(pageLayout.pageWidth * pxPerTwip);
43
+ const frameHeightPx = Math.round(pageLayout.pageHeight * pxPerTwip);
44
+ const horizontalInsetPx = Math.round(pageLayout.marginLeft * pxPerTwip);
45
+ const horizontalInsetRightPx = Math.round(pageLayout.marginRight * pxPerTwip);
46
+
47
+ return {
48
+ contentInsetStyle: {
49
+ paddingLeft: `${horizontalInsetPx}px`,
50
+ paddingRight: `${horizontalInsetRightPx}px`,
51
+ },
52
+ pageFrameStyle: {
53
+ backgroundColor: "var(--color-page-bg)",
54
+ borderRadius: "8px",
55
+ boxShadow:
56
+ "0 24px 48px -32px rgba(15, 23, 42, 0.38), 0 8px 20px -18px rgba(15, 23, 42, 0.22)",
57
+ border: "1px solid rgba(148, 163, 184, 0.2)",
58
+ },
59
+ frameWidthPx,
60
+ frameHeightPx,
61
+ };
62
+ }
63
+
64
+ // P2.c — fit-to-width / fit-to-page resolves against the active section's
65
+ // real paper size (not a global constant), so Letter and A4 produce the
66
+ // expected 1.029:1 fit-width ratio at the same viewport. Clamped so
67
+ // extreme viewports don't pin the editor at unreadable zooms.
68
+ export function resolveZoomMultiplier(
69
+ zoomLevel: number | "pageWidth" | "onePage",
70
+ frameWidthPx: number,
71
+ frameHeightPx: number,
72
+ viewportWidth: number | undefined,
73
+ viewportHeight: number | undefined,
74
+ ): number {
75
+ if (typeof zoomLevel === "number") {
76
+ return zoomLevel / 100;
77
+ }
78
+ if (!viewportWidth || frameWidthPx <= 0) return 1;
79
+ const widthFit =
80
+ (viewportWidth - FIT_WIDTH_CHROME_RESERVATION_PX) / frameWidthPx;
81
+ if (zoomLevel === "pageWidth") {
82
+ return Math.max(MIN_FIT_ZOOM, Math.min(MAX_FIT_ZOOM, widthFit));
83
+ }
84
+ if (!viewportHeight || frameHeightPx <= 0) {
85
+ return Math.max(MIN_FIT_ZOOM, Math.min(MAX_FIT_ZOOM, widthFit));
86
+ }
87
+ const heightFit =
88
+ (viewportHeight - FIT_HEIGHT_CHROME_RESERVATION_PX) / frameHeightPx;
89
+ return Math.max(
90
+ MIN_FIT_ZOOM,
91
+ Math.min(MAX_FIT_ZOOM, Math.min(widthFit, heightFit)),
92
+ );
93
+ }
94
+
95
+ export function readViewportWidth(): number | undefined {
96
+ return typeof window === "undefined" ? undefined : window.innerWidth;
97
+ }
98
+
99
+ export function readViewportHeight(): number | undefined {
100
+ return typeof window === "undefined" ? undefined : window.innerHeight;
101
+ }
@@ -0,0 +1,115 @@
1
+ import type {
2
+ EditorViewStateSnapshot,
3
+ RuntimeRenderSnapshot,
4
+ SurfaceBlockSnapshot,
5
+ } from "../../api/public-types";
6
+
7
+ export interface ActiveParagraphLayout {
8
+ leftIndent: number;
9
+ rightIndent: number;
10
+ firstLineOffset: number;
11
+ tabStops: Array<{ pos: number; val?: string; leader?: string }>;
12
+ indentationReadOnly?: boolean;
13
+ tabStopsReadOnly?: boolean;
14
+ }
15
+
16
+ export function shouldHidePageBorderForSelection(
17
+ selection: EditorViewStateSnapshot["selection"],
18
+ ): boolean {
19
+ if (selection.isCollapsed) {
20
+ return false;
21
+ }
22
+
23
+ return selection.activeRange.kind === "range";
24
+ }
25
+
26
+ export function resolveActiveParagraphLayout(
27
+ surface: RuntimeRenderSnapshot["surface"],
28
+ position: number,
29
+ ): ActiveParagraphLayout | null {
30
+ const paragraph = surface ? findActiveParagraph(surface.blocks, position) : null;
31
+ if (!paragraph) {
32
+ return null;
33
+ }
34
+ const resolvedIndentation = paragraph.resolvedNumbering?.geometry.indentation;
35
+ const resolvedTabStops = paragraph.resolvedNumbering?.geometry.tabStops;
36
+ const indentation = resolvedIndentation ?? paragraph.indentation;
37
+ const tabStops = resolvedTabStops ?? paragraph.tabStops;
38
+
39
+ return {
40
+ leftIndent: indentation?.left ?? 0,
41
+ rightIndent: indentation?.right ?? 0,
42
+ firstLineOffset:
43
+ indentation?.firstLine ??
44
+ (indentation?.hanging ? -indentation.hanging : 0),
45
+ tabStops: tabStops ? [...tabStops] : [],
46
+ indentationReadOnly:
47
+ Boolean(resolvedIndentation) &&
48
+ !areIndentationsEqual(resolvedIndentation, paragraph.indentation),
49
+ tabStopsReadOnly:
50
+ Boolean(resolvedTabStops) &&
51
+ !areTabStopsEqual(resolvedTabStops, paragraph.tabStops),
52
+ };
53
+ }
54
+
55
+ function areIndentationsEqual(
56
+ left:
57
+ | Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["indentation"]
58
+ | undefined,
59
+ right:
60
+ | Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["indentation"]
61
+ | undefined,
62
+ ): boolean {
63
+ return (
64
+ left?.left === right?.left &&
65
+ left?.right === right?.right &&
66
+ left?.firstLine === right?.firstLine &&
67
+ left?.hanging === right?.hanging
68
+ );
69
+ }
70
+
71
+ function areTabStopsEqual(
72
+ left: ReadonlyArray<{ pos: number; val?: string; leader?: string }> | undefined,
73
+ right: ReadonlyArray<{ pos: number; val?: string; leader?: string }> | undefined,
74
+ ): boolean {
75
+ if (!left?.length && !right?.length) {
76
+ return true;
77
+ }
78
+ if (!left || !right || left.length !== right.length) {
79
+ return false;
80
+ }
81
+ return left.every(
82
+ (tabStop, index) =>
83
+ tabStop.pos === right[index]?.pos &&
84
+ tabStop.val === right[index]?.val &&
85
+ tabStop.leader === right[index]?.leader,
86
+ );
87
+ }
88
+
89
+ function findActiveParagraph(
90
+ blocks: readonly SurfaceBlockSnapshot[],
91
+ position: number,
92
+ ): Extract<SurfaceBlockSnapshot, { kind: "paragraph" }> | null {
93
+ for (const block of blocks) {
94
+ if (block.kind === "paragraph" && position >= block.from && position <= block.to) {
95
+ return block;
96
+ }
97
+ if (block.kind === "table") {
98
+ for (const row of block.rows) {
99
+ for (const cell of row.cells) {
100
+ const paragraph = findActiveParagraph(cell.content, position);
101
+ if (paragraph) {
102
+ return paragraph;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (block.kind === "sdt_block") {
108
+ const paragraph = findActiveParagraph(block.children, position);
109
+ if (paragraph) {
110
+ return paragraph;
111
+ }
112
+ }
113
+ }
114
+ return null;
115
+ }
@@ -0,0 +1,97 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ import type { SelectionToolAnchor } from "../../ui/headless/selection-tool-types";
4
+
5
+ export interface SelectionToolbarPlacement {
6
+ placement: "right" | "left" | "above" | "below";
7
+ style: CSSProperties;
8
+ }
9
+
10
+ export function resolveSelectionToolbarPlacement(
11
+ anchor: SelectionToolAnchor | null | undefined,
12
+ root: HTMLDivElement | null,
13
+ zoomScale: number,
14
+ ): SelectionToolbarPlacement | null {
15
+ if (!anchor || !root) {
16
+ return null;
17
+ }
18
+
19
+ const rootRect = root.getBoundingClientRect();
20
+ if (rootRect.width <= 0 || rootRect.height <= 0 || zoomScale <= 0) {
21
+ return null;
22
+ }
23
+
24
+ const centerX = (anchor.left + anchor.right) / 2;
25
+ const centerY = (anchor.top + anchor.bottom) / 2;
26
+ const localLeftEdge = (anchor.left - rootRect.left) / zoomScale;
27
+ const localRightEdge = (anchor.right - rootRect.left) / zoomScale;
28
+ const localLeft = (centerX - rootRect.left) / zoomScale;
29
+ const localCenterY = (centerY - rootRect.top) / zoomScale;
30
+ const localTop = (anchor.top - rootRect.top) / zoomScale;
31
+ const localBottom = (anchor.bottom - rootRect.top) / zoomScale;
32
+ const edgePadding = 16 / zoomScale;
33
+ const containerWidth = rootRect.width / zoomScale;
34
+ const containerHeight = rootRect.height / zoomScale;
35
+ const gapPx = 12 / zoomScale;
36
+ const estimatedToolbarWidth = Math.min(
37
+ 260 / zoomScale,
38
+ Math.max(168 / zoomScale, containerWidth * 0.32),
39
+ );
40
+ const estimatedToolbarHeight = 44 / zoomScale;
41
+ const clampedCenterLeft = Math.max(
42
+ edgePadding,
43
+ Math.min(localLeft, Math.max(edgePadding, containerWidth - edgePadding)),
44
+ );
45
+ const clampedCenterY = Math.max(
46
+ edgePadding + estimatedToolbarHeight / 2,
47
+ Math.min(
48
+ localCenterY,
49
+ Math.max(
50
+ edgePadding + estimatedToolbarHeight / 2,
51
+ containerHeight - edgePadding - estimatedToolbarHeight / 2,
52
+ ),
53
+ ),
54
+ );
55
+ const rightClearance = containerWidth - localRightEdge - gapPx - edgePadding;
56
+ const leftClearance = localLeftEdge - gapPx - edgePadding;
57
+
58
+ if (rightClearance >= estimatedToolbarWidth) {
59
+ return {
60
+ placement: "right",
61
+ style: {
62
+ left: `${localRightEdge}px`,
63
+ top: `${clampedCenterY}px`,
64
+ maxWidth: `${Math.max(220, containerWidth - edgePadding * 2)}px`,
65
+ transform: `translate(${gapPx}px, -50%)`,
66
+ },
67
+ };
68
+ }
69
+
70
+ if (leftClearance >= estimatedToolbarWidth) {
71
+ return {
72
+ placement: "left",
73
+ style: {
74
+ left: `${localLeftEdge}px`,
75
+ top: `${clampedCenterY}px`,
76
+ maxWidth: `${Math.max(220, containerWidth - edgePadding * 2)}px`,
77
+ transform: `translate(calc(-100% - ${gapPx}px), -50%)`,
78
+ },
79
+ };
80
+ }
81
+
82
+ const placement =
83
+ localTop < estimatedToolbarHeight + gapPx + edgePadding ? "below" : "above";
84
+
85
+ return {
86
+ placement,
87
+ style: {
88
+ left: `${clampedCenterLeft}px`,
89
+ top: `${placement === "above" ? localTop : localBottom}px`,
90
+ maxWidth: `${Math.max(220, containerWidth - edgePadding * 2)}px`,
91
+ transform:
92
+ placement === "above"
93
+ ? `translate(-50%, calc(-100% - ${gapPx}px))`
94
+ : `translate(-50%, ${gapPx}px)`,
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,130 @@
1
+ import React from "react";
2
+
3
+ import * as Tooltip from "@radix-ui/react-tooltip";
4
+ import { ChevronLeft, List } from "lucide-react";
5
+
6
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
7
+ import type { DocumentNavigationSnapshot } from "../../api/public-types";
8
+
9
+ export interface TwReviewWorkspaceNavigatorProps {
10
+ /** When false, the aside and expand toggle both stay unmounted. */
11
+ enabled: boolean;
12
+ navOpen: boolean;
13
+ setNavOpen: (open: boolean) => void;
14
+ headings: DocumentNavigationSnapshot["headings"] | readonly [];
15
+ onNavigateHeading?: (headingId: string) => void;
16
+ /**
17
+ * Dismiss the selection toolbar before any navigator action. Matches
18
+ * the shipped workspace behavior: nav interactions count as a
19
+ * "commit" that should clear the ephemeral selection toolbar.
20
+ */
21
+ dismissSelectionToolbar: () => void;
22
+ }
23
+
24
+ /**
25
+ * Page-mode document navigator. Collapsed to a 0-width aside + a small
26
+ * expand toggle; expanded to a 192-px sidebar with a heading list.
27
+ *
28
+ * Pure JSX extract from `tw-review-workspace.tsx` — no behavior changes.
29
+ */
30
+ export function TwReviewWorkspaceNavigator({
31
+ enabled,
32
+ navOpen,
33
+ setNavOpen,
34
+ headings,
35
+ onNavigateHeading,
36
+ dismissSelectionToolbar,
37
+ }: TwReviewWorkspaceNavigatorProps): React.ReactElement | null {
38
+ if (!enabled) return null;
39
+
40
+ return (
41
+ <>
42
+ <aside
43
+ aria-label="Document navigator"
44
+ className={`shrink-0 border-r border-border bg-surface transition-[width] duration-200 ${
45
+ navOpen ? "w-48" : "w-0"
46
+ } overflow-hidden`}
47
+ >
48
+ {navOpen ? (
49
+ <div className="flex h-full flex-col">
50
+ <div className="flex items-center justify-between px-3 py-2 border-b border-border">
51
+ <span className="text-xs font-medium text-secondary uppercase tracking-wider">Navigator</span>
52
+ <Tooltip.Root>
53
+ <Tooltip.Trigger asChild>
54
+ <button
55
+ type="button"
56
+ aria-label="Collapse navigator"
57
+ onMouseDown={preserveEditorSelectionMouseDown}
58
+ onClick={() => {
59
+ dismissSelectionToolbar();
60
+ setNavOpen(false);
61
+ }}
62
+ className="inline-flex h-6 w-6 items-center justify-center rounded-md text-secondary hover:bg-surface-hover transition-colors"
63
+ >
64
+ <ChevronLeft className="h-3.5 w-3.5" />
65
+ </button>
66
+ </Tooltip.Trigger>
67
+ <Tooltip.Portal>
68
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
69
+ Collapse navigator
70
+ </Tooltip.Content>
71
+ </Tooltip.Portal>
72
+ </Tooltip.Root>
73
+ </div>
74
+ <nav className="flex-1 overflow-y-auto px-2 py-2" aria-label="Document headings">
75
+ {headings.length > 0 ? (
76
+ <ul className="space-y-0.5">
77
+ {headings.map((entry) => (
78
+ <li key={entry.headingId}>
79
+ <button
80
+ type="button"
81
+ className="block w-full truncate rounded-md px-2 py-1 text-left text-xs text-primary hover:bg-surface-hover"
82
+ style={{ paddingLeft: `${8 + (entry.level - 1) * 12}px` }}
83
+ onMouseDown={preserveEditorSelectionMouseDown}
84
+ onClick={() => {
85
+ dismissSelectionToolbar();
86
+ onNavigateHeading?.(entry.headingId);
87
+ setNavOpen(false);
88
+ }}
89
+ >
90
+ {entry.text}
91
+ </button>
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ ) : (
96
+ <p className="px-2 py-4 text-xs text-tertiary">No headings found.</p>
97
+ )}
98
+ </nav>
99
+ </div>
100
+ ) : null}
101
+ </aside>
102
+
103
+ {!navOpen ? (
104
+ <div className="shrink-0 flex items-start pt-2 pl-1">
105
+ <Tooltip.Root>
106
+ <Tooltip.Trigger asChild>
107
+ <button
108
+ type="button"
109
+ aria-label="Open document navigator"
110
+ onMouseDown={preserveEditorSelectionMouseDown}
111
+ onClick={() => {
112
+ dismissSelectionToolbar();
113
+ setNavOpen(true);
114
+ }}
115
+ className="inline-flex h-7 w-7 items-center justify-center rounded-md text-secondary hover:bg-surface-hover transition-colors"
116
+ >
117
+ <List className="h-3.5 w-3.5" />
118
+ </button>
119
+ </Tooltip.Trigger>
120
+ <Tooltip.Portal>
121
+ <Tooltip.Content className="rounded-md bg-primary px-2 py-1 text-xs text-white shadow-md z-50" sideOffset={6}>
122
+ Open document navigator
123
+ </Tooltip.Content>
124
+ </Tooltip.Portal>
125
+ </Tooltip.Root>
126
+ </div>
127
+ ) : null}
128
+ </>
129
+ );
130
+ }
@@ -0,0 +1,240 @@
1
+ import React from "react";
2
+
3
+ import { ChevronRight } from "lucide-react";
4
+
5
+ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
6
+ import { TwPageRuler } from "../chrome/tw-page-ruler";
7
+ import { TwLayoutPanel } from "../chrome/tw-layout-panel";
8
+ import type {
9
+ DocumentNavigationSnapshot,
10
+ EditorViewStateSnapshot,
11
+ HeaderFooterLinkPatch,
12
+ RuntimeRenderSnapshot,
13
+ SectionBreakType,
14
+ SectionLayoutPatch,
15
+ SectionPageNumberingPatch,
16
+ } from "../../api/public-types";
17
+ import type { ActiveParagraphLayout } from "./paragraph-layout";
18
+
19
+ /**
20
+ * Page-mode inline toolbar: the "Page N of M · Section N · orientation"
21
+ * summary strip plus the expandable `TwPageRuler` + `TwLayoutPanel`
22
+ * cluster. Lifted verbatim from `tw-review-workspace.tsx` — no behavior
23
+ * changes.
24
+ */
25
+ export interface TwReviewWorkspacePageToolbarProps {
26
+ /** `isPageWorkspace && chromeVisibility.pageChrome && snapshot.pageLayout` */
27
+ enabled: boolean;
28
+ pageLayout: RuntimeRenderSnapshot["pageLayout"];
29
+ activeStory: RuntimeRenderSnapshot["activeStory"];
30
+ activePage: DocumentNavigationSnapshot["pages"][number] | null;
31
+ pageCount: number;
32
+ headerVariant: HeaderFooterLinkPatch["variant"];
33
+ footerVariant: HeaderFooterLinkPatch["variant"];
34
+ allowLocalChromeMutations: boolean;
35
+ pageChromeReadOnly: boolean;
36
+ layoutToolsOpen: boolean;
37
+ setLayoutToolsOpen: (fn: (open: boolean) => boolean) => void;
38
+ viewState: EditorViewStateSnapshot;
39
+ activeParagraphLayout: ActiveParagraphLayout | null;
40
+
41
+ /** Dismiss the floating selection toolbar before committing any action. */
42
+ dismissSelectionToolbar: () => void;
43
+ /** Wrap an optional nullary callback so it dismisses the toolbar first. */
44
+ runWithSelectionToolbarDismiss: (action?: () => void) => () => void;
45
+
46
+ onCloseStory?: () => void;
47
+ onOpenHeaderStory?: () => void;
48
+ onOpenFooterStory?: () => void;
49
+ onSetHeaderFooterLink?: (sectionIndex: number, patch: HeaderFooterLinkPatch) => void;
50
+ onSetParagraphIndentation?: (indentation: Parameters<NonNullable<React.ComponentProps<typeof TwPageRuler>["onSetIndentation"]>>[0]) => void;
51
+ onSetParagraphTabStops?: (tabStops: Parameters<NonNullable<React.ComponentProps<typeof TwPageRuler>["onSetTabStops"]>>[0]) => void;
52
+ onRestartNumbering?: () => void;
53
+ onContinueNumbering?: () => void;
54
+ onInsertSectionBreak?: (type: SectionBreakType) => void;
55
+ onDeleteSectionBreak?: (sectionIndex: number) => void;
56
+ onUpdateSectionLayout?: (sectionIndex: number, patch: SectionLayoutPatch) => void;
57
+ onSetSectionPageNumbering?: (sectionIndex: number, patch: SectionPageNumberingPatch | null) => void;
58
+ }
59
+
60
+ export function TwReviewWorkspacePageToolbar({
61
+ enabled,
62
+ pageLayout,
63
+ activeStory,
64
+ activePage,
65
+ pageCount,
66
+ headerVariant,
67
+ footerVariant,
68
+ allowLocalChromeMutations,
69
+ pageChromeReadOnly,
70
+ layoutToolsOpen,
71
+ setLayoutToolsOpen,
72
+ viewState,
73
+ activeParagraphLayout,
74
+ dismissSelectionToolbar,
75
+ runWithSelectionToolbarDismiss,
76
+ onCloseStory,
77
+ onOpenHeaderStory,
78
+ onOpenFooterStory,
79
+ onSetHeaderFooterLink,
80
+ onSetParagraphIndentation,
81
+ onSetParagraphTabStops,
82
+ onRestartNumbering,
83
+ onContinueNumbering,
84
+ onInsertSectionBreak,
85
+ onDeleteSectionBreak,
86
+ onUpdateSectionLayout,
87
+ onSetSectionPageNumbering,
88
+ }: TwReviewWorkspacePageToolbarProps): React.ReactElement | null {
89
+ if (!enabled || !pageLayout) return null;
90
+
91
+ return (
92
+ <>
93
+ <div className="border-b border-border/70 bg-surface/65 px-5 py-3" data-testid="page-context-summary">
94
+ <div className="flex flex-wrap items-center justify-between gap-2">
95
+ <div className="flex flex-wrap items-center gap-2 text-xs text-secondary">
96
+ <span className="rounded-full bg-canvas px-2 py-1 font-medium text-primary">
97
+ {activePage
98
+ ? `Page ${activePage.pageIndex + 1} of ${pageCount}`
99
+ : "Page workspace"}
100
+ </span>
101
+ <span>{`Section ${pageLayout.sectionIndex + 1}`}</span>
102
+ <span className="uppercase tracking-[0.12em] text-tertiary">
103
+ {pageLayout.orientation}
104
+ </span>
105
+ </div>
106
+ <div className="flex items-center gap-2">
107
+ {activeStory.kind !== "main" ? (
108
+ <button
109
+ type="button"
110
+ aria-label="Return to document body"
111
+ onMouseDown={preserveEditorSelectionMouseDown}
112
+ onClick={runWithSelectionToolbarDismiss(onCloseStory)}
113
+ className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface"
114
+ >
115
+ Body
116
+ </button>
117
+ ) : null}
118
+ {activeStory.kind === "main" && pageLayout.sectionIndex > 0 ? (
119
+ <>
120
+ <button
121
+ type="button"
122
+ aria-label="Link header to previous"
123
+ disabled={!onSetHeaderFooterLink || !allowLocalChromeMutations}
124
+ onMouseDown={preserveEditorSelectionMouseDown}
125
+ onClick={() => {
126
+ dismissSelectionToolbar();
127
+ onSetHeaderFooterLink?.(pageLayout.sectionIndex, {
128
+ kind: "header",
129
+ variant: headerVariant,
130
+ linkToPrevious: true,
131
+ });
132
+ }}
133
+ className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
134
+ >
135
+ Link header
136
+ </button>
137
+ <button
138
+ type="button"
139
+ aria-label="Link footer to previous"
140
+ disabled={!onSetHeaderFooterLink || !allowLocalChromeMutations}
141
+ onMouseDown={preserveEditorSelectionMouseDown}
142
+ onClick={() => {
143
+ dismissSelectionToolbar();
144
+ onSetHeaderFooterLink?.(pageLayout.sectionIndex, {
145
+ kind: "footer",
146
+ variant: footerVariant,
147
+ linkToPrevious: true,
148
+ });
149
+ }}
150
+ className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
151
+ >
152
+ Link footer
153
+ </button>
154
+ </>
155
+ ) : null}
156
+ <button
157
+ type="button"
158
+ aria-label="Toggle layout tools"
159
+ aria-expanded={layoutToolsOpen}
160
+ onMouseDown={preserveEditorSelectionMouseDown}
161
+ onClick={() => {
162
+ dismissSelectionToolbar();
163
+ setLayoutToolsOpen((open) => !open);
164
+ }}
165
+ className="inline-flex items-center gap-1 rounded-md border border-border bg-canvas px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-surface"
166
+ >
167
+ <ChevronRight className={`h-3.5 w-3.5 transition-transform ${layoutToolsOpen ? "rotate-90" : ""}`} />
168
+ Layout tools
169
+ </button>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ {layoutToolsOpen ? (
174
+ <div className="px-5 pt-3">
175
+ <TwPageRuler
176
+ pageLayout={pageLayout}
177
+ viewState={viewState}
178
+ paragraphLayout={activeParagraphLayout}
179
+ readOnly={pageChromeReadOnly}
180
+ onReturnToBody={onCloseStory
181
+ ? runWithSelectionToolbarDismiss(onCloseStory)
182
+ : () => undefined}
183
+ onOpenHeader={onOpenHeaderStory
184
+ ? runWithSelectionToolbarDismiss(onOpenHeaderStory)
185
+ : undefined}
186
+ onOpenFooter={onOpenFooterStory
187
+ ? runWithSelectionToolbarDismiss(onOpenFooterStory)
188
+ : undefined}
189
+ onSetIndentation={onSetParagraphIndentation
190
+ ? (indentation) => {
191
+ dismissSelectionToolbar();
192
+ onSetParagraphIndentation?.(indentation);
193
+ }
194
+ : undefined}
195
+ onSetTabStops={onSetParagraphTabStops
196
+ ? (tabStops) => {
197
+ dismissSelectionToolbar();
198
+ onSetParagraphTabStops?.(tabStops);
199
+ }
200
+ : undefined}
201
+ onRestartNumbering={onRestartNumbering
202
+ ? runWithSelectionToolbarDismiss(onRestartNumbering)
203
+ : undefined}
204
+ onContinueNumbering={onContinueNumbering
205
+ ? runWithSelectionToolbarDismiss(onContinueNumbering)
206
+ : undefined}
207
+ />
208
+ <TwLayoutPanel
209
+ pageLayout={pageLayout}
210
+ readOnly={pageChromeReadOnly}
211
+ onInsertSectionBreak={onInsertSectionBreak
212
+ ? (type) => {
213
+ dismissSelectionToolbar();
214
+ onInsertSectionBreak?.(type);
215
+ }
216
+ : undefined}
217
+ onDeleteSectionBreak={onDeleteSectionBreak
218
+ ? (sectionIndex) => {
219
+ dismissSelectionToolbar();
220
+ onDeleteSectionBreak?.(sectionIndex);
221
+ }
222
+ : undefined}
223
+ onUpdateSectionLayout={onUpdateSectionLayout
224
+ ? (sectionIndex, patch) => {
225
+ dismissSelectionToolbar();
226
+ onUpdateSectionLayout?.(sectionIndex, patch);
227
+ }
228
+ : undefined}
229
+ onSetSectionPageNumbering={onSetSectionPageNumbering
230
+ ? (sectionIndex, patch) => {
231
+ dismissSelectionToolbar();
232
+ onSetSectionPageNumbering?.(sectionIndex, patch);
233
+ }
234
+ : undefined}
235
+ />
236
+ </div>
237
+ ) : null}
238
+ </>
239
+ );
240
+ }