@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,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project surface blocks → per-page RuntimeBlockFragments (P4 + R3).
|
|
3
|
+
*
|
|
4
|
+
* The pagination engine produces `DocumentPageSnapshot[]` keyed by offset
|
|
5
|
+
* ranges, but for render-kernel consumers to get per-block geometry (and
|
|
6
|
+
* for the chrome to anchor tables, images, etc.) we need one block
|
|
7
|
+
* fragment per top-level surface block, assigned to the page that
|
|
8
|
+
* contains its offset range.
|
|
9
|
+
*
|
|
10
|
+
* The engine also reports **split metadata** (see `BlockSplits` in
|
|
11
|
+
* `paginated-layout-engine.ts`):
|
|
12
|
+
* - paragraph-slice: one fragment per slice, keyed by `paragraphLineRange`
|
|
13
|
+
* - table-slice: one fragment per row-range slice, keyed by `tableRowRange`
|
|
14
|
+
* - otherwise: one atomic fragment per top-level block (the original P4
|
|
15
|
+
* behavior).
|
|
16
|
+
*
|
|
17
|
+
* Consumers read `fragment.kind` to choose between whole-block rendering
|
|
18
|
+
* and slice-aware rendering (line-range clipping for paragraphs, row-range
|
|
19
|
+
* rendering + repeated header row for tables).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
DocumentPageSnapshot,
|
|
24
|
+
EditorSurfaceSnapshot,
|
|
25
|
+
SurfaceBlockSnapshot,
|
|
26
|
+
} from "../../api/public-types";
|
|
27
|
+
import type { RuntimeBlockFragment } from "./page-graph.ts";
|
|
28
|
+
import type {
|
|
29
|
+
BlockSplits,
|
|
30
|
+
ParagraphLineSlice,
|
|
31
|
+
TableRowSlice,
|
|
32
|
+
} from "./paginated-layout-engine.ts";
|
|
33
|
+
|
|
34
|
+
type FragmentWithoutPageId = Omit<RuntimeBlockFragment, "pageId">;
|
|
35
|
+
|
|
36
|
+
export function projectSurfaceBlocksToPageFragments(
|
|
37
|
+
surface: EditorSurfaceSnapshot,
|
|
38
|
+
pages: readonly DocumentPageSnapshot[],
|
|
39
|
+
splits?: BlockSplits,
|
|
40
|
+
): Map<number, FragmentWithoutPageId[]> {
|
|
41
|
+
const byPage = new Map<number, FragmentWithoutPageId[]>();
|
|
42
|
+
const perPageCounter = new Map<number, number>();
|
|
43
|
+
|
|
44
|
+
const pushFragment = (
|
|
45
|
+
pageIndex: number,
|
|
46
|
+
fragment: FragmentWithoutPageId,
|
|
47
|
+
): void => {
|
|
48
|
+
const existing = byPage.get(pageIndex);
|
|
49
|
+
if (existing) {
|
|
50
|
+
existing.push(fragment);
|
|
51
|
+
} else {
|
|
52
|
+
byPage.set(pageIndex, [fragment]);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const nextOrder = (pageIndex: number): number => {
|
|
57
|
+
const n = perPageCounter.get(pageIndex) ?? 0;
|
|
58
|
+
perPageCounter.set(pageIndex, n + 1);
|
|
59
|
+
return n;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (const block of surface.blocks) {
|
|
63
|
+
// R3: table split across pages — emit one fragment per row slice.
|
|
64
|
+
// Consumers read `tableRowRange` and prepend header rows when from > 0.
|
|
65
|
+
if (block.kind === "table") {
|
|
66
|
+
const tableSliceList = splits?.tablesByBlockId.get(block.blockId);
|
|
67
|
+
if (tableSliceList && tableSliceList.length > 1) {
|
|
68
|
+
emitSlicedTable(
|
|
69
|
+
block,
|
|
70
|
+
tableSliceList,
|
|
71
|
+
(pageIndex, fragment) => {
|
|
72
|
+
pushFragment(pageIndex, {
|
|
73
|
+
...fragment,
|
|
74
|
+
orderInRegion: nextOrder(pageIndex),
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Paragraph split across pages (bleed-up slice metadata): emit one
|
|
83
|
+
// fragment per slice with `paragraphLineRange` set.
|
|
84
|
+
const sliceList = splits?.byBlockId.get(block.blockId);
|
|
85
|
+
if (sliceList && sliceList.length > 1) {
|
|
86
|
+
emitSlicedParagraph(
|
|
87
|
+
block,
|
|
88
|
+
sliceList,
|
|
89
|
+
(pageIndex, fragment) => {
|
|
90
|
+
pushFragment(pageIndex, {
|
|
91
|
+
...fragment,
|
|
92
|
+
orderInRegion: nextOrder(pageIndex),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Unsplit block: single atomic fragment on the page containing its
|
|
100
|
+
// start offset.
|
|
101
|
+
const pageIndex = findPageIndexForOffset(pages, block.from);
|
|
102
|
+
if (pageIndex === null) continue;
|
|
103
|
+
|
|
104
|
+
const fragment: FragmentWithoutPageId = {
|
|
105
|
+
fragmentId: `fragment-${block.blockId}`,
|
|
106
|
+
blockId: block.blockId,
|
|
107
|
+
orderInRegion: nextOrder(pageIndex),
|
|
108
|
+
regionKind: "body",
|
|
109
|
+
from: block.from,
|
|
110
|
+
to: block.to,
|
|
111
|
+
heightTwips: estimateBlockHeightFromSpan(block),
|
|
112
|
+
kind: "whole",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
pushFragment(pageIndex, fragment);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return byPage;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Emit one fragment per slice for a paragraph that pagination split across
|
|
123
|
+
* pages. The source `block` offset range stays intact on every slice; the
|
|
124
|
+
* `paragraphLineRange` identifies which lines the slice renders.
|
|
125
|
+
*/
|
|
126
|
+
function emitSlicedParagraph(
|
|
127
|
+
block: SurfaceBlockSnapshot,
|
|
128
|
+
slices: readonly ParagraphLineSlice[],
|
|
129
|
+
emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
|
|
130
|
+
): void {
|
|
131
|
+
for (let i = 0; i < slices.length; i += 1) {
|
|
132
|
+
const slice = slices[i]!;
|
|
133
|
+
const fragment: FragmentWithoutPageId = {
|
|
134
|
+
fragmentId: `fragment-${block.blockId}-slice-${i}`,
|
|
135
|
+
blockId: block.blockId,
|
|
136
|
+
orderInRegion: 0, // re-assigned by caller via nextOrder(pageIndex).
|
|
137
|
+
regionKind: "body",
|
|
138
|
+
from: block.from,
|
|
139
|
+
to: block.to,
|
|
140
|
+
heightTwips: estimateSliceHeightFromLines(slice.lineRange),
|
|
141
|
+
kind: "paragraph-slice",
|
|
142
|
+
paragraphLineRange: slice.lineRange,
|
|
143
|
+
};
|
|
144
|
+
emit(slice.pageIndex, fragment);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function estimateSliceHeightFromLines(lineRange: {
|
|
149
|
+
from: number;
|
|
150
|
+
to: number;
|
|
151
|
+
totalLines: number;
|
|
152
|
+
}): number {
|
|
153
|
+
const lines = Math.max(0, lineRange.to - lineRange.from);
|
|
154
|
+
return lines * 240;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* R3: emit one fragment per row-slice when a table spans multiple pages.
|
|
159
|
+
* The source `block` offset range stays intact on every slice (so selection
|
|
160
|
+
* math and incremental relayout see a single logical block); `tableRowRange`
|
|
161
|
+
* tells downstream consumers which rows to render on this page. When
|
|
162
|
+
* `rowRange.from > 0` and the owning table has `isHeader` rows, consumers
|
|
163
|
+
* prepend those header rows visually so the table's header repeats on
|
|
164
|
+
* every continuation page.
|
|
165
|
+
*/
|
|
166
|
+
function emitSlicedTable(
|
|
167
|
+
block: SurfaceBlockSnapshot,
|
|
168
|
+
slices: readonly TableRowSlice[],
|
|
169
|
+
emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
|
|
170
|
+
): void {
|
|
171
|
+
for (let i = 0; i < slices.length; i += 1) {
|
|
172
|
+
const slice = slices[i]!;
|
|
173
|
+
const fragment: FragmentWithoutPageId = {
|
|
174
|
+
fragmentId: `fragment-${block.blockId}-rowslice-${i}`,
|
|
175
|
+
blockId: block.blockId,
|
|
176
|
+
orderInRegion: 0, // re-assigned by caller via nextOrder(pageIndex).
|
|
177
|
+
regionKind: "body",
|
|
178
|
+
from: block.from,
|
|
179
|
+
to: block.to,
|
|
180
|
+
heightTwips: estimateSliceHeightFromRows(slice.rowRange),
|
|
181
|
+
kind: "table-slice",
|
|
182
|
+
tableRowRange: slice.rowRange,
|
|
183
|
+
};
|
|
184
|
+
emit(slice.pageIndex, fragment);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function estimateSliceHeightFromRows(rowRange: {
|
|
189
|
+
from: number;
|
|
190
|
+
to: number;
|
|
191
|
+
totalRows: number;
|
|
192
|
+
}): number {
|
|
193
|
+
const rows = Math.max(0, rowRange.to - rowRange.from);
|
|
194
|
+
return rows * 360; // ~1 line + padding per row, approximate
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function findPageIndexForOffset(
|
|
198
|
+
pages: readonly DocumentPageSnapshot[],
|
|
199
|
+
offset: number,
|
|
200
|
+
): number | null {
|
|
201
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
202
|
+
const page = pages[i]!;
|
|
203
|
+
if (offset >= page.startOffset && offset < page.endOffset) {
|
|
204
|
+
return page.pageIndex;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Fall back to the last page for offsets that equal storySize.
|
|
208
|
+
const last = pages[pages.length - 1];
|
|
209
|
+
if (last && offset >= last.startOffset && offset <= last.endOffset) {
|
|
210
|
+
return last.pageIndex;
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Rough height estimate from the block's offset span. Only used as a
|
|
217
|
+
* fallback when the caller did not pre-measure. Chrome consumers that
|
|
218
|
+
* need accurate heights should read `facet.getMeasurement(blockId)`.
|
|
219
|
+
*/
|
|
220
|
+
function estimateBlockHeightFromSpan(block: SurfaceBlockSnapshot): number {
|
|
221
|
+
// 240 twips per line; approximate 80 chars per line for paragraphs.
|
|
222
|
+
const span = Math.max(0, block.to - block.from);
|
|
223
|
+
const lines = Math.max(1, Math.ceil(span / 80));
|
|
224
|
+
return lines * 240;
|
|
225
|
+
}
|