@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,151 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
DocumentNavigationSnapshot,
|
|
5
|
+
RuntimeRenderSnapshot,
|
|
6
|
+
WordReviewEditorChromeVisibility,
|
|
7
|
+
ZoomLevel,
|
|
8
|
+
} from "../../api/public-types.ts";
|
|
9
|
+
import type { ActiveSelectionToolModel } from "../../ui/headless/selection-tool-types";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
buildPageChromeModel,
|
|
13
|
+
type PageChromeModel,
|
|
14
|
+
} from "./page-chrome.ts";
|
|
15
|
+
import {
|
|
16
|
+
buildPageShellMetrics,
|
|
17
|
+
resolveZoomMultiplier,
|
|
18
|
+
type PageShellMetrics,
|
|
19
|
+
} from "./page-shell-metrics.ts";
|
|
20
|
+
import {
|
|
21
|
+
resolveActiveParagraphLayout,
|
|
22
|
+
type ActiveParagraphLayout,
|
|
23
|
+
} from "./paragraph-layout.ts";
|
|
24
|
+
|
|
25
|
+
export interface UseDerivedViewStateOptions {
|
|
26
|
+
snapshot: RuntimeRenderSnapshot;
|
|
27
|
+
documentNavigation: DocumentNavigationSnapshot | undefined;
|
|
28
|
+
activeSelectionTool: ActiveSelectionToolModel | null | undefined;
|
|
29
|
+
chromeVisibility: Pick<WordReviewEditorChromeVisibility, "contextToolbars" | "pageChrome">;
|
|
30
|
+
selectionPosition: number;
|
|
31
|
+
shouldResolveActiveParagraphLayout: boolean;
|
|
32
|
+
zoomLevel: ZoomLevel;
|
|
33
|
+
numericZoomScale: number;
|
|
34
|
+
viewportWidth: number | undefined;
|
|
35
|
+
viewportHeight: number | undefined;
|
|
36
|
+
isPageWorkspace: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DerivedViewState {
|
|
40
|
+
activeParagraphLayout: ActiveParagraphLayout | null;
|
|
41
|
+
pageChromeModel: PageChromeModel;
|
|
42
|
+
gatedSelectionTool: ActiveSelectionToolModel | null;
|
|
43
|
+
pageShellMetrics: PageShellMetrics;
|
|
44
|
+
zoomScale: number;
|
|
45
|
+
pageZoomBucket: "low" | "base" | "high" | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Bundle of memoized derived view-state that TwReviewWorkspace feeds
|
|
50
|
+
* into its chrome + overlay layers:
|
|
51
|
+
*
|
|
52
|
+
* - `activeParagraphLayout` — page-chrome ruler input; resolved only
|
|
53
|
+
* when the ruler is visible (layout tools open + page chrome
|
|
54
|
+
* present + page workspace mode).
|
|
55
|
+
* - `pageChromeModel` — line numbering / page border / grid style.
|
|
56
|
+
* - `gatedSelectionTool` — chromeVisibility-scoped view of the host's
|
|
57
|
+
* selection tool model.
|
|
58
|
+
* - `pageShellMetrics` — real-twip frame + content-inset styles.
|
|
59
|
+
* - `zoomScale` — numeric → passthrough; symbolic ("pageWidth" /
|
|
60
|
+
* "onePage") → resolved against the active section's real paper
|
|
61
|
+
* dimensions via `resolveZoomMultiplier` (P2.c).
|
|
62
|
+
* - `pageZoomBucket` — chromeless classification used by
|
|
63
|
+
* `TwPageRuler` + zoom controls.
|
|
64
|
+
*/
|
|
65
|
+
export function useDerivedViewState(
|
|
66
|
+
options: UseDerivedViewStateOptions,
|
|
67
|
+
): DerivedViewState {
|
|
68
|
+
const {
|
|
69
|
+
snapshot,
|
|
70
|
+
documentNavigation,
|
|
71
|
+
activeSelectionTool,
|
|
72
|
+
chromeVisibility,
|
|
73
|
+
selectionPosition,
|
|
74
|
+
shouldResolveActiveParagraphLayout,
|
|
75
|
+
zoomLevel,
|
|
76
|
+
numericZoomScale,
|
|
77
|
+
viewportWidth,
|
|
78
|
+
viewportHeight,
|
|
79
|
+
isPageWorkspace,
|
|
80
|
+
} = options;
|
|
81
|
+
|
|
82
|
+
const activeParagraphLayout = useMemo(
|
|
83
|
+
() =>
|
|
84
|
+
shouldResolveActiveParagraphLayout
|
|
85
|
+
? resolveActiveParagraphLayout(snapshot.surface, selectionPosition)
|
|
86
|
+
: null,
|
|
87
|
+
[selectionPosition, shouldResolveActiveParagraphLayout, snapshot.surface],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const pageChromeModel = useMemo(
|
|
91
|
+
() =>
|
|
92
|
+
buildPageChromeModel(
|
|
93
|
+
snapshot.surface,
|
|
94
|
+
snapshot.pageLayout,
|
|
95
|
+
documentNavigation,
|
|
96
|
+
snapshot.activeStory,
|
|
97
|
+
),
|
|
98
|
+
[documentNavigation, snapshot.activeStory, snapshot.pageLayout, snapshot.surface],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const gatedSelectionTool = useMemo(() => {
|
|
102
|
+
if (!activeSelectionTool) return null;
|
|
103
|
+
if (
|
|
104
|
+
activeSelectionTool.kind === "structure-context" &&
|
|
105
|
+
!chromeVisibility.contextToolbars
|
|
106
|
+
) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return activeSelectionTool;
|
|
110
|
+
}, [activeSelectionTool, chromeVisibility.contextToolbars]);
|
|
111
|
+
|
|
112
|
+
const pageShellMetrics = useMemo(
|
|
113
|
+
() => buildPageShellMetrics(snapshot.pageLayout),
|
|
114
|
+
[snapshot.pageLayout],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const zoomScale = useMemo(() => {
|
|
118
|
+
if (typeof zoomLevel === "number") return numericZoomScale;
|
|
119
|
+
return resolveZoomMultiplier(
|
|
120
|
+
zoomLevel,
|
|
121
|
+
pageShellMetrics.frameWidthPx ?? 0,
|
|
122
|
+
pageShellMetrics.frameHeightPx ?? 0,
|
|
123
|
+
viewportWidth,
|
|
124
|
+
viewportHeight,
|
|
125
|
+
);
|
|
126
|
+
}, [
|
|
127
|
+
zoomLevel,
|
|
128
|
+
numericZoomScale,
|
|
129
|
+
pageShellMetrics.frameWidthPx,
|
|
130
|
+
pageShellMetrics.frameHeightPx,
|
|
131
|
+
viewportWidth,
|
|
132
|
+
viewportHeight,
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
const pageZoomBucket: "low" | "base" | "high" | undefined = !isPageWorkspace
|
|
136
|
+
? undefined
|
|
137
|
+
: zoomScale < 1
|
|
138
|
+
? "low"
|
|
139
|
+
: zoomScale > 1
|
|
140
|
+
? "high"
|
|
141
|
+
: "base";
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
activeParagraphLayout,
|
|
145
|
+
pageChromeModel,
|
|
146
|
+
gatedSelectionTool,
|
|
147
|
+
pageShellMetrics,
|
|
148
|
+
zoomScale,
|
|
149
|
+
pageZoomBucket,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
4
|
+
import type { SessionCapabilities } from "../../api/public-types";
|
|
5
|
+
|
|
6
|
+
export interface DiagnosticsSignal {
|
|
7
|
+
severity: "none" | "info" | "warning" | "blocked";
|
|
8
|
+
count: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UseDiagnosticsSignalOptions {
|
|
12
|
+
snapshot: RuntimeRenderSnapshot;
|
|
13
|
+
caps: SessionCapabilities | undefined;
|
|
14
|
+
preserveOnlyCount: number;
|
|
15
|
+
blockedReasonsCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Phase E.1 — diagnostics signal feeds `resolveChromeComposition` so the
|
|
20
|
+
* `health` rail tab can enter `visibleTabs`. Severity ladder mirrors
|
|
21
|
+
* `TwAlertBanner`'s precedence (§6.17): blocked export → workflow blocked
|
|
22
|
+
* → preserve-only / warnings → info.
|
|
23
|
+
*
|
|
24
|
+
* Perf — I1 fix: the workspace re-renders on every PM transaction
|
|
25
|
+
* (inverted-truth architecture — `view.updateState()` replaces PM state
|
|
26
|
+
* wholesale per commit, per `CLAUDE.md §Performance Invariants #4`).
|
|
27
|
+
* The derivations below run `Array.prototype.filter` over
|
|
28
|
+
* `snapshot.compatibility.featureEntries` and `snapshot.warnings`, so
|
|
29
|
+
* they MUST memoize on structural inputs. One memo for everything the
|
|
30
|
+
* signal needs keeps the hot path tight.
|
|
31
|
+
*/
|
|
32
|
+
export function useDiagnosticsSignal(
|
|
33
|
+
options: UseDiagnosticsSignalOptions,
|
|
34
|
+
): DiagnosticsSignal {
|
|
35
|
+
const { snapshot, caps, preserveOnlyCount, blockedReasonsCount } = options;
|
|
36
|
+
|
|
37
|
+
return useMemo(() => {
|
|
38
|
+
const featureEntries = snapshot.compatibility.featureEntries;
|
|
39
|
+
const unsupportedFatalCount =
|
|
40
|
+
caps?.unsupportedFatalCount ??
|
|
41
|
+
featureEntries.filter((e) => e.featureClass === "unsupported-fatal").length;
|
|
42
|
+
let infoWarningCount = 0;
|
|
43
|
+
for (const w of snapshot.warnings) {
|
|
44
|
+
if (w.severity === "info") infoWarningCount += 1;
|
|
45
|
+
}
|
|
46
|
+
const blockingWarningCount = snapshot.warnings.length - infoWarningCount;
|
|
47
|
+
const count =
|
|
48
|
+
caps?.healthIssueCount ??
|
|
49
|
+
preserveOnlyCount + unsupportedFatalCount + snapshot.warnings.length;
|
|
50
|
+
const severity: "none" | "info" | "warning" | "blocked" =
|
|
51
|
+
snapshot.compatibility.blockExport || unsupportedFatalCount > 0
|
|
52
|
+
? "blocked"
|
|
53
|
+
: preserveOnlyCount > 0 ||
|
|
54
|
+
blockingWarningCount > 0 ||
|
|
55
|
+
blockedReasonsCount > 0
|
|
56
|
+
? "warning"
|
|
57
|
+
: infoWarningCount > 0
|
|
58
|
+
? "info"
|
|
59
|
+
: "none";
|
|
60
|
+
return { severity, count };
|
|
61
|
+
}, [
|
|
62
|
+
snapshot.compatibility.featureEntries,
|
|
63
|
+
snapshot.compatibility.blockExport,
|
|
64
|
+
snapshot.warnings,
|
|
65
|
+
caps?.unsupportedFatalCount,
|
|
66
|
+
caps?.healthIssueCount,
|
|
67
|
+
preserveOnlyCount,
|
|
68
|
+
blockedReasonsCount,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
4
|
+
|
|
5
|
+
export interface GrabbedSegmentOffsets {
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* N6 — resolve grabbed-object segment offsets from the surface so the
|
|
12
|
+
* selection overlay can query the anchor index without a full surface walk.
|
|
13
|
+
*/
|
|
14
|
+
export function useGrabbedSegmentOffsets(
|
|
15
|
+
snapshot: RuntimeRenderSnapshot,
|
|
16
|
+
): GrabbedSegmentOffsets | null {
|
|
17
|
+
return useMemo(() => {
|
|
18
|
+
const objectId = snapshot.grabbedObjectId ?? null;
|
|
19
|
+
if (!objectId || !snapshot.surface) return null;
|
|
20
|
+
for (const block of snapshot.surface.blocks) {
|
|
21
|
+
if (!("segments" in block)) continue;
|
|
22
|
+
for (const seg of (block as { segments?: unknown[] }).segments ?? []) {
|
|
23
|
+
const s = seg as {
|
|
24
|
+
kind?: string;
|
|
25
|
+
mediaId?: string;
|
|
26
|
+
from?: number;
|
|
27
|
+
to?: number;
|
|
28
|
+
};
|
|
29
|
+
if (
|
|
30
|
+
(s.kind === "image" || s.kind === "shape") &&
|
|
31
|
+
s.mediaId === objectId &&
|
|
32
|
+
s.from != null
|
|
33
|
+
) {
|
|
34
|
+
return { from: s.from, to: s.to ?? s.from + 1 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}, [snapshot.grabbedObjectId, snapshot.surface]);
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* P3.b + P5b + P14.b — subscribe to layout facet events that should
|
|
5
|
+
* trigger a chrome re-projection (zoom, render frame ready, layout
|
|
6
|
+
* recomputed / committed, page count changes, measurement backend
|
|
7
|
+
* ready, etc.) and expose a monotonic revision counter. Consumers
|
|
8
|
+
* include the counter in dependency arrays of useMemo-backed placement
|
|
9
|
+
* calculations so they re-run when the layout engine emits new rects.
|
|
10
|
+
*
|
|
11
|
+
* Bumps are coalesced through `queueMicrotask` so multiple events in
|
|
12
|
+
* the same synchronous tick fold into one re-render.
|
|
13
|
+
*/
|
|
14
|
+
export function useLayoutFacetRenderSignal(
|
|
15
|
+
layoutFacet: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet | undefined,
|
|
16
|
+
): number {
|
|
17
|
+
const [renderFrameRevision, setRenderFrameRevision] = useState(0);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!layoutFacet) return;
|
|
21
|
+
let pendingBump = false;
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
const scheduleBump = () => {
|
|
24
|
+
if (pendingBump || cancelled) return;
|
|
25
|
+
pendingBump = true;
|
|
26
|
+
queueMicrotask(() => {
|
|
27
|
+
pendingBump = false;
|
|
28
|
+
if (cancelled) return;
|
|
29
|
+
setRenderFrameRevision((n) => n + 1);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const unsub = layoutFacet.subscribe((event) => {
|
|
33
|
+
switch (event.kind) {
|
|
34
|
+
case "zoom_changed":
|
|
35
|
+
case "render_frame_ready":
|
|
36
|
+
case "layout_recomputed":
|
|
37
|
+
case "incremental_relayout":
|
|
38
|
+
case "page_count_changed":
|
|
39
|
+
case "page_field_dirtied":
|
|
40
|
+
case "measurement_backend_ready":
|
|
41
|
+
case "layout_committed":
|
|
42
|
+
scheduleBump();
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
cancelled = true;
|
|
50
|
+
unsub();
|
|
51
|
+
};
|
|
52
|
+
}, [layoutFacet]);
|
|
53
|
+
|
|
54
|
+
return renderFrameRevision;
|
|
55
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
4
|
+
import {
|
|
5
|
+
useVisibleBlockRange,
|
|
6
|
+
useVisiblePageIndexRange,
|
|
7
|
+
} from "../page-stack/use-visible-block-range.ts";
|
|
8
|
+
|
|
9
|
+
export interface UsePageMarkersOptions {
|
|
10
|
+
pageStackScrollRoot: HTMLElement | null;
|
|
11
|
+
snapshot: RuntimeRenderSnapshot;
|
|
12
|
+
layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PageMarkersResult {
|
|
16
|
+
pageMarkers: readonly HTMLElement[];
|
|
17
|
+
visibleBlockRange: ReturnType<typeof useVisibleBlockRange>;
|
|
18
|
+
visiblePageIndexRange: ReturnType<typeof useVisiblePageIndexRange>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* L7 Phase 2 Task 2.2.4a — viewport-scroll wiring.
|
|
23
|
+
*
|
|
24
|
+
* Collects DOM elements with `[data-page-frame]` from the PM surface so
|
|
25
|
+
* the IntersectionObserver inside `useVisibleBlockRange` /
|
|
26
|
+
* `useVisiblePageIndexRange` can determine which pages are currently
|
|
27
|
+
* visible. A MutationObserver refreshes the set when the PM surface
|
|
28
|
+
* re-renders (new document load, page count change, etc.).
|
|
29
|
+
*
|
|
30
|
+
* The hook also derives `selectionBlockIndex` internally so the visible
|
|
31
|
+
* range always includes the selection, and pushes the resolved visible
|
|
32
|
+
* block range into the layout facet (layout's viewport-culling
|
|
33
|
+
* machinery).
|
|
34
|
+
*
|
|
35
|
+
* Returns the synthetic page-0 marker prepended when the real DOM lacks
|
|
36
|
+
* one — the boundary widgets between page N and N+1 carry
|
|
37
|
+
* `data-page-frame="N+1"`, so page 0 has no widget before it.
|
|
38
|
+
*/
|
|
39
|
+
export function usePageMarkers(options: UsePageMarkersOptions): PageMarkersResult {
|
|
40
|
+
const { pageStackScrollRoot, snapshot, layoutFacet } = options;
|
|
41
|
+
|
|
42
|
+
const [pageMarkers, setPageMarkers] = useState<readonly HTMLElement[]>([]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const root = pageStackScrollRoot;
|
|
46
|
+
if (!root) {
|
|
47
|
+
setPageMarkers([]);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const refresh = () => {
|
|
51
|
+
const found = Array.from(root.querySelectorAll<HTMLElement>("[data-page-frame]"));
|
|
52
|
+
const hasPage0 = found.some(
|
|
53
|
+
(el) => el.getAttribute("data-page-frame") === "0",
|
|
54
|
+
);
|
|
55
|
+
if (!hasPage0 && found.length > 0) {
|
|
56
|
+
const page1Marker = found.find(
|
|
57
|
+
(el) => el.getAttribute("data-page-frame") === "1",
|
|
58
|
+
);
|
|
59
|
+
const page1First = page1Marker
|
|
60
|
+
? Number(page1Marker.getAttribute("data-page-first-block-index") ?? "")
|
|
61
|
+
: NaN;
|
|
62
|
+
const page0Last = Number.isFinite(page1First) && page1First > 0
|
|
63
|
+
? page1First - 1
|
|
64
|
+
: -1;
|
|
65
|
+
const ownerDoc = found[0]!.ownerDocument;
|
|
66
|
+
const synth = ownerDoc.createElement("span");
|
|
67
|
+
synth.setAttribute("data-page-frame", "0");
|
|
68
|
+
synth.setAttribute("data-page-first-block-index", "0");
|
|
69
|
+
synth.setAttribute("data-page-last-block-index", String(Math.max(0, page0Last)));
|
|
70
|
+
setPageMarkers([synth, ...found]);
|
|
71
|
+
} else {
|
|
72
|
+
setPageMarkers(found);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
refresh();
|
|
76
|
+
const view = root.ownerDocument?.defaultView;
|
|
77
|
+
if (!view?.MutationObserver) return undefined;
|
|
78
|
+
const mo = new view.MutationObserver(refresh);
|
|
79
|
+
mo.observe(root, { childList: true, subtree: true });
|
|
80
|
+
return () => mo.disconnect();
|
|
81
|
+
// Re-run when the scroll root changes OR when a new snapshot lands
|
|
82
|
+
// (which may have a different page count and new widgets in the PM DOM).
|
|
83
|
+
// NOTE: snapshot.surface is intentionally excluded — its reference changes on
|
|
84
|
+
// every requestViewportRefresh(), which would add two extra render passes per
|
|
85
|
+
// scroll event. The page-0 fallback uses -1 when page1First is unknown,
|
|
86
|
+
// which is correct (no page-0 blocks → synthetic marker contributes nothing).
|
|
87
|
+
}, [pageStackScrollRoot, snapshot.revisionToken]);
|
|
88
|
+
|
|
89
|
+
const selectionBlockIndex = useMemo(() => {
|
|
90
|
+
const sel = snapshot.selection;
|
|
91
|
+
const blocks = snapshot.surface?.blocks;
|
|
92
|
+
if (!sel || !blocks) return null;
|
|
93
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
94
|
+
const block = blocks[i]!;
|
|
95
|
+
const blockFrom = block.from;
|
|
96
|
+
const blockTo = block.to;
|
|
97
|
+
if (sel.head >= blockFrom && sel.head <= blockTo) return i;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}, [snapshot.selection, snapshot.surface]);
|
|
101
|
+
|
|
102
|
+
const visibleBlockRange = useVisibleBlockRange({
|
|
103
|
+
pageMarkers,
|
|
104
|
+
overscanPages: 2,
|
|
105
|
+
selectionBlockIndex,
|
|
106
|
+
totalBlockCount: snapshot.surface?.blocks.length ?? 0,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// L7 Phase 2.8 — viewport cull for `TwPageStackChromeLayer`. Returns
|
|
110
|
+
// `null` while the IntersectionObserver hasn't reported yet; the chrome
|
|
111
|
+
// layer treats null as "render every page" so first paint is
|
|
112
|
+
// unaffected. Once the observer fires, the chrome layer only mounts
|
|
113
|
+
// bands for pages inside `[start, end)` plus overscan, eliminating the
|
|
114
|
+
// measured 412 ms Layout + 229 ms Pre-paint cost on 138-pp extra-large.
|
|
115
|
+
const visiblePageIndexRange = useVisiblePageIndexRange({
|
|
116
|
+
pageMarkers,
|
|
117
|
+
overscanPages: 2,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Push the visible range into the layout facet (which delegates to the
|
|
121
|
+
// runtime's viewport-culling machinery). Depend on `[start, end]` values
|
|
122
|
+
// (not the range object) so identity-preserving updates are a no-op.
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!layoutFacet) return;
|
|
125
|
+
layoutFacet.setVisibleBlockRange(visibleBlockRange);
|
|
126
|
+
layoutFacet.requestViewportRefresh();
|
|
127
|
+
}, [layoutFacet, visibleBlockRange.start, visibleBlockRange.end]);
|
|
128
|
+
|
|
129
|
+
return { pageMarkers, visibleBlockRange, visiblePageIndexRange };
|
|
130
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
export interface PmSurfaceCapture {
|
|
4
|
+
bodySlotRef: RefObject<HTMLDivElement | null>;
|
|
5
|
+
pmSurfaceElement: HTMLElement | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* P8.11 — capture the PM surface DOM element. The ProseMirror surface
|
|
10
|
+
* mounts inside `bodySlotRef` on its own schedule (the PM constructor
|
|
11
|
+
* runs inside `TwProseMirrorSurface`). A MutationObserver scoped to the
|
|
12
|
+
* body slot's `childList` picks up the PM root on first commit; once
|
|
13
|
+
* captured, the chrome layer owns reparent state (including portal-slot
|
|
14
|
+
* promotion), so the hook skips further updates unless PM is actually
|
|
15
|
+
* disconnected from the document (e.g. session / document swap tearing
|
|
16
|
+
* PM down).
|
|
17
|
+
*/
|
|
18
|
+
export function usePmSurfaceCapture(): PmSurfaceCapture {
|
|
19
|
+
const bodySlotRef = useRef<HTMLDivElement | null>(null);
|
|
20
|
+
const [pmSurfaceElement, setPmSurfaceElement] = useState<HTMLElement | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const slot = bodySlotRef.current;
|
|
24
|
+
if (!slot) return undefined;
|
|
25
|
+
|
|
26
|
+
// If we already hold a live reference, the chrome layer may have
|
|
27
|
+
// portaled PM into a per-page band — PM has left `bodySlotRef` but
|
|
28
|
+
// is still connected to the document. Keep the reference until the
|
|
29
|
+
// node is fully disconnected.
|
|
30
|
+
if (pmSurfaceElement && pmSurfaceElement.isConnected) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const readPm = (): HTMLElement | null =>
|
|
35
|
+
slot.querySelector<HTMLElement>(".ProseMirror");
|
|
36
|
+
|
|
37
|
+
const current = readPm();
|
|
38
|
+
if (current !== pmSurfaceElement) {
|
|
39
|
+
setPmSurfaceElement(current);
|
|
40
|
+
}
|
|
41
|
+
const runtime = slot.ownerDocument?.defaultView as
|
|
42
|
+
| (Window & { MutationObserver?: typeof MutationObserver })
|
|
43
|
+
| null;
|
|
44
|
+
if (!runtime?.MutationObserver) return undefined;
|
|
45
|
+
const observer = new runtime.MutationObserver(() => {
|
|
46
|
+
const next = readPm();
|
|
47
|
+
if (next !== null && next !== pmSurfaceElement) {
|
|
48
|
+
setPmSurfaceElement(next);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// `childList: true, subtree: false` — only care when children of the
|
|
52
|
+
// body slot change (e.g. PM is added for the first time). Subtree
|
|
53
|
+
// mutations (PM's own edits) fire on every keystroke and are not
|
|
54
|
+
// our concern.
|
|
55
|
+
observer.observe(slot, { childList: true, subtree: false });
|
|
56
|
+
return () => observer.disconnect();
|
|
57
|
+
}, [pmSurfaceElement]);
|
|
58
|
+
|
|
59
|
+
return { bodySlotRef, pmSurfaceElement };
|
|
60
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { Dispatch, SetStateAction } from "react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getInitialReviewRailOpen,
|
|
6
|
+
isNarrowChromeViewport,
|
|
7
|
+
} from "../chrome/responsive-chrome";
|
|
8
|
+
import { readViewportWidth } from "./page-shell-metrics.ts";
|
|
9
|
+
|
|
10
|
+
export interface UseReviewRailStateOptions {
|
|
11
|
+
reviewRailAvailable: boolean;
|
|
12
|
+
viewportWidth: number | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ReviewRailState {
|
|
16
|
+
reviewRailOpen: boolean;
|
|
17
|
+
setReviewRailOpen: Dispatch<SetStateAction<boolean>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Review-rail open/close state + the responsive transition effect.
|
|
22
|
+
*
|
|
23
|
+
* When the responsive signature flips (narrow↔wide, or
|
|
24
|
+
* `reviewRailAvailable` changes), the rail resets to its default open
|
|
25
|
+
* state per `getInitialReviewRailOpen`. A ref guards the effect so it
|
|
26
|
+
* only fires on actual transitions, not every viewport resize.
|
|
27
|
+
*
|
|
28
|
+
* Drawer-mode Escape close is NOT handled here — it lives in the
|
|
29
|
+
* parent because `showDrawerReviewRail` flows from the chrome-policy
|
|
30
|
+
* hook which consumes `reviewRailOpen` from this hook (a cycle).
|
|
31
|
+
* Keep those effects in the caller.
|
|
32
|
+
*/
|
|
33
|
+
export function useReviewRailState(
|
|
34
|
+
options: UseReviewRailStateOptions,
|
|
35
|
+
): ReviewRailState {
|
|
36
|
+
const { reviewRailAvailable, viewportWidth } = options;
|
|
37
|
+
|
|
38
|
+
const [reviewRailOpen, setReviewRailOpen] = useState(() =>
|
|
39
|
+
getInitialReviewRailOpen({
|
|
40
|
+
viewportWidth: readViewportWidth(),
|
|
41
|
+
reviewRailAvailable,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const responsiveSignatureRef = useRef<string | null>(null);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const responsiveSignature = `${reviewRailAvailable ? "1" : "0"}:${isNarrowChromeViewport(viewportWidth) ? "n" : "d"}`;
|
|
49
|
+
if (responsiveSignatureRef.current === responsiveSignature) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
responsiveSignatureRef.current = responsiveSignature;
|
|
54
|
+
setReviewRailOpen(
|
|
55
|
+
getInitialReviewRailOpen({
|
|
56
|
+
viewportWidth,
|
|
57
|
+
reviewRailAvailable,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}, [reviewRailAvailable, viewportWidth]);
|
|
61
|
+
|
|
62
|
+
return { reviewRailOpen, setReviewRailOpen };
|
|
63
|
+
}
|