@beyondwork/docx-react-component 1.0.105 → 1.0.108
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/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +10 -2
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +310 -6
- package/src/api/v3/ai/attach.ts +29 -4
- package/src/api/v3/ai/bundle.ts +6 -2
- package/src/api/v3/ai/inspect.ts +6 -2
- package/src/api/v3/ai/replacement.ts +112 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +8 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +202 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +17 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +119 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +147 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +279 -115
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +661 -16
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +188 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +23 -4
- package/src/runtime/layout/paginated-layout-engine.ts +149 -15
- package/src/runtime/layout/project-block-fragments.ts +351 -14
- package/src/runtime/layout/public-facet.ts +162 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +4 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +386 -0
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +130 -0
- package/src/ui/ui-controller-factory.ts +17 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -2,6 +2,10 @@ import { useMemo } from "react";
|
|
|
2
2
|
|
|
3
3
|
import type { RuntimeRenderSnapshot } from "../../api/public-types.ts";
|
|
4
4
|
import type { SessionCapabilities } from "../../api/public-types";
|
|
5
|
+
import {
|
|
6
|
+
filterProductWarnings,
|
|
7
|
+
isDebugOnlyCompatibilityFeature,
|
|
8
|
+
} from "./diagnostics-visibility.ts";
|
|
5
9
|
|
|
6
10
|
export interface DiagnosticsSignal {
|
|
7
11
|
severity: "none" | "info" | "warning" | "blocked";
|
|
@@ -13,13 +17,20 @@ export interface UseDiagnosticsSignalOptions {
|
|
|
13
17
|
caps: SessionCapabilities | undefined;
|
|
14
18
|
preserveOnlyCount: number;
|
|
15
19
|
blockedReasonsCount: number;
|
|
20
|
+
/**
|
|
21
|
+
* Preserve-only / opaque diagnostics are debug/operator UX. Default
|
|
22
|
+
* product chrome should not open Health or badge the toolbar solely
|
|
23
|
+
* because content is preserved for export.
|
|
24
|
+
*/
|
|
25
|
+
includePreservationDiagnostics?: boolean;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Phase E.1 — diagnostics signal feeds `resolveChromeComposition` so the
|
|
20
30
|
* `health` rail tab can enter `visibleTabs`. Severity ladder mirrors
|
|
21
31
|
* `TwAlertBanner`'s precedence (§6.17): blocked export → workflow blocked
|
|
22
|
-
* →
|
|
32
|
+
* → warnings → info. Preserve-only / opaque counts are included only
|
|
33
|
+
* for debug/operator surfaces.
|
|
23
34
|
*
|
|
24
35
|
* Perf — I1 fix: the workspace re-renders on every PM transaction
|
|
25
36
|
* (inverted-truth architecture — `view.updateState()` replaces PM state
|
|
@@ -32,25 +43,42 @@ export interface UseDiagnosticsSignalOptions {
|
|
|
32
43
|
export function useDiagnosticsSignal(
|
|
33
44
|
options: UseDiagnosticsSignalOptions,
|
|
34
45
|
): DiagnosticsSignal {
|
|
35
|
-
const {
|
|
46
|
+
const {
|
|
47
|
+
snapshot,
|
|
48
|
+
caps,
|
|
49
|
+
preserveOnlyCount,
|
|
50
|
+
blockedReasonsCount,
|
|
51
|
+
includePreservationDiagnostics = false,
|
|
52
|
+
} = options;
|
|
36
53
|
|
|
37
54
|
return useMemo(() => {
|
|
38
|
-
const featureEntries =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
const featureEntries = includePreservationDiagnostics
|
|
56
|
+
? snapshot.compatibility.featureEntries
|
|
57
|
+
: snapshot.compatibility.featureEntries.filter(
|
|
58
|
+
(entry) => !isDebugOnlyCompatibilityFeature(entry),
|
|
59
|
+
);
|
|
60
|
+
const visibleWarnings = includePreservationDiagnostics
|
|
61
|
+
? snapshot.warnings
|
|
62
|
+
: filterProductWarnings(snapshot.warnings);
|
|
63
|
+
const unsupportedFatalCount = includePreservationDiagnostics
|
|
64
|
+
? caps?.unsupportedFatalCount ??
|
|
65
|
+
featureEntries.filter((e) => e.featureClass === "unsupported-fatal").length
|
|
66
|
+
: featureEntries.filter((e) => e.featureClass === "unsupported-fatal").length;
|
|
42
67
|
let infoWarningCount = 0;
|
|
43
|
-
for (const w of
|
|
68
|
+
for (const w of visibleWarnings) {
|
|
44
69
|
if (w.severity === "info") infoWarningCount += 1;
|
|
45
70
|
}
|
|
46
|
-
const blockingWarningCount =
|
|
71
|
+
const blockingWarningCount = visibleWarnings.length - infoWarningCount;
|
|
72
|
+
const preservationCount = includePreservationDiagnostics
|
|
73
|
+
? preserveOnlyCount
|
|
74
|
+
: 0;
|
|
47
75
|
const count =
|
|
48
|
-
caps?.healthIssueCount ??
|
|
49
|
-
|
|
76
|
+
(includePreservationDiagnostics ? caps?.healthIssueCount : undefined) ??
|
|
77
|
+
preservationCount + unsupportedFatalCount + visibleWarnings.length;
|
|
50
78
|
const severity: "none" | "info" | "warning" | "blocked" =
|
|
51
79
|
snapshot.compatibility.blockExport || unsupportedFatalCount > 0
|
|
52
80
|
? "blocked"
|
|
53
|
-
:
|
|
81
|
+
: preservationCount > 0 ||
|
|
54
82
|
blockingWarningCount > 0 ||
|
|
55
83
|
blockedReasonsCount > 0
|
|
56
84
|
? "warning"
|
|
@@ -66,5 +94,6 @@ export function useDiagnosticsSignal(
|
|
|
66
94
|
caps?.healthIssueCount,
|
|
67
95
|
preserveOnlyCount,
|
|
68
96
|
blockedReasonsCount,
|
|
97
|
+
includePreservationDiagnostics,
|
|
69
98
|
]);
|
|
70
99
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import type { WordReviewEditorLayoutFacet } from "../../api/public-types.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* P3.b + P5b + P14.b — subscribe to layout facet events that should
|
|
@@ -12,7 +13,7 @@ import { useEffect, useState } from "react";
|
|
|
12
13
|
* the same synchronous tick fold into one re-render.
|
|
13
14
|
*/
|
|
14
15
|
export function useLayoutFacetRenderSignal(
|
|
15
|
-
layoutFacet:
|
|
16
|
+
layoutFacet: WordReviewEditorLayoutFacet | undefined,
|
|
16
17
|
): number {
|
|
17
18
|
const [renderFrameRevision, setRenderFrameRevision] = useState(0);
|
|
18
19
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
GeometryFacet,
|
|
5
|
+
RuntimeRenderSnapshot,
|
|
6
|
+
WordReviewEditorLayoutFacet,
|
|
7
|
+
} from "../../api/public-types.ts";
|
|
4
8
|
import {
|
|
5
9
|
resolveVisibleBlockRangesFromPageOffsets,
|
|
6
10
|
resolveVisibleBlockRangesFromPageRange,
|
|
7
11
|
resolveVisiblePageIndexRangeFromViewport,
|
|
8
|
-
useVisibleBlockRange,
|
|
9
12
|
useVisibleBlockRanges,
|
|
10
13
|
useVisiblePageIndexRange,
|
|
11
14
|
type VisiblePageIndexRange,
|
|
@@ -14,7 +17,7 @@ import {
|
|
|
14
17
|
export interface UsePageMarkersOptions {
|
|
15
18
|
pageStackScrollRoot: HTMLElement | null;
|
|
16
19
|
snapshot: RuntimeRenderSnapshot;
|
|
17
|
-
layoutFacet?:
|
|
20
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
18
21
|
geometryFacet?: GeometryFacet;
|
|
19
22
|
renderFrameRevision?: number;
|
|
20
23
|
/** CSS zoom applied to the document surface; scroll pixels are normalized by this. */
|
|
@@ -23,13 +26,6 @@ export interface UsePageMarkersOptions {
|
|
|
23
26
|
|
|
24
27
|
export interface PageMarkersResult {
|
|
25
28
|
pageMarkers: readonly HTMLElement[];
|
|
26
|
-
/**
|
|
27
|
-
* Bounding hull of {@link visibleBlockRanges}. Kept on the result for
|
|
28
|
-
* back-compat with call sites that only consumed a single range; the
|
|
29
|
-
* runtime now receives the disjoint ranges directly via
|
|
30
|
-
* `setVisibleBlockRanges`.
|
|
31
|
-
*/
|
|
32
|
-
visibleBlockRange: ReturnType<typeof useVisibleBlockRange>;
|
|
33
29
|
/** The canonical multi-interval realization set. */
|
|
34
30
|
visibleBlockRanges: ReturnType<typeof useVisibleBlockRanges>;
|
|
35
31
|
visiblePageIndexRange: ReturnType<typeof useVisiblePageIndexRange>;
|
|
@@ -81,7 +77,7 @@ function pageMarkersEqual(
|
|
|
81
77
|
function useGeometryVisiblePageIndexRange(input: {
|
|
82
78
|
scrollRoot: HTMLElement | null;
|
|
83
79
|
geometryFacet?: GeometryFacet;
|
|
84
|
-
layoutFacet?:
|
|
80
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
85
81
|
pageMarkerCount: number;
|
|
86
82
|
overscanPages: number;
|
|
87
83
|
renderFrameRevision?: number;
|
|
@@ -180,6 +176,13 @@ function useGeometryVisiblePageIndexRange(input: {
|
|
|
180
176
|
return range;
|
|
181
177
|
}
|
|
182
178
|
|
|
179
|
+
export function shouldUseCompatibilityPageMarkerScan(input: {
|
|
180
|
+
geometryFacet?: GeometryFacet;
|
|
181
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
182
|
+
}): boolean {
|
|
183
|
+
return !input.geometryFacet || !input.layoutFacet;
|
|
184
|
+
}
|
|
185
|
+
|
|
183
186
|
/**
|
|
184
187
|
* L7 Phase 2 Task 2.2.4a — viewport-scroll wiring.
|
|
185
188
|
*
|
|
@@ -212,7 +215,7 @@ export function usePageMarkers(options: UsePageMarkersOptions): PageMarkersResul
|
|
|
212
215
|
const [pageMarkers, setPageMarkers] = useState<readonly HTMLElement[]>([]);
|
|
213
216
|
|
|
214
217
|
useEffect(() => {
|
|
215
|
-
if (geometryFacet
|
|
218
|
+
if (!shouldUseCompatibilityPageMarkerScan({ geometryFacet, layoutFacet })) {
|
|
216
219
|
// Warm path: render-kernel geometry gives us page frames and the layout
|
|
217
220
|
// facet gives us page offsets, so DOM page-break markers are only legacy
|
|
218
221
|
// fallback. Skipping the marker scan avoids querySelectorAll +
|
|
@@ -385,19 +388,6 @@ export function usePageMarkers(options: UsePageMarkersOptions): PageMarkersResul
|
|
|
385
388
|
visibleBlockRangesFromPageRange ??
|
|
386
389
|
markerVisibleBlockRanges;
|
|
387
390
|
|
|
388
|
-
// Bounding hull of the disjoint ranges for the back-compat `visibleBlockRange`
|
|
389
|
-
// result field + for the effect's dep key (below).
|
|
390
|
-
const visibleBlockRange = useMemo(() => {
|
|
391
|
-
if (visibleBlockRanges.length === 0) return { start: 0, end: 0 };
|
|
392
|
-
let start = visibleBlockRanges[0]!.start;
|
|
393
|
-
let end = visibleBlockRanges[0]!.end;
|
|
394
|
-
for (let i = 1; i < visibleBlockRanges.length; i += 1) {
|
|
395
|
-
if (visibleBlockRanges[i]!.start < start) start = visibleBlockRanges[i]!.start;
|
|
396
|
-
if (visibleBlockRanges[i]!.end > end) end = visibleBlockRanges[i]!.end;
|
|
397
|
-
}
|
|
398
|
-
return { start, end };
|
|
399
|
-
}, [visibleBlockRanges]);
|
|
400
|
-
|
|
401
391
|
// Stable fingerprint of the current disjoint-range set; used as the effect
|
|
402
392
|
// dep so identity-preserving recomputes (same intervals, new memo array
|
|
403
393
|
// reference) don't re-fire the viewport refresh.
|
|
@@ -423,7 +413,6 @@ export function usePageMarkers(options: UsePageMarkersOptions): PageMarkersResul
|
|
|
423
413
|
|
|
424
414
|
return {
|
|
425
415
|
pageMarkers,
|
|
426
|
-
visibleBlockRange,
|
|
427
416
|
visibleBlockRanges,
|
|
428
417
|
visiblePageIndexRange,
|
|
429
418
|
};
|
|
@@ -3,8 +3,11 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
3
3
|
import type {
|
|
4
4
|
EditorAnchorProjection,
|
|
5
5
|
ScopeIssueAction,
|
|
6
|
+
ScopeCardModel,
|
|
7
|
+
WorkflowFacet,
|
|
6
8
|
WorkflowScopeMode,
|
|
7
9
|
} from "../../api/public-types.ts";
|
|
10
|
+
import type { ApiV3Ui } from "../../api/v3/ui";
|
|
8
11
|
import {
|
|
9
12
|
cycleScopeIndex,
|
|
10
13
|
shouldHandleScopeNavKey,
|
|
@@ -12,12 +15,12 @@ import {
|
|
|
12
15
|
import { useUiApi } from "../ui-api-context.tsx";
|
|
13
16
|
|
|
14
17
|
export interface UseScopeCardStateOptions {
|
|
15
|
-
layoutFacet?: import("../../runtime/layout/index.ts").WordReviewEditorLayoutFacet;
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
* Mounted paths
|
|
19
|
+
* Compatibility-only fallback for no-provider tests/headless mounts.
|
|
20
|
+
* Mounted editor paths must read scope identity + card models through
|
|
21
|
+
* `api.ui.scope.rail/card`.
|
|
19
22
|
*/
|
|
20
|
-
workflowFacet?:
|
|
23
|
+
workflowFacet?: WorkflowFacet;
|
|
21
24
|
onScopeModeChangeRequested?: (payload: {
|
|
22
25
|
scopeId: string;
|
|
23
26
|
mode: WorkflowScopeMode;
|
|
@@ -68,7 +71,6 @@ export interface ScopeCardState {
|
|
|
68
71
|
*/
|
|
69
72
|
export function useScopeCardState(options: UseScopeCardStateOptions): ScopeCardState {
|
|
70
73
|
const {
|
|
71
|
-
layoutFacet,
|
|
72
74
|
workflowFacet,
|
|
73
75
|
onScopeModeChangeRequested,
|
|
74
76
|
onScopeIssueActionRequested,
|
|
@@ -76,31 +78,21 @@ export function useScopeCardState(options: UseScopeCardStateOptions): ScopeCardS
|
|
|
76
78
|
onScopeRejectSuggestionGroup,
|
|
77
79
|
onScopeAskAgent,
|
|
78
80
|
} = options;
|
|
79
|
-
void layoutFacet;
|
|
80
81
|
|
|
81
82
|
const [activeScopeId, setActiveScopeId] = useState<string | null>(null);
|
|
82
83
|
const ui = useUiApi();
|
|
83
84
|
|
|
84
85
|
const readScopeIds = useCallback((): string[] => {
|
|
85
86
|
if (ui) {
|
|
86
|
-
|
|
87
|
-
const seen = new Set<string>();
|
|
88
|
-
for (const segment of ui.scope.rail().segments) {
|
|
89
|
-
if (seen.has(segment.scopeId)) continue;
|
|
90
|
-
seen.add(segment.scopeId);
|
|
91
|
-
ids.push(segment.scopeId);
|
|
92
|
-
}
|
|
93
|
-
return ids;
|
|
87
|
+
return readMountedScopeIdsFromUi(ui);
|
|
94
88
|
}
|
|
95
|
-
return workflowFacet
|
|
89
|
+
return readCompatibilityScopeIdsFromWorkflowFacet(workflowFacet);
|
|
96
90
|
}, [ui, workflowFacet]);
|
|
97
91
|
|
|
98
92
|
const readScopeCard = useCallback(
|
|
99
93
|
(scopeId: string) => {
|
|
100
94
|
if (ui) return ui.scope.card(scopeId);
|
|
101
|
-
return workflowFacet
|
|
102
|
-
?.getAllScopeCardModels()
|
|
103
|
-
.find((m) => m.scopeId === scopeId) ?? null;
|
|
95
|
+
return readCompatibilityScopeCardFromWorkflowFacet(workflowFacet, scopeId);
|
|
104
96
|
},
|
|
105
97
|
[ui, workflowFacet],
|
|
106
98
|
);
|
|
@@ -190,3 +182,28 @@ export function useScopeCardState(options: UseScopeCardStateOptions): ScopeCardS
|
|
|
190
182
|
handleScopeCardAskAgent,
|
|
191
183
|
};
|
|
192
184
|
}
|
|
185
|
+
|
|
186
|
+
function readMountedScopeIdsFromUi(ui: ApiV3Ui): string[] {
|
|
187
|
+
const ids: string[] = [];
|
|
188
|
+
const seen = new Set<string>();
|
|
189
|
+
for (const scope of ui.scope.list()) {
|
|
190
|
+
const scopeId = scope.handle.scopeId;
|
|
191
|
+
if (seen.has(scopeId)) continue;
|
|
192
|
+
seen.add(scopeId);
|
|
193
|
+
ids.push(scopeId);
|
|
194
|
+
}
|
|
195
|
+
return ids;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readCompatibilityScopeIdsFromWorkflowFacet(
|
|
199
|
+
workflowFacet: WorkflowFacet | undefined,
|
|
200
|
+
): string[] {
|
|
201
|
+
return workflowFacet?.getAllScopeCardModels().map((model) => model.scopeId) ?? [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readCompatibilityScopeCardFromWorkflowFacet(
|
|
205
|
+
workflowFacet: WorkflowFacet | undefined,
|
|
206
|
+
scopeId: string,
|
|
207
|
+
): ScopeCardModel | null {
|
|
208
|
+
return workflowFacet?.getAllScopeCardModels().find((m) => m.scopeId === scopeId) ?? null;
|
|
209
|
+
}
|
|
@@ -22,33 +22,24 @@ export interface UseSelectionToolbarPlacementOptions {
|
|
|
22
22
|
/**
|
|
23
23
|
* Compute placement for the floating selection toolbar.
|
|
24
24
|
*
|
|
25
|
-
* Resolution order
|
|
25
|
+
* Resolution order:
|
|
26
26
|
*
|
|
27
|
-
* 1.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* viewport-to-container translation + zoom scaling. It was the
|
|
31
|
-
* original pre-DS-C1 path, is demonstrably correct, and is
|
|
32
|
-
* populated every render by the shell — so it does not race with
|
|
33
|
-
* bridge publication order.
|
|
27
|
+
* 1. `ui.overlays.getAnchor({ kind: "selection" })` when the mounted
|
|
28
|
+
* `UiApiProvider` and shell bridge can resolve a frame-local
|
|
29
|
+
* selection anchor. This is the DS-C1 steady-state path.
|
|
34
30
|
*
|
|
35
|
-
* 2. `
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* placement path). Until that projection lands, this branch is
|
|
41
|
-
* best-effort for consumers that don't supply the legacy prop —
|
|
42
|
-
* e.g. future headless / Playwright drivers.
|
|
31
|
+
* 2. Legacy `resolveSelectionToolbarPlacement` driven by the
|
|
32
|
+
* host-supplied `selectionToolAnchor` prop + toolbar root
|
|
33
|
+
* `getBoundingClientRect()`. This remains the compatibility
|
|
34
|
+
* fallback for unmounted/headless paths, cold bridge frames, and
|
|
35
|
+
* legacy callers that have not adopted the UI API seam.
|
|
43
36
|
*
|
|
44
|
-
* DS-C1 (designsystem.md §8.8.1 "Selection toolbar" row)
|
|
45
|
-
*
|
|
46
|
-
* (`useShellSelectionAnchorBridge`)
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* flicker-remediation concession: same source of truth, correct
|
|
51
|
-
* coordinate space.
|
|
37
|
+
* DS-C1 (designsystem.md §8.8.1 "Selection toolbar" row) routes anchor
|
|
38
|
+
* reads through the UI API. The shell bridge
|
|
39
|
+
* (`useShellSelectionAnchorBridge`) publishes tool-aware frame rects on
|
|
40
|
+
* every render; the toolbar root is the same frame-local positioning
|
|
41
|
+
* container, so `resolveSelectionToolPlacement` can consume the rect
|
|
42
|
+
* without a DOM anchor re-measure.
|
|
52
43
|
*
|
|
53
44
|
* The memo re-runs when the geometry facet bumps `renderFrameRevision`
|
|
54
45
|
* so placement tracks new rects without a separate subscription here.
|
|
@@ -67,19 +58,19 @@ export function useSelectionToolbarPlacement(
|
|
|
67
58
|
const ui = useUiApi();
|
|
68
59
|
|
|
69
60
|
return useMemo(() => {
|
|
70
|
-
// Primary: legacy DOM-anchor path. Correct coord space; no bridge
|
|
71
|
-
// timing dependency.
|
|
72
61
|
const legacy = resolveSelectionToolbarPlacement(
|
|
73
62
|
selectionToolAnchor,
|
|
74
63
|
selectionToolbarRootRef.current,
|
|
75
64
|
zoomScale,
|
|
76
65
|
);
|
|
77
|
-
if (legacy) return legacy;
|
|
78
66
|
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
67
|
+
// Primary: mounted UI API anchor seam. The bridge returns frame-local
|
|
68
|
+
// px and the toolbar root is the frame-local absolute positioning
|
|
69
|
+
// container, so only the container size comes from DOM. If the bridge
|
|
70
|
+
// returns a broad frame anchor that would force above/below placement
|
|
71
|
+
// while the legacy measured text rect can still side-place the toolbar,
|
|
72
|
+
// keep the side placement as the precision fallback until all selection
|
|
73
|
+
// anchors are per-range precise.
|
|
83
74
|
if (ui && gatedSelectionTool) {
|
|
84
75
|
const anchorRect = ui.overlays.getAnchor({ kind: "selection" });
|
|
85
76
|
if (anchorRect && selectionToolbarRootRef.current) {
|
|
@@ -97,9 +88,27 @@ export function useSelectionToolbarPlacement(
|
|
|
97
88
|
heightPx: containerRect.height,
|
|
98
89
|
},
|
|
99
90
|
});
|
|
100
|
-
if (result)
|
|
91
|
+
if (result) {
|
|
92
|
+
if (
|
|
93
|
+
legacy &&
|
|
94
|
+
(result.placement === "above" || result.placement === "below") &&
|
|
95
|
+
(legacy.placement === "right" || legacy.placement === "left")
|
|
96
|
+
) {
|
|
97
|
+
return legacy;
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
// Compatibility fallback: legacy DOM-anchor prop for bridge-cold
|
|
105
|
+
// mounted frames and non-UI-API consumers. Removal trigger:
|
|
106
|
+
// delete this path once `UiApiProvider` is mandatory for every
|
|
107
|
+
// production WordReviewEditor mount and the shell selection anchor
|
|
108
|
+
// bridge has no cold-frame gap (tracked in L10 backlog follow-up
|
|
109
|
+
// "selection-toolbar legacy DOM-anchor fallback").
|
|
110
|
+
if (legacy) return legacy;
|
|
111
|
+
|
|
103
112
|
return null;
|
|
104
113
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
114
|
}, [
|
|
@@ -3,6 +3,7 @@ import { useMemo } from "react";
|
|
|
3
3
|
import type {
|
|
4
4
|
PublicMeasurementFidelity,
|
|
5
5
|
RuntimeRenderSnapshot,
|
|
6
|
+
WordReviewEditorLayoutFacet,
|
|
6
7
|
} from "../../api/public-types.ts";
|
|
7
8
|
|
|
8
9
|
export interface StatusBarPageFacts {
|
|
@@ -12,7 +13,7 @@ export interface StatusBarPageFacts {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface UseStatusBarPageFactsOptions {
|
|
15
|
-
layoutFacet?:
|
|
16
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
16
17
|
selectionPosition: number;
|
|
17
18
|
activeStory: RuntimeRenderSnapshot["activeStory"];
|
|
18
19
|
renderFrameRevision: number;
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect } from "react";
|
|
|
2
2
|
import type { Dispatch, SetStateAction } from "react";
|
|
3
3
|
|
|
4
4
|
import { createCanvasBackend } from "../../api/public-types.ts";
|
|
5
|
+
import type { WordReviewEditorLayoutFacet } from "../../api/public-types.ts";
|
|
5
6
|
import {
|
|
6
7
|
incrementInvalidationCounter,
|
|
7
8
|
recordPerfSample,
|
|
@@ -12,7 +13,7 @@ import type { PageChromeModel } from "./page-chrome.ts";
|
|
|
12
13
|
import type { PageShellMetrics } from "./page-shell-metrics.ts";
|
|
13
14
|
|
|
14
15
|
export interface UseWorkspaceSideEffectsOptions {
|
|
15
|
-
layoutFacet?:
|
|
16
|
+
layoutFacet?: WordReviewEditorLayoutFacet;
|
|
16
17
|
activeParagraphLayout: ActiveParagraphLayout | null;
|
|
17
18
|
pageChromeModel: PageChromeModel;
|
|
18
19
|
pageShellMetrics: PageShellMetrics;
|
|
@@ -35,9 +35,9 @@ export interface TwStatusBarProps {
|
|
|
35
35
|
measurementFidelity?: PublicMeasurementFidelity;
|
|
36
36
|
/**
|
|
37
37
|
* Lane 6b §6b.S4: opt-in diagnostics flag. When `true`, the status
|
|
38
|
-
* bar
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* bar may reveal debug/operator-only diagnostics such as measurement
|
|
39
|
+
* fidelity and preserve-only export warnings. Default product chrome
|
|
40
|
+
* keeps opaque/deferred preservation silent.
|
|
41
41
|
*/
|
|
42
42
|
debugMode?: boolean;
|
|
43
43
|
/**
|
|
@@ -67,9 +67,10 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
67
67
|
: props.isDirty
|
|
68
68
|
? "Unsaved"
|
|
69
69
|
: "Ready";
|
|
70
|
+
const showPreservationDiagnostics = props.debugMode === true;
|
|
70
71
|
const exportState = props.isExportBlocked
|
|
71
72
|
? "Blocked"
|
|
72
|
-
: props.preserveOnlyCount > 0
|
|
73
|
+
: showPreservationDiagnostics && props.preserveOnlyCount > 0
|
|
73
74
|
? "Warnings"
|
|
74
75
|
: "Ready";
|
|
75
76
|
|
|
@@ -80,7 +81,7 @@ export function TwStatusBar(props: TwStatusBarProps) {
|
|
|
80
81
|
: "bg-[var(--color-status-ready)]";
|
|
81
82
|
const exportDotColor = props.isExportBlocked
|
|
82
83
|
? "bg-[var(--color-status-blocked)]"
|
|
83
|
-
: props.preserveOnlyCount > 0
|
|
84
|
+
: showPreservationDiagnostics && props.preserveOnlyCount > 0
|
|
84
85
|
? "bg-[var(--color-semantic-warning)]"
|
|
85
86
|
: "bg-[var(--color-status-ready)]";
|
|
86
87
|
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* traversal + claim/skip/complete.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import React, {
|
|
17
|
-
import
|
|
16
|
+
import React, { useState } from "react";
|
|
17
|
+
import * as Popover from "@radix-ui/react-popover";
|
|
18
18
|
import * as Toggle from "@radix-ui/react-toggle";
|
|
19
19
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
20
20
|
import {
|
|
@@ -154,6 +154,9 @@ export function TwRoleActionRegion(
|
|
|
154
154
|
data-testid={props["data-testid"] ?? `role-action-region-${props.role}`}
|
|
155
155
|
data-role={props.role}
|
|
156
156
|
>
|
|
157
|
+
{props.role === "workflow" && props.workflowItem !== undefined ? (
|
|
158
|
+
<WorkflowActiveLabel item={props.workflowItem} />
|
|
159
|
+
) : null}
|
|
157
160
|
{inlineIds.map((id) => (
|
|
158
161
|
<RoleActionButton
|
|
159
162
|
key={id}
|
|
@@ -177,13 +180,14 @@ function isRoleActionRenderable(
|
|
|
177
180
|
switch (id) {
|
|
178
181
|
case "review-queue-prev":
|
|
179
182
|
case "review-queue-next":
|
|
180
|
-
case "review-queue-counts":
|
|
181
|
-
case "review-queue-active-label":
|
|
182
183
|
case "review-accept":
|
|
183
184
|
case "review-reject":
|
|
184
185
|
case "review-accept-all":
|
|
185
186
|
case "review-reject-all":
|
|
186
187
|
return reviewQueueTotal > 0;
|
|
188
|
+
case "review-queue-counts":
|
|
189
|
+
case "review-queue-active-label":
|
|
190
|
+
return props.reviewQueue !== undefined;
|
|
187
191
|
case "workflow-prev":
|
|
188
192
|
case "workflow-next":
|
|
189
193
|
case "workflow-mark-complete":
|
|
@@ -433,100 +437,38 @@ function RoleActionOverflow({
|
|
|
433
437
|
props,
|
|
434
438
|
}: RoleActionOverflowProps): React.JSX.Element {
|
|
435
439
|
const [open, setOpen] = useState(false);
|
|
436
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
437
440
|
|
|
438
441
|
return (
|
|
439
|
-
|
|
442
|
+
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
443
|
+
<Popover.Trigger asChild>
|
|
440
444
|
<button
|
|
441
|
-
ref={triggerRef}
|
|
442
445
|
type="button"
|
|
443
446
|
aria-label="More role actions"
|
|
444
447
|
aria-expanded={open}
|
|
445
|
-
aria-haspopup="menu"
|
|
446
448
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
447
|
-
onClick={(event) => {
|
|
448
|
-
event.preventDefault();
|
|
449
|
-
setOpen((value) => !value);
|
|
450
|
-
}}
|
|
451
449
|
title="More role actions"
|
|
452
|
-
className=
|
|
453
|
-
open ? "text-accent ring-1 ring-accent/30 shadow-sm" : ""
|
|
454
|
-
}`}
|
|
450
|
+
className="inline-flex h-6 w-6 items-center justify-center rounded-md border border-border bg-canvas text-primary transition-colors hover:bg-surface outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-canvas"
|
|
455
451
|
data-testid="role-action-overflow-trigger"
|
|
456
452
|
>
|
|
457
453
|
<MoreHorizontal className="h-3.5 w-3.5 text-tertiary" aria-hidden="true" />
|
|
458
454
|
</button>
|
|
459
|
-
|
|
455
|
+
</Popover.Trigger>
|
|
456
|
+
<Popover.Portal>
|
|
457
|
+
<Popover.Content
|
|
458
|
+
className="z-50 w-[220px] rounded-lg bg-canvas p-1 shadow-lg ring-1 ring-border"
|
|
459
|
+
sideOffset={8}
|
|
460
|
+
align="start"
|
|
461
|
+
data-testid="role-action-overflow-content"
|
|
462
|
+
>
|
|
460
463
|
{ids.map((id) => (
|
|
461
464
|
<OverflowAction key={id} id={id} props={props} onClose={() => setOpen(false)} />
|
|
462
465
|
))}
|
|
463
|
-
|
|
464
|
-
|
|
466
|
+
</Popover.Content>
|
|
467
|
+
</Popover.Portal>
|
|
468
|
+
</Popover.Root>
|
|
465
469
|
);
|
|
466
470
|
}
|
|
467
471
|
|
|
468
|
-
function RoleActionPortalMenu(props: {
|
|
469
|
-
anchorRef: React.RefObject<HTMLButtonElement | null>;
|
|
470
|
-
children: React.ReactNode;
|
|
471
|
-
open: boolean;
|
|
472
|
-
}): React.ReactPortal | null {
|
|
473
|
-
const style = useRoleActionPortalPosition(props.anchorRef, props.open);
|
|
474
|
-
const body = props.anchorRef.current?.ownerDocument?.body;
|
|
475
|
-
if (!props.open || !body) return null;
|
|
476
|
-
return createPortal(
|
|
477
|
-
<div
|
|
478
|
-
className="z-50 w-[220px] rounded-lg bg-canvas p-1 shadow-lg ring-1 ring-border"
|
|
479
|
-
data-testid="role-action-overflow-content"
|
|
480
|
-
style={style}
|
|
481
|
-
>
|
|
482
|
-
{props.children}
|
|
483
|
-
</div>,
|
|
484
|
-
body,
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function useRoleActionPortalPosition(
|
|
489
|
-
anchorRef: React.RefObject<HTMLButtonElement | null>,
|
|
490
|
-
open: boolean,
|
|
491
|
-
): CSSProperties {
|
|
492
|
-
const [style, setStyle] = useState<CSSProperties>({
|
|
493
|
-
left: 8,
|
|
494
|
-
position: "fixed",
|
|
495
|
-
top: 8,
|
|
496
|
-
zIndex: 50,
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
useLayoutEffect(() => {
|
|
500
|
-
if (!open) return;
|
|
501
|
-
const anchor = anchorRef.current;
|
|
502
|
-
const ownerWindow = anchor?.ownerDocument?.defaultView;
|
|
503
|
-
if (!anchor || !ownerWindow) return;
|
|
504
|
-
const update = () => {
|
|
505
|
-
const rect = anchor.getBoundingClientRect();
|
|
506
|
-
const width = 220;
|
|
507
|
-
const left = Math.min(
|
|
508
|
-
Math.max(8, rect.left),
|
|
509
|
-
Math.max(8, (ownerWindow.innerWidth || width + 16) - width - 8),
|
|
510
|
-
);
|
|
511
|
-
setStyle({
|
|
512
|
-
left,
|
|
513
|
-
position: "fixed",
|
|
514
|
-
top: Math.max(8, rect.bottom + 8),
|
|
515
|
-
zIndex: 50,
|
|
516
|
-
});
|
|
517
|
-
};
|
|
518
|
-
update();
|
|
519
|
-
ownerWindow.addEventListener("resize", update);
|
|
520
|
-
ownerWindow.addEventListener("scroll", update, true);
|
|
521
|
-
return () => {
|
|
522
|
-
ownerWindow.removeEventListener("resize", update);
|
|
523
|
-
ownerWindow.removeEventListener("scroll", update, true);
|
|
524
|
-
};
|
|
525
|
-
}, [anchorRef, open]);
|
|
526
|
-
|
|
527
|
-
return style;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
472
|
function OverflowAction(arg: {
|
|
531
473
|
id: ToolbarChromeItemId;
|
|
532
474
|
props: TwRoleActionRegionProps;
|
|
@@ -755,4 +697,34 @@ function ReviewActiveLabel({
|
|
|
755
697
|
);
|
|
756
698
|
}
|
|
757
699
|
|
|
700
|
+
function WorkflowActiveLabel({
|
|
701
|
+
item,
|
|
702
|
+
}: {
|
|
703
|
+
item: WorkflowWorkItemSnapshot | null;
|
|
704
|
+
}): React.JSX.Element {
|
|
705
|
+
const status = item?.status ?? "idle";
|
|
706
|
+
const statusLabel =
|
|
707
|
+
status === "done"
|
|
708
|
+
? "Done"
|
|
709
|
+
: status === "blocked"
|
|
710
|
+
? "Blocked"
|
|
711
|
+
: status === "active"
|
|
712
|
+
? "Active"
|
|
713
|
+
: status === "pending"
|
|
714
|
+
? "Pending"
|
|
715
|
+
: "Idle";
|
|
716
|
+
|
|
717
|
+
return (
|
|
718
|
+
<span
|
|
719
|
+
className="inline-flex min-w-0 items-center gap-1.5 truncate text-[11px] text-primary"
|
|
720
|
+
data-testid="role-workflow-active-label"
|
|
721
|
+
>
|
|
722
|
+
<span className="shrink-0 rounded-full bg-canvas px-1.5 py-0.5 text-[9px] font-medium text-secondary ring-1 ring-border/50">
|
|
723
|
+
{statusLabel}
|
|
724
|
+
</span>
|
|
725
|
+
<span className="truncate">{item?.title ?? "No active work item"}</span>
|
|
726
|
+
</span>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
758
730
|
export default TwRoleActionRegion;
|