@beyondwork/docx-react-component 1.0.104 → 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.
@@ -235,11 +235,27 @@ function emitSlicedParagraph(
235
235
  ...deriveStyleMetadata(block),
236
236
  kind: "paragraph-slice",
237
237
  paragraphLineRange: slice.lineRange,
238
+ continuation: buildParagraphContinuationCursor(slice, i, slices.length),
238
239
  };
239
240
  emit(slice.pageIndex, fragment);
240
241
  }
241
242
  }
242
243
 
244
+ function buildParagraphContinuationCursor(
245
+ slice: ParagraphLineSlice,
246
+ sequenceIndex: number,
247
+ sliceCount: number,
248
+ ): NonNullable<RuntimeBlockFragment["continuation"]> {
249
+ return {
250
+ kind: "paragraph",
251
+ sequenceIndex,
252
+ sliceCount,
253
+ lineRange: slice.lineRange,
254
+ continuesFromPreviousPage: slice.lineRange.from > 0,
255
+ continuesToNextPage: slice.lineRange.to < slice.lineRange.totalLines,
256
+ };
257
+ }
258
+
243
259
  function estimateSliceHeightFromLines(lineRange: {
244
260
  from: number;
245
261
  to: number;
@@ -259,7 +275,7 @@ function estimateSliceHeightFromLines(lineRange: {
259
275
  * every continuation page.
260
276
  */
261
277
  function emitSlicedTable(
262
- block: SurfaceBlockSnapshot,
278
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
263
279
  slices: readonly TableRowSlice[],
264
280
  emit: (pageIndex: number, fragment: FragmentWithoutPageId) => void,
265
281
  ): void {
@@ -276,12 +292,96 @@ function emitSlicedTable(
276
292
  ...deriveStyleMetadata(block),
277
293
  kind: "table-slice",
278
294
  tableRowRange: slice.rowRange,
295
+ continuation: buildTableContinuationCursor(block, slice, i, slices.length),
279
296
  ...(slice.columnIndex !== undefined ? { columnIndex: slice.columnIndex } : {}),
280
297
  };
281
298
  emit(slice.pageIndex, fragment);
282
299
  }
283
300
  }
284
301
 
302
+ function buildTableContinuationCursor(
303
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
304
+ slice: TableRowSlice,
305
+ sequenceIndex: number,
306
+ sliceCount: number,
307
+ ): NonNullable<RuntimeBlockFragment["continuation"]> {
308
+ return {
309
+ kind: "table",
310
+ sequenceIndex,
311
+ sliceCount,
312
+ rowRange: slice.rowRange,
313
+ continuesFromPreviousPage: slice.rowRange.from > 0,
314
+ continuesToNextPage: slice.rowRange.to < slice.rowRange.totalRows,
315
+ repeatedHeaderRowIndexes:
316
+ slice.rowRange.from > 0
317
+ ? collectRepeatedHeaderRowIndexes(block, slice.rowRange.from)
318
+ : [],
319
+ verticalMergeCarry: collectVerticalMergeCarry(block, slice.rowRange.from),
320
+ };
321
+ }
322
+
323
+ function collectRepeatedHeaderRowIndexes(
324
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
325
+ startRow: number,
326
+ ): number[] {
327
+ const headerRows: number[] = [];
328
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
329
+ if (rowIndex >= startRow) break;
330
+ if (block.rows[rowIndex]?.isHeader === true) headerRows.push(rowIndex);
331
+ }
332
+ return headerRows;
333
+ }
334
+
335
+ function collectVerticalMergeCarry(
336
+ block: Extract<SurfaceBlockSnapshot, { kind: "table" }>,
337
+ startRow: number,
338
+ ): Array<{ columnIndex: number; restartRowIndex: number }> {
339
+ if (startRow <= 0) return [];
340
+
341
+ const activeByColumn = new Map<number, number>();
342
+ for (let rowIndex = 0; rowIndex < startRow; rowIndex += 1) {
343
+ visitRowCells(block.rows[rowIndex], (columnIndex, span, verticalMerge) => {
344
+ for (let column = columnIndex; column < columnIndex + span; column += 1) {
345
+ if (verticalMerge === "restart") {
346
+ activeByColumn.set(column, rowIndex);
347
+ } else if (verticalMerge !== "continue") {
348
+ activeByColumn.delete(column);
349
+ }
350
+ }
351
+ });
352
+ }
353
+
354
+ const carried = new Map<number, number>();
355
+ visitRowCells(block.rows[startRow], (columnIndex, span, verticalMerge) => {
356
+ if (verticalMerge !== "continue") return;
357
+ for (let column = columnIndex; column < columnIndex + span; column += 1) {
358
+ const restartRowIndex = activeByColumn.get(column);
359
+ if (restartRowIndex !== undefined) carried.set(column, restartRowIndex);
360
+ }
361
+ });
362
+
363
+ return [...carried]
364
+ .sort(([a], [b]) => a - b)
365
+ .map(([columnIndex, restartRowIndex]) => ({ columnIndex, restartRowIndex }));
366
+ }
367
+
368
+ function visitRowCells(
369
+ row: Extract<SurfaceBlockSnapshot, { kind: "table" }>["rows"][number] | undefined,
370
+ visit: (
371
+ columnIndex: number,
372
+ span: number,
373
+ verticalMerge: "restart" | "continue" | null,
374
+ ) => void,
375
+ ): void {
376
+ if (!row) return;
377
+ let columnIndex = Math.max(0, row.gridBefore ?? 0);
378
+ for (const cell of row.cells) {
379
+ const span = Math.max(1, cell.gridSpan || cell.colspan || 1);
380
+ visit(columnIndex, span, cell.verticalMerge);
381
+ columnIndex += span;
382
+ }
383
+ }
384
+
285
385
  function estimateSliceHeightFromRows(rowRange: {
286
386
  from: number;
287
387
  to: number;
@@ -25,12 +25,16 @@ import type {
25
25
  } from "./page-story-resolver.ts";
26
26
  import type {
27
27
  RuntimeBlockFragment,
28
+ RuntimeLayoutContinuationCursor,
29
+ RuntimeLayoutDivergence,
28
30
  RuntimeLineBox,
29
31
  RuntimeNoteAllocation,
32
+ RuntimePageFrame,
30
33
  RuntimePageGraph,
31
34
  RuntimePageNode,
32
35
  RuntimePageRegion,
33
36
  RuntimePageRegions,
37
+ RuntimeTwipsRect,
34
38
  } from "./page-graph.ts";
35
39
  import type {
36
40
  ResolvedFormattingState,
@@ -132,6 +136,40 @@ export function emitLayoutGuardWarning(
132
136
  // Public read model types (shape-stable, cloned at the facet boundary)
133
137
  // ---------------------------------------------------------------------------
134
138
 
139
+ export interface PublicTwipsRect {
140
+ xTwips: number;
141
+ yTwips: number;
142
+ widthTwips: number;
143
+ heightTwips: number;
144
+ }
145
+
146
+ export interface PublicLayoutDivergence {
147
+ divergenceId: string;
148
+ kind: RuntimeLayoutDivergence["kind"];
149
+ source: RuntimeLayoutDivergence["source"];
150
+ severity: RuntimeLayoutDivergence["severity"];
151
+ message: string;
152
+ regionKinds?: readonly RuntimePageRegion["kind"][];
153
+ fragmentIds?: readonly string[];
154
+ }
155
+
156
+ export interface PublicPageFrame {
157
+ frameId: string;
158
+ pageId: string;
159
+ pageIndex: number;
160
+ sectionIndex: number;
161
+ displayPageNumber: number;
162
+ physicalBoundsTwips: PublicTwipsRect;
163
+ divergenceIds: readonly string[];
164
+ signature: string;
165
+ exclusionZoneCount: number;
166
+ regionFrames: readonly {
167
+ kind: RuntimePageRegion["kind"];
168
+ rectTwips?: PublicTwipsRect;
169
+ fragmentIds: readonly string[];
170
+ }[];
171
+ }
172
+
135
173
  export interface PublicPageNode {
136
174
  pageId: string;
137
175
  pageIndex: number;
@@ -157,6 +195,10 @@ export interface PublicPageNode {
157
195
  lineBoxCount: number;
158
196
  /** Footnotes reserved at the bottom of the page, if any. */
159
197
  noteAllocations: readonly PublicNoteAllocation[];
198
+ /** PE2 page-frame graph projection, when emitted by the L04 graph. */
199
+ frame?: PublicPageFrame;
200
+ /** Typed layout divergences detected while building this page. */
201
+ divergences?: readonly PublicLayoutDivergence[];
160
202
  }
161
203
 
162
204
  export interface PublicResolvedPageStories {
@@ -187,6 +229,8 @@ export interface PublicPageRegion {
187
229
  widthTwips: number;
188
230
  heightTwips: number;
189
231
  fragmentCount: number;
232
+ /** PE2 semantic twips rect, when populated by the L04 page-frame graph. */
233
+ rectTwips?: PublicTwipsRect;
190
234
  }
191
235
 
192
236
  /**
@@ -208,8 +252,23 @@ export interface PublicBlockFragment {
208
252
  to: number;
209
253
  heightTwips: number;
210
254
  orderInRegion: number;
255
+ kind?: "whole" | "paragraph-slice" | "table-slice";
256
+ paragraphLineRange?: {
257
+ from: number;
258
+ to: number;
259
+ totalLines: number;
260
+ };
261
+ tableRowRange?: {
262
+ from: number;
263
+ to: number;
264
+ totalRows: number;
265
+ };
266
+ columnIndex?: number;
267
+ continuation?: PublicLayoutContinuationCursor;
211
268
  }
212
269
 
270
+ export type PublicLayoutContinuationCursor = RuntimeLayoutContinuationCursor;
271
+
213
272
  /**
214
273
  * P8 — One block snapshot rendered into a region of a page. Returned by
215
274
  * `WordReviewEditorLayoutFacet.getStoryBlocksForRegion` and
@@ -245,6 +304,7 @@ export interface PublicNoteAllocation {
245
304
  noteKind: "footnote" | "endnote";
246
305
  noteId: string;
247
306
  reservedHeightTwips: number;
307
+ fragmentId?: string;
248
308
  }
249
309
 
250
310
  export interface PublicPageAnchor {
@@ -1367,12 +1427,74 @@ function toPublicPageNode(
1367
1427
  regions: toPublicPageRegions(node.regions),
1368
1428
  lineBoxCount: node.lineBoxes.length,
1369
1429
  noteAllocations: node.noteAllocations.map(toPublicNoteAllocation),
1430
+ ...(node.frame ? { frame: toPublicPageFrame(node.frame) } : {}),
1431
+ ...(node.divergences && node.divergences.length > 0
1432
+ ? { divergences: node.divergences.map(toPublicLayoutDivergence) }
1433
+ : {}),
1370
1434
  };
1371
1435
  publicPageNodeCache.set(node, built);
1372
1436
  void graph; // reserved for future cross-page derivations
1373
1437
  return built;
1374
1438
  }
1375
1439
 
1440
+ function toPublicTwipsRect(rect: RuntimeTwipsRect): PublicTwipsRect {
1441
+ return {
1442
+ xTwips: rect.xTwips,
1443
+ yTwips: rect.yTwips,
1444
+ widthTwips: rect.widthTwips,
1445
+ heightTwips: rect.heightTwips,
1446
+ };
1447
+ }
1448
+
1449
+ function frameRegionEntries(
1450
+ frame: RuntimePageFrame,
1451
+ ): PublicPageFrame["regionFrames"] {
1452
+ const regions: PublicPageFrame["regionFrames"][number][] = [];
1453
+ const push = (region: RuntimePageRegion | undefined) => {
1454
+ if (!region) return;
1455
+ regions.push({
1456
+ kind: region.kind,
1457
+ ...(region.rectTwips ? { rectTwips: toPublicTwipsRect(region.rectTwips) } : {}),
1458
+ fragmentIds: [...region.fragmentIds],
1459
+ });
1460
+ };
1461
+ push(frame.regions.header);
1462
+ push(frame.regions.body);
1463
+ for (const column of frame.regions.columns ?? []) push(column);
1464
+ push(frame.regions.footer);
1465
+ for (const footnote of frame.regions.footnotes ?? []) push(footnote);
1466
+ return regions;
1467
+ }
1468
+
1469
+ function toPublicPageFrame(frame: RuntimePageFrame): PublicPageFrame {
1470
+ return {
1471
+ frameId: frame.frameId,
1472
+ pageId: frame.pageId,
1473
+ pageIndex: frame.pageIndex,
1474
+ sectionIndex: frame.sectionIndex,
1475
+ displayPageNumber: frame.displayPageNumber,
1476
+ physicalBoundsTwips: toPublicTwipsRect(frame.physicalBoundsTwips),
1477
+ divergenceIds: [...frame.divergenceIds],
1478
+ signature: frame.signature,
1479
+ exclusionZoneCount: frame.regions.exclusionZones.length,
1480
+ regionFrames: frameRegionEntries(frame),
1481
+ };
1482
+ }
1483
+
1484
+ function toPublicLayoutDivergence(
1485
+ divergence: RuntimeLayoutDivergence,
1486
+ ): PublicLayoutDivergence {
1487
+ return {
1488
+ divergenceId: divergence.divergenceId,
1489
+ kind: divergence.kind,
1490
+ source: divergence.source,
1491
+ severity: divergence.severity,
1492
+ message: divergence.message,
1493
+ ...(divergence.regionKinds !== undefined ? { regionKinds: [...divergence.regionKinds] } : {}),
1494
+ ...(divergence.fragmentIds !== undefined ? { fragmentIds: [...divergence.fragmentIds] } : {}),
1495
+ };
1496
+ }
1497
+
1376
1498
  function toPublicResolvedPageStories(
1377
1499
  stories: ResolvedPageStories,
1378
1500
  ): PublicResolvedPageStories {
@@ -1404,6 +1526,7 @@ function toPublicPageRegion(region: RuntimePageRegion): PublicPageRegion {
1404
1526
  widthTwips: region.widthTwips,
1405
1527
  heightTwips: region.heightTwips,
1406
1528
  fragmentCount: region.fragmentIds.length,
1529
+ ...(region.rectTwips ? { rectTwips: toPublicTwipsRect(region.rectTwips) } : {}),
1407
1530
  };
1408
1531
  }
1409
1532
 
@@ -1422,6 +1545,34 @@ function toPublicBlockFragment(
1422
1545
  to: fragment.to,
1423
1546
  heightTwips: fragment.heightTwips,
1424
1547
  orderInRegion: fragment.orderInRegion,
1548
+ ...(fragment.kind !== undefined ? { kind: fragment.kind } : {}),
1549
+ ...(fragment.paragraphLineRange !== undefined
1550
+ ? { paragraphLineRange: { ...fragment.paragraphLineRange } }
1551
+ : {}),
1552
+ ...(fragment.tableRowRange !== undefined
1553
+ ? { tableRowRange: { ...fragment.tableRowRange } }
1554
+ : {}),
1555
+ ...(fragment.columnIndex !== undefined ? { columnIndex: fragment.columnIndex } : {}),
1556
+ ...(fragment.continuation !== undefined
1557
+ ? { continuation: cloneContinuationCursor(fragment.continuation) }
1558
+ : {}),
1559
+ };
1560
+ }
1561
+
1562
+ function cloneContinuationCursor(
1563
+ cursor: RuntimeLayoutContinuationCursor,
1564
+ ): RuntimeLayoutContinuationCursor {
1565
+ if (cursor.kind === "paragraph") {
1566
+ return {
1567
+ ...cursor,
1568
+ lineRange: { ...cursor.lineRange },
1569
+ };
1570
+ }
1571
+ return {
1572
+ ...cursor,
1573
+ rowRange: { ...cursor.rowRange },
1574
+ repeatedHeaderRowIndexes: [...cursor.repeatedHeaderRowIndexes],
1575
+ verticalMergeCarry: cursor.verticalMergeCarry.map((carry) => ({ ...carry })),
1425
1576
  };
1426
1577
  }
1427
1578
 
@@ -1440,6 +1591,7 @@ function toPublicNoteAllocation(note: RuntimeNoteAllocation): PublicNoteAllocati
1440
1591
  noteKind: note.noteKind,
1441
1592
  noteId: note.noteId,
1442
1593
  reservedHeightTwips: note.reservedHeightTwips,
1594
+ ...(note.fragmentId !== undefined ? { fragmentId: note.fragmentId } : {}),
1443
1595
  };
1444
1596
  }
1445
1597
 
@@ -3,6 +3,7 @@ import type {
3
3
  RuntimePageAnchor,
4
4
  RuntimePageFrame,
5
5
  RuntimePageGraph,
6
+ RuntimePageLocalStoryInstance,
6
7
  RuntimePageNode,
7
8
  RuntimePageRegion,
8
9
  RuntimePageRegions,
@@ -97,6 +98,9 @@ function rewriteFrame(
97
98
  return {
98
99
  ...frame,
99
100
  pageId: rewriteId(frame.pageId),
101
+ pageLocalStories: frame.pageLocalStories.map((story) =>
102
+ rewritePageLocalStoryInstance(story, rewriteId),
103
+ ),
100
104
  regions: {
101
105
  ...frame.regions,
102
106
  body: rewriteRegion(frame.regions.body, rewriteId),
@@ -116,6 +120,16 @@ function rewriteFrame(
116
120
  };
117
121
  }
118
122
 
123
+ function rewritePageLocalStoryInstance(
124
+ story: RuntimePageLocalStoryInstance,
125
+ rewriteId: (id: string) => string,
126
+ ): RuntimePageLocalStoryInstance {
127
+ return {
128
+ ...story,
129
+ pageId: rewriteId(story.pageId),
130
+ };
131
+ }
132
+
119
133
  function rewriteRegions(
120
134
  regions: RuntimePageRegions,
121
135
  rewriteId: (id: string) => string,
@@ -54,6 +54,8 @@ import type {
54
54
  ChromePosture,
55
55
  GeometryRect,
56
56
  OverlayAnchorQuery,
57
+ UiOverlayLaneKind,
58
+ UiOverlayLaneSnapshot,
57
59
  UiController,
58
60
  UiControllerFactory,
59
61
  UiListener,
@@ -119,6 +121,13 @@ export interface ShellUiControllerDeps {
119
121
  readonly subscribeOverlays?: (
120
122
  listener: UiListener<OverlayAnchorQuery>,
121
123
  ) => UiUnsubscribe;
124
+ /** PE2 overlay-lane snapshot resolver. */
125
+ readonly getOverlayLane?: (kind: UiOverlayLaneKind) => UiOverlayLaneSnapshot;
126
+ /** PE2 overlay-lane subscription channel. */
127
+ readonly subscribeOverlayLane?: (
128
+ kind: UiOverlayLaneKind,
129
+ listener: UiListener<UiOverlayLaneSnapshot>,
130
+ ) => UiUnsubscribe;
122
131
  }
123
132
 
124
133
  /**
@@ -146,6 +155,8 @@ export function makeShellUiControllerFactory(
146
155
  ...(deps.getViewport ? { getViewport: deps.getViewport } : {}),
147
156
  ...(deps.subscribeViewport ? { subscribeViewport: deps.subscribeViewport } : {}),
148
157
  ...(deps.subscribeOverlays ? { subscribeOverlays: deps.subscribeOverlays } : {}),
158
+ ...(deps.getOverlayLane ? { getOverlayLane: deps.getOverlayLane } : {}),
159
+ ...(deps.subscribeOverlayLane ? { subscribeOverlayLane: deps.subscribeOverlayLane } : {}),
149
160
  };
150
161
  return controller;
151
162
  };