@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow rail-segment projection.
|
|
3
|
+
*
|
|
4
|
+
* Per runtime-rendering-and-chrome-phase.md §5, the action rail v2 renders
|
|
5
|
+
* OUTSIDE the PM NodeView tree as an overlay layer positioned from
|
|
6
|
+
* canonical scope data. This module joins the host-supplied
|
|
7
|
+
* `WorkflowOverlay` (scopes, candidates) + blocked-reason ranges +
|
|
8
|
+
* locked-zone data with the runtime page graph to produce
|
|
9
|
+
* `ScopeRailSegment[]` — the shape chrome consumes to render the
|
|
10
|
+
* left-gutter label column and the flat block-tint.
|
|
11
|
+
*
|
|
12
|
+
* The segments are pure reads over canonical state; no DOM, no PM.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
EditorAnchorProjection,
|
|
17
|
+
EditorStoryTarget,
|
|
18
|
+
WorkflowBlockedCommandReason,
|
|
19
|
+
WorkflowCandidateRange,
|
|
20
|
+
WorkflowLockedZone,
|
|
21
|
+
WorkflowScope,
|
|
22
|
+
} from "../api/public-types";
|
|
23
|
+
import { MAIN_STORY_TARGET, storyTargetsEqual } from "../core/selection/mapping.ts";
|
|
24
|
+
import type { RuntimePageGraph } from "./layout/page-graph.ts";
|
|
25
|
+
import type { RenderFrameRect } from "./render/render-frame-types.ts";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Public shape
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export type ScopeRailPosture =
|
|
32
|
+
| "edit"
|
|
33
|
+
| "suggest"
|
|
34
|
+
| "comment"
|
|
35
|
+
| "view"
|
|
36
|
+
| "candidate"
|
|
37
|
+
| "preserve-only"
|
|
38
|
+
| "blocked-import";
|
|
39
|
+
|
|
40
|
+
export interface ScopeRailSegment {
|
|
41
|
+
/** Identifier the chrome uses to sync with the Workflow rail tab. */
|
|
42
|
+
scopeId: string;
|
|
43
|
+
/** Visual+accessibility posture keyed off the scope mode or block reason. */
|
|
44
|
+
posture: ScopeRailPosture;
|
|
45
|
+
/** Human label; empty string when none was supplied by the host. */
|
|
46
|
+
label: string;
|
|
47
|
+
/** Runtime offsets (inclusive from, exclusive to) this segment spans on the active story. */
|
|
48
|
+
fromOffset: number;
|
|
49
|
+
toOffset: number;
|
|
50
|
+
/** Story the segment sits on. */
|
|
51
|
+
storyTarget: EditorStoryTarget;
|
|
52
|
+
/** Page index the segment renders on (may span multiple pages; emitted per page). */
|
|
53
|
+
pageIndex: number;
|
|
54
|
+
/** Section index derived from the page graph. */
|
|
55
|
+
sectionIndex: number;
|
|
56
|
+
/** True when the scope is the active work item. */
|
|
57
|
+
isActiveWorkItem: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Body-tint rect in overlay-space pixels, populated when the render kernel
|
|
60
|
+
* is available. Chrome consumers read this directly instead of
|
|
61
|
+
* re-projecting per render via the overlay projector. `null` when the
|
|
62
|
+
* segment is produced without a kernel (e.g., in tests or before the
|
|
63
|
+
* facet is bound to a page graph) — consumers fall back to per-render
|
|
64
|
+
* anchor resolution in that case.
|
|
65
|
+
*/
|
|
66
|
+
bodyTintRect: RenderFrameRect | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Collector input
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
export interface CollectScopeRailSegmentsInput {
|
|
74
|
+
scopes: readonly WorkflowScope[] | undefined;
|
|
75
|
+
candidates?: readonly WorkflowCandidateRange[];
|
|
76
|
+
blockedReasons?: readonly WorkflowBlockedCommandReason[];
|
|
77
|
+
lockedZones?: readonly WorkflowLockedZone[];
|
|
78
|
+
activeWorkItemScopeIds?: readonly string[];
|
|
79
|
+
/** Active story scopes render on; segments for other stories are skipped. */
|
|
80
|
+
activeStory?: EditorStoryTarget;
|
|
81
|
+
pageGraph: RuntimePageGraph;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Entry point
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build segments for every page in the graph. Callers that only want a
|
|
90
|
+
* single page can filter by `segment.pageIndex` on the returned list.
|
|
91
|
+
*/
|
|
92
|
+
export function collectScopeRailSegments(
|
|
93
|
+
input: CollectScopeRailSegmentsInput,
|
|
94
|
+
): ScopeRailSegment[] {
|
|
95
|
+
const segments: ScopeRailSegment[] = [];
|
|
96
|
+
const activeStory = input.activeStory ?? MAIN_STORY_TARGET;
|
|
97
|
+
const activeIds = new Set(input.activeWorkItemScopeIds ?? []);
|
|
98
|
+
|
|
99
|
+
for (const scope of input.scopes ?? []) {
|
|
100
|
+
const range = anchorToRuntimeRange(scope.anchor);
|
|
101
|
+
if (!range) continue;
|
|
102
|
+
const storyTarget = scope.storyTarget ?? MAIN_STORY_TARGET;
|
|
103
|
+
if (!storyTargetsEqual(storyTarget, activeStory)) continue;
|
|
104
|
+
|
|
105
|
+
const posture = resolveScopePosture(scope);
|
|
106
|
+
const isActiveWorkItem = activeIds.has(scope.scopeId);
|
|
107
|
+
for (const page of pagesCoveringRange(input.pageGraph, range.from, range.to)) {
|
|
108
|
+
const { from, to } = clipRangeToPage(range.from, range.to, page);
|
|
109
|
+
if (from >= to) continue;
|
|
110
|
+
segments.push({
|
|
111
|
+
scopeId: scope.scopeId,
|
|
112
|
+
posture,
|
|
113
|
+
label: scope.label ?? "",
|
|
114
|
+
fromOffset: from,
|
|
115
|
+
toOffset: to,
|
|
116
|
+
storyTarget,
|
|
117
|
+
pageIndex: page.pageIndex,
|
|
118
|
+
sectionIndex: page.sectionIndex,
|
|
119
|
+
isActiveWorkItem,
|
|
120
|
+
bodyTintRect: null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Candidates render as a faint "candidate" posture so the reader knows
|
|
126
|
+
// where the host is proposing scopes before they're committed.
|
|
127
|
+
for (const candidate of input.candidates ?? []) {
|
|
128
|
+
const range = anchorToRuntimeRange(candidate.anchor);
|
|
129
|
+
if (!range) continue;
|
|
130
|
+
const storyTarget = candidate.storyTarget ?? MAIN_STORY_TARGET;
|
|
131
|
+
if (!storyTargetsEqual(storyTarget, activeStory)) continue;
|
|
132
|
+
|
|
133
|
+
for (const page of pagesCoveringRange(input.pageGraph, range.from, range.to)) {
|
|
134
|
+
const { from, to } = clipRangeToPage(range.from, range.to, page);
|
|
135
|
+
if (from >= to) continue;
|
|
136
|
+
segments.push({
|
|
137
|
+
scopeId: candidate.candidateId,
|
|
138
|
+
posture: "candidate",
|
|
139
|
+
label: candidate.label ?? "",
|
|
140
|
+
fromOffset: from,
|
|
141
|
+
toOffset: to,
|
|
142
|
+
storyTarget,
|
|
143
|
+
pageIndex: page.pageIndex,
|
|
144
|
+
sectionIndex: page.sectionIndex,
|
|
145
|
+
isActiveWorkItem: false,
|
|
146
|
+
bodyTintRect: null,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Blocked-reason anchors: render a "blocked-import" or "preserve-only"
|
|
152
|
+
// posture so the rail signal matches the chrome message in image copy.png.
|
|
153
|
+
for (const reason of input.blockedReasons ?? []) {
|
|
154
|
+
if (!reason.anchor) continue;
|
|
155
|
+
const range = anchorToRuntimeRange(reason.anchor);
|
|
156
|
+
if (!range) continue;
|
|
157
|
+
const storyTarget = reason.storyTarget ?? MAIN_STORY_TARGET;
|
|
158
|
+
if (!storyTargetsEqual(storyTarget, activeStory)) continue;
|
|
159
|
+
|
|
160
|
+
const posture: ScopeRailPosture =
|
|
161
|
+
reason.code === "workflow_blocked_import"
|
|
162
|
+
? "blocked-import"
|
|
163
|
+
: reason.code === "workflow_preserve_only"
|
|
164
|
+
? "preserve-only"
|
|
165
|
+
: "view";
|
|
166
|
+
for (const page of pagesCoveringRange(input.pageGraph, range.from, range.to)) {
|
|
167
|
+
const { from, to } = clipRangeToPage(range.from, range.to, page);
|
|
168
|
+
if (from >= to) continue;
|
|
169
|
+
segments.push({
|
|
170
|
+
scopeId: `blocked:${reason.code}:${range.from}-${range.to}`,
|
|
171
|
+
posture,
|
|
172
|
+
label: reason.label ?? reason.message ?? "",
|
|
173
|
+
fromOffset: from,
|
|
174
|
+
toOffset: to,
|
|
175
|
+
storyTarget,
|
|
176
|
+
pageIndex: page.pageIndex,
|
|
177
|
+
sectionIndex: page.sectionIndex,
|
|
178
|
+
isActiveWorkItem: false,
|
|
179
|
+
bodyTintRect: null,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Locked zones project as their own preserve-only / blocked-import rails so
|
|
185
|
+
// locked cells, locked images, and locked runs show the lock posture in
|
|
186
|
+
// the gutter even without a workflow scope covering them.
|
|
187
|
+
for (const zone of input.lockedZones ?? []) {
|
|
188
|
+
const range = anchorToRuntimeRange(zone.anchor);
|
|
189
|
+
if (!range) continue;
|
|
190
|
+
const storyTarget = zone.storyTarget ?? MAIN_STORY_TARGET;
|
|
191
|
+
if (!storyTargetsEqual(storyTarget, activeStory)) continue;
|
|
192
|
+
|
|
193
|
+
const posture: ScopeRailPosture =
|
|
194
|
+
zone.code === "workflow_blocked_import"
|
|
195
|
+
? "blocked-import"
|
|
196
|
+
: "preserve-only";
|
|
197
|
+
for (const page of pagesCoveringRange(input.pageGraph, range.from, range.to)) {
|
|
198
|
+
const { from, to } = clipRangeToPage(range.from, range.to, page);
|
|
199
|
+
if (from >= to) continue;
|
|
200
|
+
segments.push({
|
|
201
|
+
scopeId: zone.fragmentId ?? `locked:${range.from}-${range.to}`,
|
|
202
|
+
posture,
|
|
203
|
+
label: zone.label ?? "",
|
|
204
|
+
fromOffset: from,
|
|
205
|
+
toOffset: to,
|
|
206
|
+
storyTarget,
|
|
207
|
+
pageIndex: page.pageIndex,
|
|
208
|
+
sectionIndex: page.sectionIndex,
|
|
209
|
+
isActiveWorkItem: false,
|
|
210
|
+
bodyTintRect: null,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return segments;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Internals
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
function anchorToRuntimeRange(
|
|
223
|
+
anchor: EditorAnchorProjection,
|
|
224
|
+
): { from: number; to: number } | null {
|
|
225
|
+
if (anchor.kind === "range") {
|
|
226
|
+
const from = Math.min(anchor.from, anchor.to);
|
|
227
|
+
const to = Math.max(anchor.from, anchor.to);
|
|
228
|
+
return from < to ? { from, to } : null;
|
|
229
|
+
}
|
|
230
|
+
if (anchor.kind === "node") {
|
|
231
|
+
return { from: anchor.at, to: anchor.at + 1 };
|
|
232
|
+
}
|
|
233
|
+
// detached anchors cannot be rendered; use the last-known range as a
|
|
234
|
+
// best-effort ghost until the scope is re-anchored or dismissed.
|
|
235
|
+
if (anchor.lastKnownRange) {
|
|
236
|
+
const from = Math.min(anchor.lastKnownRange.from, anchor.lastKnownRange.to);
|
|
237
|
+
const to = Math.max(anchor.lastKnownRange.from, anchor.lastKnownRange.to);
|
|
238
|
+
return from < to ? { from, to } : null;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function pagesCoveringRange(
|
|
244
|
+
graph: RuntimePageGraph,
|
|
245
|
+
from: number,
|
|
246
|
+
to: number,
|
|
247
|
+
): RuntimePageGraph["pages"] {
|
|
248
|
+
return graph.pages.filter(
|
|
249
|
+
(page) =>
|
|
250
|
+
!page.isBlankFiller &&
|
|
251
|
+
page.endOffset > from &&
|
|
252
|
+
page.startOffset < to,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function clipRangeToPage(
|
|
257
|
+
from: number,
|
|
258
|
+
to: number,
|
|
259
|
+
page: RuntimePageGraph["pages"][number],
|
|
260
|
+
): { from: number; to: number } {
|
|
261
|
+
return {
|
|
262
|
+
from: Math.max(from, page.startOffset),
|
|
263
|
+
to: Math.min(to, page.endOffset),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function resolveScopePosture(scope: WorkflowScope): ScopeRailPosture {
|
|
268
|
+
switch (scope.mode) {
|
|
269
|
+
case "edit":
|
|
270
|
+
return "edit";
|
|
271
|
+
case "suggest":
|
|
272
|
+
return "suggest";
|
|
273
|
+
case "comment":
|
|
274
|
+
return "comment";
|
|
275
|
+
case "view":
|
|
276
|
+
return "view";
|
|
277
|
+
default:
|
|
278
|
+
return "view";
|
|
279
|
+
}
|
|
280
|
+
}
|