@beyondwork/docx-react-component 1.0.66 → 1.0.69
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.
- package/README.md +75 -931
- package/package.json +26 -27
- package/src/api/anchor-conversion.ts +43 -0
- package/src/api/editor-state-types.ts +2 -1
- package/src/api/public-types.ts +504 -101
- package/src/api/session-state.ts +4 -0
- package/src/api/v3/README.md +91 -0
- package/src/api/v3/_create.ts +146 -0
- package/src/api/v3/_layer-metadata.ts +362 -0
- package/src/api/v3/_mocks.ts +84 -0
- package/src/api/v3/_runtime-handle.ts +162 -0
- package/src/api/v3/_ux-response.ts +73 -0
- package/src/api/v3/ai/_metadata-audit.ts +225 -0
- package/src/api/v3/ai/attach.ts +235 -0
- package/src/api/v3/ai/bundle.ts +132 -0
- package/src/api/v3/ai/explain.ts +144 -0
- package/src/api/v3/ai/export.ts +54 -0
- package/src/api/v3/ai/inspect.ts +118 -0
- package/src/api/v3/ai/policy.ts +77 -0
- package/src/api/v3/ai/replacement.ts +341 -0
- package/src/api/v3/ai/resolve.ts +133 -0
- package/src/api/v3/index.ts +79 -0
- package/src/api/v3/runtime/chart.ts +310 -0
- package/src/api/v3/runtime/clipboard.ts +81 -0
- package/src/api/v3/runtime/collab.ts +331 -0
- package/src/api/v3/runtime/content.ts +236 -0
- package/src/api/v3/runtime/document.ts +282 -0
- package/src/api/v3/runtime/formatting.ts +186 -0
- package/src/api/v3/runtime/geometry.ts +349 -0
- package/src/api/v3/runtime/layout.ts +108 -0
- package/src/api/v3/runtime/review.ts +129 -0
- package/src/api/v3/runtime/search.ts +74 -0
- package/src/api/v3/runtime/table.ts +63 -0
- package/src/api/v3/runtime/workflow.ts +434 -0
- package/src/api/v3/ui/_context.ts +86 -0
- package/src/api/v3/ui/_create.ts +65 -0
- package/src/api/v3/ui/_types.ts +520 -0
- package/src/api/v3/ui/chrome-composition.ts +342 -0
- package/src/{ui-tailwind/chrome → api/v3/ui}/chrome-preset-model.ts +11 -1
- package/src/api/v3/ui/chrome.ts +476 -0
- package/src/api/v3/ui/debug.ts +124 -0
- package/src/api/v3/ui/index.ts +64 -0
- package/src/api/v3/ui/overlays-visibility.ts +170 -0
- package/src/api/v3/ui/overlays.ts +427 -0
- package/src/api/v3/ui/scope.ts +71 -0
- package/src/api/v3/ui/session.ts +100 -0
- package/src/api/v3/ui/surface.ts +170 -0
- package/src/api/v3/ui/viewport.ts +303 -0
- package/src/core/commands/index.ts +28 -6
- package/src/core/commands/list-commands.ts +3 -2
- package/src/core/commands/section-layout-commands.ts +9 -8
- package/src/core/schema/text-schema.ts +16 -0
- package/src/core/selection/mapping.ts +33 -72
- package/src/core/state/editor-state.ts +96 -189
- package/src/index.ts +23 -4
- package/src/io/chart-preview-resolver.ts +1 -1
- package/src/io/docx-session.ts +36 -4795
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +1 -1
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +45 -0
- package/src/io/export/serialize-run-formatting.ts +17 -2
- package/src/io/export/twip.ts +1 -1
- package/src/io/normalize/normalize-text.ts +27 -20
- package/src/io/ooxml/chart/parse-series.ts +1 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +1 -1
- package/src/io/ooxml/classify-embedding.ts +83 -33
- package/src/io/ooxml/parse-fill.ts +1 -1
- package/src/io/ooxml/parse-main-document.ts +71 -1
- package/src/io/ooxml/parse-object.ts +14 -10
- package/src/io/ooxml/parse-run-formatting.ts +47 -1
- package/src/io/ooxml/property-grab-bag.ts +2 -2
- package/src/io/ooxml/units.ts +11 -0
- package/src/io/ooxml/workflow-payload.ts +282 -7
- package/src/model/anchor.ts +85 -0
- package/src/model/canonical-document.ts +351 -15
- package/src/model/chart-types.ts +1 -1
- package/src/model/layout/index.ts +83 -0
- package/src/model/layout/page-graph-types.ts +181 -0
- package/src/model/layout/page-layout-snapshot.ts +105 -0
- package/src/model/layout/resolved-layout-types.ts +47 -0
- package/src/model/layout/runtime-page-graph-types.ts +102 -0
- package/src/model/paragraph-scope-ids.ts +72 -0
- package/src/model/review/comment-types.ts +112 -0
- package/src/model/review/index.ts +2 -0
- package/src/model/review/revision-types.ts +215 -0
- package/src/model/snapshot.ts +32 -0
- package/src/review/store/comment-store.ts +21 -47
- package/src/review/store/revision-types.ts +40 -198
- package/src/runtime/collab/base-doc-fingerprint.ts +6 -1
- package/src/runtime/collab/runtime-collab-sync.ts +13 -3
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +686 -0
- package/src/runtime/debug/event-ring-buffer.ts +64 -0
- package/src/runtime/debug/probability-sampler.ts +18 -0
- package/src/runtime/debug/runtime-debug-facet.ts +67 -0
- package/src/runtime/debug/stage-tokens.ts +31 -0
- package/src/runtime/debug/telemetry-bus.ts +271 -0
- package/src/runtime/debug/types.ts +275 -0
- package/src/runtime/debug/wrap-ref-for-telemetry.ts +118 -0
- package/src/runtime/document-layout.ts +8 -6
- package/src/runtime/document-runtime.ts +843 -1141
- package/src/runtime/document-search.ts +1 -1
- package/src/runtime/edit-ops/index.ts +1 -1
- package/src/runtime/external-send-runtime.ts +1 -1
- package/src/runtime/formatting/document-lookup.ts +235 -0
- package/src/runtime/formatting/field/registry.ts +41 -0
- package/src/runtime/{field-resolver.ts → formatting/field/resolver.ts} +27 -2
- package/src/runtime/formatting/font-resolution.ts +83 -0
- package/src/runtime/formatting/formatting-context.ts +903 -0
- package/src/runtime/formatting/formatting-types.ts +157 -0
- package/src/runtime/{hyperlink-color-resolver.ts → formatting/hyperlink-color.ts} +2 -2
- package/src/runtime/formatting/index.ts +125 -0
- package/src/runtime/{resolved-numbering-geometry.ts → formatting/numbering/geometry.ts} +1 -1
- package/src/runtime/{numbering-prefix.ts → formatting/numbering/prefix.ts} +170 -3
- package/src/runtime/formatting/paragraph-style-resolver.ts +92 -0
- package/src/runtime/formatting/projector.ts +75 -0
- package/src/runtime/formatting/resolve-effective.ts +407 -0
- package/src/runtime/formatting/revision-display.ts +105 -0
- package/src/runtime/{paragraph-style-resolver.ts → formatting/style-cascade.ts} +84 -141
- package/src/runtime/{table-style-resolver.ts → formatting/table-style-resolver.ts} +1 -1
- package/src/runtime/formatting/telemetry-bridge.ts +106 -0
- package/src/runtime/{theme-color-resolver.ts → formatting/theme-color.ts} +2 -30
- package/src/runtime/geometry/caret-geometry.ts +164 -0
- package/src/runtime/geometry/geometry-facet.ts +364 -0
- package/src/runtime/geometry/geometry-types.ts +256 -0
- package/src/runtime/geometry/hit-test.ts +125 -0
- package/src/runtime/geometry/index.ts +71 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +43 -0
- package/src/runtime/geometry/invalidation.ts +35 -0
- package/src/runtime/geometry/object-handles.ts +77 -0
- package/src/runtime/geometry/overlay-rects.ts +85 -0
- package/src/runtime/geometry/project-anchors.ts +100 -0
- package/src/runtime/geometry/project-fragments.ts +216 -0
- package/src/runtime/geometry/projector.ts +129 -0
- package/src/runtime/geometry/replacement-envelope.ts +130 -0
- package/src/runtime/geometry/viewport.ts +218 -0
- package/src/runtime/layout/compat-input-ledger.ts +211 -0
- package/src/runtime/layout/index.ts +6 -1
- package/src/runtime/layout/inert-layout-facet.ts +12 -7
- package/src/runtime/layout/layout-engine-instance.ts +189 -11
- package/src/runtime/layout/layout-engine-version.ts +450 -1
- package/src/runtime/layout/layout-facet-types.ts +60 -0
- package/src/runtime/layout/layout-measurement-provider.ts +13 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +14 -2
- package/src/runtime/layout/measurement-backend-empirical.ts +23 -4
- package/src/runtime/layout/page-graph.ts +62 -209
- package/src/runtime/layout/page-story-resolver.ts +7 -12
- package/src/runtime/layout/paginated-layout-engine.ts +186 -11
- package/src/runtime/layout/project-block-fragments.ts +11 -0
- package/src/runtime/layout/projector.ts +90 -0
- package/src/runtime/layout/public-facet.ts +187 -442
- package/src/runtime/layout/resolved-formatting-state.ts +158 -26
- package/src/runtime/layout/table-render-plan.ts +1 -1
- package/src/runtime/prerender/cache-envelope.ts +6 -1
- package/src/runtime/prerender/prerender-document.ts +18 -23
- package/src/runtime/render/decoration-resolver.ts +1 -1
- package/src/runtime/render/render-frame-types.ts +20 -0
- package/src/runtime/render/render-kernel.ts +94 -25
- package/src/runtime/scopes/_formatting-seam.ts +262 -0
- package/src/runtime/scopes/_scope-dependencies.ts +49 -0
- package/src/runtime/scopes/action-validation.ts +356 -0
- package/src/runtime/scopes/attach-explanation.ts +102 -0
- package/src/runtime/scopes/audit-bundle.ts +71 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +163 -0
- package/src/runtime/scopes/compile-scope.ts +262 -0
- package/src/runtime/scopes/compiler-service.ts +431 -0
- package/src/runtime/scopes/create-issue.ts +107 -0
- package/src/runtime/scopes/enumerate-scopes.ts +543 -0
- package/src/runtime/scopes/evidence.ts +233 -0
- package/src/runtime/scopes/index.ts +150 -0
- package/src/runtime/scopes/position-map.ts +214 -0
- package/src/runtime/scopes/preservation-boundary.ts +91 -0
- package/src/runtime/scopes/projector.ts +49 -0
- package/src/runtime/scopes/replaceability.ts +87 -0
- package/src/runtime/scopes/replacement/apply.ts +228 -0
- package/src/runtime/scopes/replacement/compile.ts +59 -0
- package/src/runtime/scopes/replacement/propose.ts +42 -0
- package/src/runtime/scopes/resolve-reference.ts +347 -0
- package/src/runtime/scopes/review-bundle.ts +141 -0
- package/src/runtime/scopes/scope-kinds/_paragraph-text.ts +57 -0
- package/src/runtime/scopes/scope-kinds/_table-text.ts +42 -0
- package/src/runtime/scopes/scope-kinds/comment-thread.ts +59 -0
- package/src/runtime/scopes/scope-kinds/field.ts +65 -0
- package/src/runtime/scopes/scope-kinds/heading.ts +84 -0
- package/src/runtime/scopes/scope-kinds/list-item.ts +77 -0
- package/src/runtime/scopes/scope-kinds/paragraph.ts +182 -0
- package/src/runtime/scopes/scope-kinds/revision.ts +62 -0
- package/src/runtime/scopes/scope-kinds/table-cell.ts +57 -0
- package/src/runtime/scopes/scope-kinds/table-row.ts +61 -0
- package/src/runtime/scopes/scope-kinds/table.ts +55 -0
- package/src/runtime/scopes/scope-range.ts +208 -0
- package/src/runtime/scopes/semantic-scope-types.ts +454 -0
- package/src/runtime/scopes/workflow-overlap.ts +92 -0
- package/src/runtime/selection/index.ts +1 -1
- package/src/runtime/structure-ops/fragment-insert.ts +1 -1
- package/src/runtime/structure-ops/index.ts +1 -1
- package/src/runtime/surface-projection.ts +232 -262
- package/src/runtime/units.ts +4 -2
- package/src/runtime/workflow/coordinator.ts +1348 -0
- package/src/runtime/workflow/derived-scope-resolver.ts +125 -0
- package/src/runtime/workflow/index.ts +25 -0
- package/src/runtime/workflow/markup-mode-policy.ts +98 -0
- package/src/runtime/{workflow-markup.ts → workflow/markup.ts} +6 -6
- package/src/runtime/workflow/metadata-persistence.ts +306 -0
- package/src/runtime/workflow/metadata-writer.ts +123 -0
- package/src/runtime/workflow/overlay-store.ts +690 -0
- package/src/runtime/workflow/projector.ts +127 -0
- package/src/runtime/{query-scopes.ts → workflow/query-scopes.ts} +3 -3
- package/src/runtime/{workflow-rail-segments.ts → workflow/rail/compose.ts} +60 -165
- package/src/runtime/workflow/rail/types.ts +198 -0
- package/src/runtime/workflow/scope-rail-composer.ts +39 -0
- package/src/runtime/{scope-resolver.ts → workflow/scope-resolver.ts} +3 -3
- package/src/runtime/workflow/scope-writer.ts +188 -0
- package/src/runtime/{tamper-gate.ts → workflow/tamper-gate.ts} +1 -1
- package/src/runtime/workflow/visibility-policy.ts +129 -0
- package/src/session/_sync-legacy.ts +66 -0
- package/src/session/export/embedded-reconstitute.ts +104 -0
- package/src/session/export/export-diagnostics.ts +85 -0
- package/src/session/export/export-validation.ts +110 -0
- package/src/session/export/index.ts +34 -0
- package/src/session/export/preservation-reattach.ts +30 -0
- package/src/session/export/serialize-dispatch.ts +165 -0
- package/src/session/export/stateful-export-pipeline.ts +432 -0
- package/src/session/export/stateful-export.ts +684 -0
- package/src/session/import/canonical-assembly.ts +227 -0
- package/src/session/import/diagnostics-session.ts +54 -0
- package/src/session/import/embedded-discovery.ts +225 -0
- package/src/session/import/embedded-offload.ts +337 -0
- package/src/session/import/import-diagnostics.ts +69 -0
- package/src/session/import/loader-types.ts +313 -0
- package/src/session/import/loader.ts +1834 -0
- package/src/session/import/normalize.ts +195 -0
- package/src/session/import/package-parts.ts +217 -0
- package/src/session/import/package-read.ts +195 -0
- package/src/session/import/parse-orchestration.ts +105 -0
- package/src/session/import/part-constants.ts +70 -0
- package/src/session/import/part-discovery.ts +94 -0
- package/src/session/import/preservation-index.ts +46 -0
- package/src/{runtime/read-only-diagnostics-runtime.ts → session/import/read-only-diagnostics.ts} +24 -3
- package/src/session/import/review-import.ts +508 -0
- package/src/session/import/styles-consolidation.ts +281 -0
- package/src/session/import/workflow-scope-import.ts +256 -0
- package/src/session/index.ts +37 -0
- package/src/session/session-state.ts +69 -0
- package/src/session/session.ts +532 -0
- package/src/session/shared/protection.ts +228 -0
- package/src/session/shared/session-utils.ts +82 -0
- package/src/session/types.ts +499 -0
- package/src/shell/chart-snapshots.ts +96 -0
- package/src/shell/media-previews.ts +85 -0
- package/src/shell/overlay-anchor-bridge.ts +53 -0
- package/src/shell/paste-adapter.ts +23 -0
- package/src/shell/ref-commands.ts +1697 -0
- package/src/shell/ref-utilities.ts +48 -0
- package/src/shell/search.ts +51 -0
- package/src/{ui/editor-runtime-boundary.ts → shell/session-bootstrap.ts} +243 -67
- package/src/shell/ui-subscriber-channels.ts +81 -0
- package/src/shell/use-collab-sync.ts +116 -0
- package/src/ui/WordReviewEditor.tsx +496 -2051
- package/src/ui/editor-shell-view.tsx +30 -1
- package/src/ui/editor-surface-controller.tsx +49 -1
- package/src/ui/headless/revision-decoration-model.ts +83 -0
- package/src/{ui-tailwind/chrome → ui/headless}/role-action-sets.ts +1 -1
- package/src/ui/headless/scoped-chrome-policy.ts +2 -2
- package/src/ui/headless/selection-tool-context.ts +1 -1
- package/src/ui/headless/selection-tool-resolver.ts +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +46 -1
- package/src/ui/ui-controller-factory.ts +221 -0
- package/src/ui-tailwind/chart/ChartSurface.tsx +2 -2
- package/src/ui-tailwind/chart/layout/legend-layout.ts +1 -1
- package/src/ui-tailwind/chart/layout/plot-area.ts +2 -2
- package/src/ui-tailwind/chart/layout/title-layout.ts +1 -1
- package/src/ui-tailwind/chart/render/area.tsx +3 -3
- package/src/ui-tailwind/chart/render/bar-column.tsx +3 -3
- package/src/ui-tailwind/chart/render/bubble.tsx +3 -3
- package/src/ui-tailwind/chart/render/combo.tsx +2 -2
- package/src/ui-tailwind/chart/render/data-labels.tsx +2 -2
- package/src/ui-tailwind/chart/render/font-metrics.ts +2 -2
- package/src/ui-tailwind/chart/render/line.tsx +3 -3
- package/src/ui-tailwind/chart/render/pie.tsx +6 -6
- package/src/ui-tailwind/chart/render/scatter.tsx +3 -3
- package/src/ui-tailwind/chart/render/svg-primitives.ts +3 -3
- package/src/ui-tailwind/chart/render/unsupported.tsx +2 -2
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +88 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +1 -1
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +1 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +553 -0
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +182 -0
- package/src/ui-tailwind/chrome/local-surface-arbiter.ts +534 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +226 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-context-band.tsx +125 -0
- package/src/ui-tailwind/chrome/tw-context-menu-portal.tsx +248 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +42 -1
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +8 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +38 -4
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +104 -6
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +66 -7
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +54 -8
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +33 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +78 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +16 -8
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +276 -0
- package/src/ui-tailwind/chrome/use-context-menu-controller.ts +201 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +22 -4
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +11 -5
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +197 -3
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +1 -1
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +35 -6
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +24 -16
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +1 -1
- package/src/ui-tailwind/debug/README.md +57 -0
- package/src/ui-tailwind/debug/index.ts +3 -0
- package/src/ui-tailwind/debug/tw-debug-overlay.tsx +186 -0
- package/src/ui-tailwind/debug/tw-debug-presentation.tsx +80 -0
- package/src/ui-tailwind/debug/tw-debug-top-bar.tsx +83 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +2 -2
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +135 -10
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +40 -13
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +3 -3
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +1 -1
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +2 -2
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +91 -9
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +1 -1
- package/src/ui-tailwind/editor-surface/surface-layer.ts +1 -1
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +1 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +23 -6
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +132 -22
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +1 -1
- package/src/ui-tailwind/index.ts +0 -5
- package/src/ui-tailwind/overlay-anchor-bridge-context.tsx +33 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +66 -29
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +25 -2
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +15 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +92 -4
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +1 -1
- package/src/ui-tailwind/review-workspace/page-chrome.ts +210 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +101 -0
- package/src/ui-tailwind/review-workspace/paragraph-layout.ts +115 -0
- package/src/ui-tailwind/review-workspace/selection-toolbar-placement.ts +97 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-navigator.tsx +130 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +240 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +59 -0
- package/src/ui-tailwind/review-workspace/types.ts +408 -0
- package/src/ui-tailwind/review-workspace/use-chrome-policy.ts +104 -0
- package/src/ui-tailwind/review-workspace/use-derived-view-state.ts +151 -0
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +70 -0
- package/src/ui-tailwind/review-workspace/use-grabbed-segment-offsets.ts +40 -0
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +130 -0
- package/src/ui-tailwind/review-workspace/use-pm-surface-capture.ts +60 -0
- package/src/ui-tailwind/review-workspace/use-review-rail-state.ts +63 -0
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +170 -0
- package/src/ui-tailwind/review-workspace/use-scroll-root-capture.ts +28 -0
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +113 -0
- package/src/ui-tailwind/review-workspace/use-shell-selection-anchor-bridge.ts +120 -0
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +55 -0
- package/src/ui-tailwind/review-workspace/use-viewport-dimensions.ts +43 -0
- package/src/ui-tailwind/review-workspace/use-workspace-arbiter.ts +25 -0
- package/src/ui-tailwind/review-workspace/use-workspace-composition.ts +86 -0
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +150 -0
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +61 -98
- package/src/ui-tailwind/tw-review-workspace.tsx +521 -1802
- package/src/ui-tailwind/ui-api-context.tsx +43 -0
- package/src/ui-tailwind/ui-shell-channels-context.tsx +49 -0
- package/src/validation/compatibility-engine.ts +6 -6
- package/src/runtime/styles-cascade.ts +0 -33
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +0 -85
- /package/src/runtime/{page-number-format.ts → formatting/field/page-number-format.ts} +0 -0
- /package/src/runtime/{ai-action-policy.ts → workflow/ai-action-policy.ts} +0 -0
- /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
|
+
}
|