@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
|
@@ -102,6 +102,42 @@ export interface RuntimeBlockFragment {
|
|
|
102
102
|
to: number;
|
|
103
103
|
/** Height consumed on this page (twips). */
|
|
104
104
|
heightTwips: number;
|
|
105
|
+
/**
|
|
106
|
+
* Fragment classification.
|
|
107
|
+
* - `"whole"` (default): the fragment represents the entire block; no slicing.
|
|
108
|
+
* - `"paragraph-slice"`: one of several fragments produced by intra-paragraph
|
|
109
|
+
* line-box splitting. `paragraphLineRange` identifies which lines this
|
|
110
|
+
* slice renders.
|
|
111
|
+
* - `"table-slice"`: one of several fragments produced by row-boundary table
|
|
112
|
+
* splitting (emitted by the table-fidelity workstream).
|
|
113
|
+
* `tableRowRange` identifies which canonical rows this slice renders.
|
|
114
|
+
* Consumers that predate multi-fragment blocks may treat an absent `kind`
|
|
115
|
+
* as `"whole"`.
|
|
116
|
+
*/
|
|
117
|
+
kind?: "whole" | "paragraph-slice" | "table-slice";
|
|
118
|
+
/**
|
|
119
|
+
* For `kind === "paragraph-slice"`, the inclusive-exclusive line-box index
|
|
120
|
+
* range rendered by this slice plus the total line count for the source
|
|
121
|
+
* paragraph. `from`/`to` still span the full paragraph offset range on
|
|
122
|
+
* every slice — only the visible lines differ.
|
|
123
|
+
*/
|
|
124
|
+
paragraphLineRange?: {
|
|
125
|
+
from: number;
|
|
126
|
+
to: number;
|
|
127
|
+
totalLines: number;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* For `kind === "table-slice"`, the inclusive-exclusive row-index range
|
|
131
|
+
* rendered by this slice. Repeated header rows (when the table has
|
|
132
|
+
* `isHeader` rows and `continuation` pages) are implied by the owning
|
|
133
|
+
* table's canonical row list — consumers prepend header rows for slices
|
|
134
|
+
* whose `from > 0`.
|
|
135
|
+
*/
|
|
136
|
+
tableRowRange?: {
|
|
137
|
+
from: number;
|
|
138
|
+
to: number;
|
|
139
|
+
totalRows: number;
|
|
140
|
+
};
|
|
105
141
|
}
|
|
106
142
|
|
|
107
143
|
export interface RuntimeLineBox {
|
|
@@ -148,6 +184,16 @@ export interface BuildPageGraphInput {
|
|
|
148
184
|
/** Optional block fragments pre-computed by pagination; when omitted the
|
|
149
185
|
* graph produces one fragment per page spanning its entire offset range. */
|
|
150
186
|
fragments?: readonly RuntimeBlockFragment[];
|
|
187
|
+
/**
|
|
188
|
+
* Optional block fragments keyed by pageIndex with the `pageId` omitted.
|
|
189
|
+
* `buildPageGraph` fills in the pageId using the graph's fresh revision
|
|
190
|
+
* stamp. Use this when the caller wants to emit per-block fragments but
|
|
191
|
+
* cannot know the pageId in advance.
|
|
192
|
+
*/
|
|
193
|
+
fragmentsByPageIndex?: ReadonlyMap<
|
|
194
|
+
number,
|
|
195
|
+
ReadonlyArray<Omit<RuntimeBlockFragment, "pageId">>
|
|
196
|
+
>;
|
|
151
197
|
/** Optional per-page line boxes. */
|
|
152
198
|
lineBoxes?: ReadonlyMap<string, RuntimeLineBox[]>;
|
|
153
199
|
/** Optional per-page note allocations. */
|
|
@@ -177,6 +223,15 @@ export function buildPageGraph(
|
|
|
177
223
|
|
|
178
224
|
const pages: RuntimePageNode[] = [];
|
|
179
225
|
const aggregatedFragments: RuntimeBlockFragment[] = [...(input.fragments ?? [])];
|
|
226
|
+
// Rehydrate fragmentsByPageIndex with the fresh graphRevision's pageIds.
|
|
227
|
+
if (input.fragmentsByPageIndex) {
|
|
228
|
+
for (const [pageIndex, fragments] of input.fragmentsByPageIndex) {
|
|
229
|
+
const pageId = `page-${graphRevision}-${pageIndex}`;
|
|
230
|
+
for (const fragment of fragments) {
|
|
231
|
+
aggregatedFragments.push({ ...fragment, pageId });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
180
235
|
const anchors: RuntimePageAnchor[] = [];
|
|
181
236
|
|
|
182
237
|
for (let index = 0; index < input.pages.length; index += 1) {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-box splitter for paragraph pagination.
|
|
3
|
+
*
|
|
4
|
+
* Given a paragraph that doesn't fit on the current page, decide how many of
|
|
5
|
+
* its lines belong on the current page and how many continue on the next.
|
|
6
|
+
* Honors the four pagination attributes that govern this in OOXML /
|
|
7
|
+
* ECMA-376 §17.3.1.33:
|
|
8
|
+
*
|
|
9
|
+
* - `keepLines` — if true, never split the paragraph.
|
|
10
|
+
* - `widowControl` — if true (Word default), keep ≥ WIDOW_MIN lines on
|
|
11
|
+
* each side of any split. Applies independently at
|
|
12
|
+
* the top (orphan) and bottom (widow) of a split.
|
|
13
|
+
* - `keepNext` — orthogonal to splitting itself; the engine's
|
|
14
|
+
* outer loop uses it to bundle the paragraph with
|
|
15
|
+
* the next. We only honour it here to avoid an
|
|
16
|
+
* awkward split on a paragraph that is about to be
|
|
17
|
+
* kept with the next (no gain from splitting).
|
|
18
|
+
* - `pageBreakBefore` — handled upstream via an unconditional page break;
|
|
19
|
+
* if set, callers do not consult this splitter.
|
|
20
|
+
*
|
|
21
|
+
* Pure function. No DOM, no side effects. Unit-testable in isolation.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_WIDOW_MIN_LINES = 2;
|
|
25
|
+
|
|
26
|
+
export interface LineSplitInput {
|
|
27
|
+
/** Total number of line boxes in the paragraph, ≥ 1. */
|
|
28
|
+
totalLines: number;
|
|
29
|
+
/**
|
|
30
|
+
* How many lines can still fit on the current page given the remaining
|
|
31
|
+
* vertical space and the paragraph's resolved line height. Callers compute
|
|
32
|
+
* this by dividing the remaining column height by the per-line height.
|
|
33
|
+
* Must be ≥ 0. Zero is legal and short-circuits to "no split" (the whole
|
|
34
|
+
* paragraph moves to the next page).
|
|
35
|
+
*/
|
|
36
|
+
availableLines: number;
|
|
37
|
+
/** OOXML `w:keepLines`. */
|
|
38
|
+
keepLines: boolean;
|
|
39
|
+
/** OOXML `w:widowControl` (true in Word's default). */
|
|
40
|
+
widowControl: boolean;
|
|
41
|
+
/** OOXML `w:keepNext`. */
|
|
42
|
+
keepNext: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this paragraph is the last block on the page-in-flight. If true,
|
|
45
|
+
* splitting buys nothing (there's no subsequent content we're squeezing in
|
|
46
|
+
* below) and we leave the whole paragraph on the current page.
|
|
47
|
+
*/
|
|
48
|
+
isLastBlockOnPage: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Minimum lines required on each side of a widow/orphan-controlled split.
|
|
51
|
+
* Defaults to {@link DEFAULT_WIDOW_MIN_LINES}. Exposed for fixtures that
|
|
52
|
+
* exercise narrow paragraphs.
|
|
53
|
+
*/
|
|
54
|
+
widowMinLines?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LineSplitResult {
|
|
58
|
+
linesOnCurrent: number;
|
|
59
|
+
linesOnNext: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Decide how to split a paragraph across a page boundary.
|
|
64
|
+
*
|
|
65
|
+
* Returns:
|
|
66
|
+
* - `null` when no split should happen. The caller's existing "move the
|
|
67
|
+
* whole paragraph to the next page" behavior applies.
|
|
68
|
+
* - A `{ linesOnCurrent, linesOnNext }` split with `linesOnCurrent +
|
|
69
|
+
* linesOnNext === totalLines` and both values ≥ 1. The caller must
|
|
70
|
+
* render `linesOnCurrent` lines in the remaining space on the current
|
|
71
|
+
* page and `linesOnNext` lines at the top of the next page.
|
|
72
|
+
*/
|
|
73
|
+
export function paginateParagraphLines(
|
|
74
|
+
input: LineSplitInput,
|
|
75
|
+
): LineSplitResult | null {
|
|
76
|
+
const totalLines = Math.max(1, Math.floor(input.totalLines));
|
|
77
|
+
const availableLines = Math.max(0, Math.floor(input.availableLines));
|
|
78
|
+
const widowMin = Math.max(1, input.widowMinLines ?? DEFAULT_WIDOW_MIN_LINES);
|
|
79
|
+
|
|
80
|
+
// keepLines wins unconditionally.
|
|
81
|
+
if (input.keepLines) return null;
|
|
82
|
+
|
|
83
|
+
// If the paragraph fits wholesale on the current page, no split.
|
|
84
|
+
if (totalLines <= availableLines) return null;
|
|
85
|
+
|
|
86
|
+
// If there's nothing worth keeping on the current page (zero lines fit, or
|
|
87
|
+
// only a single line under widow control would fit), fall through to the
|
|
88
|
+
// caller's "move whole paragraph" path.
|
|
89
|
+
const effectiveOrphanMin = input.widowControl ? widowMin : 1;
|
|
90
|
+
if (availableLines < effectiveOrphanMin) return null;
|
|
91
|
+
|
|
92
|
+
// Don't split trivially-short paragraphs whose final lines could fit a
|
|
93
|
+
// page move (keeping them intact preserves Word's visual intent).
|
|
94
|
+
if (totalLines < effectiveOrphanMin * 2) return null;
|
|
95
|
+
|
|
96
|
+
// If this paragraph is the last thing on this page anyway, splitting has
|
|
97
|
+
// no downstream benefit — the next page is empty.
|
|
98
|
+
if (input.isLastBlockOnPage) return null;
|
|
99
|
+
|
|
100
|
+
// keepNext paragraphs are meant to travel with the next block. Splitting
|
|
101
|
+
// them would move half-and-half across the break, which usually contradicts
|
|
102
|
+
// author intent. Leave the paragraph intact; the outer loop will handle
|
|
103
|
+
// the keep-with-next pairing on the next page.
|
|
104
|
+
if (input.keepNext) return null;
|
|
105
|
+
|
|
106
|
+
// Candidate split: fill the current page, put the rest on the next.
|
|
107
|
+
let linesOnCurrent = availableLines;
|
|
108
|
+
let linesOnNext = totalLines - linesOnCurrent;
|
|
109
|
+
|
|
110
|
+
// Widow control: the tail on the next page needs ≥ widowMin lines too.
|
|
111
|
+
if (input.widowControl && linesOnNext < widowMin) {
|
|
112
|
+
// Pull enough lines back to the next page to meet the widow minimum.
|
|
113
|
+
const deficit = widowMin - linesOnNext;
|
|
114
|
+
linesOnCurrent -= deficit;
|
|
115
|
+
linesOnNext += deficit;
|
|
116
|
+
|
|
117
|
+
// If pulling back violates the orphan minimum, abandon the split —
|
|
118
|
+
// the whole paragraph goes on the next page.
|
|
119
|
+
if (linesOnCurrent < widowMin) return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Sanity clamp (the arithmetic above should guarantee these but be
|
|
123
|
+
// defensive against bad input).
|
|
124
|
+
if (linesOnCurrent < 1 || linesOnNext < 1) return null;
|
|
125
|
+
if (linesOnCurrent + linesOnNext !== totalLines) return null;
|
|
126
|
+
|
|
127
|
+
return { linesOnCurrent, linesOnNext };
|
|
128
|
+
}
|