@beyondwork/docx-react-component 1.0.103 → 1.0.104

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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +63 -1
  3. package/src/api/v3/_runtime-handle.ts +2 -0
  4. package/src/api/v3/ai/outline.ts +2 -7
  5. package/src/api/v3/runtime/geometry.ts +79 -0
  6. package/src/io/ooxml/parse-drawing.ts +99 -1
  7. package/src/io/ooxml/parse-fields.ts +27 -6
  8. package/src/io/ooxml/parse-shapes.ts +130 -0
  9. package/src/model/canonical-document.ts +34 -3
  10. package/src/model/canonical-layout-inputs.ts +979 -0
  11. package/src/model/layout/index.ts +6 -0
  12. package/src/model/layout/page-graph-types.ts +61 -0
  13. package/src/model/layout/runtime-page-graph-types.ts +10 -0
  14. package/src/runtime/collab/runtime-collab-sync.ts +3 -3
  15. package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
  16. package/src/runtime/document-runtime.ts +30 -14
  17. package/src/runtime/event-refresh-hints.ts +3 -0
  18. package/src/runtime/formatting/formatting-context.ts +110 -9
  19. package/src/runtime/formatting/index.ts +2 -0
  20. package/src/runtime/formatting/layout-inputs.ts +67 -3
  21. package/src/runtime/geometry/caret-geometry.ts +82 -10
  22. package/src/runtime/geometry/geometry-facet.ts +36 -0
  23. package/src/runtime/geometry/geometry-index.ts +891 -0
  24. package/src/runtime/geometry/geometry-types.ts +221 -1
  25. package/src/runtime/geometry/index.ts +26 -0
  26. package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
  27. package/src/runtime/geometry/replacement-envelope.ts +41 -2
  28. package/src/runtime/layout/layout-engine-version.ts +16 -1
  29. package/src/runtime/layout/page-graph.ts +191 -1
  30. package/src/runtime/prerender/graph-canonicalize.ts +30 -0
  31. package/src/runtime/surface-projection.ts +43 -3
  32. package/src/runtime/workflow/coordinator.ts +57 -11
  33. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
@@ -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
- * When the kernel's per-line anchor projection matures (Slice 5+), this
92
- * function flips to emitting one rect per line. The single-rect fallback
93
- * keeps chrome surfaces that currently call `getAnchorRects({ kind:
94
- * "runtime-offset", … })[0]` in sync with the new selection API.
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,20 @@ 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
+ return summarizeGeometryCoverageFromFrame(kernel.getRenderFrame());
263
+ },
264
+
229
265
  hitTest(point) {
230
266
  // Slice 6 wrapper-deletion (2026-04-22): the layout-facet fallback
231
267
  // was deleted. `hitTest` now resolves only through the render-kernel