@beyondwork/docx-react-component 1.0.18 → 1.0.19

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 (74) hide show
  1. package/README.md +8 -2
  2. package/package.json +24 -34
  3. package/src/api/README.md +5 -1
  4. package/src/api/public-types.ts +374 -4
  5. package/src/api/session-state.ts +58 -0
  6. package/src/core/commands/formatting-commands.ts +1 -0
  7. package/src/core/commands/image-commands.ts +147 -0
  8. package/src/core/commands/index.ts +5 -1
  9. package/src/core/commands/list-commands.ts +231 -36
  10. package/src/core/commands/paragraph-layout-commands.ts +339 -0
  11. package/src/core/commands/section-layout-commands.ts +680 -0
  12. package/src/core/commands/style-commands.ts +262 -0
  13. package/src/core/search/search-text.ts +329 -0
  14. package/src/core/selection/mapping.ts +41 -0
  15. package/src/core/state/editor-state.ts +1 -1
  16. package/src/index.ts +30 -0
  17. package/src/io/docx-session.ts +260 -39
  18. package/src/io/export/serialize-main-document.ts +202 -5
  19. package/src/io/export/serialize-numbering.ts +28 -7
  20. package/src/io/normalize/normalize-text.ts +63 -25
  21. package/src/io/ooxml/numbering-sentinels.ts +44 -0
  22. package/src/io/ooxml/parse-footnotes.ts +212 -20
  23. package/src/io/ooxml/parse-headers-footers.ts +229 -25
  24. package/src/io/ooxml/parse-inline-media.ts +16 -0
  25. package/src/io/ooxml/parse-main-document.ts +411 -6
  26. package/src/io/ooxml/parse-numbering.ts +7 -0
  27. package/src/io/ooxml/parse-settings.ts +184 -0
  28. package/src/io/ooxml/parse-shapes.ts +25 -0
  29. package/src/io/ooxml/parse-styles.ts +463 -0
  30. package/src/io/ooxml/parse-theme.ts +32 -0
  31. package/src/model/canonical-document.ts +133 -3
  32. package/src/model/cds-1.0.0.ts +13 -0
  33. package/src/model/snapshot.ts +2 -1
  34. package/src/runtime/document-layout.ts +332 -0
  35. package/src/runtime/document-navigation.ts +564 -0
  36. package/src/runtime/document-runtime.ts +265 -35
  37. package/src/runtime/document-search.ts +145 -0
  38. package/src/runtime/numbering-prefix.ts +47 -26
  39. package/src/runtime/page-layout-estimation.ts +212 -0
  40. package/src/runtime/read-only-diagnostics-runtime.ts +1 -0
  41. package/src/runtime/session-capabilities.ts +2 -0
  42. package/src/runtime/story-context.ts +164 -0
  43. package/src/runtime/story-targeting.ts +162 -0
  44. package/src/runtime/surface-projection.ts +239 -12
  45. package/src/runtime/table-schema.ts +87 -5
  46. package/src/runtime/view-state.ts +459 -0
  47. package/src/ui/WordReviewEditor.tsx +1902 -312
  48. package/src/ui/browser-export.ts +52 -0
  49. package/src/ui/headless/preserve-editor-selection.ts +5 -0
  50. package/src/ui/headless/selection-helpers.ts +20 -0
  51. package/src/ui/headless/selection-toolbar-model.ts +22 -0
  52. package/src/ui/headless/use-editor-keyboard.ts +6 -1
  53. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
  54. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +125 -14
  55. package/src/ui-tailwind/editor-surface/perf-probe.ts +107 -0
  56. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +45 -6
  57. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
  58. package/src/ui-tailwind/editor-surface/pm-position-map.ts +2 -2
  59. package/src/ui-tailwind/editor-surface/pm-schema.ts +47 -5
  60. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +95 -22
  61. package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
  62. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
  63. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +394 -77
  64. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
  65. package/src/ui-tailwind/index.ts +2 -1
  66. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
  67. package/src/ui-tailwind/review/tw-review-rail.tsx +6 -6
  68. package/src/ui-tailwind/theme/editor-theme.css +123 -0
  69. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
  70. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +291 -12
  71. package/src/ui-tailwind/tw-review-workspace.tsx +926 -27
  72. package/src/validation/compatibility-engine.ts +92 -20
  73. package/src/validation/diagnostics.ts +1 -0
  74. package/src/validation/docx-comment-proof.ts +487 -0
@@ -2,6 +2,7 @@ import type {
2
2
  ThemeColorScheme,
3
3
  ThemeDefinition,
4
4
  ThemeFontScheme,
5
+ ResolvedTheme,
5
6
  } from "../../model/canonical-document.ts";
6
7
 
7
8
  // ---- XML node types (inline, no external dep) ----
@@ -85,6 +86,37 @@ export function parseThemeXml(xml: string): ThemeDefinition {
85
86
  return result;
86
87
  }
87
88
 
89
+ /**
90
+ * Resolve a ThemeDefinition into flattened runtime theme inputs.
91
+ * Maps OOXML theme slot names to CSS-usable color values and font families.
92
+ */
93
+ export function resolveTheme(theme: ThemeDefinition): ResolvedTheme {
94
+ const colors: Record<string, string> = {};
95
+
96
+ if (theme.colorScheme?.colors) {
97
+ for (const [slot, value] of Object.entries(theme.colorScheme.colors)) {
98
+ colors[slot] = value;
99
+ }
100
+ }
101
+
102
+ return {
103
+ colors,
104
+ majorFont: theme.fontScheme?.majorFont,
105
+ minorFont: theme.fontScheme?.minorFont,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Resolve a theme color reference (e.g. "accent1", "dk1") to a CSS color string.
111
+ * Returns undefined if the theme does not contain the requested slot.
112
+ */
113
+ export function resolveThemeColor(
114
+ theme: ResolvedTheme | undefined,
115
+ colorSlot: string,
116
+ ): string | undefined {
117
+ return theme?.colors[colorSlot];
118
+ }
119
+
88
120
  // ---- Internal helpers ----
89
121
 
90
122
  function parseColorScheme(
@@ -8,6 +8,7 @@ import {
8
8
  expectExactString,
9
9
  expectIso8601UtcTimestamp,
10
10
  expectString,
11
+ expectStringAllowEmpty,
11
12
  expectUuid,
12
13
  stableStringify,
13
14
  } from "./cds-1.0.0.ts";
@@ -79,12 +80,14 @@ export interface StylesCatalog {
79
80
  characters: Record<string, CharacterStyleDefinition>;
80
81
  tables: Record<string, TableStyleDefinition>;
81
82
  latentStyles?: Record<string, LatentStyleDefinition>;
83
+ fromPackage?: boolean;
82
84
  }
83
85
 
84
86
  export interface ParagraphStyleDefinition {
85
87
  styleId: string;
86
88
  basedOn?: string;
87
89
  nextStyle?: string;
90
+ outlineLevel?: number;
88
91
  displayName: string;
89
92
  kind: "paragraph";
90
93
  isDefault: boolean;
@@ -131,6 +134,8 @@ export interface NumberingLevelDefinition {
131
134
  text: string;
132
135
  startAt?: number;
133
136
  paragraphStyleId?: string;
137
+ isLegalNumbering?: boolean;
138
+ suffix?: "tab" | "space" | "nothing";
134
139
  }
135
140
 
136
141
  export interface NumberingInstance {
@@ -155,6 +160,9 @@ export interface MediaItem {
155
160
  relationshipId?: string;
156
161
  packagePartName: string;
157
162
  altText?: string;
163
+ display?: "inline" | "floating";
164
+ widthEmu?: number;
165
+ heightEmu?: number;
158
166
  }
159
167
 
160
168
  // ---- Sub-part canonical types ----
@@ -166,6 +174,7 @@ export interface HeaderDocument {
166
174
  partPath: string;
167
175
  relationshipId: string;
168
176
  blocks: BlockNode[];
177
+ sectionIndex?: number;
169
178
  }
170
179
 
171
180
  export interface FooterDocument {
@@ -173,6 +182,7 @@ export interface FooterDocument {
173
182
  partPath: string;
174
183
  relationshipId: string;
175
184
  blocks: BlockNode[];
185
+ sectionIndex?: number;
176
186
  }
177
187
 
178
188
  export interface FootnoteDefinition {
@@ -203,11 +213,25 @@ export interface ThemeDefinition {
203
213
  fontScheme?: ThemeFontScheme;
204
214
  }
205
215
 
216
+ export interface DocumentSettings {
217
+ evenAndOddHeaders?: boolean;
218
+ zoomLevel?: "pageWidth" | "onePage" | number;
219
+ }
220
+
206
221
  export interface SubPartsCatalog {
207
222
  headers: HeaderDocument[];
208
223
  footers: FooterDocument[];
209
224
  footnoteCollection?: FootnoteCollection;
210
225
  theme?: ThemeDefinition;
226
+ finalSectionProperties?: SectionProperties;
227
+ resolvedTheme?: ResolvedTheme;
228
+ settings?: DocumentSettings;
229
+ }
230
+
231
+ export interface ResolvedTheme {
232
+ colors: Record<string, string>;
233
+ majorFont?: string;
234
+ minorFont?: string;
211
235
  }
212
236
 
213
237
  // ---- Inline footnote reference node ----
@@ -407,6 +431,23 @@ export interface TableCellNode {
407
431
  verticalAlign?: "top" | "center" | "bottom";
408
432
  }
409
433
 
434
+ export interface SdtCheckboxState {
435
+ checked: boolean;
436
+ checkedChar?: string;
437
+ uncheckedChar?: string;
438
+ }
439
+
440
+ export interface SdtDatePickerState {
441
+ fullDate?: string;
442
+ dateFormat?: string;
443
+ lid?: string;
444
+ }
445
+
446
+ export interface SdtDropdownListItem {
447
+ displayText?: string;
448
+ value: string;
449
+ }
450
+
410
451
  export interface SdtNode {
411
452
  type: "sdt";
412
453
  properties: {
@@ -415,6 +456,11 @@ export interface SdtNode {
415
456
  tag?: string;
416
457
  lock?: string;
417
458
  propertiesXml?: string;
459
+ checkbox?: SdtCheckboxState;
460
+ datePicker?: SdtDatePickerState;
461
+ dropdownList?: SdtDropdownListItem[];
462
+ comboBox?: SdtDropdownListItem[];
463
+ showingPlcHdr?: boolean;
418
464
  };
419
465
  children: BlockNode[];
420
466
  }
@@ -451,7 +497,86 @@ export interface BookmarkEndNode {
451
497
 
452
498
  export interface SectionBreakNode {
453
499
  type: "section_break";
500
+ sectionPropertiesXml?: string;
501
+ /**
502
+ * @deprecated Legacy field from older snapshots. New exports should use
503
+ * sectionPropertiesXml and only contain raw <w:sectPr> content.
504
+ */
454
505
  propertiesXml?: string;
506
+ sectionProperties?: SectionProperties;
507
+ }
508
+
509
+ export interface SectionProperties {
510
+ pageSize?: PageSize;
511
+ pageMargins?: PageMargins;
512
+ columns?: ColumnProperties;
513
+ pageNumbering?: PageNumbering;
514
+ lineNumbering?: SectionLineNumbering;
515
+ pageBorders?: SectionPageBorders;
516
+ documentGrid?: SectionDocumentGrid;
517
+ headerReferences?: HeaderFooterReference[];
518
+ footerReferences?: HeaderFooterReference[];
519
+ sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
520
+ titlePage?: boolean;
521
+ }
522
+
523
+ export interface PageSize {
524
+ width: number;
525
+ height: number;
526
+ orientation?: "portrait" | "landscape";
527
+ }
528
+
529
+ export interface PageMargins {
530
+ top: number;
531
+ right: number;
532
+ bottom: number;
533
+ left: number;
534
+ header?: number;
535
+ footer?: number;
536
+ gutter?: number;
537
+ }
538
+
539
+ export interface ColumnProperties {
540
+ count?: number;
541
+ space?: number;
542
+ equalWidth?: boolean;
543
+ columns?: Array<{ width: number; space?: number }>;
544
+ separator?: boolean;
545
+ }
546
+
547
+ export interface PageNumbering {
548
+ format?: string;
549
+ start?: number;
550
+ chapStyle?: string;
551
+ chapSep?: string;
552
+ }
553
+
554
+ export interface SectionLineNumbering {
555
+ countBy?: number;
556
+ start?: number;
557
+ distance?: number;
558
+ restart?: "newPage" | "newSection" | "continuous";
559
+ }
560
+
561
+ export interface SectionPageBorders {
562
+ top?: BorderSpec;
563
+ left?: BorderSpec;
564
+ bottom?: BorderSpec;
565
+ right?: BorderSpec;
566
+ offsetFrom?: "page" | "text";
567
+ display?: "allPages" | "firstPage" | "notFirstPage";
568
+ zOrder?: "front" | "back";
569
+ }
570
+
571
+ export interface SectionDocumentGrid {
572
+ type?: "default" | "lines" | "linesAndChars" | "snapToChars";
573
+ linePitch?: number;
574
+ charSpace?: number;
575
+ }
576
+
577
+ export interface HeaderFooterReference {
578
+ variant: HeaderFooterVariant;
579
+ relationshipId: string;
455
580
  }
456
581
 
457
582
  export type InlineNode =
@@ -587,6 +712,7 @@ export interface ShapeNode {
587
712
  type: "shape";
588
713
  text?: string;
589
714
  geometry?: string;
715
+ isTextBox?: boolean;
590
716
  rawXml: string;
591
717
  }
592
718
 
@@ -781,7 +907,7 @@ export interface DiagnosticErrorEntry {
781
907
  | "internal_invariant";
782
908
  message: string;
783
909
  isFatal: boolean;
784
- source: "import" | "runtime" | "validation" | "datastore" | "export";
910
+ source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
785
911
  details?: unknown;
786
912
  }
787
913
 
@@ -1343,7 +1469,11 @@ function validateReviewStore(
1343
1469
  );
1344
1470
  }
1345
1471
  if (threadRecord.body !== undefined) {
1346
- expectString(threadRecord.body, `${path}.comments.${commentId}.body`, issues);
1472
+ expectStringAllowEmpty(
1473
+ threadRecord.body,
1474
+ `${path}.comments.${commentId}.body`,
1475
+ issues,
1476
+ );
1347
1477
  }
1348
1478
  if (!Array.isArray(threadRecord.warningIds)) {
1349
1479
  issues.push({
@@ -1489,7 +1619,7 @@ function validateCommentEntries(
1489
1619
  }
1490
1620
  expectString(record.entryId, `${path}[${index}].entryId`, issues);
1491
1621
  expectString(record.authorId, `${path}[${index}].authorId`, issues);
1492
- expectString(record.body, `${path}[${index}].body`, issues);
1622
+ expectStringAllowEmpty(record.body, `${path}[${index}].body`, issues);
1493
1623
  expectIso8601UtcTimestamp(record.createdAt, `${path}[${index}].createdAt`, issues);
1494
1624
  if (record.metadata !== undefined) {
1495
1625
  validateCommentEntryMetadata(record.metadata, `${path}[${index}].metadata`, issues);
@@ -98,6 +98,19 @@ export function expectString(
98
98
  return value;
99
99
  }
100
100
 
101
+ export function expectStringAllowEmpty(
102
+ value: unknown,
103
+ path: string,
104
+ issues: ModelValidationIssue[],
105
+ ): string | null {
106
+ if (typeof value !== "string") {
107
+ issues.push({ path, message: "Expected a string." });
108
+ return null;
109
+ }
110
+
111
+ return value;
112
+ }
113
+
101
114
  export function expectExactString<T extends string>(
102
115
  value: unknown,
103
116
  expected: T,
@@ -62,7 +62,7 @@ export interface EditorError {
62
62
  code: EditorErrorCode;
63
63
  message: string;
64
64
  isFatal: boolean;
65
- source: "import" | "runtime" | "validation" | "datastore" | "export";
65
+ source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
66
66
  details?: Record<string, unknown>;
67
67
  }
68
68
 
@@ -174,6 +174,7 @@ const EDITOR_ERROR_SOURCES = new Set<EditorError["source"]>([
174
174
  "runtime",
175
175
  "validation",
176
176
  "datastore",
177
+ "host",
177
178
  "export",
178
179
  ]);
179
180
 
@@ -0,0 +1,332 @@
1
+ import type {
2
+ EditorStoryTarget,
3
+ PageLayoutSnapshot,
4
+ } from "../api/public-types";
5
+ import { MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
6
+ import {
7
+ createSelectionSnapshot,
8
+ type CanonicalDocumentEnvelope,
9
+ type EditorState,
10
+ } from "../core/state/editor-state.ts";
11
+ import type {
12
+ FooterDocument,
13
+ HeaderDocument,
14
+ SectionProperties,
15
+ SubPartsCatalog,
16
+ } from "../model/canonical-document.ts";
17
+ import { createEditorSurfaceSnapshot } from "./surface-projection.ts";
18
+ import {
19
+ resolveSectionVariants,
20
+ sectionSupportsStoryTarget,
21
+ } from "./story-context.ts";
22
+ import { storyTargetKey } from "./story-targeting.ts";
23
+
24
+ export interface ResolvedDocumentSection {
25
+ index: number;
26
+ start: number;
27
+ end: number;
28
+ properties?: SectionProperties;
29
+ }
30
+
31
+ export function buildResolvedSections(
32
+ document: CanonicalDocumentEnvelope,
33
+ ): ResolvedDocumentSection[] {
34
+ const mainSurface = createEditorSurfaceSnapshot(
35
+ document,
36
+ createSelectionSnapshot(0, 0),
37
+ MAIN_STORY_TARGET,
38
+ );
39
+ const sections: ResolvedDocumentSection[] = [];
40
+ let sectionStart = 0;
41
+ let sectionIndex = 0;
42
+
43
+ for (const [index, block] of document.content.children.entries()) {
44
+ if (block.type !== "section_break") {
45
+ continue;
46
+ }
47
+
48
+ const surfaceBlock = mainSurface.blocks[index];
49
+ sections.push({
50
+ index: sectionIndex,
51
+ start: sectionStart,
52
+ end: surfaceBlock?.from ?? sectionStart,
53
+ properties: block.sectionProperties,
54
+ });
55
+ sectionStart = surfaceBlock?.to ?? sectionStart;
56
+ sectionIndex += 1;
57
+ }
58
+
59
+ sections.push({
60
+ index: sectionIndex,
61
+ start: sectionStart,
62
+ end: mainSurface.storySize,
63
+ properties: document.subParts?.finalSectionProperties,
64
+ });
65
+
66
+ return sections;
67
+ }
68
+
69
+ export function findSectionForPosition(
70
+ sections: ReadonlyArray<ResolvedDocumentSection>,
71
+ position: number,
72
+ ): ResolvedDocumentSection {
73
+ for (const section of sections) {
74
+ if (position < section.end) {
75
+ return section;
76
+ }
77
+ }
78
+
79
+ return sections[sections.length - 1] ?? {
80
+ index: 0,
81
+ start: 0,
82
+ end: 0,
83
+ };
84
+ }
85
+
86
+ export function buildPageLayoutSnapshot(
87
+ sectionIndex: number,
88
+ properties: SectionProperties | undefined,
89
+ subParts: SubPartsCatalog | undefined,
90
+ ): PageLayoutSnapshot {
91
+ const pageSize = properties?.pageSize ?? {
92
+ width: 12240,
93
+ height: 15840,
94
+ orientation: "portrait" as const,
95
+ };
96
+ const margins = properties?.pageMargins ?? {
97
+ top: 1440,
98
+ right: 1440,
99
+ bottom: 1440,
100
+ left: 1440,
101
+ header: 720,
102
+ footer: 720,
103
+ gutter: 0,
104
+ };
105
+ const columns = properties?.columns;
106
+ const explicitColumns = columns?.columns ?? [];
107
+ const differentOddEvenPages = Boolean(subParts?.settings?.evenAndOddHeaders);
108
+
109
+ return {
110
+ sectionIndex,
111
+ ...(properties?.sectionType ? { sectionType: properties.sectionType } : {}),
112
+ pageWidth: pageSize.width,
113
+ pageHeight: pageSize.height,
114
+ marginTop: margins.top,
115
+ marginBottom: margins.bottom,
116
+ marginLeft: margins.left,
117
+ marginRight: margins.right,
118
+ headerMargin: margins.header ?? 720,
119
+ footerMargin: margins.footer ?? 720,
120
+ gutter: margins.gutter ?? 0,
121
+ orientation: pageSize.orientation ?? "portrait",
122
+ columns:
123
+ explicitColumns.length > 0 ? explicitColumns.length : (columns?.count ?? 1),
124
+ differentFirstPage: Boolean(properties?.titlePage),
125
+ differentOddEvenPages,
126
+ ...(properties?.pageNumbering
127
+ ? {
128
+ pageNumbering: {
129
+ ...(properties.pageNumbering.format
130
+ ? { format: properties.pageNumbering.format }
131
+ : {}),
132
+ ...(properties.pageNumbering.start !== undefined
133
+ ? { start: properties.pageNumbering.start }
134
+ : {}),
135
+ ...(properties.pageNumbering.chapStyle
136
+ ? { chapterStyle: properties.pageNumbering.chapStyle }
137
+ : {}),
138
+ ...(properties.pageNumbering.chapSep
139
+ ? { chapterSeparator: properties.pageNumbering.chapSep }
140
+ : {}),
141
+ },
142
+ }
143
+ : {}),
144
+ ...(properties?.lineNumbering
145
+ ? {
146
+ lineNumbering: {
147
+ ...(properties.lineNumbering.countBy !== undefined
148
+ ? { countBy: properties.lineNumbering.countBy }
149
+ : {}),
150
+ ...(properties.lineNumbering.start !== undefined
151
+ ? { start: properties.lineNumbering.start }
152
+ : {}),
153
+ ...(properties.lineNumbering.distance !== undefined
154
+ ? { distance: properties.lineNumbering.distance }
155
+ : {}),
156
+ ...(properties.lineNumbering.restart
157
+ ? { restart: properties.lineNumbering.restart }
158
+ : {}),
159
+ },
160
+ }
161
+ : {}),
162
+ ...(properties?.pageBorders
163
+ ? {
164
+ pageBorders: {
165
+ ...(properties.pageBorders.top
166
+ ? { top: { ...properties.pageBorders.top } }
167
+ : {}),
168
+ ...(properties.pageBorders.left
169
+ ? { left: { ...properties.pageBorders.left } }
170
+ : {}),
171
+ ...(properties.pageBorders.bottom
172
+ ? { bottom: { ...properties.pageBorders.bottom } }
173
+ : {}),
174
+ ...(properties.pageBorders.right
175
+ ? { right: { ...properties.pageBorders.right } }
176
+ : {}),
177
+ ...(properties.pageBorders.offsetFrom
178
+ ? { offsetFrom: properties.pageBorders.offsetFrom }
179
+ : {}),
180
+ ...(properties.pageBorders.display
181
+ ? { display: properties.pageBorders.display }
182
+ : {}),
183
+ ...(properties.pageBorders.zOrder
184
+ ? { zOrder: properties.pageBorders.zOrder }
185
+ : {}),
186
+ },
187
+ }
188
+ : {}),
189
+ ...(properties?.documentGrid
190
+ ? {
191
+ documentGrid: {
192
+ ...(properties.documentGrid.type
193
+ ? { type: properties.documentGrid.type }
194
+ : {}),
195
+ ...(properties.documentGrid.linePitch !== undefined
196
+ ? { linePitch: properties.documentGrid.linePitch }
197
+ : {}),
198
+ ...(properties.documentGrid.charSpace !== undefined
199
+ ? { charSpace: properties.documentGrid.charSpace }
200
+ : {}),
201
+ },
202
+ }
203
+ : {}),
204
+ columnDefinitions: explicitColumns,
205
+ equalWidthColumns:
206
+ columns?.equalWidth ??
207
+ (explicitColumns.length === 0 || explicitColumns.length <= 1),
208
+ columnSeparator: Boolean(columns?.separator),
209
+ headerVariants: resolveSectionVariants(
210
+ "header",
211
+ sectionIndex,
212
+ properties?.headerReferences,
213
+ subParts?.headers ?? [],
214
+ ),
215
+ footerVariants: resolveSectionVariants(
216
+ "footer",
217
+ sectionIndex,
218
+ properties?.footerReferences,
219
+ subParts?.footers ?? [],
220
+ ),
221
+ };
222
+ }
223
+
224
+ export function resolveSectionForStoryTarget(
225
+ document: CanonicalDocumentEnvelope,
226
+ sections: ReadonlyArray<ResolvedDocumentSection>,
227
+ target: EditorStoryTarget,
228
+ ): ResolvedDocumentSection | undefined {
229
+ if (target.kind === "main" || sections.length === 0) {
230
+ return sections[0];
231
+ }
232
+
233
+ if (target.kind === "header") {
234
+ if (target.sectionIndex !== undefined) {
235
+ const section = sections.find((candidate) => candidate.index === target.sectionIndex);
236
+ return section &&
237
+ sectionSupportsStoryTarget(document, target.sectionIndex, target)
238
+ ? section
239
+ : undefined;
240
+ }
241
+ return (
242
+ findSectionByStoryReference(
243
+ document,
244
+ sections,
245
+ target,
246
+ "header",
247
+ ) ?? sections[0]
248
+ );
249
+ }
250
+
251
+ if (target.kind === "footer") {
252
+ if (target.sectionIndex !== undefined) {
253
+ const section = sections.find((candidate) => candidate.index === target.sectionIndex);
254
+ return section &&
255
+ sectionSupportsStoryTarget(document, target.sectionIndex, target)
256
+ ? section
257
+ : undefined;
258
+ }
259
+ return (
260
+ findSectionByStoryReference(
261
+ document,
262
+ sections,
263
+ target,
264
+ "footer",
265
+ ) ?? sections[0]
266
+ );
267
+ }
268
+
269
+ return undefined;
270
+ }
271
+
272
+ export function resolveActiveSection(
273
+ state: EditorState,
274
+ activeStory: EditorStoryTarget,
275
+ sections: ReadonlyArray<ResolvedDocumentSection>,
276
+ storySelections?: ReadonlyMap<string, EditorState["selection"]>,
277
+ ): ResolvedDocumentSection | undefined {
278
+ if (sections.length === 0) {
279
+ return undefined;
280
+ }
281
+
282
+ if (activeStory.kind === "main") {
283
+ return findSectionForPosition(sections, state.selection.head);
284
+ }
285
+
286
+ const referencedSection = resolveSectionForStoryTarget(
287
+ state.document,
288
+ sections,
289
+ activeStory,
290
+ );
291
+ if (referencedSection) {
292
+ return referencedSection;
293
+ }
294
+
295
+ const mainSelection =
296
+ storySelections?.get(storyTargetKey(MAIN_STORY_TARGET)) ?? state.selection;
297
+ return findSectionForPosition(sections, mainSelection.head);
298
+ }
299
+
300
+ function findSectionByStoryReference(
301
+ document: CanonicalDocumentEnvelope,
302
+ sections: ReadonlyArray<ResolvedDocumentSection>,
303
+ target:
304
+ | Extract<EditorStoryTarget, { kind: "header" }>
305
+ | Extract<EditorStoryTarget, { kind: "footer" }>,
306
+ kind: "header" | "footer",
307
+ ): ResolvedDocumentSection | undefined {
308
+ const propertyKey =
309
+ kind === "header" ? "headerReferences" : "footerReferences";
310
+ const documents =
311
+ kind === "header"
312
+ ? document.subParts?.headers ?? []
313
+ : document.subParts?.footers ?? [];
314
+
315
+ return (
316
+ sections.find((section) =>
317
+ section.properties?.[propertyKey]?.some(
318
+ (ref) =>
319
+ ref.relationshipId === target.relationshipId &&
320
+ ref.variant === target.variant,
321
+ ),
322
+ ) ??
323
+ sections.find((section) =>
324
+ documents.some(
325
+ (entry) =>
326
+ entry.relationshipId === target.relationshipId &&
327
+ entry.variant === target.variant &&
328
+ entry.sectionIndex === section.index,
329
+ ),
330
+ )
331
+ );
332
+ }