@beyondwork/docx-react-component 1.0.36 → 1.0.38

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 (107) hide show
  1. package/README.md +103 -13
  2. package/package.json +1 -1
  3. package/src/api/package-version.ts +13 -0
  4. package/src/api/public-types.ts +402 -1
  5. package/src/core/commands/index.ts +18 -1
  6. package/src/core/commands/section-layout-commands.ts +58 -0
  7. package/src/core/commands/table-grid.ts +431 -0
  8. package/src/core/commands/table-structure-commands.ts +815 -55
  9. package/src/core/selection/mapping.ts +6 -0
  10. package/src/io/docx-session.ts +24 -9
  11. package/src/io/export/build-app-properties-xml.ts +88 -0
  12. package/src/io/export/serialize-comments.ts +6 -1
  13. package/src/io/export/serialize-footnotes.ts +10 -9
  14. package/src/io/export/serialize-headers-footers.ts +11 -10
  15. package/src/io/export/serialize-main-document.ts +328 -50
  16. package/src/io/export/serialize-numbering.ts +114 -24
  17. package/src/io/export/serialize-tables.ts +87 -11
  18. package/src/io/export/table-properties-xml.ts +174 -20
  19. package/src/io/export/twip.ts +66 -0
  20. package/src/io/normalize/normalize-text.ts +20 -0
  21. package/src/io/ooxml/parse-footnotes.ts +62 -1
  22. package/src/io/ooxml/parse-headers-footers.ts +62 -1
  23. package/src/io/ooxml/parse-main-document.ts +158 -1
  24. package/src/io/ooxml/parse-tables.ts +249 -0
  25. package/src/legal/bookmarks.ts +78 -0
  26. package/src/model/canonical-document.ts +45 -0
  27. package/src/review/store/scope-tag-diff.ts +130 -0
  28. package/src/runtime/document-layout.ts +4 -2
  29. package/src/runtime/document-navigation.ts +2 -306
  30. package/src/runtime/document-runtime.ts +287 -11
  31. package/src/runtime/layout/default-page-format.ts +96 -0
  32. package/src/runtime/layout/docx-font-loader.ts +143 -0
  33. package/src/runtime/layout/index.ts +233 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +59 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +628 -0
  36. package/src/runtime/layout/layout-invalidation.ts +257 -0
  37. package/src/runtime/layout/layout-measurement-provider.ts +175 -0
  38. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  39. package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
  40. package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
  41. package/src/runtime/layout/page-format-catalog.ts +233 -0
  42. package/src/runtime/layout/page-fragment-mapper.ts +179 -0
  43. package/src/runtime/layout/page-graph.ts +452 -0
  44. package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
  45. package/src/runtime/layout/page-story-resolver.ts +195 -0
  46. package/src/runtime/layout/paginated-layout-engine.ts +921 -0
  47. package/src/runtime/layout/project-block-fragments.ts +91 -0
  48. package/src/runtime/layout/public-facet.ts +1398 -0
  49. package/src/runtime/layout/resolved-formatting-document.ts +317 -0
  50. package/src/runtime/layout/resolved-formatting-state.ts +430 -0
  51. package/src/runtime/layout/table-render-plan.ts +229 -0
  52. package/src/runtime/render/block-fragment-projection.ts +35 -0
  53. package/src/runtime/render/decoration-resolver.ts +189 -0
  54. package/src/runtime/render/index.ts +57 -0
  55. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  56. package/src/runtime/render/render-frame-types.ts +317 -0
  57. package/src/runtime/render/render-kernel.ts +755 -0
  58. package/src/runtime/scope-tag-registry.ts +95 -0
  59. package/src/runtime/surface-projection.ts +1 -0
  60. package/src/runtime/text-ack-range.ts +49 -0
  61. package/src/runtime/view-state.ts +67 -0
  62. package/src/runtime/workflow-markup.ts +1 -5
  63. package/src/runtime/workflow-rail-segments.ts +280 -0
  64. package/src/ui/WordReviewEditor.tsx +99 -15
  65. package/src/ui/editor-runtime-boundary.ts +10 -1
  66. package/src/ui/editor-shell-view.tsx +6 -0
  67. package/src/ui/editor-surface-controller.tsx +3 -0
  68. package/src/ui/headless/chrome-registry.ts +501 -0
  69. package/src/ui/headless/scoped-chrome-policy.ts +183 -0
  70. package/src/ui/headless/selection-tool-context.ts +2 -0
  71. package/src/ui/headless/selection-tool-resolver.ts +36 -17
  72. package/src/ui/headless/selection-tool-types.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
  74. package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
  75. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  76. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
  77. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
  78. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  79. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  80. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
  81. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
  82. package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
  86. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
  87. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
  88. package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
  89. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
  90. package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
  91. package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
  92. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
  93. package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
  94. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
  95. package/src/ui-tailwind/index.ts +33 -0
  96. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  97. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  98. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  99. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  100. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  101. package/src/ui-tailwind/theme/editor-theme.css +505 -144
  102. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
  103. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  104. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  105. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
  106. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
  107. package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
@@ -17,34 +17,20 @@ import type {
17
17
  EditorStoryTarget,
18
18
  EditorSurfaceSnapshot,
19
19
  SurfaceBlockSnapshot,
20
- SurfaceInlineSegment,
21
20
  } from "../api/public-types";
22
- import type {
23
- FootnoteCollection,
24
- } from "../model/canonical-document.ts";
25
21
  import type { CanonicalDocumentEnvelope } from "../core/state/editor-state.ts";
26
22
  import { createSelectionSnapshot } from "../core/state/editor-state.ts";
27
23
  import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
28
24
  import { createEditorSurfaceSnapshot } from "./surface-projection.ts";
29
25
  import {
30
- estimateBlockHeight,
31
- estimateParagraphHeight,
32
- getUsableColumnMetrics,
33
- getUsableColumnWidth,
34
- getUsablePageHeight,
35
- } from "./page-layout-estimation.ts";
36
- import {
37
- buildPageLayoutSnapshot,
38
26
  buildResolvedSections,
39
27
  findSectionForPosition as resolveSectionForPosition,
40
28
  resolveSectionForStoryTarget,
41
29
  type ResolvedDocumentSection,
42
30
  } from "./document-layout.ts";
31
+ import { buildPageStack } from "./layout/paginated-layout-engine.ts";
43
32
  import { findNoteReferencePosition } from "./view-state.ts";
44
33
 
45
- const MIN_BLOCK_HEIGHT_TWIPS = 240;
46
- const FOOTNOTE_REFERENCE_RESERVATION_TWIPS = 180;
47
-
48
34
  interface NavigationBaseSnapshot {
49
35
  mainSurface: EditorSurfaceSnapshot;
50
36
  sections: ResolvedDocumentSection[];
@@ -152,186 +138,6 @@ export function findSectionForOffset(
152
138
  return sections.length > 0 ? resolveSectionForPosition(sections, offset).index : 0;
153
139
  }
154
140
 
155
- function buildPageStack(
156
- document: CanonicalDocumentEnvelope,
157
- sections: ResolvedDocumentSection[],
158
- mainSurface: EditorSurfaceSnapshot,
159
- ): DocumentPageSnapshot[] {
160
- const pages: DocumentPageSnapshot[] = [];
161
- let globalPageIndex = 0;
162
-
163
- for (const section of sections) {
164
- const layout = buildPageLayoutSnapshot(
165
- section.index,
166
- section.properties ?? document.subParts?.finalSectionProperties,
167
- document.subParts,
168
- );
169
- const sectionBlocks = collectSectionBlocks(mainSurface.blocks, section.start, section.end);
170
- const paginated = paginateSectionBlocks(
171
- section,
172
- sectionBlocks,
173
- layout,
174
- document.subParts?.footnoteCollection,
175
- );
176
-
177
- for (const page of paginated) {
178
- pages.push({
179
- ...page,
180
- pageIndex: globalPageIndex,
181
- });
182
- globalPageIndex += 1;
183
- }
184
- }
185
-
186
- // Guarantee at least one page
187
- if (pages.length === 0) {
188
- pages.push({
189
- pageIndex: 0,
190
- sectionIndex: 0,
191
- pageInSection: 0,
192
- startOffset: 0,
193
- endOffset: mainSurface.storySize,
194
- layout: buildPageLayoutSnapshot(
195
- 0,
196
- document.subParts?.finalSectionProperties,
197
- document.subParts,
198
- ),
199
- });
200
- }
201
-
202
- return pages;
203
- }
204
-
205
- function collectSectionBlocks(
206
- blocks: readonly SurfaceBlockSnapshot[],
207
- start: number,
208
- end: number,
209
- ): SurfaceBlockSnapshot[] {
210
- return blocks.filter((block) => block.to > start && block.from < end);
211
- }
212
-
213
- function paginateSectionBlocks(
214
- section: ResolvedDocumentSection,
215
- blocks: readonly SurfaceBlockSnapshot[],
216
- layout: DocumentPageSnapshot["layout"],
217
- footnotes: FootnoteCollection | undefined,
218
- ): Omit<DocumentPageSnapshot, "pageIndex">[] {
219
- if (blocks.length === 0) {
220
- return [
221
- {
222
- sectionIndex: section.index,
223
- pageInSection: 0,
224
- startOffset: section.start,
225
- endOffset: section.end,
226
- layout,
227
- },
228
- ];
229
- }
230
-
231
- const pages: Omit<DocumentPageSnapshot, "pageIndex">[] = [];
232
- const usableHeight = getUsablePageHeight(layout);
233
- const columnMetrics = getUsableColumnMetrics(layout);
234
- const maxColumns = Math.max(1, columnMetrics.length);
235
- let pageStart = section.start;
236
- let columnHeight = 0;
237
- let columnIndex = 0;
238
- let pageInSection = 0;
239
- let reservedNoteHeight = 0;
240
- const reservedNotes = new Set<string>();
241
-
242
- const pushPage = (endOffset: number): void => {
243
- const boundedEnd = Math.max(pageStart, Math.min(endOffset, section.end));
244
- if (boundedEnd === pageStart && pages.length > 0) {
245
- return;
246
- }
247
- pages.push({
248
- sectionIndex: section.index,
249
- pageInSection,
250
- startOffset: pageStart,
251
- endOffset: boundedEnd,
252
- layout,
253
- });
254
- pageInSection += 1;
255
- pageStart = boundedEnd;
256
- columnHeight = 0;
257
- columnIndex = 0;
258
- reservedNoteHeight = 0;
259
- reservedNotes.clear();
260
- };
261
-
262
- for (let index = 0; index < blocks.length; index += 1) {
263
- const block = blocks[index]!;
264
- const nextBoundary = blocks[index + 1]?.from ?? section.end;
265
- while (true) {
266
- const columnWidth =
267
- columnMetrics[Math.min(columnIndex, columnMetrics.length - 1)]?.width ??
268
- getUsableColumnWidth(layout);
269
- const baseHeight = estimateBlockHeight(block, columnWidth);
270
- const keepWithNextHeight =
271
- block.kind === "paragraph" && block.keepNext
272
- ? baseHeight + estimateBlockHeight(blocks[index + 1], columnWidth)
273
- : baseHeight;
274
- const noteHeight = estimateFootnoteReservation(block, footnotes, columnWidth, reservedNotes);
275
- const projectedHeight = columnHeight + keepWithNextHeight + reservedNoteHeight + noteHeight;
276
-
277
- if (block.kind === "paragraph" && block.pageBreakBefore && pageStart < block.from) {
278
- pushPage(block.from);
279
- continue;
280
- }
281
- if (projectedHeight > usableHeight && pageStart < block.from) {
282
- if (columnIndex < maxColumns - 1) {
283
- columnIndex += 1;
284
- columnHeight = 0;
285
- reservedNoteHeight = 0;
286
- reservedNotes.clear();
287
- continue;
288
- }
289
- pushPage(block.from);
290
- continue;
291
- }
292
-
293
- const effectiveNoteHeight = estimateFootnoteReservation(
294
- block,
295
- footnotes,
296
- columnWidth,
297
- reservedNotes,
298
- );
299
- columnHeight += baseHeight;
300
- reservedNoteHeight += effectiveNoteHeight;
301
- currentPageNoteIds(block).forEach((noteKey) => reservedNotes.add(noteKey));
302
-
303
- if (hasColumnBreak(block)) {
304
- if (columnIndex < maxColumns - 1) {
305
- columnIndex += 1;
306
- columnHeight = 0;
307
- reservedNoteHeight = 0;
308
- reservedNotes.clear();
309
- } else {
310
- pushPage(nextBoundary);
311
- }
312
- break;
313
- }
314
-
315
- if (index === blocks.length - 1) {
316
- pushPage(section.end);
317
- }
318
- break;
319
- }
320
- }
321
-
322
- return pages.length > 0
323
- ? pages
324
- : [
325
- {
326
- sectionIndex: section.index,
327
- pageInSection: 0,
328
- startOffset: section.start,
329
- endOffset: section.end,
330
- layout,
331
- },
332
- ];
333
- }
334
-
335
141
  function resolveActiveNavigationContext(
336
142
  document: CanonicalDocumentEnvelope,
337
143
  pages: readonly DocumentPageSnapshot[],
@@ -383,116 +189,6 @@ function findFirstPageIndexForSection(
383
189
  return match >= 0 ? match : 0;
384
190
  }
385
191
 
386
- function estimateFootnoteReservation(
387
- block: SurfaceBlockSnapshot,
388
- footnotes: FootnoteCollection | undefined,
389
- columnWidth: number,
390
- reservedNotes: ReadonlySet<string>,
391
- ): number {
392
- if (!footnotes || block.kind !== "paragraph") {
393
- return 0;
394
- }
395
-
396
- let reservation = 0;
397
- for (const noteKey of currentPageNoteIds(block)) {
398
- if (reservedNotes.has(noteKey)) {
399
- continue;
400
- }
401
-
402
- const [noteKind, noteId] = noteKey.split(":");
403
- const noteCollection =
404
- noteKind === "endnote" ? footnotes.endnotes : footnotes.footnotes;
405
- const note = noteCollection[noteId];
406
- reservation += FOOTNOTE_REFERENCE_RESERVATION_TWIPS;
407
- if (note) {
408
- reservation += note.blocks.reduce(
409
- (total, child) => total + estimateCanonicalNoteBlockHeight(child, columnWidth),
410
- 0,
411
- );
412
- }
413
- }
414
-
415
- return reservation;
416
- }
417
-
418
- function estimateCanonicalNoteBlockHeight(
419
- block: FootnoteCollection["footnotes"][string]["blocks"][number],
420
- columnWidth: number,
421
- ): number {
422
- switch (block.type) {
423
- case "paragraph":
424
- return estimateParagraphHeight(
425
- {
426
- blockId: "note",
427
- kind: "paragraph",
428
- from: 0,
429
- to: 0,
430
- ...(block.styleId ? { styleId: block.styleId } : {}),
431
- segments: createEstimatedNoteSegments(block.children),
432
- },
433
- columnWidth,
434
- );
435
- case "table":
436
- return MIN_BLOCK_HEIGHT_TWIPS * Math.max(1, block.rows.length);
437
- default:
438
- return MIN_BLOCK_HEIGHT_TWIPS;
439
- }
440
- }
441
-
442
- function createEstimatedNoteSegments(
443
- children: Extract<FootnoteCollection["footnotes"][string]["blocks"][number], { type: "paragraph" }>["children"],
444
- ): SurfaceInlineSegment[] {
445
- const segments: SurfaceInlineSegment[] = [];
446
-
447
- children.forEach((child, index) => {
448
- if (child.type === "text") {
449
- segments.push({
450
- segmentId: `note-${index}`,
451
- kind: "text",
452
- from: 0,
453
- to: Array.from(child.text).length,
454
- text: child.text,
455
- });
456
- return;
457
- }
458
-
459
- if (child.type === "hard_break" || child.type === "tab") {
460
- segments.push({
461
- segmentId: `note-${index}`,
462
- kind: child.type,
463
- from: 0,
464
- to: 1,
465
- });
466
- }
467
- });
468
-
469
- return segments;
470
- }
471
-
472
- function currentPageNoteIds(
473
- block: SurfaceBlockSnapshot,
474
- ): Set<string> {
475
- const notes = new Set<string>();
476
- if (block.kind !== "paragraph") {
477
- return notes;
478
- }
479
-
480
- for (const segment of block.segments) {
481
- if (segment.kind === "note_ref" && segment.noteId) {
482
- notes.add(`${segment.noteKind}:${segment.noteId}`);
483
- }
484
- }
485
- return notes;
486
- }
487
-
488
- function hasColumnBreak(block: SurfaceBlockSnapshot): boolean {
489
- return block.kind === "paragraph" && block.segments.some(
490
- (segment) =>
491
- segment.kind === "opaque_inline" &&
492
- segment.label === "Column break",
493
- );
494
- }
495
-
496
192
  // ---------------------------------------------------------------------------
497
193
  // Heading outline
498
194
  // ---------------------------------------------------------------------------
@@ -510,7 +206,7 @@ function headingLevelFromStyleId(styleId?: string): number | null {
510
206
  return null;
511
207
  }
512
208
 
513
- function buildHeadingOutline(
209
+ export function buildHeadingOutline(
514
210
  document: CanonicalDocumentEnvelope,
515
211
  mainSurface: EditorSurfaceSnapshot,
516
212
  sections: ResolvedDocumentSection[],