@beyondwork/docx-react-component 1.0.1 → 1.0.2

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 (172) hide show
  1. package/README.md +44 -104
  2. package/package.json +76 -46
  3. package/src/README.md +85 -0
  4. package/src/api/README.md +22 -0
  5. package/src/api/public-types.ts +525 -0
  6. package/src/compare/diff-engine.ts +530 -0
  7. package/src/compare/export-redlines.ts +162 -0
  8. package/src/compare/snapshot.ts +37 -0
  9. package/src/component-inventory.md +99 -0
  10. package/src/core/README.md +10 -0
  11. package/src/core/commands/README.md +3 -0
  12. package/src/core/commands/formatting-commands.ts +161 -0
  13. package/src/core/commands/image-commands.ts +144 -0
  14. package/src/core/commands/index.ts +1013 -0
  15. package/src/core/commands/list-commands.ts +370 -0
  16. package/src/core/commands/review-commands.ts +108 -0
  17. package/src/core/commands/text-commands.ts +119 -0
  18. package/src/core/schema/README.md +3 -0
  19. package/src/core/schema/text-schema.ts +512 -0
  20. package/src/core/selection/README.md +3 -0
  21. package/src/core/selection/mapping.ts +238 -0
  22. package/src/core/selection/review-anchors.ts +94 -0
  23. package/src/core/state/README.md +3 -0
  24. package/src/core/state/editor-state.ts +580 -0
  25. package/src/core/state/text-transaction.ts +276 -0
  26. package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
  27. package/src/formats/xlsx/io/parse-sheet.ts +289 -0
  28. package/src/formats/xlsx/io/parse-styles.ts +57 -0
  29. package/src/formats/xlsx/io/parse-workbook.ts +75 -0
  30. package/src/formats/xlsx/io/xlsx-session.ts +306 -0
  31. package/src/formats/xlsx/model/cell.ts +189 -0
  32. package/src/formats/xlsx/model/sheet.ts +244 -0
  33. package/src/formats/xlsx/model/styles.ts +118 -0
  34. package/src/formats/xlsx/model/workbook.ts +449 -0
  35. package/src/index.ts +45 -0
  36. package/src/io/README.md +10 -0
  37. package/src/io/docx-session.ts +1763 -0
  38. package/src/io/export/README.md +3 -0
  39. package/src/io/export/export-session.ts +165 -0
  40. package/src/io/export/minimal-docx.ts +115 -0
  41. package/src/io/export/reattach-preserved-parts.ts +54 -0
  42. package/src/io/export/serialize-comments.ts +876 -0
  43. package/src/io/export/serialize-footnotes.ts +217 -0
  44. package/src/io/export/serialize-headers-footers.ts +200 -0
  45. package/src/io/export/serialize-main-document.ts +982 -0
  46. package/src/io/export/serialize-numbering.ts +97 -0
  47. package/src/io/export/serialize-revisions.ts +389 -0
  48. package/src/io/export/serialize-runtime-revisions.ts +265 -0
  49. package/src/io/export/serialize-tables.ts +147 -0
  50. package/src/io/export/split-review-boundaries.ts +194 -0
  51. package/src/io/normalize/README.md +3 -0
  52. package/src/io/normalize/normalize-text.ts +437 -0
  53. package/src/io/ooxml/README.md +3 -0
  54. package/src/io/ooxml/parse-comments.ts +779 -0
  55. package/src/io/ooxml/parse-complex-content.ts +287 -0
  56. package/src/io/ooxml/parse-fields.ts +438 -0
  57. package/src/io/ooxml/parse-footnotes.ts +403 -0
  58. package/src/io/ooxml/parse-headers-footers.ts +483 -0
  59. package/src/io/ooxml/parse-inline-media.ts +431 -0
  60. package/src/io/ooxml/parse-main-document.ts +1846 -0
  61. package/src/io/ooxml/parse-numbering.ts +425 -0
  62. package/src/io/ooxml/parse-revisions.ts +658 -0
  63. package/src/io/ooxml/parse-shapes.ts +271 -0
  64. package/src/io/ooxml/parse-tables.ts +568 -0
  65. package/src/io/ooxml/parse-theme.ts +314 -0
  66. package/src/io/ooxml/part-manifest.ts +136 -0
  67. package/src/io/ooxml/revision-boundaries.ts +351 -0
  68. package/src/io/opc/README.md +3 -0
  69. package/src/io/opc/corrupt-package.ts +166 -0
  70. package/src/io/opc/docx-package.ts +74 -0
  71. package/src/io/opc/package-reader.ts +320 -0
  72. package/src/io/opc/package-writer.ts +273 -0
  73. package/src/legal/bookmarks.ts +196 -0
  74. package/src/legal/cross-references.ts +356 -0
  75. package/src/legal/defined-terms.ts +203 -0
  76. package/src/model/README.md +3 -0
  77. package/src/model/canonical-document.ts +1911 -0
  78. package/src/model/cds-1.0.0.ts +196 -0
  79. package/src/model/snapshot.ts +393 -0
  80. package/src/preservation/README.md +3 -0
  81. package/src/preservation/markup-compatibility.ts +48 -0
  82. package/src/preservation/opaque-fragment-store.ts +89 -0
  83. package/src/preservation/opaque-region.ts +233 -0
  84. package/src/preservation/package-preservation.ts +120 -0
  85. package/src/preservation/preserved-part-manifest.ts +56 -0
  86. package/src/preservation/relationship-retention.ts +57 -0
  87. package/src/preservation/store.ts +185 -0
  88. package/src/review/README.md +16 -0
  89. package/src/review/store/README.md +3 -0
  90. package/src/review/store/comment-anchors.ts +70 -0
  91. package/src/review/store/comment-remapping.ts +154 -0
  92. package/src/review/store/comment-store.ts +331 -0
  93. package/src/review/store/comment-thread.ts +109 -0
  94. package/src/review/store/revision-actions.ts +394 -0
  95. package/src/review/store/revision-store.ts +303 -0
  96. package/src/review/store/revision-types.ts +168 -0
  97. package/src/review/store/runtime-comment-store.ts +43 -0
  98. package/src/runtime/README.md +3 -0
  99. package/src/runtime/ai-action-policy.ts +764 -0
  100. package/src/runtime/document-runtime.ts +967 -0
  101. package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
  102. package/src/runtime/review-runtime.ts +44 -0
  103. package/src/runtime/revision-runtime.ts +107 -0
  104. package/src/runtime/session-capabilities.ts +138 -0
  105. package/src/runtime/surface-projection.ts +570 -0
  106. package/src/runtime/table-commands.ts +87 -0
  107. package/src/runtime/table-schema.ts +140 -0
  108. package/src/runtime/virtualized-rendering.ts +258 -0
  109. package/src/ui/README.md +30 -0
  110. package/src/ui/WordReviewEditor.tsx +1504 -0
  111. package/src/ui/comments/README.md +3 -0
  112. package/src/ui/compatibility/README.md +3 -0
  113. package/src/ui/editor-surface/README.md +3 -0
  114. package/src/ui/headless/comment-decoration-model.ts +124 -0
  115. package/src/ui/headless/revision-decoration-model.ts +128 -0
  116. package/src/ui/headless/selection-helpers.ts +34 -0
  117. package/src/ui/headless/use-editor-keyboard.ts +98 -0
  118. package/src/ui/review/README.md +3 -0
  119. package/src/ui/shared/revision-filters.ts +31 -0
  120. package/src/ui/status/README.md +3 -0
  121. package/src/ui/theme/README.md +3 -0
  122. package/src/ui/toolbar/README.md +3 -0
  123. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
  124. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
  125. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  126. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  127. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
  128. package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
  129. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  130. package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
  131. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
  132. package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
  133. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  134. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  135. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
  136. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
  137. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  138. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
  139. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  140. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
  141. package/src/ui-tailwind/index.ts +61 -0
  142. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
  143. package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
  144. package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
  145. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  146. package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
  147. package/src/ui-tailwind/theme/editor-theme.css +190 -0
  148. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
  149. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
  150. package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
  151. package/src/validation/README.md +3 -0
  152. package/src/validation/compatibility-engine.ts +317 -0
  153. package/src/validation/compatibility-report.ts +160 -0
  154. package/src/validation/diagnostics.ts +203 -0
  155. package/src/validation/import-diagnostics.ts +128 -0
  156. package/src/validation/low-priority-word-surfaces.ts +373 -0
  157. package/dist/chunk-32W6IVQE.js +0 -7725
  158. package/dist/chunk-32W6IVQE.js.map +0 -1
  159. package/dist/index.cjs +0 -23722
  160. package/dist/index.cjs.map +0 -1
  161. package/dist/index.d.cts +0 -7
  162. package/dist/index.d.ts +0 -7
  163. package/dist/index.js +0 -16011
  164. package/dist/index.js.map +0 -1
  165. package/dist/public-types-DqCURAz8.d.cts +0 -1152
  166. package/dist/public-types-DqCURAz8.d.ts +0 -1152
  167. package/dist/tailwind.cjs +0 -8295
  168. package/dist/tailwind.cjs.map +0 -1
  169. package/dist/tailwind.d.cts +0 -323
  170. package/dist/tailwind.d.ts +0 -323
  171. package/dist/tailwind.js +0 -553
  172. package/dist/tailwind.js.map +0 -1
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ProseMirror table node specifications for first-class table editing.
3
+ *
4
+ * These specs define the structure required for tables in the ProseMirror schema,
5
+ * compatible with prosemirror-tables when used as a dependency, or standalone for
6
+ * basic cell-content editing.
7
+ *
8
+ * All four specs must be included together when merging into a schema because
9
+ * table_row references both table_cell and table_header_cell in its content model.
10
+ */
11
+
12
+ import type { NodeSpec } from "prosemirror-model";
13
+
14
+ function resolveRenderedColspan(attrs: {
15
+ colspan?: number | null;
16
+ gridSpan?: number | null;
17
+ }): number {
18
+ if (typeof attrs.colspan === "number" && attrs.colspan > 1) {
19
+ return attrs.colspan;
20
+ }
21
+ if (typeof attrs.gridSpan === "number" && attrs.gridSpan > 1) {
22
+ return attrs.gridSpan;
23
+ }
24
+ return 1;
25
+ }
26
+
27
+ export const tableNodeSpec: NodeSpec = {
28
+ content: "table_row+",
29
+ tableRole: "table",
30
+ group: "block",
31
+ isolating: true,
32
+ attrs: {
33
+ styleId: { default: null },
34
+ propertiesXml: { default: null },
35
+ gridColumns: { default: [] },
36
+ },
37
+ parseDOM: [{ tag: "table" }],
38
+ toDOM() {
39
+ return ["table", { class: "border-collapse w-full my-2 text-sm" }, ["tbody", 0]];
40
+ },
41
+ };
42
+
43
+ export const tableRowNodeSpec: NodeSpec = {
44
+ content: "(table_cell | table_header_cell)+",
45
+ tableRole: "row",
46
+ attrs: {
47
+ propertiesXml: { default: null },
48
+ },
49
+ parseDOM: [{ tag: "tr" }],
50
+ toDOM() {
51
+ return ["tr", 0];
52
+ },
53
+ };
54
+
55
+ export const tableCellNodeSpec: NodeSpec = {
56
+ content: "paragraph+",
57
+ tableRole: "cell",
58
+ isolating: true,
59
+ attrs: {
60
+ propertiesXml: { default: null },
61
+ gridSpan: { default: 1 },
62
+ verticalMerge: { default: null },
63
+ colspan: { default: 1 },
64
+ rowspan: { default: 1 },
65
+ colwidth: { default: null },
66
+ },
67
+ parseDOM: [
68
+ {
69
+ tag: "td",
70
+ getAttrs(dom: HTMLElement) {
71
+ const colspan = dom.getAttribute("colspan");
72
+ const rowspan = dom.getAttribute("rowspan");
73
+ return {
74
+ colspan: colspan ? Number.parseInt(colspan, 10) : 1,
75
+ rowspan: rowspan ? Number.parseInt(rowspan, 10) : 1,
76
+ };
77
+ },
78
+ },
79
+ ],
80
+ toDOM(node) {
81
+ const attrs: Record<string, string> = {
82
+ class: "border border-primary/20 p-2 align-top",
83
+ };
84
+ const colspan = resolveRenderedColspan(node.attrs as { colspan?: number; gridSpan?: number });
85
+ if (colspan > 1) attrs.colspan = String(colspan);
86
+ if (node.attrs.rowspan > 1) attrs.rowspan = String(node.attrs.rowspan);
87
+ return ["td", attrs, 0];
88
+ },
89
+ };
90
+
91
+ export const tableHeaderCellNodeSpec: NodeSpec = {
92
+ content: "paragraph+",
93
+ tableRole: "header_cell",
94
+ isolating: true,
95
+ attrs: {
96
+ propertiesXml: { default: null },
97
+ gridSpan: { default: 1 },
98
+ verticalMerge: { default: null },
99
+ colspan: { default: 1 },
100
+ rowspan: { default: 1 },
101
+ colwidth: { default: null },
102
+ },
103
+ parseDOM: [
104
+ {
105
+ tag: "th",
106
+ getAttrs(dom: HTMLElement) {
107
+ const colspan = dom.getAttribute("colspan");
108
+ const rowspan = dom.getAttribute("rowspan");
109
+ return {
110
+ colspan: colspan ? Number.parseInt(colspan, 10) : 1,
111
+ rowspan: rowspan ? Number.parseInt(rowspan, 10) : 1,
112
+ };
113
+ },
114
+ },
115
+ ],
116
+ toDOM(node) {
117
+ const attrs: Record<string, string> = {
118
+ class: "border border-primary/20 p-2 align-top font-semibold bg-surface-raised",
119
+ };
120
+ const colspan = resolveRenderedColspan(node.attrs as { colspan?: number; gridSpan?: number });
121
+ if (colspan > 1) attrs.colspan = String(colspan);
122
+ if (node.attrs.rowspan > 1) attrs.rowspan = String(node.attrs.rowspan);
123
+ return ["th", attrs, 0];
124
+ },
125
+ };
126
+
127
+ /**
128
+ * Returns a record of all four table node specs to merge into an existing ProseMirror Schema.
129
+ *
130
+ * All four must be included together because table_row references both table_cell and
131
+ * table_header_cell in its content model.
132
+ */
133
+ export function getTableNodeSpecs(): Record<string, NodeSpec> {
134
+ return {
135
+ table: tableNodeSpec,
136
+ table_row: tableRowNodeSpec,
137
+ table_cell: tableCellNodeSpec,
138
+ table_header_cell: tableHeaderCellNodeSpec,
139
+ };
140
+ }
@@ -0,0 +1,258 @@
1
+ import type {
2
+ EditorSurfaceSnapshot,
3
+ SurfaceBlockSnapshot,
4
+ } from "../api/public-types";
5
+
6
+ export interface VirtualizedRenderingMetrics {
7
+ blockId: string;
8
+ index: number;
9
+ top: number;
10
+ height: number;
11
+ bottom: number;
12
+ }
13
+
14
+ export interface VirtualizedRenderingSession {
15
+ blocks: SurfaceBlockSnapshot[];
16
+ metrics: VirtualizedRenderingMetrics[];
17
+ totalHeight: number;
18
+ averageBlockHeight: number;
19
+ estimatedPageCount: number;
20
+ }
21
+
22
+ export interface CreateVirtualizedRenderingSessionOptions {
23
+ averagePageHeight?: number;
24
+ paragraphBaseHeight?: number;
25
+ paragraphLineHeight?: number;
26
+ segmentWrapWidth?: number;
27
+ tableRowHeight?: number;
28
+ opaqueBlockHeight?: number;
29
+ sdtPaddingHeight?: number;
30
+ }
31
+
32
+ export interface VirtualizedViewport {
33
+ scrollTop: number;
34
+ viewportHeight: number;
35
+ overscanHeight?: number;
36
+ }
37
+
38
+ export interface VirtualizedWindow {
39
+ startIndex: number;
40
+ endIndex: number;
41
+ totalHeight: number;
42
+ offsetTop: number;
43
+ offsetBottom: number;
44
+ visibleBlocks: SurfaceBlockSnapshot[];
45
+ visibleMetrics: VirtualizedRenderingMetrics[];
46
+ }
47
+
48
+ const DEFAULT_PAGE_HEIGHT = 1120;
49
+ const DEFAULT_PARAGRAPH_BASE_HEIGHT = 28;
50
+ const DEFAULT_PARAGRAPH_LINE_HEIGHT = 18;
51
+ const DEFAULT_SEGMENT_WRAP_WIDTH = 96;
52
+ const DEFAULT_TABLE_ROW_HEIGHT = 44;
53
+ const DEFAULT_OPAQUE_BLOCK_HEIGHT = 72;
54
+ const DEFAULT_SDT_PADDING_HEIGHT = 24;
55
+
56
+ export function createVirtualizedRenderingSession(
57
+ surface: EditorSurfaceSnapshot,
58
+ previous: VirtualizedRenderingSession | undefined = undefined,
59
+ options: CreateVirtualizedRenderingSessionOptions = {},
60
+ ): VirtualizedRenderingSession {
61
+ const blocks = surface.blocks;
62
+ const metrics = previous?.metrics ?? [];
63
+
64
+ if (metrics.length > blocks.length) {
65
+ metrics.length = blocks.length;
66
+ }
67
+
68
+ let cursor = 0;
69
+ for (let index = 0; index < blocks.length; index += 1) {
70
+ const block = blocks[index]!;
71
+ const height = estimateBlockHeight(block, options);
72
+ const top = cursor;
73
+ const bottom = top + height;
74
+ const metric = metrics[index];
75
+ if (metric) {
76
+ metric.blockId = block.blockId;
77
+ metric.index = index;
78
+ metric.top = top;
79
+ metric.height = height;
80
+ metric.bottom = bottom;
81
+ } else {
82
+ metrics.push({
83
+ blockId: block.blockId,
84
+ index,
85
+ top,
86
+ height,
87
+ bottom,
88
+ });
89
+ }
90
+ cursor = bottom;
91
+ }
92
+
93
+ const totalHeight = cursor;
94
+ const averageBlockHeight = blocks.length > 0 ? totalHeight / blocks.length : 0;
95
+ const estimatedPageCount =
96
+ totalHeight > 0
97
+ ? Math.max(
98
+ 1,
99
+ Math.ceil(totalHeight / (options.averagePageHeight ?? DEFAULT_PAGE_HEIGHT)),
100
+ )
101
+ : 0;
102
+
103
+ if (previous) {
104
+ previous.blocks = blocks;
105
+ previous.metrics = metrics;
106
+ previous.totalHeight = totalHeight;
107
+ previous.averageBlockHeight = averageBlockHeight;
108
+ previous.estimatedPageCount = estimatedPageCount;
109
+ return previous;
110
+ }
111
+
112
+ return {
113
+ blocks,
114
+ metrics,
115
+ totalHeight,
116
+ averageBlockHeight,
117
+ estimatedPageCount,
118
+ };
119
+ }
120
+
121
+ export function computeVirtualizedWindow(
122
+ session: VirtualizedRenderingSession,
123
+ viewport: VirtualizedViewport,
124
+ ): VirtualizedWindow {
125
+ if (session.metrics.length === 0) {
126
+ return {
127
+ startIndex: 0,
128
+ endIndex: 0,
129
+ totalHeight: 0,
130
+ offsetTop: 0,
131
+ offsetBottom: 0,
132
+ visibleBlocks: [],
133
+ visibleMetrics: [],
134
+ };
135
+ }
136
+
137
+ const overscanHeight = Math.max(0, viewport.overscanHeight ?? viewport.viewportHeight * 0.5);
138
+ const windowTop = Math.max(0, viewport.scrollTop - overscanHeight);
139
+ const windowBottom = Math.min(
140
+ session.totalHeight,
141
+ viewport.scrollTop + viewport.viewportHeight + overscanHeight,
142
+ );
143
+ const startIndex = findFirstVisibleIndex(session.metrics, windowTop);
144
+ const endIndexExclusive = findLastVisibleIndex(session.metrics, windowBottom) + 1;
145
+ const safeEndIndex = Math.max(startIndex + 1, endIndexExclusive);
146
+ const visibleMetrics = session.metrics.slice(startIndex, safeEndIndex);
147
+ const visibleBlocks = session.blocks.slice(startIndex, safeEndIndex);
148
+ const lastMetric = visibleMetrics[visibleMetrics.length - 1]!;
149
+
150
+ return {
151
+ startIndex,
152
+ endIndex: safeEndIndex,
153
+ totalHeight: session.totalHeight,
154
+ offsetTop: visibleMetrics[0]?.top ?? 0,
155
+ offsetBottom: Math.max(0, session.totalHeight - lastMetric.bottom),
156
+ visibleBlocks,
157
+ visibleMetrics,
158
+ };
159
+ }
160
+
161
+ function findFirstVisibleIndex(
162
+ metrics: VirtualizedRenderingMetrics[],
163
+ top: number,
164
+ ): number {
165
+ let low = 0;
166
+ let high = metrics.length - 1;
167
+ let result = 0;
168
+
169
+ while (low <= high) {
170
+ const middle = (low + high) >> 1;
171
+ const metric = metrics[middle]!;
172
+ if (metric.bottom >= top) {
173
+ result = middle;
174
+ high = middle - 1;
175
+ } else {
176
+ low = middle + 1;
177
+ }
178
+ }
179
+
180
+ return result;
181
+ }
182
+
183
+ function findLastVisibleIndex(
184
+ metrics: VirtualizedRenderingMetrics[],
185
+ bottom: number,
186
+ ): number {
187
+ let low = 0;
188
+ let high = metrics.length - 1;
189
+ let result = metrics.length - 1;
190
+
191
+ while (low <= high) {
192
+ const middle = (low + high) >> 1;
193
+ const metric = metrics[middle]!;
194
+ if (metric.top <= bottom) {
195
+ result = middle;
196
+ low = middle + 1;
197
+ } else {
198
+ high = middle - 1;
199
+ }
200
+ }
201
+
202
+ return result;
203
+ }
204
+
205
+ function estimateBlockHeight(
206
+ block: SurfaceBlockSnapshot,
207
+ options: CreateVirtualizedRenderingSessionOptions,
208
+ ): number {
209
+ switch (block.kind) {
210
+ case "paragraph":
211
+ return estimateParagraphHeight(block, options);
212
+ case "table":
213
+ return Math.max(
214
+ options.tableRowHeight ?? DEFAULT_TABLE_ROW_HEIGHT,
215
+ block.rows.length * (options.tableRowHeight ?? DEFAULT_TABLE_ROW_HEIGHT),
216
+ );
217
+ case "opaque_block":
218
+ return options.opaqueBlockHeight ?? DEFAULT_OPAQUE_BLOCK_HEIGHT;
219
+ case "sdt_block":
220
+ return (
221
+ (options.sdtPaddingHeight ?? DEFAULT_SDT_PADDING_HEIGHT) +
222
+ block.children.reduce(
223
+ (total, child) => total + estimateBlockHeight(child, options),
224
+ 0,
225
+ )
226
+ );
227
+ default:
228
+ return options.paragraphBaseHeight ?? DEFAULT_PARAGRAPH_BASE_HEIGHT;
229
+ }
230
+ }
231
+
232
+ function estimateParagraphHeight(
233
+ block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
234
+ options: CreateVirtualizedRenderingSessionOptions,
235
+ ): number {
236
+ const wrapWidth = options.segmentWrapWidth ?? DEFAULT_SEGMENT_WRAP_WIDTH;
237
+ let consumedCharacters = 0;
238
+
239
+ for (const segment of block.segments) {
240
+ switch (segment.kind) {
241
+ case "text":
242
+ consumedCharacters += segment.text.length;
243
+ break;
244
+ case "image":
245
+ consumedCharacters += wrapWidth;
246
+ break;
247
+ default:
248
+ consumedCharacters += 4;
249
+ break;
250
+ }
251
+ }
252
+
253
+ const estimatedLines = Math.max(1, Math.ceil(consumedCharacters / wrapWidth));
254
+ return (
255
+ (options.paragraphBaseHeight ?? DEFAULT_PARAGRAPH_BASE_HEIGHT) +
256
+ estimatedLines * (options.paragraphLineHeight ?? DEFAULT_PARAGRAPH_LINE_HEIGHT)
257
+ );
258
+ }
@@ -0,0 +1,30 @@
1
+ # UI
2
+
3
+ This directory contains the public entry point and shared headless logic for the editor UI.
4
+
5
+ ## Entry Point
6
+
7
+ `WordReviewEditor.tsx` is the public component. It bridges the `DocumentRuntime` to the Tailwind rendering layer in `src/ui-tailwind/`.
8
+
9
+ ## Headless Logic
10
+
11
+ `headless/` contains pure, framework-free utilities shared across implementations:
12
+
13
+ - `use-editor-keyboard.ts` — Keyboard event handler factory
14
+ - `comment-decoration-model.ts` — Comment anchor → highlight class computation
15
+ - `revision-decoration-model.ts` — Revision anchor → highlight class computation
16
+ - `selection-helpers.ts` — Selection snapshot utilities
17
+
18
+ ## Session Capabilities
19
+
20
+ `src/runtime/session-capabilities.ts` derives all UI control states from `RuntimeRenderSnapshot`. The `deriveCapabilities(snapshot, reviewMode)` function is called on every render in `WordReviewEditor.tsx` and the result is passed to the workspace components.
21
+
22
+ ## Shared Utilities
23
+
24
+ `shared/revision-filters.ts` — Shared revision filtering and empty state helpers.
25
+
26
+ ## Rendering
27
+
28
+ All visual rendering is in `src/ui-tailwind/`. See `docs/reference/word-review-editor-frontend-architecture.md` for the canonical architecture.
29
+
30
+ Legacy inline-CSSProperties components have been removed.