@beyondwork/docx-react-component 1.0.103 → 1.0.105
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 +1 -1
- package/src/api/public-types.ts +66 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/_pe2-evidence.ts +153 -0
- package/src/api/v3/ai/bundle.ts +13 -5
- package/src/api/v3/ai/inspect.ts +7 -1
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/ai/replacement.ts +113 -0
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/api/v3/ui/_types.ts +86 -0
- package/src/api/v3/ui/index.ts +5 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/io/ooxml/parse-drawing.ts +99 -1
- package/src/io/ooxml/parse-fields.ts +27 -6
- package/src/io/ooxml/parse-shapes.ts +130 -0
- package/src/model/canonical-document.ts +34 -3
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +9 -0
- package/src/model/layout/page-graph-types.ts +150 -0
- package/src/model/layout/runtime-page-graph-types.ts +23 -0
- package/src/runtime/collab/runtime-collab-sync.ts +3 -3
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
- package/src/runtime/document-runtime.ts +30 -14
- package/src/runtime/event-refresh-hints.ts +35 -5
- package/src/runtime/formatting/formatting-context.ts +110 -9
- package/src/runtime/formatting/index.ts +2 -0
- package/src/runtime/formatting/layout-inputs.ts +67 -3
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +44 -0
- package/src/runtime/geometry/geometry-index.ts +1268 -0
- package/src/runtime/geometry/geometry-types.ts +227 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/object-handles.ts +7 -4
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +44 -1
- package/src/runtime/layout/page-graph.ts +877 -2
- package/src/runtime/layout/project-block-fragments.ts +101 -1
- package/src/runtime/layout/public-facet.ts +152 -0
- package/src/runtime/prerender/graph-canonicalize.ts +44 -0
- package/src/runtime/surface-projection.ts +43 -3
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/ui/ui-controller-factory.ts +11 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -7,6 +7,10 @@ import type {
|
|
|
7
7
|
FieldRegistryEntry,
|
|
8
8
|
TabStop,
|
|
9
9
|
} from "../../model/canonical-document.ts";
|
|
10
|
+
import {
|
|
11
|
+
MAIN_STORY_KEY,
|
|
12
|
+
type CanonicalFieldRegionIdentity,
|
|
13
|
+
} from "../../model/canonical-layout-inputs.ts";
|
|
10
14
|
import type { ResolvedField } from "./field/resolver.ts";
|
|
11
15
|
import type {
|
|
12
16
|
EffectiveParagraphFormatting,
|
|
@@ -43,7 +47,11 @@ export interface NumberingLayoutInput {
|
|
|
43
47
|
|
|
44
48
|
export interface FieldLayoutInput {
|
|
45
49
|
readonly fieldId: string;
|
|
50
|
+
readonly regionId: string;
|
|
51
|
+
readonly regionKind: "field" | "toc-region";
|
|
52
|
+
readonly storyKey: string;
|
|
46
53
|
readonly fieldIndex: number;
|
|
54
|
+
readonly paragraphIndex: number;
|
|
47
55
|
readonly family: FieldFamily;
|
|
48
56
|
readonly instruction: string;
|
|
49
57
|
readonly cachedText: string;
|
|
@@ -58,6 +66,11 @@ export interface FieldLayoutInput {
|
|
|
58
66
|
|
|
59
67
|
export interface RevisionLayoutPosture {
|
|
60
68
|
readonly mode: "clean" | "simple" | "all" | "original" | "no-markup";
|
|
69
|
+
/**
|
|
70
|
+
* True only when the active revision display changes measured text
|
|
71
|
+
* for the paragraph. Paint-only redline posture is compositor data,
|
|
72
|
+
* not an L04 measurement input.
|
|
73
|
+
*/
|
|
61
74
|
readonly affectsMeasuredText: boolean;
|
|
62
75
|
readonly hiddenRevisionIds: readonly string[];
|
|
63
76
|
readonly visibleRevisionIds: readonly string[];
|
|
@@ -100,6 +113,27 @@ export function toLayoutTabStops(
|
|
|
100
113
|
.sort((a, b) => a.positionTwips - b.positionTwips);
|
|
101
114
|
}
|
|
102
115
|
|
|
116
|
+
export function mergeLayoutTabStops(
|
|
117
|
+
...groups: readonly (readonly LayoutTabStopInput[] | undefined)[]
|
|
118
|
+
): readonly LayoutTabStopInput[] {
|
|
119
|
+
const byKey = new Map<string, LayoutTabStopInput>();
|
|
120
|
+
for (const group of groups) {
|
|
121
|
+
for (const tabStop of group ?? []) {
|
|
122
|
+
const key = [
|
|
123
|
+
tabStop.source,
|
|
124
|
+
tabStop.positionTwips,
|
|
125
|
+
tabStop.align,
|
|
126
|
+
tabStop.leader ?? "",
|
|
127
|
+
].join(":");
|
|
128
|
+
byKey.set(key, tabStop);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return [...byKey.values()].sort((a, b) => {
|
|
132
|
+
if (a.positionTwips !== b.positionTwips) return a.positionTwips - b.positionTwips;
|
|
133
|
+
return a.source.localeCompare(b.source);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
export function toNumberingLayoutInput(
|
|
104
138
|
numbering: NumberingPrefixResult | null | undefined,
|
|
105
139
|
): NumberingLayoutInput | undefined {
|
|
@@ -138,10 +172,15 @@ export function toNumberingLayoutInput(
|
|
|
138
172
|
export function toFieldLayoutInput(
|
|
139
173
|
entry: FieldRegistryEntry,
|
|
140
174
|
resolved?: ResolvedField,
|
|
175
|
+
region?: CanonicalFieldRegionIdentity,
|
|
141
176
|
): FieldLayoutInput {
|
|
142
177
|
return {
|
|
143
178
|
fieldId: `field-${entry.fieldIndex}`,
|
|
179
|
+
regionId: region?.regionId ?? `field:${entry.fieldIndex}`,
|
|
180
|
+
regionKind: region?.kind ?? "field",
|
|
181
|
+
storyKey: region?.storyKey ?? entry.storyKey ?? MAIN_STORY_KEY,
|
|
144
182
|
fieldIndex: entry.fieldIndex,
|
|
183
|
+
paragraphIndex: region?.paragraphIndex ?? entry.paragraphIndex,
|
|
145
184
|
family: entry.fieldFamily,
|
|
146
185
|
instruction: entry.instruction,
|
|
147
186
|
cachedText: entry.displayText,
|
|
@@ -156,20 +195,32 @@ export function toFieldLayoutInput(
|
|
|
156
195
|
export function collectFieldLayoutInputs(
|
|
157
196
|
registry: FieldRegistry | undefined,
|
|
158
197
|
resolve?: (entry: FieldRegistryEntry) => ResolvedField | undefined,
|
|
198
|
+
regions: readonly CanonicalFieldRegionIdentity[] = [],
|
|
159
199
|
): readonly FieldLayoutInput[] {
|
|
160
200
|
if (!registry) return [];
|
|
161
201
|
const inputs: FieldLayoutInput[] = [];
|
|
162
202
|
const entries = [...registry.supported, ...registry.preserveOnly];
|
|
203
|
+
const fieldRegions = new Map(
|
|
204
|
+
regions
|
|
205
|
+
.filter((region) => region.kind === "field")
|
|
206
|
+
.map((region) => [region.fieldIndex, region]),
|
|
207
|
+
);
|
|
208
|
+
const tocRegions = new Map(
|
|
209
|
+
regions
|
|
210
|
+
.filter((region) => region.kind === "toc-region" && region.tocId !== undefined)
|
|
211
|
+
.map((region) => [region.tocId!, region]),
|
|
212
|
+
);
|
|
163
213
|
for (const entry of entries) {
|
|
164
|
-
inputs.push(toFieldLayoutInput(entry, resolve?.(entry)));
|
|
214
|
+
inputs.push(toFieldLayoutInput(entry, resolve?.(entry), fieldRegions.get(entry.fieldIndex)));
|
|
165
215
|
}
|
|
166
216
|
for (const region of registry.tocRegions ?? []) {
|
|
167
217
|
const source = registry.supported.find(
|
|
168
218
|
(entry) => entry.fieldIndex === region.sourceFieldIndex,
|
|
169
219
|
);
|
|
170
220
|
if (!source) continue;
|
|
221
|
+
const identity = tocRegions.get(region.tocId);
|
|
171
222
|
inputs.push({
|
|
172
|
-
...toFieldLayoutInput(source, resolve?.(source)),
|
|
223
|
+
...toFieldLayoutInput(source, resolve?.(source), identity),
|
|
173
224
|
fieldId: `toc-${region.tocId}`,
|
|
174
225
|
family: "TOC",
|
|
175
226
|
instruction: region.instruction.raw,
|
|
@@ -200,6 +251,18 @@ export function buildRevisionLayoutPosture(
|
|
|
200
251
|
};
|
|
201
252
|
}
|
|
202
253
|
|
|
254
|
+
export function toMeasuredRevisionLayoutPosture(
|
|
255
|
+
posture: RevisionLayoutPosture | undefined,
|
|
256
|
+
): RevisionLayoutPosture | undefined {
|
|
257
|
+
if (!posture?.affectsMeasuredText || posture.hiddenRevisionIds.length === 0) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
...posture,
|
|
262
|
+
visibleRevisionIds: [],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
203
266
|
export function buildEffectiveLayoutFormatting(input: {
|
|
204
267
|
readonly paragraph?: EffectiveParagraphFormatting;
|
|
205
268
|
readonly runs?: readonly EffectiveRunFormatting[];
|
|
@@ -213,6 +276,7 @@ export function buildEffectiveLayoutFormatting(input: {
|
|
|
213
276
|
readonly compatFlags?: readonly string[];
|
|
214
277
|
readonly revisionPosture?: RevisionLayoutPosture;
|
|
215
278
|
}): EffectiveLayoutFormatting {
|
|
279
|
+
const revisionPosture = toMeasuredRevisionLayoutPosture(input.revisionPosture);
|
|
216
280
|
const withoutHash = {
|
|
217
281
|
...(input.paragraph ? { paragraph: input.paragraph } : {}),
|
|
218
282
|
runs: input.runs ?? [],
|
|
@@ -221,7 +285,7 @@ export function buildEffectiveLayoutFormatting(input: {
|
|
|
221
285
|
...(input.numbering ? { numbering: input.numbering } : {}),
|
|
222
286
|
tabs: input.tabs ?? [],
|
|
223
287
|
compatFlags: input.compatFlags ?? [],
|
|
224
|
-
...(
|
|
288
|
+
...(revisionPosture ? { revisionPosture } : {}),
|
|
225
289
|
};
|
|
226
290
|
return {
|
|
227
291
|
...withoutHash,
|
|
@@ -37,6 +37,7 @@ import type { EditorStoryTarget } from "../../api/public-types";
|
|
|
37
37
|
import type {
|
|
38
38
|
RenderFrame,
|
|
39
39
|
RenderFrameRect,
|
|
40
|
+
RenderStoryRegion,
|
|
40
41
|
} from "../render/index.ts";
|
|
41
42
|
import type {
|
|
42
43
|
CaretGeometry,
|
|
@@ -86,12 +87,14 @@ export function resolveCaretGeometry(
|
|
|
86
87
|
* Resolve `GeometryRect[]` for a selection range. Slice 4 produces:
|
|
87
88
|
* - an empty list when either endpoint can't be resolved
|
|
88
89
|
* - a single zero-width caret rect when `from === to`
|
|
90
|
+
* - one line rect per covered render line when line ranges are available
|
|
89
91
|
* - a single union rect otherwise (via `frame.anchorIndex.bySelection`)
|
|
90
92
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* keeps chrome surfaces that currently call
|
|
94
|
-
* "runtime-offset", … })[0]` in sync with the
|
|
93
|
+
* The line-aware path still tags rects `within-tolerance`: without per-run
|
|
94
|
+
* anchors we can identify covered lines but not the first/last glyph bounds.
|
|
95
|
+
* The single-rect fallback keeps chrome surfaces that currently call
|
|
96
|
+
* `getAnchorRects({ kind: "runtime-offset", … })[0]` in sync with the
|
|
97
|
+
* selection API when a frame lacks line boxes.
|
|
95
98
|
*/
|
|
96
99
|
export function resolveSelectionRects(
|
|
97
100
|
frame: RenderFrame | null,
|
|
@@ -119,14 +122,11 @@ export function resolveSelectionRects(
|
|
|
119
122
|
return [caret.rect];
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
const lineRects = resolveLineSelectionRects(frame, lo, hi, range.story);
|
|
126
|
+
if (lineRects.length > 0) return lineRects;
|
|
127
|
+
|
|
122
128
|
const union = frame.anchorIndex.bySelection(lo, hi, range.story);
|
|
123
129
|
if (!union) return [];
|
|
124
|
-
// Slice 7b (2026-04-22): the substrate returns ONE union rect for any
|
|
125
|
-
// non-collapsed range. That rect is geometrically correct at the
|
|
126
|
-
// block level but loses per-line detail — tag it `within-tolerance`
|
|
127
|
-
// so callers can gate per-line chrome work. When the render kernel
|
|
128
|
-
// ships per-run anchors (refactor/05 Slice 7 Task 2), this site
|
|
129
|
-
// upgrades to multiple `"exact"` rects automatically.
|
|
130
130
|
return [toGeometryRect(union, "within-tolerance")];
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -151,6 +151,78 @@ function toGeometryRect(
|
|
|
151
151
|
return out;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
function resolveLineSelectionRects(
|
|
155
|
+
frame: RenderFrame,
|
|
156
|
+
from: number,
|
|
157
|
+
to: number,
|
|
158
|
+
story?: EditorStoryTarget,
|
|
159
|
+
): readonly GeometryRect[] {
|
|
160
|
+
if (!Array.isArray(frame.pages)) return [];
|
|
161
|
+
const rects: GeometryRect[] = [];
|
|
162
|
+
for (const page of frame.pages) {
|
|
163
|
+
for (const region of collectRegions(page.regions)) {
|
|
164
|
+
if (!storyMatches(region.storyTarget, story)) continue;
|
|
165
|
+
for (const block of region.blocks) {
|
|
166
|
+
const blockFrom = block.fragment.from;
|
|
167
|
+
const blockTo = block.fragment.to;
|
|
168
|
+
if (blockTo <= from || blockFrom >= to) continue;
|
|
169
|
+
if (block.lines.length === 0) continue;
|
|
170
|
+
const lineCount = block.lines.length;
|
|
171
|
+
const span = Math.max(1, blockTo - blockFrom);
|
|
172
|
+
for (let i = 0; i < lineCount; i += 1) {
|
|
173
|
+
const line = block.lines[i]!;
|
|
174
|
+
const lineFrom =
|
|
175
|
+
blockFrom + Math.floor((span * i) / Math.max(1, lineCount));
|
|
176
|
+
const lineTo =
|
|
177
|
+
i === lineCount - 1
|
|
178
|
+
? blockTo
|
|
179
|
+
: blockFrom +
|
|
180
|
+
Math.floor((span * (i + 1)) / Math.max(1, lineCount));
|
|
181
|
+
if (lineTo <= from || lineFrom >= to) continue;
|
|
182
|
+
rects.push(toGeometryRect(line.frame, "within-tolerance"));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return rects;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function collectRegions(
|
|
191
|
+
regions: RenderFrame["pages"][number]["regions"],
|
|
192
|
+
): readonly RenderStoryRegion[] {
|
|
193
|
+
return [
|
|
194
|
+
regions.body,
|
|
195
|
+
regions.header,
|
|
196
|
+
regions.footer,
|
|
197
|
+
...(regions.columns ?? []),
|
|
198
|
+
regions.footnoteArea,
|
|
199
|
+
...(regions.footnotes ?? []),
|
|
200
|
+
].filter((region): region is RenderStoryRegion => Boolean(region));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function storyMatches(
|
|
204
|
+
actual: EditorStoryTarget,
|
|
205
|
+
expected: EditorStoryTarget | undefined,
|
|
206
|
+
): boolean {
|
|
207
|
+
if (!expected) return true;
|
|
208
|
+
if (actual.kind !== expected.kind) return false;
|
|
209
|
+
switch (expected.kind) {
|
|
210
|
+
case "main":
|
|
211
|
+
return true;
|
|
212
|
+
case "header":
|
|
213
|
+
case "footer":
|
|
214
|
+
return (
|
|
215
|
+
"relationshipId" in actual &&
|
|
216
|
+
actual.relationshipId === expected.relationshipId &&
|
|
217
|
+
actual.variant === expected.variant &&
|
|
218
|
+
actual.sectionIndex === expected.sectionIndex
|
|
219
|
+
);
|
|
220
|
+
case "footnote":
|
|
221
|
+
case "endnote":
|
|
222
|
+
return "noteId" in actual && actual.noteId === expected.noteId;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
154
226
|
/**
|
|
155
227
|
* Approximate the baseline offset within a line rect. The render frame
|
|
156
228
|
* does not yet carry per-anchor baseline metadata, so Slice 4 uses a
|
|
@@ -37,6 +37,8 @@ import type {
|
|
|
37
37
|
BlockGeometry,
|
|
38
38
|
CaretGeometry,
|
|
39
39
|
EnvelopeBundle,
|
|
40
|
+
GeometryIndex,
|
|
41
|
+
GeometryIndexCoverage,
|
|
40
42
|
GeometryRect,
|
|
41
43
|
HitTestPoint,
|
|
42
44
|
HitTestResult,
|
|
@@ -51,6 +53,11 @@ import {
|
|
|
51
53
|
import { resolveHitTest } from "./hit-test.ts";
|
|
52
54
|
import { resolveObjectHandles } from "./object-handles.ts";
|
|
53
55
|
import { resolveAnchorRects } from "./project-anchors.ts";
|
|
56
|
+
import {
|
|
57
|
+
createUnavailableGeometryCoverage,
|
|
58
|
+
projectGeometryIndexFromFrame,
|
|
59
|
+
summarizeGeometryCoverageFromFrame,
|
|
60
|
+
} from "./geometry-index.ts";
|
|
54
61
|
import {
|
|
55
62
|
resolveReplacementEnvelope,
|
|
56
63
|
type ReplacementScope,
|
|
@@ -67,6 +74,21 @@ import { createViewport, type ViewportHandle } from "./viewport.ts";
|
|
|
67
74
|
// ---------------------------------------------------------------------------
|
|
68
75
|
|
|
69
76
|
export interface GeometryFacet {
|
|
77
|
+
// PE2 index / coverage -------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Renderer-neutral geometry index for the current frame. This is the PE2
|
|
80
|
+
* Slice-1 substrate: pages, regions, slices, lines, anchors, hit targets,
|
|
81
|
+
* and coverage metadata. Returns null when no render kernel/frame is warm.
|
|
82
|
+
*/
|
|
83
|
+
getGeometryIndex(): GeometryIndex | null;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Lightweight coverage summary for evidence/debug consumers. Unlike
|
|
87
|
+
* `getGeometryIndex`, this always returns a value; unwired/pre-paint states
|
|
88
|
+
* report `status: "unavailable"` with zero counts.
|
|
89
|
+
*/
|
|
90
|
+
getGeometryCoverage(): GeometryIndexCoverage;
|
|
91
|
+
|
|
70
92
|
// Hit-test -------------------------------------------------------------
|
|
71
93
|
/**
|
|
72
94
|
* Resolve a point in the shell's overlay coordinate space to a canonical
|
|
@@ -226,6 +248,28 @@ export function createGeometryFacet(
|
|
|
226
248
|
const viewport = input.viewport ?? createViewport();
|
|
227
249
|
|
|
228
250
|
return {
|
|
251
|
+
getGeometryIndex() {
|
|
252
|
+
const kernel = input.renderKernel?.();
|
|
253
|
+
if (!kernel) return null;
|
|
254
|
+
return projectGeometryIndexFromFrame(kernel.getRenderFrame(), {
|
|
255
|
+
canonicalDocument: input.getCanonicalDocument?.() ?? null,
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
getGeometryCoverage() {
|
|
260
|
+
const kernel = input.renderKernel?.();
|
|
261
|
+
if (!kernel) return createUnavailableGeometryCoverage();
|
|
262
|
+
const frame = kernel.getRenderFrame();
|
|
263
|
+
const canonicalDocument = input.getCanonicalDocument?.() ?? null;
|
|
264
|
+
if (canonicalDocument) {
|
|
265
|
+
return (
|
|
266
|
+
projectGeometryIndexFromFrame(frame, { canonicalDocument })?.coverage ??
|
|
267
|
+
createUnavailableGeometryCoverage()
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return summarizeGeometryCoverageFromFrame(frame);
|
|
271
|
+
},
|
|
272
|
+
|
|
229
273
|
hitTest(point) {
|
|
230
274
|
// Slice 6 wrapper-deletion (2026-04-22): the layout-facet fallback
|
|
231
275
|
// was deleted. `hitTest` now resolves only through the render-kernel
|