@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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
export type LayerDebugPaneId =
|
|
2
|
+
| "01"
|
|
3
|
+
| "02"
|
|
4
|
+
| "03"
|
|
5
|
+
| "04"
|
|
6
|
+
| "05"
|
|
7
|
+
| "06"
|
|
8
|
+
| "07"
|
|
9
|
+
| "08"
|
|
10
|
+
| "09"
|
|
11
|
+
| "10"
|
|
12
|
+
| "11";
|
|
13
|
+
|
|
14
|
+
export type LayerDebugSnippet = {
|
|
15
|
+
label: string;
|
|
16
|
+
expression: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type LayerDebugPaneDefinition = {
|
|
20
|
+
id: LayerDebugPaneId;
|
|
21
|
+
label: string;
|
|
22
|
+
owner: string;
|
|
23
|
+
focus: string;
|
|
24
|
+
snippets: readonly LayerDebugSnippet[];
|
|
25
|
+
routed: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const LAYER_DEBUG_PANES: readonly LayerDebugPaneDefinition[] = [
|
|
29
|
+
{
|
|
30
|
+
id: "01",
|
|
31
|
+
label: "Package / session",
|
|
32
|
+
owner: "Layer 01",
|
|
33
|
+
focus: "Source package posture, session lifecycle, export and validation proof signals.",
|
|
34
|
+
snippets: [
|
|
35
|
+
{ label: "Runtime view", expression: "ref?.getViewState?.()" },
|
|
36
|
+
{ label: "Compatibility", expression: "ref?.getCompatibilityReport?.()" },
|
|
37
|
+
{ label: "Navigation", expression: "ref?.getDocumentNavigationSnapshot?.()" },
|
|
38
|
+
],
|
|
39
|
+
routed: [
|
|
40
|
+
"Durable source/session ids, load revision, and export ids live in the harness session client.",
|
|
41
|
+
"Package ownership and reconstitution internals remain Layer 01-owned.",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "02",
|
|
46
|
+
label: "Canonical document",
|
|
47
|
+
owner: "Layer 02",
|
|
48
|
+
focus: "Canonical review state projected from comments, revisions, suggestions, and compatibility.",
|
|
49
|
+
snippets: [
|
|
50
|
+
{ label: "Comments", expression: "ref?.getCommentSidebarSnapshot?.()" },
|
|
51
|
+
{ label: "Tracked changes", expression: "ref?.getTrackedChangesSnapshot?.()" },
|
|
52
|
+
{ label: "Suggestions", expression: "ref?.getSuggestionsSnapshot?.()" },
|
|
53
|
+
],
|
|
54
|
+
routed: [
|
|
55
|
+
"Canonical block tree summaries are Layer 02-owned and should not be inferred from rendered DOM.",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "03",
|
|
60
|
+
label: "Formatting semantics",
|
|
61
|
+
owner: "Layer 03",
|
|
62
|
+
focus: "Inline, paragraph, style catalog, and formatting-state readbacks.",
|
|
63
|
+
snippets: [
|
|
64
|
+
{ label: "Formatting state", expression: "ref?.getFormattingState?.()" },
|
|
65
|
+
{ label: "Style catalog", expression: "ref?.getStyleCatalog?.()" },
|
|
66
|
+
{ label: "View mode", expression: "ref?.getViewState?.().documentMode" },
|
|
67
|
+
],
|
|
68
|
+
routed: [
|
|
69
|
+
"Formatting provenance and cascade explanations are Layer 03-owned.",
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "04",
|
|
74
|
+
label: "Layout semantics",
|
|
75
|
+
owner: "Layer 04",
|
|
76
|
+
focus: "Page, section, story, note, region, and navigation readbacks.",
|
|
77
|
+
snippets: [
|
|
78
|
+
{ label: "Navigation", expression: "ref?.getDocumentNavigationSnapshot?.()" },
|
|
79
|
+
{ label: "View state", expression: "ref?.getViewState?.()" },
|
|
80
|
+
{ label: "Active story", expression: "ref?.getViewState?.().activeStory" },
|
|
81
|
+
],
|
|
82
|
+
routed: [
|
|
83
|
+
"Section geometry, headers/footers, and twip layout internals are Layer 04-owned.",
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "05",
|
|
88
|
+
label: "Geometry projection",
|
|
89
|
+
owner: "Layer 05",
|
|
90
|
+
focus: "Selection range, object frame, caret, measurement, and page-region projection.",
|
|
91
|
+
snippets: [
|
|
92
|
+
{ label: "Selection", expression: "ref?.getViewState?.().selection" },
|
|
93
|
+
{ label: "Measurement", expression: "ref?.getViewState?.().measurement" },
|
|
94
|
+
{ label: "Object frame", expression: "ref?.getViewState?.().activeObjectFrame" },
|
|
95
|
+
],
|
|
96
|
+
routed: [
|
|
97
|
+
"BBox/page-local geometry indexes and object handle ledgers are Layer 05-owned.",
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "06",
|
|
102
|
+
label: "Workflow review",
|
|
103
|
+
owner: "Layer 06",
|
|
104
|
+
focus: "Review queues, suggestions, blocked reasons, and workflow guard posture.",
|
|
105
|
+
snippets: [
|
|
106
|
+
{ label: "Guard", expression: "ref?.getInteractionGuardSnapshot?.()" },
|
|
107
|
+
{ label: "Suggestions", expression: "ref?.getSuggestionsSnapshot?.()" },
|
|
108
|
+
{ label: "Comments", expression: "ref?.getCommentSidebarSnapshot?.()" },
|
|
109
|
+
],
|
|
110
|
+
routed: [
|
|
111
|
+
"Workflow session mutation persistence and policy inputs are harness/Layer 06 integration-owned.",
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "07",
|
|
116
|
+
label: "Runtime API",
|
|
117
|
+
owner: "Layer 07",
|
|
118
|
+
focus: "Public ref snapshots, context analytics, runtime events, and API readback shape.",
|
|
119
|
+
snippets: [
|
|
120
|
+
{ label: "Selection context", expression: "ref?.getRuntimeContextAnalytics?.()" },
|
|
121
|
+
{ label: "Document context", expression: "ref?.getRuntimeContextAnalytics?.({ scopeKind: 'document' })" },
|
|
122
|
+
{ label: "View state", expression: "ref?.getViewState?.()" },
|
|
123
|
+
],
|
|
124
|
+
routed: [
|
|
125
|
+
"Command traces and v3 UxResponse event tails are Layer 07/debug-infra-owned.",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "08",
|
|
130
|
+
label: "Semantic scopes",
|
|
131
|
+
owner: "Layer 08",
|
|
132
|
+
focus: "Workflow scopes, markup, metadata, candidates, and scope-derived context.",
|
|
133
|
+
snippets: [
|
|
134
|
+
{ label: "Scope snapshot", expression: "ref?.getWorkflowScopeSnapshot?.()" },
|
|
135
|
+
{ label: "Query scopes", expression: "ref?.queryScopes?.({ includeHidden: true, includeInvisible: true })" },
|
|
136
|
+
{ label: "Workflow markup", expression: "ref?.getWorkflowMarkupSnapshot?.()" },
|
|
137
|
+
],
|
|
138
|
+
routed: [
|
|
139
|
+
"Compiler replacement planning and semantic scope normalization remain Layer 08-owned.",
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "09",
|
|
144
|
+
label: "AI API",
|
|
145
|
+
owner: "Layer 09",
|
|
146
|
+
focus: "Agent/AI action context, runtime analytics, next actions, and unavailable provenance.",
|
|
147
|
+
snippets: [
|
|
148
|
+
{ label: "Document analytics", expression: "ref?.getRuntimeContextAnalytics?.({ scopeKind: 'document' })" },
|
|
149
|
+
{ label: "Workflow analytics", expression: "ref?.getRuntimeContextAnalytics?.({ scopeKind: 'workflow_scope', scopeId: ref?.getInteractionGuardSnapshot?.()?.matchedScopeId })" },
|
|
150
|
+
{ label: "Available scopes", expression: "ref?.queryScopes?.({ includeHidden: true, includeInvisible: true })" },
|
|
151
|
+
],
|
|
152
|
+
routed: [
|
|
153
|
+
"AI audit references and action policy payloads are Layer 09-owned.",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "10",
|
|
158
|
+
label: "UI API",
|
|
159
|
+
owner: "Layer 10",
|
|
160
|
+
focus: "UI-facing state snapshots, selection posture, formatting controls, and style catalog.",
|
|
161
|
+
snippets: [
|
|
162
|
+
{ label: "View state", expression: "ref?.getViewState?.()" },
|
|
163
|
+
{ label: "Formatting state", expression: "ref?.getFormattingState?.()" },
|
|
164
|
+
{ label: "Style catalog", expression: "ref?.getStyleCatalog?.()" },
|
|
165
|
+
],
|
|
166
|
+
routed: [
|
|
167
|
+
"UI API command-surface contracts and chrome composition policy are Layer 10/chrome-owned.",
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "11",
|
|
172
|
+
label: "Presentation surfaces",
|
|
173
|
+
owner: "Layer 11",
|
|
174
|
+
focus: "Mounted surface posture, review presentation, compatibility, and navigation readbacks.",
|
|
175
|
+
snippets: [
|
|
176
|
+
{ label: "Navigation", expression: "ref?.getDocumentNavigationSnapshot?.()" },
|
|
177
|
+
{ label: "Review surface", expression: "({ comments: ref?.getCommentSidebarSnapshot?.(), changes: ref?.getTrackedChangesSnapshot?.() })" },
|
|
178
|
+
{ label: "Compatibility", expression: "ref?.getCompatibilityReport?.()" },
|
|
179
|
+
],
|
|
180
|
+
routed: [
|
|
181
|
+
"Visual screenshots, surface inventory, and presentation composition remain Layer 11/chrome-owned.",
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
] as const;
|
|
185
|
+
|
|
186
|
+
export function getLayerDebugPane(id: LayerDebugPaneId): LayerDebugPaneDefinition {
|
|
187
|
+
return LAYER_DEBUG_PANES.find((pane) => pane.id === id) ?? LAYER_DEBUG_PANES[0];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function getLayerDebugShortcutLabel(id: LayerDebugPaneId): string {
|
|
191
|
+
if (id === "10") return "Alt+0";
|
|
192
|
+
if (id === "11") return "Alt+-";
|
|
193
|
+
return `Alt+${Number(id)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getLayerDebugIdFromShortcut(
|
|
197
|
+
event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "metaKey" | "shiftKey" | "key">,
|
|
198
|
+
): LayerDebugPaneId | null {
|
|
199
|
+
if (!event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
if (/^[1-9]$/.test(event.key)) {
|
|
203
|
+
return `0${event.key}` as LayerDebugPaneId;
|
|
204
|
+
}
|
|
205
|
+
if (event.key === "0") return "10";
|
|
206
|
+
if (event.key === "-") return "11";
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
* - hyperlink → `<a>` tag.
|
|
34
34
|
* - opaque-block → `data-block-kind="opaque"`
|
|
35
35
|
* added to `TwOpaqueBlock` in Phase D.5 as the explicit marker.
|
|
36
|
+
* - template-slot → `data-node-type="sdt_block"` OR
|
|
37
|
+
* `data-block-kind="sdt"` / `data-sdt-type`
|
|
38
|
+
* emitted by the PM/page-stack SDT renderers.
|
|
36
39
|
*
|
|
37
40
|
* Multiple kinds can apply — a table cell that contains a hyperlink
|
|
38
41
|
* returns both `table-cell` and `hyperlink`. Caller merges the
|
|
@@ -215,6 +218,16 @@ export function resolveTargetKind(
|
|
|
215
218
|
if (hasAncestorAttributeValue(el, "data-block-kind", "opaque", root)) {
|
|
216
219
|
kinds.push("opaque-block");
|
|
217
220
|
}
|
|
221
|
+
|
|
222
|
+
// SDT / content-control template slots. These are user-facing chrome
|
|
223
|
+
// affordances only; import/export/model truth stays with the lower layers.
|
|
224
|
+
if (
|
|
225
|
+
hasAncestorAttributeValue(el, "data-node-type", "sdt_block", root) ||
|
|
226
|
+
hasAncestorAttributeValue(el, "data-block-kind", "sdt", root) ||
|
|
227
|
+
hasAncestorAttribute(el, ["data-sdt-type"], root)
|
|
228
|
+
) {
|
|
229
|
+
if (!kinds.includes("template-slot")) kinds.push("template-slot");
|
|
230
|
+
}
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
// Plain-text baseline so clipboard remains available regardless of
|
|
@@ -18,6 +18,12 @@ export interface TwAlertBannerProps {
|
|
|
18
18
|
* the prop preserves the signal-only banner.
|
|
19
19
|
*/
|
|
20
20
|
onShowDetail?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Opaque/preserve-only diagnostics are debug/operator UX. Default
|
|
23
|
+
* product chrome keeps them silent because the document content is
|
|
24
|
+
* preserved for export, not actionable for the reader.
|
|
25
|
+
*/
|
|
26
|
+
showPreservationDiagnostics?: boolean;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -25,7 +31,8 @@ export interface TwAlertBannerProps {
|
|
|
25
31
|
*
|
|
26
32
|
* Lane 6b §6b.S5 contract:
|
|
27
33
|
* (1) ONE banner at a time, highest severity wins.
|
|
28
|
-
* (2) Precedence: fatalError > blockExport > workflowBlocked
|
|
34
|
+
* (2) Precedence: fatalError > blockExport > workflowBlocked; preserveOnly
|
|
35
|
+
* is debug/operator-only via `showPreservationDiagnostics`.
|
|
29
36
|
* (3) Severity tones bind Lane 6a `--color-semantic-*` tokens — no legacy
|
|
30
37
|
* `bg-danger-soft` / `bg-warning-soft` / `bg-amber-50` class names.
|
|
31
38
|
* (4) Error (fatal / blockExport) → `--color-semantic-error(-soft)`
|
|
@@ -84,6 +91,7 @@ export function TwAlertBanner(
|
|
|
84
91
|
preserveOnlyCount,
|
|
85
92
|
workflowBlockedReasons = [],
|
|
86
93
|
onShowDetail,
|
|
94
|
+
showPreservationDiagnostics = false,
|
|
87
95
|
} = props;
|
|
88
96
|
|
|
89
97
|
const showDetailProp: Pick<BannerRender, "onShowDetail"> = onShowDetail
|
|
@@ -143,8 +151,8 @@ export function TwAlertBanner(
|
|
|
143
151
|
});
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
// 4. Preserve-only features —
|
|
147
|
-
if (preserveOnlyCount > 0) {
|
|
154
|
+
// 4. Preserve-only features — debug/operator-only warning.
|
|
155
|
+
if (showPreservationDiagnostics && preserveOnlyCount > 0) {
|
|
148
156
|
return renderBanner({
|
|
149
157
|
severity: "warning",
|
|
150
158
|
icon: (
|
|
@@ -72,6 +72,30 @@ interface FlatItem extends CommandPaletteItem {
|
|
|
72
72
|
groupLabel: string;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function firstEnabledIndex(items: readonly FlatItem[]): number {
|
|
76
|
+
return items.findIndex((item) => !item.disabled);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function nextEnabledIndex(
|
|
80
|
+
items: readonly FlatItem[],
|
|
81
|
+
currentIndex: number,
|
|
82
|
+
direction: 1 | -1,
|
|
83
|
+
): number {
|
|
84
|
+
if (items.length === 0) return 0;
|
|
85
|
+
const start =
|
|
86
|
+
currentIndex >= 0 && currentIndex < items.length
|
|
87
|
+
? currentIndex
|
|
88
|
+
: direction > 0
|
|
89
|
+
? -1
|
|
90
|
+
: 0;
|
|
91
|
+
for (let offset = 1; offset <= items.length; offset += 1) {
|
|
92
|
+
const index = (start + direction * offset + items.length) % items.length;
|
|
93
|
+
if (!items[index]?.disabled) return index;
|
|
94
|
+
}
|
|
95
|
+
const first = firstEnabledIndex(items);
|
|
96
|
+
return first >= 0 ? first : 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
/**
|
|
76
100
|
* Fuzzy matcher — returns `true` if the needle matches the haystack as
|
|
77
101
|
* substring OR as a character subsequence. Cheap, deterministic, and
|
|
@@ -145,8 +169,16 @@ export function TwCommandPalette(
|
|
|
145
169
|
|
|
146
170
|
// Keep activeIndex inside the filtered result range.
|
|
147
171
|
useEffect(() => {
|
|
148
|
-
if (
|
|
149
|
-
|
|
172
|
+
if (flat.length === 0) {
|
|
173
|
+
if (activeIndex !== 0) setActiveIndex(0);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const active = flat[activeIndex];
|
|
177
|
+
if (!active || active.disabled) {
|
|
178
|
+
const firstEnabled = firstEnabledIndex(flat);
|
|
179
|
+
setActiveIndex(firstEnabled >= 0 ? firstEnabled : 0);
|
|
180
|
+
}
|
|
181
|
+
}, [activeIndex, flat]);
|
|
150
182
|
|
|
151
183
|
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
152
184
|
|
|
@@ -169,14 +201,12 @@ export function TwCommandPalette(
|
|
|
169
201
|
}
|
|
170
202
|
if (event.key === "ArrowDown") {
|
|
171
203
|
event.preventDefault();
|
|
172
|
-
setActiveIndex((i) => (flat
|
|
204
|
+
setActiveIndex((i) => nextEnabledIndex(flat, i, 1));
|
|
173
205
|
return;
|
|
174
206
|
}
|
|
175
207
|
if (event.key === "ArrowUp") {
|
|
176
208
|
event.preventDefault();
|
|
177
|
-
setActiveIndex((i) =>
|
|
178
|
-
flat.length === 0 ? 0 : (i - 1 + flat.length) % flat.length,
|
|
179
|
-
);
|
|
209
|
+
setActiveIndex((i) => nextEnabledIndex(flat, i, -1));
|
|
180
210
|
return;
|
|
181
211
|
}
|
|
182
212
|
if (event.key === "Enter") {
|
|
@@ -46,6 +46,8 @@ export interface ContextMenuItem {
|
|
|
46
46
|
id: string;
|
|
47
47
|
/** Display label. */
|
|
48
48
|
label: string;
|
|
49
|
+
/** Optional explanation, used for disabled product-signpost rows. */
|
|
50
|
+
description?: string;
|
|
49
51
|
/** Optional icon element rendered left of the label. */
|
|
50
52
|
icon?: React.ReactNode;
|
|
51
53
|
/** Shortcut key sequence shown right-aligned via TwShortcutHint. */
|
|
@@ -210,13 +212,16 @@ interface ContextMenuRowProps {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
function ContextMenuRow({ item, platform }: ContextMenuRowProps): React.JSX.Element {
|
|
213
|
-
const { label, icon, shortcut, disabled, onSelect } = item;
|
|
215
|
+
const { label, description, icon, shortcut, disabled, onSelect } = item;
|
|
216
|
+
const disabledExplanation = disabled && description ? description : undefined;
|
|
214
217
|
|
|
215
218
|
return (
|
|
216
219
|
<button
|
|
217
220
|
role="menuitem"
|
|
218
221
|
data-testid={`context-menu-item-${item.id}`}
|
|
219
222
|
disabled={disabled}
|
|
223
|
+
title={disabledExplanation}
|
|
224
|
+
data-disabled-reason={disabledExplanation}
|
|
220
225
|
onClick={disabled ? undefined : onSelect}
|
|
221
226
|
className={[
|
|
222
227
|
"flex items-center justify-between h-[30px] w-full px-3",
|
|
@@ -13,15 +13,14 @@
|
|
|
13
13
|
* names sees the right option highlighted without code changes.
|
|
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 { ChevronDown, Eye, EyeOff, Highlighter, Scroll } from "lucide-react";
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
21
|
normalizeMarkupDisplay,
|
|
22
22
|
type MarkupDisplay,
|
|
23
23
|
} from "../../ui/headless/comment-decoration-model";
|
|
24
|
-
import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-editor-selection";
|
|
25
24
|
|
|
26
25
|
export type DisplayMode = "all-markup" | "simple-markup" | "no-markup" | "original";
|
|
27
26
|
|
|
@@ -68,136 +67,70 @@ const MODES: readonly ModeEntry[] = [
|
|
|
68
67
|
|
|
69
68
|
export function TwDisplayModeSelector(props: TwDisplayModeSelectorProps): React.ReactElement {
|
|
70
69
|
const [open, setOpen] = useState(false);
|
|
71
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
72
70
|
const canonical = normalizeMarkupDisplay(props.value);
|
|
73
71
|
const activeEntry = MODES.find((m) => m.mode === canonical) ?? MODES[0]!;
|
|
74
72
|
|
|
75
73
|
return (
|
|
76
|
-
|
|
74
|
+
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
75
|
+
<Popover.Trigger asChild>
|
|
77
76
|
<button
|
|
78
|
-
ref={triggerRef}
|
|
79
77
|
type="button"
|
|
80
78
|
disabled={props.disabled}
|
|
81
79
|
data-testid={props["data-testid"] ?? "display-mode-selector-trigger"}
|
|
82
80
|
aria-label={`Display mode: ${activeEntry.label}`}
|
|
83
|
-
|
|
84
|
-
aria-haspopup="menu"
|
|
85
|
-
onMouseDown={preserveEditorSelectionMouseDown}
|
|
86
|
-
onClick={(event) => {
|
|
87
|
-
event.preventDefault();
|
|
88
|
-
setOpen((value) => !value);
|
|
89
|
-
}}
|
|
90
|
-
className={[
|
|
91
|
-
"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-medium text-primary",
|
|
92
|
-
"hover:bg-surface focus-visible:outline-none focus-visible:bg-surface disabled:opacity-50",
|
|
93
|
-
open ? "bg-canvas text-accent ring-1 ring-accent/30 shadow-sm" : "",
|
|
94
|
-
].join(" ")}
|
|
81
|
+
className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-medium text-primary hover:bg-surface focus-visible:outline-none focus-visible:bg-surface disabled:opacity-50"
|
|
95
82
|
>
|
|
96
83
|
<activeEntry.icon className="h-3.5 w-3.5 text-tertiary" />
|
|
97
84
|
<span>{activeEntry.label}</span>
|
|
98
85
|
<ChevronDown className="h-3.5 w-3.5 text-tertiary" />
|
|
99
86
|
</button>
|
|
100
|
-
|
|
87
|
+
</Popover.Trigger>
|
|
88
|
+
<Popover.Portal>
|
|
89
|
+
<Popover.Content
|
|
90
|
+
className="z-50 w-[260px] rounded-lg bg-canvas p-1 shadow-lg ring-1 ring-border"
|
|
91
|
+
sideOffset={8}
|
|
92
|
+
align="end"
|
|
93
|
+
data-testid="display-mode-selector-content"
|
|
94
|
+
>
|
|
101
95
|
<div className="mb-1 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
|
|
102
96
|
Display mode
|
|
103
97
|
</div>
|
|
104
98
|
{MODES.map((entry) => {
|
|
105
99
|
const isActive = entry.mode === canonical;
|
|
106
100
|
return (
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
101
|
+
<Popover.Close key={entry.mode} asChild>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
role="menuitemradio"
|
|
105
|
+
aria-checked={isActive}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
props.onChange(entry.mode);
|
|
108
|
+
}}
|
|
109
|
+
className="flex w-full items-start gap-2 rounded-md px-2 py-1.5 text-left text-[11px] transition-colors hover:bg-surface focus-visible:outline-none focus-visible:bg-surface"
|
|
110
|
+
data-testid={`display-mode-option-${entry.mode}`}
|
|
111
|
+
data-mode={entry.mode}
|
|
112
|
+
data-active={isActive ? "true" : undefined}
|
|
113
|
+
>
|
|
114
|
+
<entry.icon
|
|
115
|
+
className={[
|
|
116
|
+
"mt-0.5 h-3.5 w-3.5 shrink-0",
|
|
117
|
+
isActive ? "text-accent" : "text-tertiary",
|
|
118
|
+
].join(" ")}
|
|
119
|
+
/>
|
|
120
|
+
<span className="flex flex-col">
|
|
121
|
+
<span className={`font-medium ${isActive ? "text-accent" : "text-primary"}`}>
|
|
122
|
+
{entry.label}
|
|
123
|
+
</span>
|
|
124
|
+
<span className="text-[10px] text-secondary">{entry.hint}</span>
|
|
130
125
|
</span>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</button>
|
|
126
|
+
</button>
|
|
127
|
+
</Popover.Close>
|
|
134
128
|
);
|
|
135
129
|
})}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function DisplayModePortalMenu(props: {
|
|
142
|
-
anchorRef: React.RefObject<HTMLButtonElement | null>;
|
|
143
|
-
children: React.ReactNode;
|
|
144
|
-
open: boolean;
|
|
145
|
-
}): React.ReactPortal | null {
|
|
146
|
-
const style = useDisplayModePortalPosition(props.anchorRef, props.open);
|
|
147
|
-
const body = props.anchorRef.current?.ownerDocument?.body;
|
|
148
|
-
if (!props.open || !body) return null;
|
|
149
|
-
return createPortal(
|
|
150
|
-
<div
|
|
151
|
-
className="z-50 w-[260px] rounded-lg bg-canvas p-1 shadow-lg ring-1 ring-border"
|
|
152
|
-
data-testid="display-mode-selector-content"
|
|
153
|
-
style={style}
|
|
154
|
-
>
|
|
155
|
-
{props.children}
|
|
156
|
-
</div>,
|
|
157
|
-
body,
|
|
130
|
+
</Popover.Content>
|
|
131
|
+
</Popover.Portal>
|
|
132
|
+
</Popover.Root>
|
|
158
133
|
);
|
|
159
134
|
}
|
|
160
135
|
|
|
161
|
-
function useDisplayModePortalPosition(
|
|
162
|
-
anchorRef: React.RefObject<HTMLButtonElement | null>,
|
|
163
|
-
open: boolean,
|
|
164
|
-
): CSSProperties {
|
|
165
|
-
const [style, setStyle] = useState<CSSProperties>({
|
|
166
|
-
left: 8,
|
|
167
|
-
position: "fixed",
|
|
168
|
-
top: 8,
|
|
169
|
-
zIndex: 50,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
useLayoutEffect(() => {
|
|
173
|
-
if (!open) return;
|
|
174
|
-
const anchor = anchorRef.current;
|
|
175
|
-
const ownerWindow = anchor?.ownerDocument?.defaultView;
|
|
176
|
-
if (!anchor || !ownerWindow) return;
|
|
177
|
-
const update = () => {
|
|
178
|
-
const rect = anchor.getBoundingClientRect();
|
|
179
|
-
const width = 260;
|
|
180
|
-
const left = Math.min(
|
|
181
|
-
Math.max(8, rect.right - width),
|
|
182
|
-
Math.max(8, (ownerWindow.innerWidth || width + 16) - width - 8),
|
|
183
|
-
);
|
|
184
|
-
setStyle({
|
|
185
|
-
left,
|
|
186
|
-
position: "fixed",
|
|
187
|
-
top: Math.max(8, rect.bottom + 8),
|
|
188
|
-
zIndex: 50,
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
update();
|
|
192
|
-
ownerWindow.addEventListener("resize", update);
|
|
193
|
-
ownerWindow.addEventListener("scroll", update, true);
|
|
194
|
-
return () => {
|
|
195
|
-
ownerWindow.removeEventListener("resize", update);
|
|
196
|
-
ownerWindow.removeEventListener("scroll", update, true);
|
|
197
|
-
};
|
|
198
|
-
}, [anchorRef, open]);
|
|
199
|
-
|
|
200
|
-
return style;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
136
|
export default TwDisplayModeSelector;
|
|
@@ -16,6 +16,16 @@ export interface TwInlineFindBarProps {
|
|
|
16
16
|
export function TwInlineFindBar(props: TwInlineFindBarProps): React.JSX.Element {
|
|
17
17
|
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
18
18
|
const hasResults = props.resultCount > 0;
|
|
19
|
+
const hasQuery = props.query.trim().length > 0;
|
|
20
|
+
const resultState = hasQuery ? (hasResults ? "matches" : "empty") : "idle";
|
|
21
|
+
const statusLabel = hasQuery
|
|
22
|
+
? hasResults
|
|
23
|
+
? `${props.activeIndex + 1}/${props.resultCount}`
|
|
24
|
+
: "No results"
|
|
25
|
+
: "Find";
|
|
26
|
+
const navDisabledReason = hasQuery
|
|
27
|
+
? "No matches to navigate."
|
|
28
|
+
: "Enter a query to navigate matches.";
|
|
19
29
|
|
|
20
30
|
React.useEffect(() => {
|
|
21
31
|
inputRef.current?.focus();
|
|
@@ -26,6 +36,7 @@ export function TwInlineFindBar(props: TwInlineFindBarProps): React.JSX.Element
|
|
|
26
36
|
<div
|
|
27
37
|
className="pointer-events-auto flex w-[min(420px,calc(100vw-2rem))] items-center gap-2 rounded-2xl border border-[color:color-mix(in_srgb,var(--color-accent-primary)_28%,var(--color-border-subtle))] bg-[color:color-mix(in_srgb,var(--color-bg-canvas)_94%,white)] px-2.5 py-2 shadow-[0_18px_50px_rgba(20,31,29,0.18)]"
|
|
28
38
|
data-testid="inline-find-bar"
|
|
39
|
+
data-result-state={resultState}
|
|
29
40
|
role="search"
|
|
30
41
|
aria-label="Find in document"
|
|
31
42
|
>
|
|
@@ -33,6 +44,7 @@ export function TwInlineFindBar(props: TwInlineFindBarProps): React.JSX.Element
|
|
|
33
44
|
<input
|
|
34
45
|
ref={inputRef}
|
|
35
46
|
aria-label="Find text"
|
|
47
|
+
aria-describedby="inline-find-bar-status"
|
|
36
48
|
className="min-w-0 flex-1 bg-transparent px-1 text-[13px] font-medium text-primary outline-none placeholder:text-tertiary"
|
|
37
49
|
placeholder="Find in document"
|
|
38
50
|
type="search"
|
|
@@ -55,19 +67,24 @@ export function TwInlineFindBar(props: TwInlineFindBarProps): React.JSX.Element
|
|
|
55
67
|
}}
|
|
56
68
|
/>
|
|
57
69
|
<span
|
|
58
|
-
|
|
70
|
+
id="inline-find-bar-status"
|
|
71
|
+
data-testid="inline-find-bar-status"
|
|
72
|
+
className={`min-w-[72px] rounded-full px-2 py-1 text-center text-[11px] font-semibold tabular-nums ${
|
|
73
|
+
resultState === "empty"
|
|
74
|
+
? "bg-[var(--color-semantic-warning-soft)] text-[var(--color-semantic-warning)]"
|
|
75
|
+
: "bg-[color:color-mix(in_srgb,var(--color-accent-primary)_10%,transparent)] text-accent"
|
|
76
|
+
}`}
|
|
59
77
|
aria-live="polite"
|
|
60
78
|
>
|
|
61
|
-
{
|
|
62
|
-
? hasResults
|
|
63
|
-
? `${props.activeIndex + 1}/${props.resultCount}`
|
|
64
|
-
: "0/0"
|
|
65
|
-
: "Find"}
|
|
79
|
+
{statusLabel}
|
|
66
80
|
</span>
|
|
67
81
|
<button
|
|
68
82
|
type="button"
|
|
69
83
|
aria-label="Previous match"
|
|
84
|
+
aria-keyshortcuts="Shift+Enter"
|
|
70
85
|
disabled={!hasResults}
|
|
86
|
+
title={hasResults ? "Previous match (Shift+Enter)" : navDisabledReason}
|
|
87
|
+
data-testid="inline-find-bar-previous"
|
|
71
88
|
className="inline-flex h-7 w-7 items-center justify-center rounded-full text-secondary transition-colors hover:bg-hover disabled:cursor-not-allowed disabled:opacity-35"
|
|
72
89
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
73
90
|
onClick={props.onPrevious}
|
|
@@ -77,7 +94,10 @@ export function TwInlineFindBar(props: TwInlineFindBarProps): React.JSX.Element
|
|
|
77
94
|
<button
|
|
78
95
|
type="button"
|
|
79
96
|
aria-label="Next match"
|
|
97
|
+
aria-keyshortcuts="Enter"
|
|
80
98
|
disabled={!hasResults}
|
|
99
|
+
title={hasResults ? "Next match (Enter)" : navDisabledReason}
|
|
100
|
+
data-testid="inline-find-bar-next"
|
|
81
101
|
className="inline-flex h-7 w-7 items-center justify-center rounded-full text-secondary transition-colors hover:bg-hover disabled:cursor-not-allowed disabled:opacity-35"
|
|
82
102
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
83
103
|
onClick={props.onNext}
|